No OneTemporary

File Metadata

Created
Sun, Jun 23, 7:09 PM
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/core/app/date/ddatepicker.cpp b/core/app/date/ddatepicker.cpp
index 811caea33f..89d5f9c4c4 100644
--- a/core/app/date/ddatepicker.cpp
+++ b/core/app/date/ddatepicker.cpp
@@ -1,579 +1,579 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 1997-04-21
* Description : A date selection widget.
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 1997 by Tim D. Gilman <tdgilman at best dot org>
* Copyright (C) 1998-2001 by Mirko Boehm <mirko at kde dot org>
* Copyright (C) 2007 by John Layt <john at layt dot net>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "ddatepicker.h"
#include "ddatepicker_p.h"
// Qt includes
#include <QApplication>
#include <QFont>
#include <QFontDatabase>
#include <QLayout>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QStyle>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "ddatetable_p.h"
#include "dpopupframe.h"
namespace Digikam
{
DDatePicker::DDatePicker(QWidget* const parent)
: QFrame(parent),
d(new Private(this))
{
initWidget(QDate::currentDate());
}
DDatePicker::DDatePicker(const QDate& dt, QWidget* const parent)
: QFrame(parent),
d(new Private(this))
{
initWidget(dt);
}
void DDatePicker::initWidget(const QDate& dt)
{
const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
QBoxLayout* const topLayout = new QVBoxLayout(this);
topLayout->setSpacing(0);
topLayout->setMargin(0);
d->navigationLayout = new QHBoxLayout();
d->navigationLayout->setSpacing(0);
d->navigationLayout->setMargin(0);
topLayout->addLayout(d->navigationLayout);
d->navigationLayout->addStretch();
d->yearBackward = new QToolButton(this);
d->yearBackward->setAutoRaise(true);
d->navigationLayout->addWidget(d->yearBackward);
d->monthBackward = new QToolButton(this);
d->monthBackward ->setAutoRaise(true);
d->navigationLayout->addWidget(d->monthBackward);
d->navigationLayout->addSpacing(spacingHint);
d->selectMonth = new QToolButton(this);
d->selectMonth ->setAutoRaise(true);
d->navigationLayout->addWidget(d->selectMonth);
d->selectYear = new QToolButton(this);
d->selectYear->setCheckable(true);
d->selectYear->setAutoRaise(true);
d->navigationLayout->addWidget(d->selectYear);
d->navigationLayout->addSpacing(spacingHint);
d->monthForward = new QToolButton(this);
d->monthForward ->setAutoRaise(true);
d->navigationLayout->addWidget(d->monthForward);
d->yearForward = new QToolButton(this);
d->yearForward ->setAutoRaise(true);
d->navigationLayout->addWidget(d->yearForward);
d->navigationLayout->addStretch();
d->line = new QLineEdit(this);
d->val = new DatePickerValidator(this);
d->table = new DDateTable(this);
setFocusProxy(d->table);
d->fontsize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize();
if (d->fontsize == -1)
{
d->fontsize = QFontInfo(QFontDatabase::systemFont(QFontDatabase::GeneralFont)).pointSize();
}
d->fontsize++; // Make a little bigger
d->selectWeek = new QComboBox(this); // read only week selection
d->selectWeek->setFocusPolicy(Qt::NoFocus);
d->todayButton = new QToolButton(this);
d->todayButton->setIcon(QIcon::fromTheme(QLatin1String("go-jump-today")));
d->yearForward->setToolTip(i18n("Next year"));
d->yearBackward->setToolTip(i18n("Previous year"));
d->monthForward->setToolTip(i18n("Next month"));
d->monthBackward->setToolTip(i18n("Previous month"));
d->selectWeek->setToolTip(i18n("Select a week"));
d->selectMonth->setToolTip(i18n("Select a month"));
d->selectYear->setToolTip(i18n("Select a year"));
d->todayButton->setToolTip(i18n("Select the current day"));
// -----
setFontSize(d->fontsize);
d->line->setValidator(d->val);
d->line->installEventFilter(this);
if (QApplication::isRightToLeft())
{
d->yearForward->setIcon(QIcon::fromTheme(QLatin1String("arrow-left-double")));
d->yearBackward->setIcon(QIcon::fromTheme(QLatin1String("arrow-right-double")));
d->monthForward->setIcon(QIcon::fromTheme(QLatin1String("go-previous")));
d->monthBackward->setIcon(QIcon::fromTheme(QLatin1String("go-next")));
}
else
{
d->yearForward->setIcon(QIcon::fromTheme(QLatin1String("arrow-right-double")));
d->yearBackward->setIcon(QIcon::fromTheme(QLatin1String("arrow-left-double")));
d->monthForward->setIcon(QIcon::fromTheme(QLatin1String("go-next")));
d->monthBackward->setIcon(QIcon::fromTheme(QLatin1String("go-previous")));
}
connect(d->table, SIGNAL(dateChanged(QDate)),
this, SLOT(dateChangedSlot(QDate)));
connect(d->table, &DDateTable::tableClicked,
this, &DDatePicker::tableClickedSlot);
connect(d->monthForward, &QAbstractButton::clicked,
this, &DDatePicker::monthForwardClicked);
connect(d->monthBackward, &QAbstractButton::clicked,
this, &DDatePicker::monthBackwardClicked);
connect(d->yearForward, &QAbstractButton::clicked,
this, &DDatePicker::yearForwardClicked);
connect(d->yearBackward, &QAbstractButton::clicked,
this, &DDatePicker::yearBackwardClicked);
connect(d->selectWeek, SIGNAL(activated(int)),
this, SLOT(weekSelected(int)));
connect(d->todayButton, &QAbstractButton::clicked,
this, &DDatePicker::todayButtonClicked);
connect(d->selectMonth, &QAbstractButton::clicked,
this, &DDatePicker::selectMonthClicked);
connect(d->selectYear, &QAbstractButton::toggled,
this, &DDatePicker::selectYearClicked);
connect(d->line, &QLineEdit::returnPressed,
this, &DDatePicker::lineEnterPressed);
topLayout->addWidget(d->table);
QBoxLayout* const bottomLayout = new QHBoxLayout();
bottomLayout->setMargin(0);
bottomLayout->setSpacing(0);
topLayout->addLayout(bottomLayout);
bottomLayout->addWidget(d->todayButton);
bottomLayout->addWidget(d->line);
bottomLayout->addWidget(d->selectWeek);
d->table->setDate(dt);
dateChangedSlot(dt); // needed because table emits changed only when newDate != oldDate
}
DDatePicker::~DDatePicker()
{
delete d;
}
bool DDatePicker::eventFilter(QObject* o, QEvent* e)
{
if (e->type() == QEvent::KeyPress)
{
QKeyEvent* const k = (QKeyEvent *)e;
if ((k->key() == Qt::Key_PageUp) ||
(k->key() == Qt::Key_PageDown) ||
(k->key() == Qt::Key_Up) ||
(k->key() == Qt::Key_Down))
{
QApplication::sendEvent(d->table, e);
d->table->setFocus();
return true; // eat event
}
}
return QFrame::eventFilter(o, e);
}
void DDatePicker::resizeEvent(QResizeEvent* e)
{
QWidget::resizeEvent(e);
}
void DDatePicker::dateChangedSlot(const QDate& dt)
{
QString dateFormat = locale().dateFormat(QLocale::ShortFormat);
if (!dateFormat.contains(QLatin1String("yyyy")))
{
dateFormat.replace(QLatin1String("yy"),
QLatin1String("yyyy"));
}
d->line->setText(dt.toString(dateFormat));
d->selectMonth->setText(locale().standaloneMonthName(dt.month(), QLocale::LongFormat));
d->fillWeeksCombo();
// calculate the item num in the week combo box; normalize selected day so as if 1.1. is the first day of the week
QDate firstDay(dt.year(), 1, 1);
// If we cannot successfully create the 1st of the year, this can only mean that
// the 1st is before the earliest valid date in the current calendar system, so use
// the earliestValidDate as the first day.
// In particular covers the case of Gregorian where 1/1/-4713 is not a valid QDate
d->selectWeek->setCurrentIndex((dt.dayOfYear() + firstDay.dayOfWeek() - 2) / 7);
d->selectYear->setText(QString::number(dt.year()).rightJustified(4, QLatin1Char('0')));
emit(dateChanged(dt));
}
void DDatePicker::tableClickedSlot()
{
emit(dateSelected(date()));
emit(tableClicked());
}
const QDate &DDatePicker::date() const
{
return d->table->date();
}
bool DDatePicker::setDate(const QDate& dt)
{
// the table setDate does validity checking for us
// this also emits dateChanged() which then calls our dateChangedSlot()
return d->table->setDate(dt);
}
void DDatePicker::monthForwardClicked()
{
if (! setDate(date().addMonths(1)))
{
QApplication::beep();
}
d->table->setFocus();
}
void DDatePicker::monthBackwardClicked()
{
if (! setDate(date().addMonths(-1)))
{
QApplication::beep();
}
d->table->setFocus();
}
void DDatePicker::yearForwardClicked()
{
if (! setDate(d->table->date().addYears(1)))
{
QApplication::beep();
}
d->table->setFocus();
}
void DDatePicker::yearBackwardClicked()
{
if (! setDate(d->table->date().addYears(-1)))
{
QApplication::beep();
}
d->table->setFocus();
}
void DDatePicker::weekSelected(int index)
{
QDate targetDay = d->selectWeek->itemData(index).toDateTime().date();
if (! setDate(targetDay))
{
QApplication::beep();
}
d->table->setFocus();
}
void DDatePicker::selectMonthClicked()
{
QDate thisDate(date());
d->table->setFocus();
QMenu popup(d->selectMonth);
// Populate the pick list with all the month names, this may change by year
- // JPL do we need to do somethng here for months that fall outside valid range?
+ // JPL do we need to do something here for months that fall outside valid range?
const int monthsInYear = QDate(thisDate.year() + 1, 1, 1).addDays(-1).month();
for (int m = 1; m <= monthsInYear; m++)
{
popup.addAction(locale().standaloneMonthName(m))->setData(m);
}
QAction* item = popup.actions()[ thisDate.month() - 1 ];
// if this happens the above should already given an assertion
if (item)
{
popup.setActiveAction(item);
}
// cancelled
if ((item = popup.exec(d->selectMonth->mapToGlobal(QPoint(0, 0)), item)) == 0)
{
return;
}
// We need to create a valid date in the month selected so we can find out how many days are
// in the month.
QDate newDate(thisDate.year(), item->data().toInt(), 1);
// If we have succeeded in creating a date in the new month, then try to create the new date,
// checking we don't set a day after the last day of the month
newDate.setDate(newDate.year(), newDate.month(), qMin(thisDate.day(), newDate.daysInMonth()));
// Set the date, if it's invalid in any way then alert user and don't update
if (! setDate(newDate))
{
QApplication::beep();
}
}
void DDatePicker::selectYearClicked()
{
if (!d->selectYear->isChecked())
{
return;
}
QDate thisDate(date());
DPopupFrame* const popup = new DPopupFrame(this);
DatePickerYearSelector* const picker = new DatePickerYearSelector(date(), popup);
picker->resize(picker->sizeHint());
picker->setYear(thisDate.year());
picker->selectAll();
popup->setMainWidget(picker);
connect(picker, SIGNAL(closeMe(int)),
popup, SLOT(close(int)));
picker->setFocus();
if (popup->exec(d->selectYear->mapToGlobal(QPoint(0, d->selectMonth->height()))))
{
// We need to create a valid date in the year/month selected so we can find out how many
// days are in the month.
QDate newDate(picker->year(), thisDate.month(), 1);
// If we have succeeded in creating a date in the new month, then try to create the new
// date, checking we don't set a day after the last day of the month
newDate = QDate(newDate.year(), newDate.month(), qMin(thisDate.day(), newDate.daysInMonth()));
// Set the date, if it's invalid in any way then alert user and don't update
if (! setDate(newDate))
{
QApplication::beep();
}
}
delete popup;
d->selectYear->setChecked(false);
}
void DDatePicker::uncheckYearSelector()
{
d->selectYear->setChecked(false);
d->selectYear->update();
}
void DDatePicker::changeEvent(QEvent* e)
{
if (e && e->type() == QEvent::EnabledChange)
{
if (isEnabled())
{
d->table->setFocus();
}
}
}
DDateTable *DDatePicker::dateTable() const
{
return d->table;
}
void DDatePicker::lineEnterPressed()
{
QString dateFormat = locale().dateFormat(QLocale::ShortFormat);
if (!dateFormat.contains(QLatin1String("yyyy")))
{
dateFormat.replace(QLatin1String("yy"),
QLatin1String("yyyy"));
}
QDate newDate = QDate::fromString(d->line->text(), dateFormat);
if (newDate.isValid())
{
emit(dateEntered(newDate));
setDate(newDate);
d->table->setFocus();
}
else
{
QApplication::beep();
}
}
void DDatePicker::todayButtonClicked()
{
setDate(QDate::currentDate());
d->table->setFocus();
}
QSize DDatePicker::sizeHint() const
{
return QWidget::sizeHint();
}
void DDatePicker::setFontSize(int s)
{
QWidget* const buttons[] =
{
d->selectMonth,
d->selectYear,
};
const int NoOfButtons = sizeof(buttons) / sizeof(buttons[0]);
int count;
QFont font;
QRect r;
// -----
d->fontsize = s;
for (count = 0; count < NoOfButtons; ++count)
{
font = buttons[count]->font();
font.setPointSize(s);
buttons[count]->setFont(font);
}
d->table->setFontSize(s);
QFontMetrics metrics(d->selectMonth->fontMetrics());
QString longestMonth;
for (int i = 1;; ++i)
{
QString str = locale().standaloneMonthName(i, QLocale::LongFormat);
if (str.isNull())
{
break;
}
r = metrics.boundingRect(str);
if (r.width() > d->maxMonthRect.width())
{
d->maxMonthRect.setWidth(r.width());
longestMonth = str;
}
if (r.height() > d->maxMonthRect.height())
{
d->maxMonthRect.setHeight(r.height());
}
}
QStyleOptionToolButton opt;
opt.initFrom(d->selectMonth);
opt.text = longestMonth;
// stolen from QToolButton
QSize textSize = metrics.size(Qt::TextShowMnemonic, longestMonth);
textSize.setWidth(textSize.width() + metrics.width(QLatin1Char(' ')) * 2);
int w = textSize.width();
int h = textSize.height();
opt.rect.setHeight(h); // PM_MenuButtonIndicator depends on the height
QSize metricBound = style()->sizeFromContents(QStyle::CT_ToolButton, &opt, QSize(w, h), d->selectMonth)
.expandedTo(QApplication::globalStrut());
d->selectMonth->setMinimumSize(metricBound);
}
int DDatePicker::fontSize() const
{
return d->fontsize;
}
void DDatePicker::setCloseButton(bool enable)
{
if (enable == (d->closeButton != 0L))
{
return;
}
if (enable)
{
d->closeButton = new QToolButton(this);
d->closeButton->setAutoRaise(true);
const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
d->navigationLayout->addSpacing(spacingHint);
d->navigationLayout->addWidget(d->closeButton);
d->closeButton->setToolTip(i18nc("@action:button", "Close"));
d->closeButton->setIcon(QIcon::fromTheme(QLatin1String("window-close")));
connect(d->closeButton, &QAbstractButton::clicked,
topLevelWidget(), &QWidget::close);
}
else
{
delete d->closeButton;
d->closeButton = 0L;
}
updateGeometry();
}
bool DDatePicker::hasCloseButton() const
{
return (d->closeButton);
}
} // namespace Digikam
diff --git a/core/app/date/timelinewidget.h b/core/app/date/timelinewidget.h
index ae03c4ff71..3118a3fb4a 100644
--- a/core/app/date/timelinewidget.h
+++ b/core/app/date/timelinewidget.h
@@ -1,166 +1,166 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-12-08
* Description : a widget to display date and time statistics of pictures
*
* Copyright (C) 2007-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011-2012 by Andi Clemens <andi dot clemens at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_TIME_LINE_WIDGET_H
#define DIGIKAM_TIME_LINE_WIDGET_H
// Qt includes
#include <QString>
#include <QWidget>
#include <QDateTime>
#include <QPaintEvent>
#include <QWheelEvent>
#include <QMouseEvent>
-// Local inclues
+// Local includes
#include "searchmodificationhelper.h"
namespace Digikam
{
class TimeLineWidget : public QWidget
{
Q_OBJECT
public:
enum TimeUnit
{
Day = 0,
Week,
Month,
Year
};
enum SelectionMode
{
Unselected = 0, // No selection.
FuzzySelection, // Partially selected.
Selected // Fully selected.
};
enum ScaleMode
{
LinScale = 0, // Linear scale.
LogScale // Logarithmic scale.
};
public:
explicit TimeLineWidget(QWidget* const parent = 0);
~TimeLineWidget();
void setTimeUnit(TimeUnit timeUnit);
TimeUnit timeUnit() const;
void setScaleMode(ScaleMode scaleMode);
ScaleMode scaleMode() const;
void setCursorDateTime(const QDateTime& dateTime);
QDateTime cursorDateTime() const;
int cursorInfo(QString& infoDate) const;
/**
* Return a list of Date-Range based on selection performed on days-map
*/
DateRangeList selectedDateRange(int& totalCount) const;
void setSelectedDateRange(const DateRangeList& list);
int totalIndex() const;
int indexForRefDateTime() const;
int indexForCursorDateTime() const;
void setCurrentIndex(int index);
Q_SIGNALS:
void signalCursorPositionChanged();
void signalSelectionChanged();
void signalRefDateTimeChanged();
void signalDateMapChanged();
public Q_SLOTS:
void slotDatesMap(const QMap<QDateTime, int>&);
void slotPrevious();
void slotNext();
void slotBackward();
void slotForward();
void slotResetSelection();
private Q_SLOTS:
void slotThemeChanged();
private:
QDateTime prevDateTime(const QDateTime& dt) const;
QDateTime nextDateTime(const QDateTime& dt) const;
int maxCount() const;
int indexForDateTime(const QDateTime& date) const;
int statForDateTime(const QDateTime& dt, SelectionMode& selected) const;
void setRefDateTime(const QDateTime& dateTime);
void paintEvent(QPaintEvent*);
void wheelEvent(QWheelEvent*);
void mousePressEvent(QMouseEvent*);
void mouseMoveEvent(QMouseEvent*);
void mouseReleaseEvent(QMouseEvent*);
void keyPressEvent(QKeyEvent *e);
void keyReleaseEvent(QKeyEvent *);
void keyScroll(bool isScrollNext);
QDateTime dateTimeForPoint(const QPoint& pt, bool& isOnSelectionArea);
QDateTime firstDayOfWeek(int year, int weekNumber) const;
void resetSelection();
void setDateTimeSelected(const QDateTime& dt, SelectionMode selected);
void setDaysRangeSelection(const QDateTime& dts, const QDateTime& dte, SelectionMode selected);
SelectionMode checkSelectionForDaysRange(const QDateTime& dts, const QDateTime& dte) const;
void updateWeekSelection(const QDateTime& dts, const QDateTime& dte);
void updateMonthSelection(const QDateTime& dts, const QDateTime& dte);
void updateYearSelection(const QDateTime& dts, const QDateTime& dte);
void updateAllSelection();
// helper methods for painting
int calculateTop(int& val) const;
void paintItem(QPainter& p, const QRect& barRect,
const QDateTime& ref, const int& separatorPosition,
const QColor& dateColor, const QColor& subDateColor);
void handleSelectionRange(QDateTime& selEndDateTime);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_TIME_LINE_WIDGET_H
diff --git a/core/app/items/imagedelegate.cpp b/core/app/items/imagedelegate.cpp
index c8dad969ad..7d74ed9040 100644
--- a/core/app/items/imagedelegate.cpp
+++ b/core/app/items/imagedelegate.cpp
@@ -1,610 +1,610 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-04-19
* Description : Qt item view for images - the delegate
*
* Copyright (C) 2002-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
* Copyright (C) 2002-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "imagedelegate.h"
#include "imagedelegatepriv.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QCache>
#include <QPainter>
#include <QIcon>
#include <QApplication>
// Local includes
#include "digikam_debug.h"
#include "albummanager.h"
#include "imagecategorydrawer.h"
#include "imagecategorizedview.h"
#include "imagedelegateoverlay.h"
#include "imagemodel.h"
#include "imagefiltermodel.h"
#include "imagethumbnailmodel.h"
#include "thumbnailloadthread.h"
#include "applicationsettings.h"
namespace Digikam
{
void ImageDelegate::ImageDelegatePrivate::clearRects()
{
ItemViewImageDelegatePrivate::clearRects();
dateRect = QRect(0, 0, 0, 0);
modDateRect = QRect(0, 0, 0, 0);
pixmapRect = QRect(0, 0, 0, 0);
nameRect = QRect(0, 0, 0, 0);
titleRect = QRect(0, 0, 0, 0);
commentsRect = QRect(0, 0, 0, 0);
resolutionRect = QRect(0, 0, 0, 0);
coordinatesRect = QRect(0, 0, 0, 0);
arRect = QRect(0, 0, 0, 0);
sizeRect = QRect(0, 0, 0, 0);
tagRect = QRect(0, 0, 0, 0);
imageInformationRect = QRect(0, 0, 0, 0);
pickLabelRect = QRect(0, 0, 0, 0);
groupRect = QRect(0, 0, 0, 0);
}
ImageDelegate::ImageDelegate(QObject* const parent)
: ItemViewImageDelegate(*new ImageDelegatePrivate, parent)
{
}
ImageDelegate::ImageDelegate(ImageDelegate::ImageDelegatePrivate& dd, QObject* parent)
: ItemViewImageDelegate(dd, parent)
{
}
ImageDelegate::~ImageDelegate()
{
Q_D(ImageDelegate);
// crashes for a lot of people, see bug 230515. Cause unknown.
//delete d->categoryDrawer;
Q_UNUSED(d); // To please compiler about warnings.
}
void ImageDelegate::setView(ImageCategorizedView* view)
{
Q_D(ImageDelegate);
setViewOnAllOverlays(view);
if (d->currentView)
{
disconnect(d->currentView, SIGNAL(modelChanged()),
this, SLOT(modelChanged()));
}
d->currentView = view;
setModel(view ? view->model() : 0);
if (d->currentView)
{
connect(d->currentView, SIGNAL(modelChanged()),
this, SLOT(modelChanged()));
}
}
void ImageDelegate::setModel(QAbstractItemModel* model)
{
Q_D(ImageDelegate);
// 1) We only need the model to invalidate model-index based caches on change
// 2) We do not need to care for overlays. The view calls setActive() on them on model change
if (model == d->currentModel)
{
return;
}
if (d->currentModel)
{
disconnect(d->currentModel, 0, this, 0);
}
d->currentModel = model;
if (d->currentModel)
{
connect(d->currentModel, SIGNAL(layoutAboutToBeChanged()),
this, SLOT(modelContentsChanged()));
connect(d->currentModel, SIGNAL(modelAboutToBeReset()),
this, SLOT(modelContentsChanged()));
connect(d->currentModel, SIGNAL(rowsAboutToBeInserted(QModelIndex,int,int)),
this, SLOT(modelContentsChanged()));
connect(d->currentModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
this, SLOT(modelContentsChanged()));
connect(d->currentModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(modelContentsChanged()));
}
}
void ImageDelegate::setSpacing(int spacing)
{
Q_D(ImageDelegate);
if (d->categoryDrawer)
{
d->categoryDrawer->setLowerSpacing(spacing);
}
ItemViewImageDelegate::setSpacing(spacing);
}
ImageCategoryDrawer* ImageDelegate::categoryDrawer() const
{
Q_D(const ImageDelegate);
return d->categoryDrawer;
}
QRect ImageDelegate::commentsRect() const
{
Q_D(const ImageDelegate);
return d->commentsRect;
}
QRect ImageDelegate::tagsRect() const
{
Q_D(const ImageDelegate);
return d->tagRect;
}
QRect ImageDelegate::pixmapRect() const
{
Q_D(const ImageDelegate);
return d->pixmapRect;
}
QRect ImageDelegate::imageInformationRect() const
{
Q_D(const ImageDelegate);
return d->imageInformationRect;
}
QRect ImageDelegate::groupIndicatorRect() const
{
Q_D(const ImageDelegate);
return d->groupRect;
}
QRect ImageDelegate::coordinatesIndicatorRect() const
{
Q_D(const ImageDelegate);
return d->coordinatesRect;
}
void ImageDelegate::prepareThumbnails(ImageThumbnailModel* thumbModel, const QList<QModelIndex>& indexes)
{
thumbModel->prepareThumbnails(indexes, thumbnailSize());
}
QPixmap ImageDelegate::retrieveThumbnailPixmap(const QModelIndex& index, int thumbnailSize)
{
// work around constness
QAbstractItemModel* const model = const_cast<QAbstractItemModel*>(index.model());
// set requested thumbnail size
model->setData(index, thumbnailSize, ImageModel::ThumbnailRole);
// get data from model
QVariant thumbData = index.data(ImageModel::ThumbnailRole);
// reset to default thumbnail size
model->setData(index, QVariant(), ImageModel::ThumbnailRole);
return thumbData.value<QPixmap>();
}
QPixmap ImageDelegate::thumbnailPixmap(const QModelIndex& index) const
{
Q_D(const ImageDelegate);
return retrieveThumbnailPixmap(index, d->thumbSize.size());
}
void ImageDelegate::paint(QPainter* p, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
Q_D(const ImageDelegate);
ImageInfo info = ImageModel::retrieveImageInfo(index);
if (info.isNull())
{
return;
}
// state of painter must not be changed
p->save();
p->translate(option.rect.topLeft());
QRect r;
bool isSelected = (option.state & QStyle::State_Selected);
// Thumbnail
QPixmap pix;
if (isSelected)
{
pix = d->selPixmap;
}
else
{
pix = d->regPixmap;
}
bool groupedAndClosed = (info.hasGroupedImages() &&
!index.data(ImageFilterModel::GroupIsOpenRole).toBool() &&
ApplicationSettings::instance()->getDrawFramesToGrouped());
QRect actualPixmapRect = drawThumbnail(p, d->pixmapRect,
pix, thumbnailPixmap(index),
groupedAndClosed);
if (!actualPixmapRect.isNull())
{
const_cast<ImageDelegate*>(this)->updateActualPixmapRect(index, actualPixmapRect);
}
if (!d->ratingRect.isNull())
{
drawRating(p, index, d->ratingRect, info.rating(), isSelected);
}
// Draw Color Label rectangle
drawColorLabelRect(p, option, isSelected, info.colorLabel());
p->setPen(isSelected ? qApp->palette().color(QPalette::HighlightedText)
: qApp->palette().color(QPalette::Text));
/*
// If there is ImageHistory present, paint a small icon over the thumbnail to indicate that this is derived image
if (info.hasImageHistory())
{
p->drawPixmap(d->pixmapRect.right()-24, d->pixmapRect.bottom()-24, QIcon::fromTheme(QLatin1String("svn_switch")).pixmap(22, 22));
}
*/
if (!d->nameRect.isNull())
{
drawName(p, d->nameRect, info.name());
}
if (!d->titleRect.isNull())
{
drawTitle(p, d->titleRect, info.title());
}
if (!d->commentsRect.isNull())
{
drawComments(p, d->commentsRect, info.comment());
}
if (!d->dateRect.isNull())
{
drawCreationDate(p, d->dateRect, info.dateTime());
}
if (!d->modDateRect.isNull() && info.modDateTime().isValid() &&
info.modDateTime() != info.dateTime())
{
drawModificationDate(p, d->modDateRect, info.modDateTime());
}
if (!d->resolutionRect.isNull())
{
drawImageSize(p, d->resolutionRect, info.dimensions());
}
if (!d->arRect.isNull())
{
drawAspectRatio(p, d->arRect, info.dimensions());
}
if (!d->sizeRect.isNull())
{
drawFileSize(p, d->sizeRect, info.fileSize());
}
if (!d->groupRect.isNull())
{
drawGroupIndicator(p, d->groupRect, info.numberOfGroupedImages(),
index.data(ImageFilterModel::GroupIsOpenRole).toBool());
}
if (!d->tagRect.isNull())
{
QStringList tagsList = AlbumManager::instance()->tagNames(info.tagIds());
tagsList.sort();
QString tags = tagsList.join(QLatin1String(", "));
drawTags(p, d->tagRect, tags, isSelected);
}
if (!d->pickLabelRect.isNull())
{
drawPickLabelIcon(p, d->pickLabelRect, info.pickLabel());
}
bool left = index.data(ImageModel::LTLeftPanelRole).toBool();
bool right = index.data(ImageModel::LTRightPanelRole).toBool();
drawPanelSideIcon(p, left, right);
if (d->drawImageFormat)
{
QString frm = info.format();
if (frm.contains(QLatin1Char('-')))
- frm = frm.section(QLatin1Char('-'), -1); // For RAW format annoted as "RAW-xxx" => "xxx"
+ frm = frm.section(QLatin1Char('-'), -1); // For RAW format annotated as "RAW-xxx" => "xxx"
drawImageFormat(p, actualPixmapRect, frm, d->drawImageFormatTop);
}
if (info.id() == info.currentReferenceImage())
{
drawSpecialInfo(p, actualPixmapRect, i18n("Reference Image"));
}
if (d->drawCoordinates && info.hasCoordinates())
{
drawGeolocationIndicator(p, d->coordinatesRect);
}
if (d->drawFocusFrame)
{
drawFocusRect(p, option, isSelected);
}
if (d->drawMouseOverFrame)
{
drawMouseOverRect(p, option);
}
p->restore();
drawOverlays(p, option, index);
}
QPixmap ImageDelegate::pixmapForDrag(const QStyleOptionViewItem& option, const QList<QModelIndex>& indexes) const
{
QPixmap icon;
if (!indexes.isEmpty())
{
icon = thumbnailPixmap(indexes.first());
}
return makeDragPixmap(option, indexes, icon);
}
bool ImageDelegate::acceptsToolTip(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
QRect* toolTipRect) const
{
return onActualPixmapRect(pos, visualRect, index, toolTipRect);
}
bool ImageDelegate::acceptsActivation(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
QRect* activationRect) const
{
return onActualPixmapRect(pos, visualRect, index, activationRect);
}
bool ImageDelegate::onActualPixmapRect(const QPoint& pos, const QRect& visualRect, const QModelIndex& index,
QRect* returnRect) const
{
QRect actualRect = actualPixmapRect(index);
if (actualRect.isNull())
{
return false;
}
actualRect.translate(visualRect.topLeft());
if (returnRect)
{
*returnRect = actualRect;
}
return actualRect.contains(pos);
}
void ImageDelegate::setDefaultViewOptions(const QStyleOptionViewItem& option)
{
Q_D(ImageDelegate);
if (d->categoryDrawer)
{
d->categoryDrawer->setDefaultViewOptions(option);
}
ItemViewImageDelegate::setDefaultViewOptions(option);
}
void ImageDelegate::invalidatePaintingCache()
{
Q_D(ImageDelegate);
if (d->categoryDrawer)
{
d->categoryDrawer->invalidatePaintingCache();
}
ItemViewImageDelegate::invalidatePaintingCache();
}
void ImageDelegate::updateContentWidth()
{
Q_D(ImageDelegate);
d->contentWidth = d->thumbSize.size() + 2*d->radius;
}
void ImageDelegate::updateSizeRectsAndPixmaps()
{
Q_D(ImageDelegate);
// ---- Reset rects and prepare fonts ----
d->clearRects();
prepareFonts();
// ---- Fixed sizes and metrics ----
updateContentWidth();
prepareMetrics(d->contentWidth);
// ---- Calculate rects ----
updateRects();
// ---- Cached pixmaps ----
prepareBackground();
if (!d->ratingRect.isNull())
{
// Normally we prepare the pixmaps over the background of the rating rect.
// If the rating is drawn over the thumbnail, we can only draw over a transparent pixmap.
prepareRatingPixmaps(!d->ratingOverThumbnail);
}
// ---- Drawing related caches ----
clearCaches();
}
void ImageDelegate::clearCaches()
{
Q_D(ImageDelegate);
ItemViewImageDelegate::clearCaches();
d->actualPixmapRectCache.clear();
}
void ImageDelegate::clearModelDataCaches()
{
Q_D(ImageDelegate);
d->actualPixmapRectCache.clear();
}
void ImageDelegate::modelChanged()
{
Q_D(ImageDelegate);
clearModelDataCaches();
setModel(d->currentView ? d->currentView->model() : 0);
}
void ImageDelegate::modelContentsChanged()
{
clearModelDataCaches();
}
QRect ImageDelegate::actualPixmapRect(const QModelIndex& index) const
{
Q_D(const ImageDelegate);
// We do not recompute if not found. Assumption is cache is always properly updated.
QRect* rect = d->actualPixmapRectCache.object(index.row());
if (rect)
{
return *rect;
}
else
{
return d->pixmapRect;
}
}
void ImageDelegate::updateActualPixmapRect(const QModelIndex& index, const QRect& rect)
{
Q_D(ImageDelegate);
QRect* const old = d->actualPixmapRectCache.object(index.row());
if (!old || *old != rect)
{
d->actualPixmapRectCache.insert(index.row(), new QRect(rect));
}
}
int ImageDelegate::calculatethumbSizeToFit(int ws)
{
Q_D(ImageDelegate);
int ts = thumbnailSize().size();
int gs = gridSize().width();
int sp = spacing();
ws = ws - 2*sp;
// Thumbnails size loop to check (upper/lower)
int ts1, ts2;
// New grid size used in loop
int ngs;
double rs1 = fmod((double)ws, (double)gs);
for (ts1 = ts ; ts1 < ThumbnailSize::maxThumbsSize() ; ++ts1)
{
ngs = ts1 + 2*(d->margin + d->radius) + sp;
double nrs = fmod((double)ws, (double)ngs);
if (nrs <= rs1)
{
rs1 = nrs;
}
else
{
break;
}
}
double rs2 = fmod((double)ws, (double)gs);
for (ts2 = ts ; ts2 > ThumbnailSize::Small ; --ts2)
{
ngs = ts2 + 2*(d->margin + d->radius) + sp;
double nrs = fmod((double)ws, (double)ngs);
if (nrs >= rs2)
{
rs2 = nrs;
}
else
{
rs2 = nrs;
break;
}
}
if (rs1 > rs2)
{
return (ts2);
}
return (ts1);
}
} // namespace Digikam
diff --git a/core/app/views/leftsidebarwidgets.cpp b/core/app/views/leftsidebarwidgets.cpp
index 66206e9172..ee417863fe 100644
--- a/core/app/views/leftsidebarwidgets.cpp
+++ b/core/app/views/leftsidebarwidgets.cpp
@@ -1,1502 +1,1502 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2000-12-05
* Description : left sidebar widgets
*
* Copyright (C) 2009-2010 by Johannes Wienke <languitar at semipol dot de>
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2012 by Andi Clemens <andi dot clemens at gmail dot com>
* Copyright (C) 2014 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "leftsidebarwidgets.h"
// Qt includes
#include <QButtonGroup>
#include <QLabel>
#include <QScrollBar>
#include <QTimer>
#include <QToolButton>
#include <QRadioButton>
#include <QApplication>
#include <QStyle>
#include <QComboBox>
#include <QPushButton>
#include <QLineEdit>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "albummanager.h"
#include "albummodificationhelper.h"
#include "albumselectiontreeview.h"
#include "applicationsettings.h"
#include "datefolderview.h"
#include "editablesearchtreeview.h"
#include "fuzzysearchview.h"
#include "searchfolderview.h"
#include "searchtabheader.h"
#include "searchtextbar.h"
#include "coredbsearchxml.h"
#include "tagfolderview.h"
#include "timelinewidget.h"
#include "facescandialog.h"
#include "facesdetector.h"
#include "tagsmanager.h"
#include "albumlabelstreeview.h"
#include "coredb.h"
#include "dexpanderbox.h"
namespace Digikam
{
class Q_DECL_HIDDEN AlbumFolderViewSideBarWidget::Private
{
public:
explicit Private()
: albumModificationHelper(0),
albumFolderView(0),
searchTextBar(0)
{
}
AlbumModificationHelper* albumModificationHelper;
AlbumSelectionTreeView* albumFolderView;
SearchTextBar* searchTextBar;
};
AlbumFolderViewSideBarWidget::AlbumFolderViewSideBarWidget(QWidget* const parent,
AlbumModel* const model,
AlbumModificationHelper* const albumModificationHelper)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("AlbumFolderView Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F1);
d->albumModificationHelper = albumModificationHelper;
QVBoxLayout* const layout = new QVBoxLayout(this);
d->albumFolderView = new AlbumSelectionTreeView(this, model, d->albumModificationHelper);
d->albumFolderView->setObjectName(QLatin1String("AlbumFolderView"));
d->albumFolderView->setConfigGroup(getConfigGroup());
d->albumFolderView->setExpandNewCurrentItem(true);
d->albumFolderView->setAlbumManagerCurrentAlbum(true);
d->searchTextBar = new SearchTextBar(this, QLatin1String("DigikamViewFolderSearchBar"));
d->searchTextBar->setHighlightOnResult(true);
d->searchTextBar->setModel(model, AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole);
d->searchTextBar->setFilterModel(d->albumFolderView->albumFilterModel());
layout->addWidget(d->albumFolderView);
layout->addWidget(d->searchTextBar);
// setup connection
connect(d->albumFolderView, SIGNAL(signalFindDuplicates(PAlbum*)),
this, SIGNAL(signalFindDuplicates(PAlbum*)));
}
AlbumFolderViewSideBarWidget::~AlbumFolderViewSideBarWidget()
{
delete d;
}
void AlbumFolderViewSideBarWidget::setActive(bool active)
{
if (active)
{
AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << d->albumFolderView->currentAlbum());
}
}
void AlbumFolderViewSideBarWidget::doLoadState()
{
d->albumFolderView->loadState();
}
void AlbumFolderViewSideBarWidget::doSaveState()
{
d->albumFolderView->saveState();
}
void AlbumFolderViewSideBarWidget::applySettings()
{
ApplicationSettings* const settings = ApplicationSettings::instance();
d->albumFolderView->setEnableToolTips(settings->getShowAlbumToolTips());
}
void AlbumFolderViewSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
d->albumFolderView->setCurrentAlbums(album);
}
AlbumPointer<PAlbum> AlbumFolderViewSideBarWidget::currentAlbum() const
{
return AlbumPointer<PAlbum> (d->albumFolderView->currentAlbum());
}
void AlbumFolderViewSideBarWidget::setCurrentAlbum(PAlbum* album)
{
// Change the current album in list view.
d->albumFolderView->setCurrentAlbums(QList<Album*>() << album);
}
const QIcon AlbumFolderViewSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("folder-pictures"));
}
const QString AlbumFolderViewSideBarWidget::getCaption()
{
return i18n("Albums");
}
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN TagViewSideBarWidget::Private
{
public:
enum TagsSource
{
NoTags = 0,
ExistingTags
};
public:
explicit Private()
: openTagMngr(0),
tagSearchBar(0),
tagFolderView(0),
btnGroup(0),
noTagsBtn(0),
tagsBtn(0),
noTagsWasChecked(false),
ExistingTagsWasChecked(false)
{
}
public:
QPushButton* openTagMngr;
SearchTextBar* tagSearchBar;
TagFolderView* tagFolderView;
QButtonGroup* btnGroup;
QRadioButton* noTagsBtn;
QRadioButton* tagsBtn;
bool noTagsWasChecked;
bool ExistingTagsWasChecked;
QString noTagsSearchXml;
static const QString configTagsSourceEntry;
};
const QString TagViewSideBarWidget::Private::configTagsSourceEntry(QLatin1String("TagsSource"));
TagViewSideBarWidget::TagViewSideBarWidget(QWidget* const parent, TagModel* const model)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("TagView Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F2);
QVBoxLayout* const layout = new QVBoxLayout(this);
d->openTagMngr = new QPushButton( i18n("Open Tag Manager"));
d->noTagsBtn = new QRadioButton(i18n("No Tags"), this);
d->tagsBtn = new QRadioButton(i18n("Existing Tags"), this);
d->btnGroup = new QButtonGroup(this);
d->btnGroup->addButton(d->noTagsBtn);
d->btnGroup->addButton(d->tagsBtn);
d->btnGroup->setId(d->noTagsBtn, 0);
d->btnGroup->setId(d->tagsBtn, 1);
d->btnGroup->setExclusive(true);
d->tagFolderView = new TagFolderView(this, model);
d->tagFolderView->setConfigGroup(getConfigGroup());
d->tagFolderView->setExpandNewCurrentItem(true);
d->tagFolderView->setAlbumManagerCurrentAlbum(true);
d->tagSearchBar = new SearchTextBar(this, QLatin1String("DigikamViewTagSearchBar"));
d->tagSearchBar->setHighlightOnResult(true);
d->tagSearchBar->setModel(model, AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole);
d->tagSearchBar->setFilterModel(d->tagFolderView->albumFilterModel());
layout->addWidget(d->openTagMngr);
layout->addWidget(d->noTagsBtn);
layout->addWidget(d->tagsBtn);
layout->addWidget(d->tagFolderView);
layout->addWidget(d->tagSearchBar);
connect(d->openTagMngr, SIGNAL(clicked()),
this,SLOT(slotOpenTagManager()));
connect(d->tagFolderView, SIGNAL(signalFindDuplicates(QList<TAlbum*>)),
this, SIGNAL(signalFindDuplicates(QList<TAlbum*>)));
connect(d->btnGroup, SIGNAL(buttonClicked(int)),
this, SLOT(slotToggleTagsSelection(int)));
}
TagViewSideBarWidget::~TagViewSideBarWidget()
{
delete d;
}
void TagViewSideBarWidget::setActive(bool active)
{
if (active)
{
if(d->noTagsBtn->isChecked())
{
setNoTagsAlbum();
}
else
{
AlbumManager::instance()->setCurrentAlbums(d->tagFolderView->selectedTags());
}
}
}
void TagViewSideBarWidget::doLoadState()
{
KConfigGroup group = getConfigGroup();
bool noTagsBtnWasChecked = group.readEntry(d->configTagsSourceEntry, false);
d->noTagsBtn->setChecked(noTagsBtnWasChecked);
d->tagsBtn->setChecked(!noTagsBtnWasChecked);
d->noTagsWasChecked = noTagsBtnWasChecked;
d->ExistingTagsWasChecked = !noTagsBtnWasChecked;
d->tagFolderView->loadState();
d->tagFolderView->setDisabled(noTagsBtnWasChecked);
}
void TagViewSideBarWidget::doSaveState()
{
KConfigGroup group = getConfigGroup();
group.writeEntry(d->configTagsSourceEntry, d->noTagsBtn->isChecked());
d->tagFolderView->saveState();
group.sync();
}
void TagViewSideBarWidget::applySettings()
{
}
void TagViewSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
if (album.first()->type() == Album::TAG)
{
d->tagsBtn->setChecked(true);
d->tagFolderView->setEnabled(true);
d->ExistingTagsWasChecked = true;
d->noTagsWasChecked = false;
d->tagFolderView->setCurrentAlbums(album);
}
else
{
d->noTagsBtn->setChecked(true);
d->tagFolderView->setDisabled(true);
d->noTagsWasChecked = true;
d->ExistingTagsWasChecked = false;
}
}
AlbumPointer<TAlbum> TagViewSideBarWidget::currentAlbum() const
{
return AlbumPointer<TAlbum> (d->tagFolderView->currentAlbum());
}
void TagViewSideBarWidget::setNoTagsAlbum()
{
if (d->noTagsSearchXml.isEmpty())
{
SearchXmlWriter writer;
writer.setFieldOperator((SearchXml::standardFieldOperator()));
writer.writeGroup();
writer.writeField(QLatin1String("notag"), SearchXml::Equal);
writer.finishField();
writer.finishGroup();
writer.finish();
d->noTagsSearchXml = writer.xml();
}
QString title = SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch);
SAlbum* album = AlbumManager::instance()->findSAlbum(title);
int id;
if (album)
{
id = album->id();
CoreDbAccess().db()->updateSearch(id,DatabaseSearch::AdvancedSearch,
SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->noTagsSearchXml);
}
else
{
id = CoreDbAccess().db()->addSearch(DatabaseSearch::AdvancedSearch,
SAlbum::getTemporaryTitle(DatabaseSearch::AdvancedSearch), d->noTagsSearchXml);
}
album = new SAlbum(i18n("No Tags Album"), id);
if (album)
{
AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << album);
}
}
const QIcon TagViewSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("tag"));
}
const QString TagViewSideBarWidget::getCaption()
{
return i18n("Tags");
}
void TagViewSideBarWidget::setCurrentAlbum(TAlbum* album)
{
d->tagFolderView->setCurrentAlbums(QList<Album*>() << album);
}
void TagViewSideBarWidget::slotOpenTagManager()
{
TagsManager* const tagMngr = TagsManager::instance();
tagMngr->show();
tagMngr->activateWindow();
tagMngr->raise();
}
void TagViewSideBarWidget::slotToggleTagsSelection(int radioClicked)
{
switch (Private::TagsSource(radioClicked))
{
case Private::NoTags:
{
if (!d->noTagsWasChecked)
{
setNoTagsAlbum();
d->tagFolderView->setDisabled(true);
d->noTagsWasChecked = d->noTagsBtn->isChecked();
d->ExistingTagsWasChecked = d->tagsBtn->isChecked();
}
break;
}
case Private::ExistingTags:
{
if (!d->ExistingTagsWasChecked)
{
d->tagFolderView->setEnabled(true);
setActive(true);
d->noTagsWasChecked = d->noTagsBtn->isChecked();
d->ExistingTagsWasChecked = d->tagsBtn->isChecked();
}
break;
}
}
}
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN LabelsSideBarWidget::Private
{
public:
explicit Private()
: labelsTree(0)
{
}
AlbumLabelsTreeView* labelsTree;
};
LabelsSideBarWidget::LabelsSideBarWidget(QWidget* const parent)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("Labels Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F3);
QVBoxLayout* const layout = new QVBoxLayout(this);
d->labelsTree = new AlbumLabelsTreeView(this);
d->labelsTree->setConfigGroup(getConfigGroup());
layout->addWidget(d->labelsTree);
}
LabelsSideBarWidget::~LabelsSideBarWidget()
{
delete d;
}
AlbumLabelsTreeView *LabelsSideBarWidget::labelsTree()
{
return d->labelsTree;
}
void LabelsSideBarWidget::setActive(bool active)
{
if (active)
{
d->labelsTree->setCurrentAlbum();
}
}
void LabelsSideBarWidget::applySettings()
{
}
void LabelsSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
Q_UNUSED(album);
}
void LabelsSideBarWidget::doLoadState()
{
d->labelsTree->doLoadState();
}
void LabelsSideBarWidget::doSaveState()
{
d->labelsTree->doSaveState();
}
const QIcon LabelsSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("folder-favorites"));
}
const QString LabelsSideBarWidget::getCaption()
{
return i18n("Labels");
}
QHash<AlbumLabelsTreeView::Labels, QList<int> > LabelsSideBarWidget::selectedLabels()
{
return d->labelsTree->selectedLabels();
}
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN DateFolderViewSideBarWidget::Private
{
public:
explicit Private()
: dateFolderView(0)
{
}
DateFolderView* dateFolderView;
};
DateFolderViewSideBarWidget::DateFolderViewSideBarWidget(QWidget* const parent,
DateAlbumModel* const model,
ImageAlbumFilterModel* const imageFilterModel)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("DateFolderView Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F4);
QVBoxLayout* const layout = new QVBoxLayout(this);
d->dateFolderView = new DateFolderView(this, model);
d->dateFolderView->setConfigGroup(getConfigGroup());
d->dateFolderView->setImageModel(imageFilterModel);
layout->addWidget(d->dateFolderView);
}
DateFolderViewSideBarWidget::~DateFolderViewSideBarWidget()
{
delete d;
}
void DateFolderViewSideBarWidget::setActive(bool active)
{
d->dateFolderView->setActive(active);
}
void DateFolderViewSideBarWidget::doLoadState()
{
d->dateFolderView->loadState();
}
void DateFolderViewSideBarWidget::doSaveState()
{
d->dateFolderView->saveState();
}
void DateFolderViewSideBarWidget::applySettings()
{
}
void DateFolderViewSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
d->dateFolderView->changeAlbumFromHistory(dynamic_cast<DAlbum*>(album.first()));
}
AlbumPointer<DAlbum> DateFolderViewSideBarWidget::currentAlbum() const
{
return d->dateFolderView->currentAlbum();
}
void DateFolderViewSideBarWidget::gotoDate(const QDate& date)
{
d->dateFolderView->gotoDate(date);
}
const QIcon DateFolderViewSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("view-calendar-list"));
}
const QString DateFolderViewSideBarWidget::getCaption()
{
return i18n("Dates");
}
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN TimelineSideBarWidget::Private
{
public:
explicit Private()
: scaleBG(0),
cursorCountLabel(0),
scrollBar(0),
timer(0),
resetButton(0),
saveButton(0),
timeUnitCB(0),
nameEdit(0),
cursorDateLabel(0),
searchDateBar(0),
timeLineFolderView(0),
timeLineWidget(0),
searchModificationHelper(0)
{
}
static const QString configHistogramTimeUnitEntry;
static const QString configHistogramScaleEntry;
static const QString configCursorPositionEntry;
QButtonGroup* scaleBG;
QLabel* cursorCountLabel;
QScrollBar* scrollBar;
QTimer* timer;
QToolButton* resetButton;
QToolButton* saveButton;
QComboBox* timeUnitCB;
QLineEdit* nameEdit;
DAdjustableLabel* cursorDateLabel;
SearchTextBar* searchDateBar;
EditableSearchTreeView* timeLineFolderView;
TimeLineWidget* timeLineWidget;
SearchModificationHelper* searchModificationHelper;
AlbumPointer<SAlbum> currentTimelineSearch;
};
const QString TimelineSideBarWidget::Private::configHistogramTimeUnitEntry(QLatin1String("Histogram TimeUnit"));
const QString TimelineSideBarWidget::Private::configHistogramScaleEntry(QLatin1String("Histogram Scale"));
const QString TimelineSideBarWidget::Private::configCursorPositionEntry(QLatin1String("Cursor Position"));
// --------------------------------------------------------
TimelineSideBarWidget::TimelineSideBarWidget(QWidget* const parent,
SearchModel* const searchModel,
SearchModificationHelper* const searchModificationHelper)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("TimeLine Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F5);
d->searchModificationHelper = searchModificationHelper;
d->timer = new QTimer(this);
setAttribute(Qt::WA_DeleteOnClose);
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
QVBoxLayout* const vlay = new QVBoxLayout(this);
QFrame* const panel = new QFrame(this);
panel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
panel->setLineWidth(1);
QGridLayout* const grid = new QGridLayout(panel);
// ---------------------------------------------------------------
QWidget* const hbox1 = new QWidget(panel);
QHBoxLayout* const hlay = new QHBoxLayout(hbox1);
QLabel* const label1 = new QLabel(i18n("Time Unit:"), hbox1);
d->timeUnitCB = new QComboBox(hbox1);
d->timeUnitCB->addItem(i18n("Day"), TimeLineWidget::Day);
d->timeUnitCB->addItem(i18n("Week"), TimeLineWidget::Week);
d->timeUnitCB->addItem(i18n("Month"), TimeLineWidget::Month);
d->timeUnitCB->addItem(i18n("Year"), TimeLineWidget::Year);
d->timeUnitCB->setCurrentIndex((int)TimeLineWidget::Month);
d->timeUnitCB->setFocusPolicy(Qt::NoFocus);
d->timeUnitCB->setWhatsThis(i18n("<p>Select the histogram time unit.</p>"
"<p>You can change the graph decade to zoom in or zoom out over time.</p>"));
QWidget* const scaleBox = new QWidget(hbox1);
QHBoxLayout* const hlay2 = new QHBoxLayout(scaleBox);
d->scaleBG = new QButtonGroup(scaleBox);
d->scaleBG->setExclusive(true);
scaleBox->setWhatsThis( i18n("<p>Select the histogram scale.</p>"
"<p>If the date's maximal counts are small, you can use the linear scale.</p>"
"<p>Logarithmic scale can be used when the maximal counts are big; "
"if it is used, all values (small and large) will be visible on the "
"graph.</p>"));
QToolButton* const linHistoButton = new QToolButton(scaleBox);
linHistoButton->setToolTip( i18n( "Linear" ) );
linHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-linear")));
linHistoButton->setCheckable(true);
d->scaleBG->addButton(linHistoButton, TimeLineWidget::LinScale);
QToolButton* const logHistoButton = new QToolButton(scaleBox);
logHistoButton->setToolTip( i18n( "Logarithmic" ) );
logHistoButton->setIcon(QIcon::fromTheme(QLatin1String("view-object-histogram-logarithmic")));
logHistoButton->setCheckable(true);
d->scaleBG->addButton(logHistoButton, TimeLineWidget::LogScale);
hlay2->setContentsMargins(QMargins());
hlay2->setSpacing(0);
hlay2->addWidget(linHistoButton);
hlay2->addWidget(logHistoButton);
hlay->setContentsMargins(QMargins());
hlay->setSpacing(spacing);
hlay->addWidget(label1);
hlay->addWidget(d->timeUnitCB);
hlay->addItem(new QSpacerItem(10, 10, QSizePolicy::Expanding, QSizePolicy::Minimum));
hlay->addWidget(scaleBox);
// ---------------------------------------------------------------
d->timeLineWidget = new TimeLineWidget(panel);
d->scrollBar = new QScrollBar(panel);
d->scrollBar->setOrientation(Qt::Horizontal);
d->scrollBar->setMinimum(0);
d->scrollBar->setSingleStep(1);
d->cursorDateLabel = new DAdjustableLabel(panel);
d->cursorCountLabel = new QLabel(panel);
d->cursorCountLabel->setAlignment(Qt::AlignRight);
// ---------------------------------------------------------------
DHBox* const hbox2 = new DHBox(panel);
hbox2->setContentsMargins(QMargins());
hbox2->setSpacing(spacing);
d->resetButton = new QToolButton(hbox2);
d->resetButton->setIcon(QIcon::fromTheme(QLatin1String("document-revert")));
d->resetButton->setToolTip(i18n("Clear current selection"));
d->resetButton->setWhatsThis(i18n("If you press this button, the current date selection on the time-line will be cleared."));
d->nameEdit = new QLineEdit(hbox2);
d->nameEdit->setClearButtonEnabled(true);
d->nameEdit->setWhatsThis(i18n("Enter the name of the current dates search to save in the "
"\"Searches\" view"));
d->saveButton = new QToolButton(hbox2);
d->saveButton->setIcon(QIcon::fromTheme(QLatin1String("document-save")));
d->saveButton->setEnabled(false);
d->saveButton->setToolTip(i18n("Save current selection to a new virtual Album"));
d->saveButton->setWhatsThis(i18n("If you press this button, the dates selected on the time-line will be "
"saved to a new search virtual Album using the name set on the left."));
// ---------------------------------------------------------------
grid->addWidget(hbox1, 0, 0, 1, 4);
grid->addWidget(d->cursorDateLabel, 1, 0, 1, 3);
grid->addWidget(d->cursorCountLabel, 1, 3, 1, 1);
grid->addWidget(d->timeLineWidget, 2, 0, 1, 4);
grid->addWidget(d->scrollBar, 3, 0, 1, 4);
grid->addWidget(hbox2, 4, 0, 1, 4);
grid->setColumnStretch(2, 10);
grid->setContentsMargins(spacing, spacing, spacing, spacing);
grid->setSpacing(spacing);
// ---------------------------------------------------------------
d->timeLineFolderView = new EditableSearchTreeView(this, searchModel, searchModificationHelper);
d->timeLineFolderView->setConfigGroup(getConfigGroup());
d->timeLineFolderView->filteredModel()->listTimelineSearches();
d->timeLineFolderView->filteredModel()->setListTemporarySearches(false);
d->timeLineFolderView->setAlbumManagerCurrentAlbum(false);
d->searchDateBar = new SearchTextBar(this, QLatin1String("TimeLineViewSearchDateBar"));
d->searchDateBar->setModel(d->timeLineFolderView->filteredModel(),
AbstractAlbumModel::AlbumIdRole,
AbstractAlbumModel::AlbumTitleRole);
d->searchDateBar->setFilterModel(d->timeLineFolderView->albumFilterModel());
vlay->addWidget(panel);
vlay->addWidget(d->timeLineFolderView);
vlay->addItem(new QSpacerItem(spacing, spacing, QSizePolicy::Minimum, QSizePolicy::Minimum));
vlay->addWidget(d->searchDateBar);
vlay->setContentsMargins(QMargins());
vlay->setSpacing(0);
// ---------------------------------------------------------------
connect(AlbumManager::instance(), SIGNAL(signalDatesMapDirty(QMap<QDateTime,int>)),
d->timeLineWidget, SLOT(slotDatesMap(QMap<QDateTime,int>)));
connect(d->timeLineFolderView, SIGNAL(currentAlbumChanged(Album*)),
this, SLOT(slotAlbumSelected(Album*)));
connect(d->timeUnitCB, SIGNAL(activated(int)),
this, SLOT(slotTimeUnitChanged(int)));
connect(d->scaleBG, SIGNAL(buttonReleased(int)),
this, SLOT(slotScaleChanged(int)));
connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()),
this, SLOT(slotInit()));
connect(d->timeLineWidget, SIGNAL(signalCursorPositionChanged()),
this, SLOT(slotCursorPositionChanged()));
connect(d->timeLineWidget, SIGNAL(signalSelectionChanged()),
this, SLOT(slotSelectionChanged()));
connect(d->timeLineWidget, SIGNAL(signalRefDateTimeChanged()),
this, SLOT(slotRefDateTimeChanged()));
connect(d->timer, SIGNAL(timeout()),
this, SLOT(slotUpdateCurrentDateSearchAlbum()));
connect(d->resetButton, SIGNAL(clicked()),
this, SLOT(slotResetSelection()));
connect(d->saveButton, SIGNAL(clicked()),
this, SLOT(slotSaveSelection()));
connect(d->scrollBar, SIGNAL(valueChanged(int)),
this, SLOT(slotScrollBarValueChanged(int)));
connect(d->nameEdit, SIGNAL(textChanged(QString)),
this, SLOT(slotCheckAboutSelection()));
connect(d->nameEdit, SIGNAL(returnPressed()),
d->saveButton, SLOT(animateClick()));
}
TimelineSideBarWidget::~TimelineSideBarWidget()
{
delete d;
}
void TimelineSideBarWidget::slotInit()
{
// Date Maps are loaded from AlbumManager to TimeLineWidget after than GUI is initialized.
// AlbumManager query Date KIO slave to stats items from database and it can take a while.
// We waiting than TimeLineWidget is ready before to set last config from users.
loadState();
disconnect(d->timeLineWidget, SIGNAL(signalDateMapChanged()),
this, SLOT(slotInit()));
connect(d->timeLineWidget, SIGNAL(signalDateMapChanged()),
this, SLOT(slotCursorPositionChanged()));
}
void TimelineSideBarWidget::setActive(bool active)
{
if (active)
{
if (!d->currentTimelineSearch)
{
d->currentTimelineSearch = d->timeLineFolderView->currentAlbum();
}
if (d->currentTimelineSearch)
{
AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << d->currentTimelineSearch);
}
else
{
slotUpdateCurrentDateSearchAlbum();
}
}
}
void TimelineSideBarWidget::doLoadState()
{
KConfigGroup group = getConfigGroup();
d->timeUnitCB->setCurrentIndex(group.readEntry(d->configHistogramTimeUnitEntry, (int)TimeLineWidget::Month));
slotTimeUnitChanged(d->timeUnitCB->currentIndex());
int id = group.readEntry(d->configHistogramScaleEntry, (int)TimeLineWidget::LinScale);
if (d->scaleBG->button(id))
{
d->scaleBG->button(id)->setChecked(true);
}
slotScaleChanged(d->scaleBG->checkedId());
QDateTime now = QDateTime::currentDateTime();
d->timeLineWidget->setCursorDateTime(group.readEntry(d->configCursorPositionEntry, now));
d->timeLineWidget->setCurrentIndex(d->timeLineWidget->indexForCursorDateTime());
d->timeLineFolderView->loadState();
}
void TimelineSideBarWidget::doSaveState()
{
KConfigGroup group = getConfigGroup();
group.writeEntry(d->configHistogramTimeUnitEntry, d->timeUnitCB->currentIndex());
group.writeEntry(d->configHistogramScaleEntry, d->scaleBG->checkedId());
group.writeEntry(d->configCursorPositionEntry, d->timeLineWidget->cursorDateTime());
d->timeLineFolderView->saveState();
group.sync();
}
void TimelineSideBarWidget::applySettings()
{
// nothing to do here right now
}
void TimelineSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
d->timeLineFolderView->setCurrentAlbums(album);
}
const QIcon TimelineSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("player-time"));
}
const QString TimelineSideBarWidget::getCaption()
{
return i18n("Timeline");
}
void TimelineSideBarWidget::slotRefDateTimeChanged()
{
d->scrollBar->blockSignals(true);
d->scrollBar->setMaximum(d->timeLineWidget->totalIndex()-1);
d->scrollBar->setValue(d->timeLineWidget->indexForRefDateTime()-1);
d->scrollBar->blockSignals(false);
}
void TimelineSideBarWidget::slotTimeUnitChanged(int mode)
{
d->timeLineWidget->setTimeUnit((TimeLineWidget::TimeUnit)mode);
}
void TimelineSideBarWidget::slotScrollBarValueChanged(int val)
{
d->timeLineWidget->setCurrentIndex(val);
}
void TimelineSideBarWidget::slotScaleChanged(int mode)
{
d->timeLineWidget->setScaleMode((TimeLineWidget::ScaleMode)mode);
}
void TimelineSideBarWidget::slotCursorPositionChanged()
{
QString txt;
int val = d->timeLineWidget->cursorInfo(txt);
d->cursorDateLabel->setAdjustedText(txt);
d->cursorCountLabel->setText((val == 0) ? i18n("no item")
: i18np("1 item", "%1 items", val));
}
void TimelineSideBarWidget::slotSelectionChanged()
{
d->timer->setSingleShot(true);
d->timer->start(500);
}
/** Called from d->timer event.*/
void TimelineSideBarWidget::slotUpdateCurrentDateSearchAlbum()
{
slotCheckAboutSelection();
int totalCount = 0;
DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount);
d->currentTimelineSearch = d->searchModificationHelper->
slotCreateTimeLineSearch(SAlbum::getTemporaryTitle(DatabaseSearch::TimeLineSearch), dateRanges, true);
d->timeLineFolderView->setCurrentAlbum(0); // "temporary" search is not listed in view
}
void TimelineSideBarWidget::slotSaveSelection()
{
QString name = d->nameEdit->text();
int totalCount = 0;
DateRangeList dateRanges = d->timeLineWidget->selectedDateRange(totalCount);
d->currentTimelineSearch = d->searchModificationHelper->slotCreateTimeLineSearch(name, dateRanges);
}
void TimelineSideBarWidget::slotAlbumSelected(Album* album)
{
if (d->currentTimelineSearch == album)
{
return;
}
SAlbum* const salbum = dynamic_cast<SAlbum*>(album);
if (!salbum)
{
return;
}
d->currentTimelineSearch = salbum;
AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << salbum);
SearchXmlReader reader(salbum->query());
// The timeline query consists of groups, with two date time fields each
DateRangeList list;
while (!reader.atEnd())
{
// read groups
if (reader.readNext() == SearchXml::Group)
{
QDateTime start, end;
int numberOfFields = 0;
while (!reader.atEnd())
{
// read fields
reader.readNext();
if (reader.isEndElement())
{
break;
}
if (reader.isFieldElement())
{
if (numberOfFields == 0)
{
start = reader.valueToDateTime();
}
else if (numberOfFields == 1)
{
end = reader.valueToDateTime();
}
++numberOfFields;
}
}
if (numberOfFields)
{
list << DateRange(start, end);
}
}
}
d->timeLineWidget->setSelectedDateRange(list);
}
void TimelineSideBarWidget::slotResetSelection()
{
d->timeLineWidget->slotResetSelection();
slotCheckAboutSelection();
AlbumManager::instance()->setCurrentAlbums(QList<Album*>());
}
void TimelineSideBarWidget::slotCheckAboutSelection()
{
int totalCount = 0;
DateRangeList list = d->timeLineWidget->selectedDateRange(totalCount);
if (!list.isEmpty())
{
d->nameEdit->setEnabled(true);
if (!d->nameEdit->text().isEmpty())
{
d->saveButton->setEnabled(true);
}
}
else
{
d->nameEdit->setEnabled(false);
d->saveButton->setEnabled(false);
}
}
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN SearchSideBarWidget::Private
{
public:
explicit Private()
: searchSearchBar(0),
searchTreeView(0),
searchTabHeader(0)
{
}
SearchTextBar* searchSearchBar;
NormalSearchTreeView* searchTreeView;
SearchTabHeader* searchTabHeader;
};
SearchSideBarWidget::SearchSideBarWidget(QWidget* const parent,
SearchModel* const searchModel,
SearchModificationHelper* const searchModificationHelper)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("Search Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F6);
QVBoxLayout* const layout = new QVBoxLayout(this);
d->searchTabHeader = new SearchTabHeader(this);
d->searchTreeView = new NormalSearchTreeView(this, searchModel, searchModificationHelper);
d->searchTreeView->setConfigGroup(getConfigGroup());
d->searchTreeView->filteredModel()->listNormalSearches();
d->searchTreeView->filteredModel()->setListTemporarySearches(true);
d->searchTreeView->setAlbumManagerCurrentAlbum(true);
d->searchSearchBar = new SearchTextBar(this, QLatin1String("DigikamViewSearchSearchBar"));
d->searchSearchBar->setModel(d->searchTreeView->filteredModel(),
AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole);
d->searchSearchBar->setFilterModel(d->searchTreeView->albumFilterModel());
layout->addWidget(d->searchTabHeader);
layout->addWidget(d->searchTreeView);
layout->setStretchFactor(d->searchTreeView, 1);
layout->addWidget(d->searchSearchBar);
connect(d->searchTreeView, SIGNAL(newSearch()),
d->searchTabHeader, SLOT(newAdvancedSearch()));
connect(d->searchTreeView, SIGNAL(editSearch(SAlbum*)),
d->searchTabHeader, SLOT(editSearch(SAlbum*)));
connect(d->searchTreeView, SIGNAL(currentAlbumChanged(Album*)),
d->searchTabHeader, SLOT(selectedSearchChanged(Album*)));
connect(d->searchTabHeader, SIGNAL(searchShallBeSelected(QList<Album*>)),
d->searchTreeView, SLOT(setCurrentAlbums(QList<Album*>)));
}
SearchSideBarWidget::~SearchSideBarWidget()
{
delete d;
}
void SearchSideBarWidget::setActive(bool active)
{
if (active)
{
AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << d->searchTreeView->currentAlbum());
d->searchTabHeader->selectedSearchChanged(d->searchTreeView->currentAlbum());
}
}
void SearchSideBarWidget::doLoadState()
{
d->searchTreeView->loadState();
}
void SearchSideBarWidget::doSaveState()
{
d->searchTreeView->saveState();
}
void SearchSideBarWidget::applySettings()
{
}
void SearchSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
d->searchTreeView->setCurrentAlbums(album);
}
const QIcon SearchSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("edit-find"));
}
const QString SearchSideBarWidget::getCaption()
{
- return i18nc("Avanced search images, access stored searches", "Search");
+ return i18nc("Advanced search images, access stored searches", "Search");
}
void SearchSideBarWidget::newKeywordSearch()
{
d->searchTabHeader->newKeywordSearch();
}
void SearchSideBarWidget::newAdvancedSearch()
{
d->searchTabHeader->newAdvancedSearch();
}
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN FuzzySearchSideBarWidget::Private
{
public:
explicit Private()
: fuzzySearchView(0),
searchModificationHelper(0)
{
}
FuzzySearchView* fuzzySearchView;
SearchModificationHelper* searchModificationHelper;
};
FuzzySearchSideBarWidget::FuzzySearchSideBarWidget(QWidget* const parent,
SearchModel* const searchModel,
SearchModificationHelper* const searchModificationHelper)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("Fuzzy Search Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F7);
d->fuzzySearchView = new FuzzySearchView(searchModel, searchModificationHelper, this);
d->fuzzySearchView->setConfigGroup(getConfigGroup());
QVBoxLayout* const layout = new QVBoxLayout(this);
layout->addWidget(d->fuzzySearchView);
}
FuzzySearchSideBarWidget::~FuzzySearchSideBarWidget()
{
delete d;
}
void FuzzySearchSideBarWidget::setActive(bool active)
{
d->fuzzySearchView->setActive(active);
if (active)
{
AlbumManager::instance()->setCurrentAlbums(QList<Album*>() << d->fuzzySearchView->currentAlbum());
}
emit signalActive(active);
}
void FuzzySearchSideBarWidget::doLoadState()
{
d->fuzzySearchView->loadState();
}
void FuzzySearchSideBarWidget::doSaveState()
{
d->fuzzySearchView->saveState();
}
void FuzzySearchSideBarWidget::applySettings()
{
}
void FuzzySearchSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
SAlbum* const salbum = dynamic_cast<SAlbum*>(album.first());
d->fuzzySearchView->setCurrentAlbum(salbum);
}
const QIcon FuzzySearchSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("tools-wizard"));
}
const QString FuzzySearchSideBarWidget::getCaption()
{
- return i18nc("Fuzzy Search images, as dupplicates, sketch, searches by similarities", "Similarity");
+ return i18nc("Fuzzy Search images, as duplicates, sketch, searches by similarities", "Similarity");
}
void FuzzySearchSideBarWidget::newDuplicatesSearch(PAlbum* album)
{
d->fuzzySearchView->newDuplicatesSearch(album);
}
void FuzzySearchSideBarWidget::newDuplicatesSearch(const QList<PAlbum*>& albums)
{
d->fuzzySearchView->newDuplicatesSearch(albums);
}
void FuzzySearchSideBarWidget::newDuplicatesSearch(const QList<TAlbum*>& albums)
{
d->fuzzySearchView->newDuplicatesSearch(albums);
}
void FuzzySearchSideBarWidget::newSimilarSearch(const ImageInfo& imageInfo)
{
if (imageInfo.isNull())
{
return;
}
d->fuzzySearchView->setImageInfo(imageInfo);
}
// -----------------------------------------------------------------------------
#ifdef HAVE_MARBLE
class Q_DECL_HIDDEN GPSSearchSideBarWidget::Private
{
public:
explicit Private()
: gpsSearchView(0)
{
}
GPSSearchView* gpsSearchView;
};
GPSSearchSideBarWidget::GPSSearchSideBarWidget(QWidget* const parent,
SearchModel* const searchModel,
SearchModificationHelper* const searchModificationHelper,
ImageFilterModel* const imageFilterModel,
QItemSelectionModel* const itemSelectionModel)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("GPS Search Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F8);
d->gpsSearchView = new GPSSearchView(this, searchModel, searchModificationHelper, imageFilterModel, itemSelectionModel);
d->gpsSearchView->setConfigGroup(getConfigGroup());
QScrollArea* const scrollArea = new QScrollArea(this);
QVBoxLayout* const layout = new QVBoxLayout(this);
layout->addWidget(scrollArea);
scrollArea->setWidget(d->gpsSearchView);
scrollArea->setWidgetResizable(true);
connect(d->gpsSearchView, SIGNAL(signalMapSoloItems(QList<qlonglong>,QString)),
this, SIGNAL(signalMapSoloItems(QList<qlonglong>,QString)));
}
GPSSearchSideBarWidget::~GPSSearchSideBarWidget()
{
delete d;
}
void GPSSearchSideBarWidget::setActive(bool active)
{
d->gpsSearchView->setActive(active);
}
void GPSSearchSideBarWidget::doLoadState()
{
d->gpsSearchView->loadState();
}
void GPSSearchSideBarWidget::doSaveState()
{
d->gpsSearchView->saveState();
}
void GPSSearchSideBarWidget::applySettings()
{
}
void GPSSearchSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
d->gpsSearchView->changeAlbumFromHistory(dynamic_cast<SAlbum*>(album.first()));
}
const QIcon GPSSearchSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("globe"));
}
const QString GPSSearchSideBarWidget::getCaption()
{
return i18nc("Search images on a map", "Map");
}
#endif // HAVE_MARBLE
// -----------------------------------------------------------------------------
class Q_DECL_HIDDEN PeopleSideBarWidget::Private : public TagViewSideBarWidget::Private
{
public:
explicit Private()
{
personIcon = 0;
textLabel = 0;
rescanButton = 0;
searchModificationHelper = 0;
}
QLabel* personIcon;
QLabel* textLabel;
QPushButton* rescanButton;
SearchModificationHelper* searchModificationHelper;
};
PeopleSideBarWidget::PeopleSideBarWidget(QWidget* const parent,
TagModel* const model,
SearchModificationHelper* const searchModificationHelper)
: SidebarWidget(parent),
d(new Private)
{
setObjectName(QLatin1String("People Sidebar"));
setProperty("Shortcut", Qt::META + Qt::CTRL + Qt::Key_F9);
d->searchModificationHelper = searchModificationHelper;
QVBoxLayout* const layout = new QVBoxLayout;
QHBoxLayout* const hlay = new QHBoxLayout;
d->tagFolderView = new TagFolderView(this, model);
d->tagFolderView->setConfigGroup(getConfigGroup());
d->tagFolderView->setExpandNewCurrentItem(true);
d->tagFolderView->setAlbumManagerCurrentAlbum(true);
d->tagFolderView->setShowDeleteFaceTagsAction(true);
d->tagFolderView->filteredModel()->listOnlyTagsWithProperty(TagPropertyName::person());
d->tagFolderView->filteredModel()->setFilterBehavior(AlbumFilterModel::StrictFiltering);
d->tagSearchBar = new SearchTextBar(this, QLatin1String("DigikamViewPeopleSearchBar"));
d->tagSearchBar->setHighlightOnResult(true);
d->tagSearchBar->setModel(d->tagFolderView->filteredModel(),
AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole);
d->tagSearchBar->setFilterModel(d->tagFolderView->albumFilterModel());
d->rescanButton = new QPushButton;
d->rescanButton->setText(i18n("Scan collection for faces"));
d->personIcon = new QLabel;
d->personIcon->setPixmap(QIcon::fromTheme(QLatin1String("edit-image-face-show")).pixmap(48));
d->textLabel = new QLabel(i18n("People Tags"));
hlay->addWidget(d->personIcon);
hlay->addWidget(d->textLabel);
layout->addLayout(hlay);
layout->addWidget(d->rescanButton);
layout->addWidget(d->tagFolderView);
layout->addWidget(d->tagSearchBar);
setLayout(layout);
connect(d->tagFolderView, SIGNAL(signalFindDuplicates(QList<TAlbum*>)),
this, SIGNAL(signalFindDuplicates(QList<TAlbum*>)));
connect(d->rescanButton, SIGNAL(pressed()),
this, SLOT(slotScanForFaces()) );
}
PeopleSideBarWidget::~PeopleSideBarWidget()
{
delete d;
}
void PeopleSideBarWidget::slotInit()
{
loadState();
}
void PeopleSideBarWidget::setActive(bool active)
{
emit requestFaceMode(active);
if (active)
{
d->tagFolderView->setCurrentAlbums(QList<Album*>() << d->tagFolderView->currentAlbum());
}
}
void PeopleSideBarWidget::doLoadState()
{
d->tagFolderView->loadState();
}
void PeopleSideBarWidget::doSaveState()
{
d->tagFolderView->saveState();
}
void PeopleSideBarWidget::applySettings()
{
}
void PeopleSideBarWidget::changeAlbumFromHistory(const QList<Album*>& album)
{
d->tagFolderView->setCurrentAlbums(album);
}
void PeopleSideBarWidget::slotScanForFaces()
{
FaceScanDialog dialog;
if (dialog.exec() == QDialog::Accepted)
{
FacesDetector* const tool = new FacesDetector(dialog.settings());
tool->start();
}
}
const QIcon PeopleSideBarWidget::getIcon()
{
return QIcon::fromTheme(QLatin1String("edit-image-face-show"));
}
const QString PeopleSideBarWidget::getCaption()
{
return i18nc("Browse images sorted by depicted people", "People");
}
} // namespace Digikam
diff --git a/core/libs/album/albummodificationhelper.cpp b/core/libs/album/albummodificationhelper.cpp
index 40c26a5a0a..e02ca2c590 100644
--- a/core/libs/album/albummodificationhelper.cpp
+++ b/core/libs/album/albummodificationhelper.cpp
@@ -1,345 +1,345 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2000-12-05
* Description : helper class used to modify physical albums in views
*
* Copyright (C) 2009-2011 by Johannes Wienke <languitar at semipol dot de>
* Copyright (C) 2014-2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "albummodificationhelper.h"
// Qt includes
#include <QApplication>
#include <QAction>
#include <QInputDialog>
#include <QUrl>
#include <QMessageBox>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "albummanager.h"
#include "albumpropsedit.h"
#include "applicationsettings.h"
#include "collectionmanager.h"
#include "deletedialog.h"
#include "dio.h"
#include "digikamview.h"
#include "digikamapp.h"
#include "coredb.h"
#include "coredbaccess.h"
namespace Digikam
{
class Q_DECL_HIDDEN AlbumModificationHelper::Private
{
public:
explicit Private()
: dialogParent(0)
{
}
QWidget* dialogParent;
};
AlbumModificationHelper::AlbumModificationHelper(QObject* const parent, QWidget* const dialogParent)
: QObject(parent), d(new Private)
{
d->dialogParent = dialogParent;
}
AlbumModificationHelper::~AlbumModificationHelper()
{
delete d;
}
void AlbumModificationHelper::bindAlbum(QAction* const action, PAlbum* const album) const
{
action->setData(QVariant::fromValue(AlbumPointer<PAlbum>(album)));
}
PAlbum* AlbumModificationHelper::boundAlbum(QObject* const sender) const
{
QAction* action = 0;
if ( (action = qobject_cast<QAction*>(sender)) )
{
return action->data().value<AlbumPointer<PAlbum> >();
}
return 0;
}
PAlbum* AlbumModificationHelper::slotAlbumNew()
{
return slotAlbumNew(boundAlbum(sender()));
}
PAlbum* AlbumModificationHelper::slotAlbumNew(PAlbum* parent)
{
if (!parent)
{
qCWarning(DIGIKAM_GENERAL_LOG) << "No parent album given";
return 0;
}
ApplicationSettings* settings = ApplicationSettings::instance();
if (!settings)
{
qCWarning(DIGIKAM_GENERAL_LOG) << "could not get Album Settings";
return 0;
}
/*
QDir libraryDir(settings->getAlbumLibraryPath());
if(!libraryDir.exists())
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("The album library has not been set correctly.\n"
"Select \"Configure Digikam\" from the Settings "
"menu and choose a folder to use for the album "
"library."));
return;
}
*/
// if we create an album under root, need to supply the album root path.
QString albumRootPath;
albumRootPath = CollectionManager::instance()->oneAlbumRootPath();
QString title;
QString comments;
QString category;
QDate date;
QStringList albumCategories;
int parentSelector;
if (!AlbumPropsEdit::createNew(parent, title, comments, date, category,
albumCategories, parentSelector))
{
return 0;
}
QStringList oldAlbumCategories(ApplicationSettings::instance()->getAlbumCategoryNames());
if (albumCategories != oldAlbumCategories)
{
ApplicationSettings::instance()->setAlbumCategoryNames(albumCategories);
}
QString errMsg;
PAlbum* album = 0;
if (parent->isRoot() || parentSelector == 1)
{
album = AlbumManager::instance()->createPAlbum(albumRootPath, title, comments,
date, category, errMsg);
}
else
{
album = AlbumManager::instance()->createPAlbum(parent, title, comments,
date, category, errMsg);
}
if (!album)
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg);
return 0;
}
return album;
}
void AlbumModificationHelper::slotAlbumDelete()
{
slotAlbumDelete(boundAlbum(sender()));
}
void AlbumModificationHelper::slotAlbumDelete(PAlbum* album)
{
if (!album || album->isRoot() || album->isAlbumRoot())
{
return;
}
// find subalbums
QList<QUrl> childrenList;
addAlbumChildrenToList(childrenList, album);
DeleteDialog dialog(d->dialogParent);
// All subalbums will be presented in the list as well
if (!dialog.confirmDeleteList(childrenList,
childrenList.size() == 1 ?
DeleteDialogMode::Albums : DeleteDialogMode::Subalbums,
DeleteDialogMode::UserPreference))
{
return;
}
bool useTrash = !dialog.shouldDelete();
DIO::del(album, useTrash);
}
void AlbumModificationHelper::slotAlbumRename()
{
slotAlbumRename(boundAlbum(sender()));
}
void AlbumModificationHelper::slotAlbumRename(PAlbum* album)
{
if (!album)
{
return;
}
QString oldTitle(album->title());
bool ok;
QString title = QInputDialog::getText(d->dialogParent,
i18n("Rename Album (%1)", oldTitle),
i18n("Enter new album name:"),
QLineEdit::Normal,
oldTitle,
&ok);
if (!ok)
{
return;
}
if (title != oldTitle)
{
QString errMsg;
if (!AlbumManager::instance()->renamePAlbum(album, title, errMsg))
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(), errMsg);
}
}
}
void AlbumModificationHelper::addAlbumChildrenToList(QList<QUrl>& list, Album* const album)
{
// simple recursive helper function
if (album)
{
if (!list.contains(album->databaseUrl()))
{
list.append(album->databaseUrl());
}
AlbumIterator it(album);
while (it.current())
{
addAlbumChildrenToList(list, *it);
++it;
}
}
}
void AlbumModificationHelper::slotAlbumEdit()
{
slotAlbumEdit(boundAlbum(sender()));
}
void AlbumModificationHelper::slotAlbumEdit(PAlbum* album)
{
if (!album || album->isRoot() || album->isAlbumRoot())
{
return;
}
QString oldTitle(album->title());
QString oldComments(album->caption());
QString oldCategory(album->category());
QDate oldDate(album->date());
QStringList oldAlbumCategories(ApplicationSettings::instance()->getAlbumCategoryNames());
QString title, comments, category;
QDate date;
QStringList albumCategories;
if (AlbumPropsEdit::editProps(album, title, comments, date,
category, albumCategories))
{
if (comments != oldComments)
{
album->setCaption(comments);
}
if (date != oldDate && date.isValid())
{
album->setDate(date);
}
if (category != oldCategory)
{
album->setCategory(category);
}
ApplicationSettings::instance()->setAlbumCategoryNames(albumCategories);
// Do this last : so that if anything else changed we can
- // successfuly save to the db with the old name
+ // successfully save to the db with the old name
if (title != oldTitle)
{
QString errMsg;
if (!AlbumManager::instance()->renamePAlbum(album, title, errMsg))
{
QMessageBox::critical(d->dialogParent, qApp->applicationName(), errMsg);
}
}
// Resorting the tree View after changing metadata
DigikamApp::instance()->view()->slotSortAlbums(ApplicationSettings::instance()->getAlbumSortRole());
}
}
void AlbumModificationHelper::slotAlbumResetIcon(PAlbum* album)
{
if (!album)
{
return;
}
QString err;
AlbumManager::instance()->updatePAlbumIcon(album, 0, err);
}
void AlbumModificationHelper::slotAlbumResetIcon()
{
slotAlbumResetIcon(boundAlbum(sender()));
}
} // namespace Digikam
diff --git a/core/libs/database/collection/collectionmanager.h b/core/libs/database/collection/collectionmanager.h
index 708c0dbe39..3ae4518c4d 100644
--- a/core/libs/database/collection/collectionmanager.h
+++ b/core/libs/database/collection/collectionmanager.h
@@ -1,296 +1,296 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-04-09
* Description : Collection location management
*
* Copyright (C) 2007-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_COLLECTION_MANAGER_H
#define DIGIKAM_COLLECTION_MANAGER_H
// Qt includes
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QObject>
// Local includes
#include "digikam_export.h"
namespace Digikam
{
class CollectionLocation;
class AlbumRootChangeset;
class CollectionManagerPrivate;
class DIGIKAM_DATABASE_EXPORT CollectionManager : public QObject
{
Q_OBJECT
public:
static CollectionManager* instance();
static void cleanUp();
/**
* Disables the collection watch.
* It will be reenabled as soon as refresh() is called
* or any other action triggered.
*/
void setWatchDisabled();
/**
* Clears all locations and re-reads the lists of collection locations.
* Enables the watch.
*/
void refresh();
/** CollectionLocation objects returned are simple data containers.
* If the corresponding location is returned, the data is still safe to access,
* but does not represent anything.
* Therefore, do not store returned objects, but prefer to retrieve them freshly.
*/
/**
* Add the given file system location as new collection location.
* Type and availability will be detected.
* On failure returns null. This would be the case if the given
* url is already contained in another collection location.
* You may pass an optional user-visible label that will be stored in the database.
* The label has no further meaning and can be freely chosen.
*/
CollectionLocation addLocation(const QUrl& fileUrl, const QString& label = QString());
CollectionLocation addNetworkLocation(const QUrl& fileUrl, const QString& label = QString());
enum LocationCheckResult
{
/// The check did not succeed, status unknown
LocationInvalidCheck,
/// All right. The accompanying message may be empty.
LocationAllRight,
/// Location can be added, but the user should be aware of a problem
LocationHasProblems,
/// Adding the location will fail (e.g. there is already a location for the path)
LocationNotAllowed
};
/**
* Analyzes the given file path. Creates an info message
* describing the result of identification or possible problems.
* The text is i18n'ed and can be presented to the user.
* The returned result enum describes the test result.
*/
LocationCheckResult checkLocation(const QUrl& fileUrl, QList<CollectionLocation> assumeDeleted,
QString* message = 0, QString* suggestedMessageIconName = 0);
LocationCheckResult checkNetworkLocation(const QUrl& fileUrl, QList<CollectionLocation> assumeDeleted,
QString* message = 0, QString* suggestedMessageIconName = 0);
/**
* Removes the given location. This means that all images contained on the
* location will be removed from the database, all tags will be lost.
*/
void removeLocation(const CollectionLocation& location);
/**
* Sets the label of the given location
*/
void setLabel(const CollectionLocation& location, const QString& label);
/**
* Changes the CollectionLocation::Type of the given location
*/
void changeType(const CollectionLocation& location, int type);
/**
* Checks the locations of type HardWired. If one of these is not available currently,
- * it is added to the list of disappared locations.
+ * it is added to the list of disappeared locations.
* This case may happen if a file system is changed, a backup restored or other actions
* taken that change the UUID, although the data may still be available and mounted.
* If there are hard-wired volumes available which are candidates for a newly appeared
* volume (in fact those that do not contain any collections currently), they are
* added to the map, identifier -> i18n'ed user presentable description.
* The identifier can be used for changeVolume.
*/
QList<CollectionLocation> checkHardWiredLocations();
/**
* For a given disappeared location (retrieved from checkHardWiredLocations())
* retrieve a user-presentable technical description (excluding the CollectionLocation's label)
* and a list of identifiers and corresponding user presentable strings of candidates
* to where the given location may have been moved.
*/
void migrationCandidates(const CollectionLocation& disappearedLocation,
QString* const technicalDescription,
QStringList* const candidateIdentifiers,
QStringList* const candidateDescriptions);
/**
* Migrates the existing collection to a new volume, identified by an internal identifier
* as returned by checkHardWiredLocations().
* Use this _only_ to react to changes like those detailed for checkHardWiredLocations;
* the actual data pointed to shall be unchanged.
*/
void migrateToVolume(const CollectionLocation& location, const QString& identifier);
/**
* Returns a list of all CollectionLocations stored in the database
*/
QList<CollectionLocation> allLocations();
/**
* Returns a list of all currently available CollectionLocations
*/
QList<CollectionLocation> allAvailableLocations();
/**
* Returns a list of the paths of all currently available CollectionLocations
*/
QStringList allAvailableAlbumRootPaths();
/**
* Returns the location for the given album root id
*/
CollectionLocation locationForAlbumRootId(int id);
/**
* Returns the CollectionLocation that contains the given album root.
* The path must be an album root with isAlbumRoot() == true.
* Returns 0 if no collection location matches.
* Only available (or hidden, but available) locations are guaranteed to be found.
*/
CollectionLocation locationForAlbumRoot(const QUrl& fileUrl);
CollectionLocation locationForAlbumRootPath(const QString& albumRootPath);
/**
* Returns the CollectionLocation that contains the given path.
* Equivalent to calling locationForAlbumRoot(albumRoot(fileUrl)).
* Only available (or hidden, but available) locations are guaranteed to be found.
*/
CollectionLocation locationForUrl(const QUrl& fileUrl);
CollectionLocation locationForPath(const QString& filePath);
/**
* Returns the album root path for the location with the given id.
* Returns a null QString if the location does not exist or is not available.
*/
QString albumRootPath(int id);
/**
* Returns the album root label for the location with the given id.
* Returns a null QString if the location does not exist or is not available.
*/
QString albumRootLabel(int id);
/**
* For a given path, the part of the path that forms the album root is returned,
* ending without a slash. Example: "/media/fotos/Paris 2007" gives "/media/fotos".
* Only available (or hidden, but available) album roots are guaranteed to be found.
*/
QUrl albumRoot(const QUrl& fileUrl);
QString albumRootPath(const QUrl& fileUrl);
QString albumRootPath(const QString& filePath);
/**
* Returns true if the given path forms an album root.
* It will return false if the path is a path below an album root,
* or if the path does not belong to an album root.
* Example: "/media/fotos/Paris 2007" is an album with album root "/media/fotos".
* "/media/fotos" returns true, "/media/fotos/Paris 2007" and "/media" return false.
* Only available (or hidden, but available) album roots are guaranteed to be found.
*/
bool isAlbumRoot(const QUrl& fileUrl);
/// the file path should not end with the directory slash. Using CoreDbUrl's method is fine.
bool isAlbumRoot(const QString& filePath);
/**
* Returns the album part of the given file path, i.e. the album root path
* at the beginning is removed and the second part, starting with "/", ending without a slash,
* is returned. Example: "/media/fotos/Paris 2007" gives "/Paris 2007"
* Returns a null QString if the file path is not located in an album root.
* Returns "/" if the file path is an album root.
* Note that trailing slashes are removed in the return value, regardless if there was
* one or not.
* Note that you have to feed a path/url pointing to a directory. File names cannot
* be recognized as such by this method, and will be treated as a directory.
*/
QString album(const QUrl& fileUrl);
QString album(const QString& filePath);
QString album(const CollectionLocation& location, const QUrl& fileUrl);
QString album(const CollectionLocation& location, const QString& filePath);
/**
* Returns just one album root, out of the list of available location,
* the one that is most suitable to serve as a default, e.g.
* to suggest as default place when the user wants to add files.
*/
QUrl oneAlbumRoot();
QString oneAlbumRootPath();
Q_SIGNALS:
/** Emitted when the status of a collection location changed.
* This means that the location became available, hidden or unavailable.
*
* An added location will change its status after addition,
* from Null to Available, Hidden or Unavailable.
*
* A removed location will change its status to Deleted
* during the removal; in this case, you shall not use the object
* passed with this signal with any method of CollectionManager.
*
* The second signal argument is of type CollectionLocation::Status
* and describes the status before the state change occurred
*/
void locationStatusChanged(const CollectionLocation& location, int oldStatus);
/** Emitted when the label of a collection location is changed */
void locationPropertiesChanged(const CollectionLocation& location);
private Q_SLOTS:
void deviceAdded(const QString&);
void deviceRemoved(const QString&);
void accessibilityChanged(bool, const QString&);
void slotAlbumRootChange(const AlbumRootChangeset& changeset);
private:
CollectionManager();
~CollectionManager();
static CollectionManager* m_instance;
void updateLocations();
void clear_locked();
Q_PRIVATE_SLOT(d, void slotTriggerUpdateVolumesList())
CollectionManagerPrivate* const d;
friend class CollectionManagerPrivate;
friend class CoreDbWatch;
friend class CoreDbAccess;
Q_SIGNALS: // private
void triggerUpdateVolumesList();
};
} // namespace Digikam
#endif // DIGIKAM_COLLECTION_MANAGER_H
diff --git a/core/libs/database/coredb/coredb.cpp b/core/libs/database/coredb/coredb.cpp
index 88c805c4eb..43f011694f 100644
--- a/core/libs/database/coredb/coredb.cpp
+++ b/core/libs/database/coredb/coredb.cpp
@@ -1,4981 +1,4981 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-06-18
* Description : Core database interface.
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2012 by Andi Clemens <andi dot clemens at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "coredb.h"
// C ANSI includes
extern "C"
{
#include <sys/time.h>
}
// C++ includes
#include <cstdio>
#include <cstdlib>
#include <ctime>
// Qt includes
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QVariant>
// KDE includes
#include <ksharedconfig.h>
#include <kconfiggroup.h>
// Local includes
#include "digikam_debug.h"
#include "coredbbackend.h"
#include "collectionmanager.h"
#include "collectionlocation.h"
#include "dbengineactiontype.h"
#include "tagscache.h"
#include "album.h"
namespace Digikam
{
class Q_DECL_HIDDEN CoreDB::Private
{
public:
explicit Private()
: db(0),
uniqueHashVersion(-1)
{
}
static const QString configGroupName;
static const QString configRecentlyUsedTags;
CoreDbBackend* db;
QList<int> recentlyAssignedTags;
int uniqueHashVersion;
public:
QString constructRelatedImagesSQL(bool fromOrTo, DatabaseRelation::Type type, bool boolean);
QList<qlonglong> execRelatedImagesQuery(DbEngineSqlQuery& query, qlonglong id, DatabaseRelation::Type type);
};
const QString CoreDB::Private::configGroupName(QLatin1String("CoreDB Settings"));
const QString CoreDB::Private::configRecentlyUsedTags(QLatin1String("Recently Used Tags"));
QString CoreDB::Private::constructRelatedImagesSQL(bool fromOrTo, DatabaseRelation::Type type, bool boolean)
{
QString sql;
if (fromOrTo)
{
sql = QString::fromUtf8("SELECT object FROM ImageRelations "
"INNER JOIN Images ON ImageRelations.object=Images.id "
"WHERE subject=? %1 AND status!=3 %2;");
}
else
{
sql = QString::fromUtf8("SELECT subject FROM ImageRelations "
"INNER JOIN Images ON ImageRelations.subject=Images.id "
"WHERE object=? %1 AND status!=3 %2;");
}
if (type != DatabaseRelation::UndefinedType)
{
sql = sql.arg(QString::fromUtf8("AND type=?"));
}
else
{
sql = sql.arg(QString());
}
if (boolean)
{
sql = sql.arg(QString::fromUtf8("LIMIT 1"));
}
else
{
sql = sql.arg(QString());
}
return sql;
}
QList<qlonglong> CoreDB::Private::execRelatedImagesQuery(DbEngineSqlQuery& query, qlonglong id, DatabaseRelation::Type type)
{
QVariantList values;
if (type == DatabaseRelation::UndefinedType)
{
db->execSql(query, id, &values);
}
else
{
db->execSql(query, id, type, &values);
}
QList<qlonglong> imageIds;
if (values.isEmpty())
{
return imageIds;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
imageIds << (*it).toInt();
}
return imageIds;
}
// --------------------------------------------------------
CoreDB::CoreDB(CoreDbBackend* const backend)
: d(new Private)
{
d->db = backend;
readSettings();
}
CoreDB::~CoreDB()
{
writeSettings();
delete d;
}
QList<AlbumRootInfo> CoreDB::getAlbumRoots()
{
QList<AlbumRootInfo> list;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, label, status, type, identifier, specificPath FROM AlbumRoots;"), &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
AlbumRootInfo info;
info.id = (*it).toInt();
++it;
info.label = (*it).toString();
++it;
info.status = (*it).toInt();
++it;
info.type = (AlbumRoot::Type)(*it).toInt();
++it;
info.identifier = (*it).toString();
++it;
info.specificPath = (*it).toString();
++it;
list << info;
}
return list;
}
int CoreDB::addAlbumRoot(AlbumRoot::Type type, const QString& identifier, const QString& specificPath, const QString& label)
{
QVariant id;
d->db->execSql(QString::fromUtf8("REPLACE INTO AlbumRoots (type, label, status, identifier, specificPath) "
"VALUES(?, ?, 0, ?, ?);"),
(int)type, label, identifier, specificPath, 0, &id);
d->db->recordChangeset(AlbumRootChangeset(id.toInt(), AlbumRootChangeset::Added));
return id.toInt();
}
void CoreDB::deleteAlbumRoot(int rootId)
{
d->db->execSql(QString::fromUtf8("DELETE FROM AlbumRoots WHERE id=?;"),
rootId);
QMap<QString, QVariant> parameters;
parameters.insert(QLatin1String(":albumRoot"), rootId);
if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRoot")), parameters))
{
return;
}
d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::Deleted));
}
void CoreDB::migrateAlbumRoot(int rootId, const QString& identifier)
{
d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET identifier=? WHERE id=?;"),
identifier, rootId);
d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged));
}
void CoreDB::setAlbumRootLabel(int rootId, const QString& newLabel)
{
d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET label=? WHERE id=?;"),
newLabel, rootId);
d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged));
}
void CoreDB::changeAlbumRootType(int rootId, AlbumRoot::Type newType)
{
d->db->execSql(QString::fromUtf8("UPDATE AlbumRoots SET type=? WHERE id=?;"),
(int)newType, rootId);
d->db->recordChangeset(AlbumRootChangeset(rootId, AlbumRootChangeset::PropertiesChanged));
}
AlbumInfo::List CoreDB::scanAlbums()
{
AlbumInfo::List aList;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT albumRoot, id, relativePath, date, caption, collection, icon FROM Albums "
" WHERE albumRoot != 0;"), // exclude stale albums
&values);
QString iconAlbumUrl, iconName;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
AlbumInfo info;
info.albumRootId = (*it).toInt();
++it;
info.id = (*it).toInt();
++it;
info.relativePath = (*it).toString();
++it;
info.date = (*it).toDate();
++it;
info.caption = (*it).toString();
++it;
info.category = (*it).toString();
++it;
info.iconId = (*it).toLongLong();
++it;
aList.append(info);
}
return aList;
}
TagInfo::List CoreDB::scanTags()
{
TagInfo::List tList;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, pid, name, icon, iconkde FROM Tags;"), &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
TagInfo info;
info.id = (*it).toInt();
++it;
info.pid = (*it).toInt();
++it;
info.name = (*it).toString();
++it;
info.iconId = (*it).toLongLong();
++it;
info.icon = (*it).toString();
++it;
tList.append(info);
}
return tList;
}
TagInfo CoreDB::getTagInfo(int tagId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, pid, name, icon, iconkde WHERE id=? FROM Tags;"), tagId, &values);
TagInfo info;
if (!values.isEmpty() && values.size() == 5)
{
QList<QVariant>::const_iterator it = values.constBegin();
info.id = (*it).toInt();
++it;
info.pid = (*it).toInt();
++it;
info.name = (*it).toString();
++it;
info.iconId = (*it).toLongLong();
++it;
info.icon = (*it).toString();
++it;
}
return info;
}
SearchInfo::List CoreDB::scanSearches()
{
SearchInfo::List searchList;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, type, name, query FROM Searches;"), &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
SearchInfo info;
info.id = (*it).toInt();
++it;
info.type = (DatabaseSearch::Type)(*it).toInt();
++it;
info.name = (*it).toString();
++it;
info.query = (*it).toString();
++it;
searchList.append(info);
}
return searchList;
}
QList<AlbumShortInfo> CoreDB::getAlbumShortInfos()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Albums.id, Albums.relativePath, Albums.albumRoot from Albums ORDER BY Albums.id;"),
&values);
QList<AlbumShortInfo> albumList;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
AlbumShortInfo info;
info.id = (*it).toInt();
++it;
info.relativePath = (*it).toString();
++it;
info.albumRootId = (*it).toInt();
++it;
albumList << info;
}
return albumList;
}
QList<TagShortInfo> CoreDB::getTagShortInfos()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, pid, name FROM Tags ORDER BY id;"), &values);
QList<TagShortInfo> tagList;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
TagShortInfo info;
info.id = (*it).toInt();
++it;
info.pid = (*it).toInt();
++it;
info.name = (*it).toString();
++it;
tagList << info;
}
return tagList;
}
/*
QStringList CoreDB::getSubalbumsForPath(const QString& albumRoot,
const QString& path,
bool onlyDirectSubalbums)
{
CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot);
if (location.isNull())
return QStringList();
QString subURL = path;
if (!path.endsWith(QLatin1String("/")))
subURL += QLatin1Char('/');
subURL = (subURL);
QList<QVariant> values;
if (onlyDirectSubalbums)
{
d->db->execSql( QString::fromUtf8("SELECT relativePath FROM Albums WHERE albumRoot=? AND relativePath LIKE '") +
subURL + QString::fromUtf8("%' ") + QString::fromUtf8("AND relativePath NOT LIKE '") +
subURL + QString::fromUtf8("%/%'; "),
location.id(),
&values );
}
else
{
d->db->execSql( QString::fromUtf8("SELECT relativePath FROM Albums WHERE albumRoot=? AND relativePath LIKE '") +
subURL + QString::fromUtf8("%'; "),
location.id(),
&values );
}
QStringList subalbums;
QString albumRootPath = location.albumRootPath();
for (QList<QVariant>::iterator it = values.begin(); it != values.end(); ++it)
subalbums << albumRootPath + it->toString();
return subalbums;
}
*/
/*
int CoreDB::addAlbum(const QString& albumRoot, const QString& relativePath,
const QString& caption,
const QDate& date, const QString& collection)
{
CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot);
if (location.isNull())
return -1;
return addAlbum(location.id(), relativePath, caption, date, collection);
}
*/
int CoreDB::addAlbum(int albumRootId, const QString& relativePath,
const QString& caption,
const QDate& date, const QString& collection)
{
QVariant id;
QList<QVariant> boundValues;
boundValues << albumRootId << relativePath << date << caption << collection;
d->db->execSql(QString::fromUtf8("REPLACE INTO Albums (albumRoot, relativePath, date, caption, collection) "
"VALUES(?, ?, ?, ?, ?);"),
boundValues, 0, &id);
d->db->recordChangeset(AlbumChangeset(id.toInt(), AlbumChangeset::Added));
return id.toInt();
}
void CoreDB::setAlbumCaption(int albumID, const QString& caption)
{
d->db->execSql(QString::fromUtf8("UPDATE Albums SET caption=? WHERE id=?;"),
caption, albumID);
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged));
}
void CoreDB::setAlbumCategory(int albumID, const QString& category)
{
- // TODO : change "collection" propertie in DB ALbum table to "category"
+ // TODO : change "collection" property in DB ALbum table to "category"
d->db->execSql(QString::fromUtf8("UPDATE Albums SET collection=? WHERE id=?;"),
category, albumID);
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged));
}
void CoreDB::setAlbumDate(int albumID, const QDate& date)
{
d->db->execSql(QString::fromUtf8("UPDATE Albums SET date=? WHERE id=?;"),
date,
albumID);
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged));
}
void CoreDB::setAlbumIcon(int albumID, qlonglong iconID)
{
if (iconID == 0)
{
d->db->execSql(QString::fromUtf8("UPDATE Albums SET icon=NULL WHERE id=?;"),
albumID);
}
else
{
d->db->execSql(QString::fromUtf8("UPDATE Albums SET icon=? WHERE id=?;"),
iconID,
albumID);
}
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::PropertiesChanged));
}
void CoreDB::deleteAlbum(int albumID)
{
QMap<QString, QVariant> parameters;
parameters.insert(QLatin1String(":albumId"), albumID);
if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumID")), parameters))
{
return;
}
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Deleted));
}
void CoreDB::makeStaleAlbum(int albumID)
{
// We need to work around the table constraint, no we want to delete older stale albums with
// the same relativePath, and adjust relativePaths depending on albumRoot.
QList<QVariant> values;
// retrieve information
d->db->execSql(QString::fromUtf8("SELECT Albums.albumRoot, Albums.relativePath from Albums WHERE id=?;"),
albumID, &values);
if (values.isEmpty())
{
return;
}
// prepend albumRootId to relativePath. relativePath is unused and officially undefined after this call.
QString newRelativePath = values.at(0).toString() + QLatin1Char('-') + values.at(1).toString();
// delete older stale albums
QMap<QString, QVariant> parameters;
parameters.insert(QLatin1String(":albumRoot"), 0);
parameters.insert(QLatin1String(":relativePath"), newRelativePath);
if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRootPath")), parameters))
{
return;
}
// now do our update
d->db->setForeignKeyChecks(false);
d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=0, relativePath=? WHERE id=?;"),
newRelativePath, albumID);
// for now, we make no distinction to deleteAlbums wrt to changeset
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Deleted));
d->db->setForeignKeyChecks(true);
}
void CoreDB::deleteStaleAlbums()
{
QMap<QString, QVariant> parameters;
parameters.insert(QLatin1String(":albumRoot"), 0);
if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("deleteAlbumRoot")), parameters))
{
return;
}
// deliberately no changeset here, is done above
}
int CoreDB::addTag(int parentTagID, const QString& name, const QString& iconKDE,
qlonglong iconID)
{
QVariant id;
QMap<QString, QVariant> parameters;
parameters.insert(QLatin1String(":tagPID"), parentTagID);
parameters.insert(QLatin1String(":tagname"), name);
if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QLatin1String("InsertTag")), parameters, 0 , &id))
{
return -1;
}
if (!iconKDE.isEmpty())
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=? WHERE id=?;"),
iconKDE,
id.toInt());
}
else if (iconID == 0)
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET icon=NULL WHERE id=?;"),
id.toInt());
}
else
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET icon=? WHERE id=?;"),
iconID,
id.toInt());
}
d->db->recordChangeset(TagChangeset(id.toInt(), TagChangeset::Added));
return id.toInt();
}
void CoreDB::deleteTag(int tagID)
{
/*
QString("DELETE FROM Tags WHERE id=?;"), tagID
*/
QMap<QString, QVariant> bindingMap;
bindingMap.insert(QLatin1String(":tagID"), tagID);
d->db->execDBAction(d->db->getDBAction(QLatin1String("DeleteTag")), bindingMap);
d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Deleted));
}
void CoreDB::setTagIcon(int tagID, const QString& iconKDE, qlonglong iconID)
{
int _iconID = iconKDE.isEmpty() ? iconID : 0;
QString _iconKDE = iconKDE;
if (iconKDE.isEmpty() || iconKDE.toLower() == QLatin1String("tag"))
{
_iconKDE.clear();
}
if (_iconID == 0)
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=?, icon=NULL WHERE id=?;"),
_iconKDE, tagID);
}
else
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET iconkde=?, icon=? WHERE id=?;"),
_iconKDE, _iconID, tagID);
}
d->db->recordChangeset(TagChangeset(tagID, TagChangeset::IconChanged));
}
void CoreDB::setTagParentID(int tagID, int newParentTagID)
{
if (d->db->databaseType() == BdEngineBackend::DbType::SQLite)
{
d->db->execSql(QString::fromUtf8("UPDATE OR REPLACE Tags SET pid=? WHERE id=?;"),
newParentTagID, tagID);
}
else
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET pid=? WHERE id=?;"),
newParentTagID, tagID);
// NOTE: Update the Mysql TagsTree table which is used only in some search SQL queries (See lft/rgt tag ID properties).
// In SQlite, it is nicely maintained by Triggers.
// With MySQL, this did not work for some reason, and we patch a tree structure mimics in a different way.
QMap<QString, QVariant> bindingMap;
bindingMap.insert(QLatin1String(":tagID"), tagID);
bindingMap.insert(QLatin1String(":newTagPID"), newParentTagID);
d->db->execDBAction(d->db->getDBAction(QLatin1String("MoveTagTree")), bindingMap);
}
d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Reparented));
}
QList<TagProperty> CoreDB::getTagProperties(int tagId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT property, value FROM TagProperties WHERE tagid=?;"),
tagId, &values);
QList<TagProperty> properties;
if (values.isEmpty())
{
return properties;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
TagProperty property;
property.tagId = tagId;
property.property = (*it).toString();
++it;
property.value = (*it).toString();
++it;
properties << property;
}
return properties;
}
QList<TagProperty> CoreDB::getTagProperties(const QString& property)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM TagProperties WHERE property=?;"),
property, &values);
QList<TagProperty> properties;
if (values.isEmpty())
{
return properties;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
TagProperty property;
property.tagId = (*it).toInt();
++it;
property.property = (*it).toString();
++it;
property.value = (*it).toString();
++it;
properties << property;
}
return properties;
}
QList<TagProperty> CoreDB::getTagProperties()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM TagProperties ORDER BY tagid, property;"), &values);
QList<TagProperty> properties;
if (values.isEmpty())
{
return properties;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
TagProperty property;
property.tagId = (*it).toInt();
++it;
property.property = (*it).toString();
++it;
property.value = (*it).toString();
++it;
properties << property;
}
return properties;
}
QList< int > CoreDB::getTagsWithProperty(const QString& property)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT DISTINCT tagid FROM TagProperties WHERE property=?;"),
property, &values);
QList<int> tagIds;
foreach(const QVariant& var, values)
{
tagIds << var.toInt();
}
return tagIds;
}
void CoreDB::addTagProperty(int tagId, const QString& property, const QString& value)
{
d->db->execSql(QString::fromUtf8("INSERT INTO TagProperties (tagid, property, value) VALUES(?, ?, ?);"),
tagId, property, value);
d->db->recordChangeset(TagChangeset(tagId, TagChangeset::PropertiesChanged));
}
void CoreDB::addTagProperty(const TagProperty& property)
{
addTagProperty(property.tagId, property.property, property.value);
}
void CoreDB::removeTagProperties(int tagId, const QString& property, const QString& value)
{
if (property.isNull())
{
d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=?;"),
tagId);
}
else if (value.isNull())
{
d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=? AND property=?;"),
tagId, property);
}
else
{
d->db->execSql(QString::fromUtf8("DELETE FROM TagProperties WHERE tagid=? AND property=? AND value=?;"),
tagId, property, value);
}
d->db->recordChangeset(TagChangeset(tagId, TagChangeset::PropertiesChanged));
}
int CoreDB::addSearch(DatabaseSearch::Type type, const QString& name, const QString& query)
{
QVariant id;
if (!d->db->execSql(QString::fromUtf8("INSERT INTO Searches (type, name, query) VALUES(?, ?, ?);"),
type, name, query, 0, &id))
{
return -1;
}
d->db->recordChangeset(SearchChangeset(id.toInt(), SearchChangeset::Added));
return id.toInt();
}
void CoreDB::updateSearch(int searchID, DatabaseSearch::Type type,
const QString& name, const QString& query)
{
d->db->execSql(QString::fromUtf8("UPDATE Searches SET type=?, name=?, query=? WHERE id=?;"),
type, name, query, searchID);
d->db->recordChangeset(SearchChangeset(searchID, SearchChangeset::Changed));
}
void CoreDB::deleteSearch(int searchID)
{
d->db->execSql(QString::fromUtf8("DELETE FROM Searches WHERE id=?;"),
searchID);
d->db->recordChangeset(SearchChangeset(searchID, SearchChangeset::Deleted));
}
void CoreDB::deleteSearches(DatabaseSearch::Type type)
{
d->db->execSql(QString::fromUtf8("DELETE FROM Searches WHERE type=?;"),
type);
d->db->recordChangeset(SearchChangeset(0, SearchChangeset::Deleted));
}
QString CoreDB::getSearchQuery(int searchId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT query FROM Searches WHERE id=?;"),
searchId, &values);
if (values.isEmpty())
{
return QString();
}
else
{
return values.first().toString();
}
}
SearchInfo CoreDB::getSearchInfo(int searchId)
{
SearchInfo info;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, type, name, query FROM Searches WHERE id=?;"),
searchId, &values);
if (values.size() == 4)
{
QList<QVariant>::const_iterator it = values.constBegin();
info.id = (*it).toInt();
++it;
info.type = (DatabaseSearch::Type)(*it).toInt();
++it;
info.name = (*it).toString();
++it;
info.query = (*it).toString();
++it;
}
return info;
}
void CoreDB::setSetting(const QString& keyword, const QString& value)
{
d->db->execSql(QString::fromUtf8("REPLACE INTO Settings VALUES (?,?);"),
keyword, value);
}
QString CoreDB::getSetting(const QString& keyword)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT value FROM Settings "
"WHERE keyword=?;"),
keyword, &values);
if (values.isEmpty())
{
return QString();
}
else
{
return values.first().toString();
}
}
// helper method
static QStringList joinMainAndUserFilterString(const QChar& sep, const QString& filter,
const QString& userFilter)
{
QSet<QString> filterSet;
QStringList userFilterList;
QStringList sortedList;
filterSet = filter.split(sep, QString::SkipEmptyParts).toSet();
userFilterList = userFilter.split(sep, QString::SkipEmptyParts);
foreach(const QString& userFormat, userFilterList)
{
if (userFormat.startsWith(QLatin1Char('-')))
{
filterSet.remove(userFormat.mid(1));
}
else
{
filterSet << userFormat;
}
}
sortedList = filterSet.toList();
sortedList.sort();
return sortedList;
}
void CoreDB::getFilterSettings(QStringList* imageFilter, QStringList* videoFilter, QStringList* audioFilter)
{
QString imageFormats, videoFormats, audioFormats, userImageFormats, userVideoFormats, userAudioFormats;
if (imageFilter)
{
imageFormats = getSetting(QLatin1String("databaseImageFormats"));
userImageFormats = getSetting(QLatin1String("databaseUserImageFormats"));
*imageFilter = joinMainAndUserFilterString(QLatin1Char(';'), imageFormats, userImageFormats);
}
if (videoFilter)
{
videoFormats = getSetting(QLatin1String("databaseVideoFormats"));
userVideoFormats = getSetting(QLatin1String("databaseUserVideoFormats"));
*videoFilter = joinMainAndUserFilterString(QLatin1Char(';'), videoFormats, userVideoFormats);
}
if (audioFilter)
{
audioFormats = getSetting(QLatin1String("databaseAudioFormats"));
userAudioFormats = getSetting(QLatin1String("databaseUserAudioFormats"));
*audioFilter = joinMainAndUserFilterString(QLatin1Char(';'), audioFormats, userAudioFormats);
}
}
void CoreDB::getUserFilterSettings(QString* imageFilterString, QString* videoFilterString, QString* audioFilterString)
{
if (imageFilterString)
{
*imageFilterString = getSetting(QLatin1String("databaseUserImageFormats"));
}
if (videoFilterString)
{
*videoFilterString = getSetting(QLatin1String("databaseUserVideoFormats"));
}
if (audioFilterString)
{
*audioFilterString = getSetting(QLatin1String("databaseUserAudioFormats"));
}
}
void CoreDB::getUserIgnoreDirectoryFilterSettings(QString* ignoreDirectoryFilterString)
{
*ignoreDirectoryFilterString = getSetting(QLatin1String("databaseUserIgnoreDirectoryFormats"));
}
void CoreDB::getIgnoreDirectoryFilterSettings(QStringList* ignoreDirectoryFilter)
{
QString ignoreDirectoryFormats, userIgnoreDirectoryFormats;
ignoreDirectoryFormats = getSetting(QLatin1String("databaseIgnoreDirectoryFormats"));
userIgnoreDirectoryFormats = getSetting(QLatin1String("databaseUserIgnoreDirectoryFormats"));
*ignoreDirectoryFilter = joinMainAndUserFilterString(QLatin1Char(';'), ignoreDirectoryFormats, userIgnoreDirectoryFormats);
}
void CoreDB::setFilterSettings(const QStringList& imageFilter, const QStringList& videoFilter, const QStringList& audioFilter)
{
setSetting(QLatin1String("databaseImageFormats"), imageFilter.join(QLatin1Char(';')));
setSetting(QLatin1String("databaseVideoFormats"), videoFilter.join(QLatin1Char(';')));
setSetting(QLatin1String("databaseAudioFormats"), audioFilter.join(QLatin1Char(';')));
}
void CoreDB::setIgnoreDirectoryFilterSettings(const QStringList& ignoreDirectoryFilter)
{
setSetting(QLatin1String("databaseIgnoreDirectoryFormats"), ignoreDirectoryFilter.join(QLatin1Char(';')));
}
void CoreDB::setUserFilterSettings(const QStringList& imageFilter,
const QStringList& videoFilter,
const QStringList& audioFilter)
{
setSetting(QLatin1String("databaseUserImageFormats"), imageFilter.join(QLatin1Char(';')));
setSetting(QLatin1String("databaseUserVideoFormats"), videoFilter.join(QLatin1Char(';')));
setSetting(QLatin1String("databaseUserAudioFormats"), audioFilter.join(QLatin1Char(';')));
}
void CoreDB::setUserIgnoreDirectoryFilterSettings(const QStringList& ignoreDirectoryFilters)
{
qCDebug(DIGIKAM_DATABASE_LOG) << "CoreDB::setUserIgnoreDirectoryFilterSettings. "
"ignoreDirectoryFilterString: "
<< ignoreDirectoryFilters.join(QLatin1Char(';'));
setSetting(QLatin1String("databaseUserIgnoreDirectoryFormats"), ignoreDirectoryFilters.join(QLatin1Char(';')));
}
QUuid CoreDB::databaseUuid()
{
QString uuidString = getSetting(QLatin1String("databaseUUID"));
QUuid uuid = QUuid(uuidString);
if (uuidString.isNull() || uuid.isNull())
{
uuid = QUuid::createUuid();
setSetting(QLatin1String("databaseUUID"), uuid.toString());
}
return uuid;
}
int CoreDB::getUniqueHashVersion()
{
if (d->uniqueHashVersion == -1)
{
QString v = getSetting(QLatin1String("uniqueHashVersion"));
if (v.isEmpty())
{
d->uniqueHashVersion = 1;
}
else
{
d->uniqueHashVersion = v.toInt();
}
}
return d->uniqueHashVersion;
}
bool CoreDB::isUniqueHashV2()
{
return (getUniqueHashVersion() == 2);
}
void CoreDB::setUniqueHashVersion(int version)
{
d->uniqueHashVersion = version;
setSetting(QLatin1String("uniqueHashVersion"), QString::number(d->uniqueHashVersion));
}
/*
QString CoreDB::getItemCaption(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT caption FROM Images "
"WHERE id=?;"),
imageID, &values );
if (!values.isEmpty())
return values.first().toString();
else
return QString();
}
QString CoreDB::getItemCaption(int albumID, const QString& name)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT caption FROM Images "
"WHERE dirid=? AND name=?;"),
albumID,
name,
&values );
if (!values.isEmpty())
return values.first().toString();
else
return QString();
}
QDateTime CoreDB::getItemDate(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT datetime FROM Images "
"WHERE id=?;"),
imageID,
&values );
if (!values.isEmpty())
return QDateTime::fromString(values.first().toString(), Qt::ISODate);
else
return QDateTime();
}
QDateTime CoreDB::getItemDate(int albumID, const QString& name)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT datetime FROM Images "
"WHERE dirid=? AND name=?;"),
albumID,
name,
&values );
if (values.isEmpty())
return QDateTime::fromString(values.first().toString(), Qt::ISODate);
else
return QDateTime();
}
*/
qlonglong CoreDB::getImageId(int albumID, const QString& name)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE album=? AND name=?;"),
albumID,
name,
&values);
if (values.isEmpty())
{
return -1;
}
else
{
return values.first().toLongLong();
}
}
QList<qlonglong> CoreDB::getImageIds(int albumID, const QString& name, DatabaseItem::Status status)
{
QList<QVariant> values;
if (albumID == -1)
{
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE album IS NULL AND name=? AND status=?;"),
name,
status,
&values);
}
else
{
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE album=? AND name=? AND status=?;"),
albumID,
name,
status,
&values);
}
QList<qlonglong> items;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
items << it->toLongLong();
}
return items;
}
QList<qlonglong> CoreDB::getImageIds(DatabaseItem::Status status)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE status=?;"),
status,
&values);
QList<qlonglong> imageIds;
foreach(const QVariant& object, values)
{
imageIds << object.toLongLong();
}
return imageIds;
}
QList<qlonglong> CoreDB::getImageIds(DatabaseItem::Status status, DatabaseItem::Category category)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE status=? AND category=?;"),
status, category,
&values);
QList<qlonglong> imageIds;
foreach(const QVariant& object, values)
{
imageIds << object.toLongLong();
}
return imageIds;
}
qlonglong CoreDB::getImageId(int albumID, const QString& name,
DatabaseItem::Status status,
DatabaseItem::Category category,
const QDateTime& modificationDate,
qlonglong fileSize,
const QString& uniqueHash)
{
QList<QVariant> values;
QVariantList boundValues;
// Add the standard bindings
boundValues << name << (int)status << (int)category
<< modificationDate << fileSize << uniqueHash;
// If the album id is -1, no album is assigned. Get all images with NULL album
if (albumID == -1)
{
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE name=? AND status=? "
"AND category=? AND modificationDate=? "
"AND fileSize=? AND uniqueHash=? "
"AND album IS NULL;"),
boundValues,
&values);
}
else
{
boundValues << albumID;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
"WHERE name=? AND status=? "
"AND category=? AND modificationDate=? "
"AND fileSize=? AND uniqueHash=? "
"AND album=?;"),
boundValues,
&values);
}
if (values.isEmpty() || ( values.size() > 1 ))
{
return -1;
}
else
{
return values.first().toLongLong();
}
}
QStringList CoreDB::getItemTagNames(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT name FROM Tags "
"WHERE id IN (SELECT tagid FROM ImageTags "
"WHERE imageid=?) "
"ORDER BY name;"),
imageID,
&values);
QStringList names;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
names << it->toString();
}
return names;
}
QList<int> CoreDB::getItemTagIDs(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT tagid FROM ImageTags WHERE imageID=?;"),
imageID,
&values);
QList<int> ids;
if (values.isEmpty())
{
return ids;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
ids << it->toInt();
}
return ids;
}
QVector<QList<int> > CoreDB::getItemsTagIDs(const QList<qlonglong> imageIds)
{
if (imageIds.isEmpty())
{
return QVector<QList<int> >();
}
QVector<QList<int> > results(imageIds.size());
DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("SELECT tagid FROM ImageTags WHERE imageID=?;"));
QVariantList values;
for (int i = 0 ; i < imageIds.size() ; i++)
{
d->db->execSql(query, imageIds[i], &values);
QList<int>& tagIds = results[i];
foreach(const QVariant& v, values)
{
tagIds << v.toInt();
}
}
return results;
}
QList<ImageTagProperty> CoreDB::getImageTagProperties(qlonglong imageId, int tagId)
{
QList<QVariant> values;
if (tagId == -1)
{
d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM ImageTagProperties "
"WHERE imageid=?;"),
imageId,
&values);
}
else
{
d->db->execSql(QString::fromUtf8("SELECT tagid, property, value FROM ImageTagProperties "
"WHERE imageid=? AND tagid=?;"),
imageId, tagId,
&values);
}
QList<ImageTagProperty> properties;
if (values.isEmpty())
{
return properties;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
ImageTagProperty property;
property.imageId = imageId;
property.tagId = (*it).toInt();
++it;
property.property = (*it).toString();
++it;
property.value = (*it).toString();
++it;
properties << property;
}
return properties;
}
QList<int> CoreDB::getTagIdsWithProperties(qlonglong imageId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT DISTINCT tagid FROM ImageTagProperties WHERE imageid=?;"),
imageId,
&values);
QList<int> tagIds;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
tagIds << (*it).toInt();
}
return tagIds;
}
void CoreDB::addImageTagProperty(qlonglong imageId, int tagId, const QString& property, const QString& value)
{
d->db->execSql(QString::fromUtf8("INSERT INTO ImageTagProperties (imageid, tagid, property, value) VALUES(?, ?, ?, ?);"),
imageId, tagId, property, value);
d->db->recordChangeset(ImageTagChangeset(imageId, tagId, ImageTagChangeset::PropertiesChanged));
}
void CoreDB::addImageTagProperty(const ImageTagProperty& property)
{
addImageTagProperty(property.imageId, property.tagId, property.property, property.value);
}
void CoreDB::removeImageTagProperties(qlonglong imageId, int tagId, const QString& property,
const QString& value)
{
if (tagId == -1)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=?;"),
imageId);
}
else if (property.isNull())
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=? AND tagid=?;"),
imageId, tagId);
}
else if (value.isNull())
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=? AND tagid=? AND property=?;"),
imageId, tagId, property);
}
else
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=? AND tagid=? AND property=? AND value=?;"),
imageId, tagId, property, value);
}
d->db->recordChangeset(ImageTagChangeset(imageId, tagId, ImageTagChangeset::PropertiesChanged));
}
ItemShortInfo CoreDB::getItemShortInfo(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Images.name, Albums.albumRoot, Albums.relativePath, Albums.id "
"FROM Images "
" LEFT JOIN Albums ON Albums.id=Images.album "
"WHERE Images.id=?;"),
imageID,
&values);
ItemShortInfo info;
if (!values.isEmpty())
{
info.id = imageID;
info.itemName = values.at(0).toString();
info.albumRootID = values.at(1).toInt();
info.album = values.at(2).toString();
info.albumID = values.at(3).toInt();
}
return info;
}
ItemShortInfo CoreDB::getItemShortInfo(int albumRootId, const QString& relativePath, const QString& name)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.id "
" FROM Images INNER JOIN Albums "
" ON Images.album=Albums.id "
" WHERE name=? AND albumRoot=? AND relativePath=?;"),
name, albumRootId, relativePath,
&values);
ItemShortInfo info;
if (!values.isEmpty())
{
info.id = values.at(0).toLongLong();
info.itemName = name;
info.albumRootID = albumRootId;
info.album = relativePath;
info.albumID = values.at(1).toInt();
}
return info;
}
bool CoreDB::hasTags(const QList<qlonglong>& imageIDList)
{
QList<int> ids;
if (imageIDList.isEmpty())
{
return false;
}
QList<QVariant> values;
QList<QVariant> boundValues;
QString sql = QString::fromUtf8("SELECT count(tagid) FROM ImageTags "
"WHERE imageid=? ");
boundValues << imageIDList.first();
QList<qlonglong>::const_iterator it = imageIDList.constBegin();
++it;
for (; it != imageIDList.constEnd() ; ++it)
{
sql += QString::fromUtf8(" OR imageid=? ");
boundValues << (*it);
}
sql += QString::fromUtf8(";");
d->db->execSql(sql, boundValues, &values);
if (values.isEmpty() || values.first().toInt() == 0)
{
return false;
}
else
{
return true;
}
}
QList<int> CoreDB::getItemCommonTagIDs(const QList<qlonglong>& imageIDList)
{
QList<int> ids;
if (imageIDList.isEmpty())
{
return ids;
}
QList<QVariant> values;
QList<QVariant> boundValues;
QString sql = QString::fromUtf8("SELECT DISTINCT tagid FROM ImageTags "
"WHERE imageid=? ");
boundValues << imageIDList.first();
QList<qlonglong>::const_iterator it = imageIDList.constBegin();
++it;
for (; it != imageIDList.constEnd() ; ++it)
{
sql += QString::fromUtf8(" OR imageid=? ");
boundValues << (*it);
}
sql += QString::fromUtf8(";");
d->db->execSql(sql, boundValues, &values);
if (values.isEmpty())
{
return ids;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
ids << it->toInt();
}
return ids;
}
QVariantList CoreDB::getImagesFields(qlonglong imageID, DatabaseFields::Images fields)
{
QVariantList values;
if (fields != DatabaseFields::ImagesNone)
{
QString query(QString::fromUtf8("SELECT "));
QStringList fieldNames = imagesFieldList(fields);
query += fieldNames.join(QString::fromUtf8(", "));
query += QString::fromUtf8(" FROM Images WHERE id=?;");
d->db->execSql(query, imageID, &values);
// Convert date times to QDateTime, they come as QString
if ((fields & DatabaseFields::ModificationDate) && !values.isEmpty())
{
int index = fieldNames.indexOf(QLatin1String("modificationDate"));
values[index] = values.at(index).toDateTime();
}
}
return values;
}
QVariantList CoreDB::getImageInformation(qlonglong imageID, DatabaseFields::ImageInformation fields)
{
QVariantList values;
if (fields != DatabaseFields::ImageInformationNone)
{
QString query(QString::fromUtf8("SELECT "));
QStringList fieldNames = imageInformationFieldList(fields);
query += fieldNames.join(QString::fromUtf8(", "));
query += QString::fromUtf8(" FROM ImageInformation WHERE imageid=?;");
d->db->execSql(query, imageID, &values);
// Convert date times to QDateTime, they come as QString
if ((fields & DatabaseFields::CreationDate) && !values.isEmpty())
{
int index = fieldNames.indexOf(QLatin1String("creationDate"));
values[index] = values.at(index).toDateTime();
}
if ((fields & DatabaseFields::DigitizationDate) && !values.isEmpty())
{
int index = fieldNames.indexOf(QLatin1String("digitizationDate"));
values[index] = values.at(index).toDateTime();
}
}
return values;
}
QVariantList CoreDB::getImageMetadata(qlonglong imageID, DatabaseFields::ImageMetadata fields)
{
QVariantList values;
if (fields != DatabaseFields::ImageMetadataNone)
{
QString query(QString::fromUtf8("SELECT "));
QStringList fieldNames = imageMetadataFieldList(fields);
query += fieldNames.join(QString::fromUtf8(", "));
query += QString::fromUtf8(" FROM ImageMetadata WHERE imageid=?;");
d->db->execSql(query, imageID, &values);
// For some reason, if REAL values may be required from variables stored as QString QVariants. Convert code will come here.
}
return values;
}
QVariantList CoreDB::getVideoMetadata(qlonglong imageID, DatabaseFields::VideoMetadata fields)
{
QVariantList values;
if (fields != DatabaseFields::VideoMetadataNone)
{
QString query(QString::fromUtf8("SELECT "));
QStringList fieldNames = videoMetadataFieldList(fields);
query += fieldNames.join(QString::fromUtf8(", "));
query += QString::fromUtf8(" FROM VideoMetadata WHERE imageid=?;");
d->db->execSql(query, imageID, &values);
// For some reason REAL values may come as QString QVariants. Convert here.
if (values.size() == fieldNames.size() &&
((fields & DatabaseFields::Aperture) ||
(fields & DatabaseFields::FocalLength) ||
(fields & DatabaseFields::FocalLength35) ||
(fields & DatabaseFields::ExposureTime) ||
(fields & DatabaseFields::SubjectDistance))
)
{
for (int i = 0 ; i < values.size() ; ++i)
{
if (values.at(i).type() == QVariant::String &&
(fieldNames.at(i) == QLatin1String("aperture") ||
fieldNames.at(i) == QLatin1String("focalLength") ||
fieldNames.at(i) == QLatin1String("focalLength35") ||
fieldNames.at(i) == QLatin1String("exposureTime") ||
fieldNames.at(i) == QLatin1String("subjectDistance"))
)
{
values[i] = values.at(i).toDouble();
}
}
}
}
return values;
}
QVariantList CoreDB::getImagePosition(qlonglong imageID, DatabaseFields::ImagePositions fields)
{
QVariantList values;
if (fields != DatabaseFields::ImagePositionsNone)
{
QString query(QString::fromUtf8("SELECT "));
QStringList fieldNames = imagePositionsFieldList(fields);
query += fieldNames.join(QString::fromUtf8(", "));
query += QString::fromUtf8(" FROM ImagePositions WHERE imageid=?;");
d->db->execSql(query, imageID, &values);
// For some reason REAL values may come as QString QVariants. Convert here.
if (values.size() == fieldNames.size() &&
((fields & DatabaseFields::LatitudeNumber) ||
(fields & DatabaseFields::LongitudeNumber) ||
(fields & DatabaseFields::Altitude) ||
(fields & DatabaseFields::PositionOrientation) ||
(fields & DatabaseFields::PositionTilt) ||
(fields & DatabaseFields::PositionRoll) ||
(fields & DatabaseFields::PositionAccuracy))
)
{
for (int i = 0 ; i < values.size() ; ++i)
{
if (values.at(i).type() == QVariant::String &&
(fieldNames.at(i) == QLatin1String("latitudeNumber") ||
fieldNames.at(i) == QLatin1String("longitudeNumber") ||
fieldNames.at(i) == QLatin1String("altitude") ||
fieldNames.at(i) == QLatin1String("orientation") ||
fieldNames.at(i) == QLatin1String("tilt") ||
fieldNames.at(i) == QLatin1String("roll") ||
fieldNames.at(i) == QLatin1String("accuracy"))
)
{
if (!values.at(i).isNull())
values[i] = values.at(i).toDouble();
}
}
}
}
return values;
}
QVariantList CoreDB::getImagePositions(QList<qlonglong> imageIDs, DatabaseFields::ImagePositions fields)
{
QVariantList values;
if (fields != DatabaseFields::ImagePositionsNone)
{
QString sql(QString::fromUtf8("SELECT "));
QStringList fieldNames = imagePositionsFieldList(fields);
sql += fieldNames.join(QString::fromUtf8(", "));
sql += QString::fromUtf8(" FROM ImagePositions WHERE imageid=?;");
DbEngineSqlQuery query = d->db->prepareQuery(sql);
foreach(const qlonglong& imageid, imageIDs)
{
QVariantList singleValueList;
d->db->execSql(query, imageid, &singleValueList);
values << singleValueList;
}
// For some reason REAL values may come as QString QVariants. Convert here.
if (values.size() == fieldNames.size() &&
(fields & DatabaseFields::LatitudeNumber ||
fields & DatabaseFields::LongitudeNumber ||
fields & DatabaseFields::Altitude ||
fields & DatabaseFields::PositionOrientation ||
fields & DatabaseFields::PositionTilt ||
fields & DatabaseFields::PositionRoll ||
fields & DatabaseFields::PositionAccuracy)
)
{
for (int i = 0 ; i < values.size() ; ++i)
{
if (values.at(i).type() == QVariant::String &&
(fieldNames.at(i) == QLatin1String("latitudeNumber") ||
fieldNames.at(i) == QLatin1String("longitudeNumber") ||
fieldNames.at(i) == QLatin1String("altitude") ||
fieldNames.at(i) == QLatin1String("orientation") ||
fieldNames.at(i) == QLatin1String("tilt") ||
fieldNames.at(i) == QLatin1String("roll") ||
fieldNames.at(i) == QLatin1String("accuracy"))
)
{
if (!values.at(i).isNull())
values[i] = values.at(i).toDouble();
}
}
}
}
return values;
}
void CoreDB::addImageInformation(qlonglong imageID, const QVariantList& infos,
DatabaseFields::ImageInformation fields)
{
if (fields == DatabaseFields::ImageInformationNone)
{
return;
}
QString query(QString::fromUtf8("REPLACE INTO ImageInformation ( imageid, "));
QStringList fieldNames = imageInformationFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QLatin1String(", "));
query += QString::fromUtf8(" ) VALUES (");
addBoundValuePlaceholders(query, infos.size() + 1);
query += QString::fromUtf8(");");
QVariantList boundValues;
boundValues << imageID;
boundValues << infos;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageID, fields));
}
void CoreDB::changeImageInformation(qlonglong imageId, const QVariantList& infos,
DatabaseFields::ImageInformation fields)
{
if (fields == DatabaseFields::ImageInformationNone)
{
return;
}
QStringList fieldNames = imageInformationFieldList(fields);
d->db->execUpsertDBAction(QLatin1String("changeImageInformation"),
imageId, fieldNames, infos);
d->db->recordChangeset(ImageChangeset(imageId, fields));
}
void CoreDB::addImageMetadata(qlonglong imageID, const QVariantList& infos,
DatabaseFields::ImageMetadata fields)
{
if (fields == DatabaseFields::ImageMetadataNone)
{
return;
}
QString query(QString::fromUtf8("REPLACE INTO ImageMetadata ( imageid, "));
QStringList fieldNames = imageMetadataFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QLatin1String(", "));
query += QString::fromUtf8(" ) VALUES (");
addBoundValuePlaceholders(query, infos.size() + 1);
query += QString::fromUtf8(");");
QVariantList boundValues;
boundValues << imageID << infos;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageID, fields));
}
void CoreDB::changeImageMetadata(qlonglong imageId, const QVariantList& infos,
DatabaseFields::ImageMetadata fields)
{
if (fields == DatabaseFields::ImageMetadataNone)
{
return;
}
QString query(QString::fromUtf8("UPDATE ImageMetadata SET "));
QStringList fieldNames = imageMetadataFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QString::fromUtf8("=?,"));
query += QString::fromUtf8("=? WHERE imageid=?;");
QVariantList boundValues;
boundValues << infos << imageId;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageId, fields));
}
void CoreDB::addVideoMetadata(qlonglong imageID, const QVariantList& infos, DatabaseFields::VideoMetadata fields)
{
if (fields == DatabaseFields::VideoMetadataNone)
{
return;
}
QString query(QString::fromUtf8("REPLACE INTO VideoMetadata ( imageid, ")); // need to create this database
QStringList fieldNames = videoMetadataFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QLatin1String(", "));
query += QString::fromUtf8(" ) VALUES (");
addBoundValuePlaceholders(query, infos.size() + 1);
query += QString::fromUtf8(");");
QVariantList boundValues;
boundValues << imageID << infos;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageID, fields));
}
void CoreDB::changeVideoMetadata(qlonglong imageId, const QVariantList& infos,
DatabaseFields::VideoMetadata fields)
{
if (fields == DatabaseFields::VideoMetadataNone)
{
return;
}
QString query(QString::fromUtf8("UPDATE VideoMetadata SET "));
QStringList fieldNames = videoMetadataFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QString::fromUtf8("=?,"));
query += QString::fromUtf8("=? WHERE imageid=?;");
QVariantList boundValues;
boundValues << infos << imageId;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageId, fields));
}
void CoreDB::addImagePosition(qlonglong imageID, const QVariantList& infos, DatabaseFields::ImagePositions fields)
{
if (fields == DatabaseFields::ImagePositionsNone)
{
return;
}
QString query(QString::fromUtf8("REPLACE INTO ImagePositions ( imageid, "));
QStringList fieldNames = imagePositionsFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QLatin1String(", "));
query += QString::fromUtf8(" ) VALUES (");
addBoundValuePlaceholders(query, infos.size() + 1);
query += QString::fromUtf8(");");
QVariantList boundValues;
boundValues << imageID << infos;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageID, fields));
}
void CoreDB::changeImagePosition(qlonglong imageId, const QVariantList& infos,
DatabaseFields::ImagePositions fields)
{
if (fields == DatabaseFields::ImagePositionsNone)
{
return;
}
QString query(QString::fromUtf8("UPDATE ImagePositions SET "));
QStringList fieldNames = imagePositionsFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QString::fromUtf8("=?,"));
query += QString::fromUtf8("=? WHERE imageid=?;");
QVariantList boundValues;
boundValues << infos << imageId;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageId, fields));
}
void CoreDB::removeImagePosition(qlonglong imageid)
{
d->db->execSql(QString(QString::fromUtf8("DELETE FROM ImagePositions WHERE imageid=?;")),
imageid);
d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::ImagePositionsAll));
}
void CoreDB::removeImagePositionAltitude(qlonglong imageid)
{
d->db->execSql(QString(QString::fromUtf8("UPDATE ImagePositions SET altitude=NULL WHERE imageid=?;")),
imageid);
d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::Altitude));
}
QList<CommentInfo> CoreDB::getImageComments(qlonglong imageID)
{
QList<CommentInfo> list;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, type, language, author, date, comment "
"FROM ImageComments WHERE imageid=?;"),
imageID, &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
CommentInfo info;
info.imageId = imageID;
info.id = (*it).toInt();
++it;
info.type = (DatabaseComment::Type)(*it).toInt();
++it;
info.language = (*it).toString();
++it;
info.author = (*it).toString();
++it;
info.date = (*it).toDateTime();
++it;
info.comment = (*it).toString();
++it;
list << info;
}
return list;
}
int CoreDB::setImageComment(qlonglong imageID, const QString& comment, DatabaseComment::Type type,
const QString& language, const QString& author, const QDateTime& date)
{
QVariantList boundValues;
boundValues << imageID << (int)type << language << author << date << comment;
QVariant id;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageComments "
"( imageid, type, language, author, date, comment ) "
" VALUES (?,?,?,?,?,?);"),
boundValues, 0, &id);
d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::ImageCommentsAll));
return id.toInt();
}
void CoreDB::changeImageComment(int commentId, qlonglong imageID, const QVariantList& infos, DatabaseFields::ImageComments fields)
{
if (fields == DatabaseFields::ImageCommentsNone)
{
return;
}
QString query(QString::fromUtf8("UPDATE ImageComments SET "));
QStringList fieldNames = imageCommentsFieldList(fields);
Q_ASSERT(fieldNames.size() == infos.size());
query += fieldNames.join(QString::fromUtf8("=?,"));
query += QString::fromUtf8("=? WHERE id=?;");
QVariantList boundValues;
boundValues << infos << commentId;
d->db->execSql(query, boundValues);
d->db->recordChangeset(ImageChangeset(imageID, fields));
}
void CoreDB::removeImageComment(int commentid, qlonglong imageid)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageComments WHERE id=?;"),
commentid);
d->db->recordChangeset(ImageChangeset(imageid, DatabaseFields::ImageCommentsAll));
}
QString CoreDB::getImageProperty(qlonglong imageID, const QString& property)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT value FROM ImageProperties "
"WHERE imageid=? and property=?;"),
imageID, property,
&values);
if (!values.isEmpty())
{
return values.first().toString();
}
else
{
return QString();
}
}
void CoreDB::setImageProperty(qlonglong imageID, const QString& property, const QString& value)
{
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageProperties "
"(imageid, property, value) "
"VALUES(?, ?, ?);"),
imageID, property, value);
}
void CoreDB::removeImageProperty(qlonglong imageID, const QString& property)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE imageid=? AND property=?;"),
imageID, property);
}
void CoreDB::removeImagePropertyByName(const QString& property)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageProperties WHERE property=?;"),
property);
}
QList<CopyrightInfo> CoreDB::getImageCopyright(qlonglong imageID, const QString& property)
{
QList<CopyrightInfo> list;
QList<QVariant> values;
if (property.isNull())
{
d->db->execSql(QString::fromUtf8("SELECT property, value, extraValue FROM ImageCopyright "
"WHERE imageid=?;"),
imageID, &values);
}
else
{
d->db->execSql(QString::fromUtf8("SELECT property, value, extraValue FROM ImageCopyright "
"WHERE imageid=? and property=?;"),
imageID, property, &values);
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
CopyrightInfo info;
info.id = imageID;
info.property = (*it).toString();
++it;
info.value = (*it).toString();
++it;
info.extraValue = (*it).toString();
++it;
list << info;
}
return list;
}
void CoreDB::setImageCopyrightProperty(qlonglong imageID, const QString& property,
const QString& value, const QString& extraValue,
CopyrightPropertyUnique uniqueness)
{
if (uniqueness == PropertyUnique)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright "
"WHERE imageid=? AND property=?;"),
imageID, property);
}
else if (uniqueness == PropertyExtraValueUnique)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright "
"WHERE imageid=? AND property=? AND extraValue=?;"),
imageID, property, extraValue);
}
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageCopyright "
"(imageid, property, value, extraValue) "
"VALUES(?, ?, ?, ?);"),
imageID, property, value, extraValue);
}
void CoreDB::removeImageCopyrightProperties(qlonglong imageID, const QString& property,
const QString& extraValue, const QString& value)
{
int removeBy = 0;
if (!property.isNull())
{
++removeBy;
}
if (!extraValue.isNull())
{
++removeBy;
}
if (!value.isNull())
{
++removeBy;
}
switch (removeBy)
{
case 0:
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright "
"WHERE imageid=?;"),
imageID);
break;
case 1:
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright "
"WHERE imageid=? AND property=?;"),
imageID, property);
break;
case 2:
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright "
"WHERE imageid=? AND property=? AND extraValue=?;"),
imageID, property, extraValue);
break;
case 3:
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright "
"WHERE imageid=? AND property=? AND extraValue=? AND value=?;"),
imageID, property, extraValue, value);
break;
}
}
QList<qlonglong> CoreDB::findByNameAndCreationDate(const QString& fileName, const QDateTime& creationDate)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images "
" LEFT JOIN ImageInformation ON id=imageid "
"WHERE name=? AND creationDate=? AND status!=3;"),
fileName, creationDate, &values);
QList<qlonglong> ids;
foreach(const QVariant& var, values)
{
ids << var.toLongLong();
}
return ids;
}
bool CoreDB::hasImageHistory(qlonglong imageId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT history FROM ImageHistory WHERE imageid=?;"),
imageId, &values);
return !values.isEmpty();
}
ImageHistoryEntry CoreDB::getImageHistory(qlonglong imageId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT uuid, history FROM ImageHistory WHERE imageid=?;"),
imageId, &values);
ImageHistoryEntry entry;
entry.imageId = imageId;
if (values.count() != 2)
{
return entry;
}
QList<QVariant>::const_iterator it = values.constBegin();
entry.uuid = (*it).toString();
++it;
entry.history = (*it).toString();
++it;
return entry;
}
QList<qlonglong> CoreDB::getItemsForUuid(const QString& uuid)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT imageid FROM ImageHistory "
"INNER JOIN Images ON imageid=id "
"WHERE uuid=? AND status!=3;"),
uuid, &values);
QList<qlonglong> imageIds;
if (values.isEmpty())
{
return imageIds;
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
imageIds << (*it).toInt();
}
return imageIds;
}
QString CoreDB::getImageUuid(qlonglong imageId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT uuid FROM ImageHistory WHERE imageid=?;"),
imageId, &values);
if (values.isEmpty())
{
return QString();
}
QString uuid = values.first().toString();
if (uuid.isEmpty())
{
return QString();
}
return uuid;
}
void CoreDB::setImageHistory(qlonglong imageId, const QString& history)
{
d->db->execUpsertDBAction(QLatin1String("changeImageHistory"), imageId, QStringList() << QLatin1String("history"), QVariantList() << history);
d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::ImageHistory));
}
void CoreDB::setImageUuid(qlonglong imageId, const QString& uuid)
{
d->db->execUpsertDBAction(QLatin1String("changeImageHistory"), imageId, QStringList() << QLatin1String("uuid"), QVariantList() << uuid);
d->db->recordChangeset(ImageChangeset(imageId, DatabaseFields::ImageUUID));
}
void CoreDB::addImageRelation(qlonglong subjectId, qlonglong objectId, DatabaseRelation::Type type)
{
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations (subject, object, type) VALUES (?, ?, ?);"),
subjectId, objectId, type);
d->db->recordChangeset(ImageChangeset(QList<qlonglong>() << subjectId << objectId, DatabaseFields::ImageRelations));
}
void CoreDB::addImageRelations(const QList<qlonglong>& subjectIds, const QList<qlonglong>& objectIds, DatabaseRelation::Type type)
{
DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("REPLACE INTO ImageRelations (subject, object, type) VALUES (?, ?, ?);"));
QVariantList subjects, objects, types;
for (int i = 0 ; i < subjectIds.size() ; ++i)
{
subjects << subjectIds.at(i);
objects << objectIds.at(i);
types << type;
}
query.addBindValue(subjects);
query.addBindValue(objects);
query.addBindValue(types);
d->db->execBatch(query);
d->db->recordChangeset(ImageChangeset(subjectIds + objectIds, DatabaseFields::ImageRelations));
}
void CoreDB::addImageRelation(const ImageRelation& relation)
{
addImageRelation(relation.subjectId, relation.objectId, relation.type);
}
void CoreDB::removeImageRelation(qlonglong subjectId, qlonglong objectId, DatabaseRelation::Type type)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE subject=? AND object=? AND type=?;"),
subjectId, objectId, type);
d->db->recordChangeset(ImageChangeset(QList<qlonglong>() << subjectId << objectId, DatabaseFields::ImageRelations));
}
void CoreDB::removeImageRelation(const ImageRelation& relation)
{
removeImageRelation(relation.subjectId, relation.objectId, relation.type);
}
QList<qlonglong> CoreDB::removeAllImageRelationsTo(qlonglong objectId, DatabaseRelation::Type type)
{
QList<qlonglong> affected = getImagesRelatingTo(objectId, type);
if (affected.isEmpty())
{
return affected;
}
d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE object=? AND type=?;"),
objectId, type);
d->db->recordChangeset(ImageChangeset(QList<qlonglong>() << affected << objectId, DatabaseFields::ImageRelations));
return affected;
}
QList<qlonglong> CoreDB::removeAllImageRelationsFrom(qlonglong subjectId, DatabaseRelation::Type type)
{
QList<qlonglong> affected = getImagesRelatedFrom(subjectId, type);
if (affected.isEmpty())
{
return affected;
}
d->db->execSql(QString::fromUtf8("DELETE FROM ImageRelations WHERE subject=? AND type=?;"),
subjectId, type);
d->db->recordChangeset(ImageChangeset(QList<qlonglong>() << affected << subjectId, DatabaseFields::ImageRelations));
return affected;
}
QList<qlonglong> CoreDB::getImagesRelatedFrom(qlonglong subjectId, DatabaseRelation::Type type)
{
return getRelatedImages(subjectId, true, type, false);
}
QVector<QList<qlonglong> > CoreDB::getImagesRelatedFrom(QList<qlonglong> subjectIds, DatabaseRelation::Type type)
{
return getRelatedImages(subjectIds, true, type, false);
}
bool CoreDB::hasImagesRelatedFrom(qlonglong subjectId, DatabaseRelation::Type type)
{
// returns 0 or 1 item in list
return !getRelatedImages(subjectId, true, type, true).isEmpty();
}
QList<qlonglong> CoreDB::getImagesRelatingTo(qlonglong objectId, DatabaseRelation::Type type)
{
return getRelatedImages(objectId, false, type, false);
}
QVector<QList<qlonglong> > CoreDB::getImagesRelatingTo(QList<qlonglong> objectIds, DatabaseRelation::Type type)
{
return getRelatedImages(objectIds, false, type, false);
}
bool CoreDB::hasImagesRelatingTo(qlonglong objectId, DatabaseRelation::Type type)
{
// returns 0 or 1 item in list
return !getRelatedImages(objectId, false, type, true).isEmpty();
}
QList<qlonglong> CoreDB::getRelatedImages(qlonglong id, bool fromOrTo, DatabaseRelation::Type type, bool boolean)
{
QString sql = d->constructRelatedImagesSQL(fromOrTo, type, boolean);
DbEngineSqlQuery query = d->db->prepareQuery(sql);
return d->execRelatedImagesQuery(query, id, type);
}
QVector<QList<qlonglong> > CoreDB::getRelatedImages(QList<qlonglong> ids,
bool fromOrTo, DatabaseRelation::Type type, bool boolean)
{
if (ids.isEmpty())
{
return QVector<QList<qlonglong> >();
}
QVector<QList<qlonglong> > result(ids.size());
QString sql = d->constructRelatedImagesSQL(fromOrTo, type, boolean);
DbEngineSqlQuery query = d->db->prepareQuery(sql);
for (int i = 0 ; i < ids.size() ; i++)
{
result[i] = d->execRelatedImagesQuery(query, ids[i], type);
}
return result;
}
QList<QPair<qlonglong, qlonglong> > CoreDB::getRelationCloud(qlonglong imageId, DatabaseRelation::Type type)
{
QSet<qlonglong> todo, done;
QSet<QPair<qlonglong, qlonglong> > pairs;
todo << imageId;
QString sql = QString::fromUtf8(
"SELECT subject, object FROM ImageRelations "
"INNER JOIN Images AS SubjectImages ON ImageRelations.subject=SubjectImages.id "
"INNER JOIN Images AS ObjectImages ON ImageRelations.object=ObjectImages.id "
"WHERE (subject=? OR object=?) %1 AND SubjectImages.status!=3 AND ObjectImages.status!=3;");
if (type == DatabaseRelation::UndefinedType)
{
sql = sql.arg(QString());
}
else
{
sql = sql.arg(QString::fromUtf8("AND type=?"));
}
DbEngineSqlQuery query = d->db->prepareQuery(sql);
QList<QVariant> values;
qlonglong subject, object;
while (!todo.isEmpty())
{
qlonglong id = *todo.begin();
todo.erase(todo.begin());
done << id;
if (type == DatabaseRelation::UndefinedType)
{
d->db->execSql(query, id, id, &values);
}
else
{
d->db->execSql(query, id, id, type, &values);
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
subject = (*it).toLongLong();
++it;
object = (*it).toLongLong();
++it;
pairs << qMakePair(subject, object);
if (!done.contains(subject))
{
todo << subject;
}
if (!done.contains(object))
{
todo << object;
}
}
}
return pairs.toList();
}
QList<qlonglong> CoreDB::getOneRelatedImageEach(const QList<qlonglong>& ids, DatabaseRelation::Type type)
{
QString sql = QString::fromUtf8(
"SELECT subject, object FROM ImageRelations "
"INNER JOIN Images AS SubjectImages ON ImageRelations.subject=SubjectImages.id "
"INNER JOIN Images AS ObjectImages ON ImageRelations.object=ObjectImages.id "
"WHERE ( (subject=? AND ObjectImages.status!=3) "
" OR (object=? AND SubjectImages.status!=3) ) "
" %1 LIMIT 1;");
if (type == DatabaseRelation::UndefinedType)
{
sql = sql.arg(QString());
}
else
{
sql = sql.arg(QString::fromUtf8("AND type=?"));
}
DbEngineSqlQuery query = d->db->prepareQuery(sql);
QSet<qlonglong> result;
QList<QVariant> values;
foreach(const qlonglong& id, ids)
{
if (type == DatabaseRelation::UndefinedType)
{
d->db->execSql(query, id, id, &values);
}
else
{
d->db->execSql(query, id, id, type, &values);
}
if (values.size() != 2)
{
continue;
}
// one of subject and object is the given id, the other our result
if (values.first() != id)
{
result << values.first().toLongLong();
}
else
{
result << values.last().toLongLong();
}
}
return result.toList();
}
QStringList CoreDB::getItemsURLsWithTag(int tagId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images "
"LEFT JOIN ImageTags ON Images.id=ImageTags.imageid "
"LEFT JOIN Albums ON Albums.id=Images.album "
" WHERE Images.status=1 AND Images.category=1 AND ImageTags.tagid=?;"),
tagId, &values);
QStringList urls;
QString albumRootPath, relativePath, name;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt());
++it;
relativePath = (*it).toString();
++it;
name = (*it).toString();
++it;
if (relativePath == QLatin1String("/"))
{
urls << albumRootPath + relativePath + name;
}
else
{
urls << albumRootPath + relativePath + QLatin1Char('/') + name;
}
}
return urls;
}
QStringList CoreDB::getDirtyOrMissingFaceImageUrls()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Albums.albumRoot, Albums.relativePath, Images.name FROM Images "
"LEFT JOIN ImageScannedMatrix ON Images.id=ImageScannedMatrix.imageid "
"LEFT JOIN Albums ON Albums.id=Images.album "
" WHERE Images.status=1 AND Images.category=1 AND "
" ( ImageScannedMatrix.imageid IS NULL "
" OR Images.modificationDate != ImageScannedMatrix.modificationDate "
" OR Images.uniqueHash != ImageScannedMatrix.uniqueHash );"),
&values);
QStringList urls;
QString albumRootPath, relativePath, name;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt());
++it;
relativePath = (*it).toString();
++it;
name = (*it).toString();
++it;
if (relativePath == QLatin1String("/"))
{
urls << albumRootPath + relativePath + name;
}
else
{
urls << albumRootPath + relativePath + QLatin1Char('/') + name;
}
}
return urls;
}
QList<ItemScanInfo> CoreDB::getIdenticalFiles(qlonglong id)
{
if (!id)
{
return QList<ItemScanInfo>();
}
QList<QVariant> values;
// retrieve unique hash and file size
d->db->execSql(QString::fromUtf8("SELECT uniqueHash, fileSize FROM Images WHERE id=?;"),
id,
&values);
if (values.isEmpty())
{
return QList<ItemScanInfo>();
}
QString uniqueHash = values.at(0).toString();
qlonglong fileSize = values.at(1).toLongLong();
return getIdenticalFiles(uniqueHash, fileSize, id);
}
QList<ItemScanInfo> CoreDB::getIdenticalFiles(const QString& uniqueHash, qlonglong fileSize, qlonglong sourceId)
{
// enforce validity
if (uniqueHash.isEmpty() || fileSize <= 0)
{
return QList<ItemScanInfo>();
}
QList<QVariant> values;
// find items with same fingerprint
d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize FROM Images "
" WHERE fileSize=? AND uniqueHash=? AND album IS NOT NULL;"),
fileSize, uniqueHash,
&values);
QList<ItemScanInfo> list;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
ItemScanInfo info;
info.id = (*it).toLongLong();
++it;
info.albumID = (*it).toInt();
++it;
info.itemName = (*it).toString();
++it;
info.status = (DatabaseItem::Status)(*it).toInt();
++it;
info.category = (DatabaseItem::Category)(*it).toInt();
++it;
info.modificationDate = (*it).toDateTime();
++it;
info.fileSize = (*it).toLongLong();
++it;
// exclude one source id from list
if (sourceId == info.id)
{
continue;
}
// same for all here, per definition
info.uniqueHash = uniqueHash;
list << info;
}
return list;
}
QStringList CoreDB::imagesFieldList(DatabaseFields::Images fields)
{
// adds no spaces at beginning or end
QStringList list;
if (fields & DatabaseFields::Album)
{
list << QLatin1String("album");
}
if (fields & DatabaseFields::Name)
{
list << QLatin1String("name");
}
if (fields & DatabaseFields::Status)
{
list << QLatin1String("status");
}
if (fields & DatabaseFields::Category)
{
list << QLatin1String("category");
}
if (fields & DatabaseFields::ModificationDate)
{
list << QLatin1String("modificationDate");
}
if (fields & DatabaseFields::FileSize)
{
list << QLatin1String("fileSize");
}
if (fields & DatabaseFields::UniqueHash)
{
list << QLatin1String("uniqueHash");
}
if (fields & DatabaseFields::ManualOrder)
{
list << QLatin1String("manualOrder");
}
return list;
}
QStringList CoreDB::imageInformationFieldList(DatabaseFields::ImageInformation fields)
{
// adds no spaces at beginning or end
QStringList list;
if (fields & DatabaseFields::Rating)
{
list << QLatin1String("rating");
}
if (fields & DatabaseFields::CreationDate)
{
list << QLatin1String("creationDate");
}
if (fields & DatabaseFields::DigitizationDate)
{
list << QLatin1String("digitizationDate");
}
if (fields & DatabaseFields::Orientation)
{
list << QLatin1String("orientation");
}
if (fields & DatabaseFields::Width)
{
list << QLatin1String("width");
}
if (fields & DatabaseFields::Height)
{
list << QLatin1String("height");
}
if (fields & DatabaseFields::Format)
{
list << QLatin1String("format");
}
if (fields & DatabaseFields::ColorDepth)
{
list << QLatin1String("colorDepth");
}
if (fields & DatabaseFields::ColorModel)
{
list << QLatin1String("colorModel");
}
return list;
}
QStringList CoreDB::videoMetadataFieldList(DatabaseFields::VideoMetadata fields)
{
// adds no spaces at beginning or end
QStringList list;
if (fields & DatabaseFields::AspectRatio)
{
list << QLatin1String("aspectRatio");
}
if (fields & DatabaseFields::AudioBitRate)
{
list << QLatin1String("audioBitRate");
}
if (fields & DatabaseFields::AudioChannelType)
{
list << QLatin1String("audioChannelType");
}
if (fields & DatabaseFields::AudioCodec)
{
list << QLatin1String("audioCompressor");
}
if (fields & DatabaseFields::Duration)
{
list << QLatin1String("duration");
}
if (fields & DatabaseFields::FrameRate)
{
list << QLatin1String("frameRate");
}
if (fields & DatabaseFields::VideoCodec)
{
list << QLatin1String("videoCodec");
}
return list;
}
QStringList CoreDB::imageMetadataFieldList(DatabaseFields::ImageMetadata fields)
{
// adds no spaces at beginning or end
QStringList list;
if (fields & DatabaseFields::Make)
{
list << QLatin1String("make");
}
if (fields & DatabaseFields::Model)
{
list << QLatin1String("model");
}
if (fields & DatabaseFields::Lens)
{
list << QLatin1String("lens");
}
if (fields & DatabaseFields::Aperture)
{
list << QLatin1String("aperture");
}
if (fields & DatabaseFields::FocalLength)
{
list << QLatin1String("focalLength");
}
if (fields & DatabaseFields::FocalLength35)
{
list << QLatin1String("focalLength35");
}
if (fields & DatabaseFields::ExposureTime)
{
list << QLatin1String("exposureTime");
}
if (fields & DatabaseFields::ExposureProgram)
{
list << QLatin1String("exposureProgram");
}
if (fields & DatabaseFields::ExposureMode)
{
list << QLatin1String("exposureMode");
}
if (fields & DatabaseFields::Sensitivity)
{
list << QLatin1String("sensitivity");
}
if (fields & DatabaseFields::FlashMode)
{
list << QLatin1String("flash");
}
if (fields & DatabaseFields::WhiteBalance)
{
list << QLatin1String("whiteBalance");
}
if (fields & DatabaseFields::WhiteBalanceColorTemperature)
{
list << QLatin1String("whiteBalanceColorTemperature");
}
if (fields & DatabaseFields::MeteringMode)
{
list << QLatin1String("meteringMode");
}
if (fields & DatabaseFields::SubjectDistance)
{
list << QLatin1String("subjectDistance");
}
if (fields & DatabaseFields::SubjectDistanceCategory)
{
list << QLatin1String("subjectDistanceCategory");
}
return list;
}
QStringList CoreDB::imagePositionsFieldList(DatabaseFields::ImagePositions fields)
{
// adds no spaces at beginning or end
QStringList list;
if (fields & DatabaseFields::Latitude)
{
list << QLatin1String("latitude");
}
if (fields & DatabaseFields::LatitudeNumber)
{
list << QLatin1String("latitudeNumber");
}
if (fields & DatabaseFields::Longitude)
{
list << QLatin1String("longitude");
}
if (fields & DatabaseFields::LongitudeNumber)
{
list << QLatin1String("longitudeNumber");
}
if (fields & DatabaseFields::Altitude)
{
list << QLatin1String("altitude");
}
if (fields & DatabaseFields::PositionOrientation)
{
list << QLatin1String("orientation");
}
if (fields & DatabaseFields::PositionTilt)
{
list << QLatin1String("tilt");
}
if (fields & DatabaseFields::PositionRoll)
{
list << QLatin1String("roll");
}
if (fields & DatabaseFields::PositionAccuracy)
{
list << QLatin1String("accuracy");
}
if (fields & DatabaseFields::PositionDescription)
{
list << QLatin1String("description");
}
return list;
}
QStringList CoreDB::imageCommentsFieldList(DatabaseFields::ImageComments fields)
{
// adds no spaces at beginning or end
QStringList list;
if (fields & DatabaseFields::CommentType)
{
list << QLatin1String("type");
}
if (fields & DatabaseFields::CommentLanguage)
{
list << QLatin1String("language");
}
if (fields & DatabaseFields::CommentAuthor)
{
list << QLatin1String("author");
}
if (fields & DatabaseFields::CommentDate)
{
list << QLatin1String("date");
}
if (fields & DatabaseFields::Comment)
{
list << QLatin1String("comment");
}
return list;
}
void CoreDB::addBoundValuePlaceholders(QString& query, int count)
{
// adds no spaces at beginning or end
QString questionMarks;
questionMarks.reserve(count * 2);
QString questionMark(QString::fromUtf8("?,"));
for (int i = 0 ; i < count ; ++i)
{
questionMarks += questionMark;
}
// remove last ','
questionMarks.chop(1);
query += questionMarks;
}
int CoreDB::findInDownloadHistory(const QString& identifier, const QString& name, qlonglong fileSize, const QDateTime& date)
{
QList<QVariant> values;
QVariantList boundValues;
boundValues << identifier << name << fileSize << date.addSecs(-2) << date.addSecs(2);
d->db->execSql(QString::fromUtf8("SELECT id FROM DownloadHistory WHERE "
"identifier=? AND filename=? AND filesize=? AND (filedate>? AND filedate<?);"),
boundValues, &values);
if (values.isEmpty())
{
return -1;
}
return values.first().toInt();
}
int CoreDB::addToDownloadHistory(const QString& identifier, const QString& name, qlonglong fileSize, const QDateTime& date)
{
QVariant id;
d->db->execSql(QString::fromUtf8("REPLACE INTO DownloadHistory "
"(identifier, filename, filesize, filedate) "
"VALUES (?,?,?,?);"),
identifier, name, fileSize, date, 0, &id);
return id.toInt();
}
/*
void CoreDB::setItemCaption(qlonglong imageID,const QString& caption)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("UPDATE Images SET caption=? "
"WHERE id=?;"),
caption,
imageID );
CoreDbAccess::attributesWatch()
->sendImageFieldChanged(imageID, DatabaseAttributesWatch::ImageComment);
}
void CoreDB::setItemCaption(int albumID, const QString& name, const QString& caption)
{
/ *
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("UPDATE Images SET caption=? "
"WHERE dirid=? AND name=?;")
.(caption,
QString::number(albumID),
(name)) );
* /
// easier because of attributes watch
return setItemCaption(getImageId(albumID, name), caption);
}
*/
void CoreDB::addItemTag(qlonglong imageID, int tagID)
{
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) "
"VALUES(?, ?);"),
imageID,
tagID);
d->db->recordChangeset(ImageTagChangeset(imageID, tagID, ImageTagChangeset::Added));
//don't save pick or color tags
if (TagsCache::instance()->isInternalTag(tagID))
return;
//move current tag to front
d->recentlyAssignedTags.removeAll(tagID);
d->recentlyAssignedTags.prepend(tagID);
if (d->recentlyAssignedTags.size() > 10)
{
d->recentlyAssignedTags.removeLast();
}
}
void CoreDB::addItemTag(int albumID, const QString& name, int tagID)
{
/*
d->db->execSql( QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) \n "
"(SELECT id, ? FROM Images \n "
" WHERE dirid=? AND name=?);")
.tagID
.albumID
.(name) );
*/
// easier because of attributes watch
return addItemTag(getImageId(albumID, name), tagID);
}
void CoreDB::addTagsToItems(QList<qlonglong> imageIDs, QList<int> tagIDs)
{
if (imageIDs.isEmpty() || tagIDs.isEmpty())
{
return;
}
DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("REPLACE INTO ImageTags (imageid, tagid) VALUES(?, ?);"));
QVariantList images;
QVariantList tags;
foreach(const qlonglong& imageid, imageIDs)
{
foreach(int tagid, tagIDs)
{
images << imageid;
tags << tagid;
}
}
query.addBindValue(images);
query.addBindValue(tags);
d->db->execBatch(query);
d->db->recordChangeset(ImageTagChangeset(imageIDs, tagIDs, ImageTagChangeset::Added));
}
QList<int> CoreDB::getRecentlyAssignedTags() const
{
return d->recentlyAssignedTags;
}
void CoreDB::removeItemTag(qlonglong imageID, int tagID)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags "
"WHERE imageID=? AND tagid=?;"),
imageID,
tagID);
d->db->recordChangeset(ImageTagChangeset(imageID, tagID, ImageTagChangeset::Removed));
}
void CoreDB::removeItemAllTags(qlonglong imageID, const QList<int>& currentTagIds)
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags "
"WHERE imageID=?;"),
imageID);
d->db->recordChangeset(ImageTagChangeset(imageID, currentTagIds, ImageTagChangeset::RemovedAll));
}
void CoreDB::removeTagsFromItems(QList<qlonglong> imageIDs, const QList<int>& tagIDs)
{
if (imageIDs.isEmpty() || tagIDs.isEmpty())
{
return;
}
DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("DELETE FROM ImageTags WHERE imageID=? AND tagid=?;"));
QVariantList images;
QVariantList tags;
foreach(const qlonglong& imageid, imageIDs)
{
foreach(int tagid, tagIDs)
{
images << imageid;
tags << tagid;
}
}
query.addBindValue(images);
query.addBindValue(tags);
d->db->execBatch(query);
d->db->recordChangeset(ImageTagChangeset(imageIDs, tagIDs, ImageTagChangeset::Removed));
}
QStringList CoreDB::getItemNamesInAlbum(int albumID, bool recursive)
{
QList<QVariant> values;
if (recursive)
{
int rootId = getAlbumRootId(albumID);
QString path = getAlbumRelativePath(albumID);
d->db->execSql(QString::fromUtf8("SELECT Images.name FROM Images WHERE Images.album IN "
" (SELECT DISTINCT id FROM Albums "
" WHERE albumRoot=? AND (relativePath=? OR relativePath LIKE ?));"),
rootId, path, path == QString::fromUtf8("/") ? QString::fromUtf8("/%")
: QString(path + QLatin1String("/%")),
&values);
}
else
{
d->db->execSql(QString::fromUtf8("SELECT Images.name "
"FROM Images "
"WHERE Images.album=?;"),
albumID, &values);
}
QStringList names;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
names << it->toString();
}
return names;
}
qlonglong CoreDB::getItemFromAlbum(int albumID, const QString& fileName)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Images.id "
"FROM Images "
"WHERE Images.album=? AND Images.name=?;"),
albumID, fileName, &values);
if (values.isEmpty())
{
return -1;
}
else
{
return values.first().toLongLong();
}
}
/*
QStringList CoreDB::getAllItemURLsWithoutDate()
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT AlbumRoots.absolutePath||Albums.relativePath||'/'||Images.name "
"FROM Images "
" LEFT JOIN Albums ON Images.album=Albums.id "
" LEFT JOIN AlbumRoots ON AlbumRoots.id=Albums.albumRoot "
"WHERE (Images.datetime is null or "
" Images.datetime == '');"),
&values );
QStringList urls;
for (QList<QVariant>::iterator it = values.begin(); it != values.end(); ++it)
urls << it->toString();
return urls;
}
*/
QList<QDateTime> CoreDB::getAllCreationDates()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation "
" INNER JOIN Images ON Images.id=ImageInformation.imageid "
" WHERE Images.status=1;"), &values);
QList<QDateTime> list;
foreach(const QVariant& value, values)
{
if (!value.isNull())
{
list << value.toDateTime();
}
}
return list;
}
QMap<QDateTime, int> CoreDB::getAllCreationDatesAndNumberOfImages()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation "
" INNER JOIN Images ON Images.id=ImageInformation.imageid "
" WHERE Images.status=1;"), &values);
QMap<QDateTime, int> datesStatMap;
foreach(const QVariant& value, values)
{
if (!value.isNull())
{
QDateTime dateTime = value.toDateTime();
if (!dateTime.isValid())
{
continue;
}
QMap<QDateTime, int>::iterator it2 = datesStatMap.find(dateTime);
if (it2 == datesStatMap.end())
{
datesStatMap.insert(dateTime, 1);
}
else
{
it2.value()++;
}
}
}
return datesStatMap;
}
QMap<int, int> CoreDB::getNumberOfImagesInAlbums()
{
QList<QVariant> values, allAbumIDs;
QMap<int, int> albumsStatMap;
int albumID;
// initialize allAbumIDs with all existing albums from db to prevent
// wrong album image counters
d->db->execSql(QString::fromUtf8("SELECT id from Albums"), &allAbumIDs);
for (QList<QVariant>::const_iterator it = allAbumIDs.constBegin() ; it != allAbumIDs.constEnd() ; ++it)
{
albumID = (*it).toInt();
albumsStatMap.insert(albumID, 0);
}
d->db->execSql(QString::fromUtf8("SELECT album FROM Images WHERE Images.status=1;"), &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
albumID = (*it).toInt();
++it;
QMap<int, int>::iterator it2 = albumsStatMap.find(albumID);
if (it2 == albumsStatMap.end())
{
albumsStatMap.insert(albumID, 1);
}
else
{
it2.value()++;
}
}
return albumsStatMap;
}
QMap<int, int> CoreDB::getNumberOfImagesInTags()
{
QList<QVariant> values, allTagIDs;
QMap<int, int> tagsStatMap;
int tagID;
// initialize allTagIDs with all existing tags from db to prevent
// wrong tag counters
d->db->execSql(QString::fromUtf8("SELECT id from Tags"), &allTagIDs);
for (QList<QVariant>::const_iterator it = allTagIDs.constBegin(); it != allTagIDs.constEnd(); ++it)
{
tagID = (*it).toInt();
tagsStatMap.insert(tagID, 0);
}
d->db->execSql(QString::fromUtf8("SELECT tagid FROM ImageTags "
" LEFT JOIN Images ON Images.id=ImageTags.imageid "
" WHERE Images.status=1;"), &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
tagID = (*it).toInt();
++it;
QMap<int, int>::iterator it2 = tagsStatMap.find(tagID);
if (it2 == tagsStatMap.end())
{
tagsStatMap.insert(tagID, 1);
}
else
{
it2.value()++;
}
}
return tagsStatMap;
}
QMap<int, int> CoreDB::getNumberOfImagesInTagProperties(const QString& property)
{
QList<QVariant> values;
QMap<int, int> tagsStatMap;
int tagID, count;
d->db->execSql(QString::fromUtf8("SELECT tagid, COUNT(*) FROM ImageTagProperties "
" LEFT JOIN Images ON Images.id=ImageTagProperties.imageid "
" WHERE ImageTagProperties.property=? AND Images.status=1 "
" GROUP BY tagid;"),
property, &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
tagID = (*it).toInt();
++it;
count = (*it).toInt();
++it;
tagsStatMap[tagID] = count;
}
return tagsStatMap;
}
int CoreDB::getNumberOfImagesInTagProperties(int tagId, const QString& property)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT COUNT(*) FROM ImageTagProperties "
" LEFT JOIN Images ON Images.id=ImageTagProperties.imageid "
" WHERE ImageTagProperties.property=? AND Images.status=1 "
" AND ImageTagProperties.tagid=? ;"),
property, tagId, &values);
return values.first().toInt();
}
QList<qlonglong> CoreDB::getImagesWithImageTagProperty(int tagId, const QString& property)
{
QList<QVariant> values;
QList<qlonglong> imageIds;
d->db->execSql(QString::fromUtf8("SELECT DISTINCT Images.id FROM ImageTagProperties "
" LEFT JOIN Images ON Images.id=ImageTagProperties.imageid "
" WHERE ImageTagProperties.property=? AND Images.status=1 "
" AND ImageTagProperties.tagid=? ;"),
property, tagId, &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
imageIds.append((*it).toInt());
}
return imageIds;
}
QMap<QString, int> CoreDB::getFormatStatistics()
{
return getFormatStatistics(DatabaseItem::UndefinedCategory);
}
QMap<QString, int> CoreDB::getFormatStatistics(DatabaseItem::Category category)
{
QMap<QString, int> map;
QString queryString = QString::fromUtf8(
"SELECT COUNT(*), II.format "
" FROM ImageInformation AS II "
" INNER JOIN Images ON II.imageid=Images.id "
" WHERE Images.status=1 ");
if (category != DatabaseItem::UndefinedCategory)
{
queryString.append(QString::fromUtf8("AND Images.category=%1").arg(category));
}
queryString.append(QString::fromUtf8(" GROUP BY II.format;"));
qCDebug(DIGIKAM_DATABASE_LOG) << queryString;
DbEngineSqlQuery query = d->db->prepareQuery(queryString);
if (d->db->exec(query))
{
while (query.next())
{
QString quantity = query.value(0).toString();
QString format = query.value(1).toString();
if (format.isEmpty())
{
continue;
}
map[format] = quantity.isEmpty() ? 0 : quantity.toInt();
}
}
return map;
}
QStringList CoreDB::getListFromImageMetadata(DatabaseFields::ImageMetadata field)
{
QStringList list;
QList<QVariant> values;
QStringList fieldName = imageMetadataFieldList(field);
if (fieldName.count() != 1)
{
return list;
}
QString sql = QString::fromUtf8("SELECT DISTINCT %1 FROM ImageMetadata "
" INNER JOIN Images ON imageid=Images.id;");
sql = sql.arg(fieldName.first());
d->db->execSql(sql, &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
if (!it->isNull())
{
list << it->toString();
}
}
return list;
}
/*
QList<QPair<QString, QDateTime> > CoreDB::getItemsAndDate()
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT Images.name, datetime FROM Images;", &values ));
QList<QPair<QString, QDateTime> > data;
for ( QList<QVariant>::iterator it = values.begin(); it != values.end(); )
{
QPair<QString, QDateTime> pair;
pair.first = (*it).toString();
++it;
pair.second = QDateTime::fromString( (*it).toString(), Qt::ISODate );
++it;
if (!pair.second.isValid())
continue;
data << pair;
}
return data;
}
*/
/*
int CoreDB::getAlbumForPath(const QString& albumRoot, const QString& folder, bool create)
{
CollectionLocation location = CollectionManager::instance()->locationForAlbumRootPath(albumRoot);
if (location.isNull())
return -1;
return getAlbumForPath(location.id(), folder, create);
}
*/
int CoreDB::getAlbumForPath(int albumRootId, const QString& folder, bool create)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Albums WHERE albumRoot=? AND relativePath=?;"),
albumRootId, folder, &values);
int albumID = -1;
if (values.isEmpty())
{
if (create)
{
albumID = addAlbum(albumRootId, folder, QString(), QDate::currentDate(), QString());
}
}
else
{
albumID = values.first().toInt();
}
return albumID;
}
QList<int> CoreDB::getAlbumAndSubalbumsForPath(int albumRootId, const QString& relativePath)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, relativePath FROM Albums WHERE albumRoot=? AND (relativePath=? OR relativePath LIKE ?);"),
albumRootId, relativePath, (relativePath == QLatin1String("/") ? QLatin1String("/%")
: QString(relativePath + QLatin1String("/%"))), &values);
QList<int> albumIds;
int id;
QString albumRelativePath;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
id = (*it).toInt();
++it;
QString albumRelativePath = (*it).toString();
++it;
// bug #223050: The LIKE operator is case insensitive
if (albumRelativePath.startsWith(relativePath))
{
albumIds << id;
}
}
return albumIds;
}
QList<int> CoreDB::getAlbumsOnAlbumRoot(int albumRootId)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Albums WHERE albumRoot=?;"),
albumRootId, &values);
QList<int> albumIds;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
albumIds << (*it).toInt();
}
return albumIds;
}
qlonglong CoreDB::addItem(int albumID, const QString& name,
DatabaseItem::Status status,
DatabaseItem::Category category,
const QDateTime& modificationDate,
qlonglong fileSize,
const QString& uniqueHash)
{
QVariantList boundValues;
boundValues << albumID << name << (int)status << (int)category
<< modificationDate << fileSize << uniqueHash;
QVariant id;
d->db->execSql(QString::fromUtf8("REPLACE INTO Images "
" ( album, name, status, category, modificationDate, fileSize, uniqueHash ) "
" VALUES (?,?,?,?,?,?,?);"),
boundValues,
0, &id);
if (id.isNull())
{
return -1;
}
d->db->recordChangeset(ImageChangeset(id.toLongLong(), DatabaseFields::ImagesAll));
d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), albumID, CollectionImageChangeset::Added));
return id.toLongLong();
}
void CoreDB::updateItem(qlonglong imageID, DatabaseItem::Category category,
const QDateTime& modificationDate,
qlonglong fileSize, const QString& uniqueHash)
{
QVariantList boundValues;
boundValues << (int)category << modificationDate << fileSize << uniqueHash << imageID;
d->db->execSql(QString::fromUtf8("UPDATE Images SET category=?, modificationDate=?, fileSize=?, uniqueHash=? WHERE id=?;"),
boundValues);
d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Category
| DatabaseFields::ModificationDate
| DatabaseFields::FileSize
| DatabaseFields::UniqueHash));
}
void CoreDB::setItemStatus(qlonglong imageID, DatabaseItem::Status status)
{
QVariantList boundValues;
boundValues << (int)status << imageID;
d->db->execSql(QString::fromUtf8("UPDATE Images SET status=? WHERE id=?;"),
boundValues);
d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Status));
}
void CoreDB::setItemAlbum(qlonglong imageID, qlonglong album)
{
QVariantList boundValues;
boundValues << album << imageID;
d->db->execSql(QString::fromUtf8("UPDATE Images SET album=? WHERE id=?;"),
boundValues);
// record that the image was assigned a new album
d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::Album));
// also record that the collection was changed by adding an image to an album.
d->db->recordChangeset(CollectionImageChangeset(imageID, album, CollectionImageChangeset::Added));
}
void CoreDB::setItemManualOrder(qlonglong imageID, qlonglong value)
{
QVariantList boundValues;
boundValues << value << imageID;
d->db->execSql(QString::fromUtf8("UPDATE Images SET manualOrder=? WHERE id=?;"),
boundValues);
d->db->recordChangeset(ImageChangeset(imageID, DatabaseFields::ManualOrder));
}
void CoreDB::renameItem(qlonglong imageID, const QString& newName)
{
d->db->execSql(QString::fromUtf8("UPDATE Images SET name=? WHERE id=?;"),
newName, imageID);
}
/*
QList<int> CoreDB::getTagsFromTagPaths(const QStringList& keywordsList, bool create)
{
if (keywordsList.isEmpty())
return QList<int>();
QList<int> tagIDs;
QStringList keywordsList2Create;
// Create a list of the tags currently in database
TagInfo::List currentTagsList;
QList<QVariant> values;
d->db->execSql( "SELECT id, pid, name FROM Tags;", &values );
for (QList<QVariant>::const_iterator it = values.constBegin(); it != values.constEnd();)
{
TagInfo info;
info.id = (*it).toInt();
++it;
info.pid = (*it).toInt();
++it;
info.name = (*it).toString();
++it;
currentTagsList.append(info);
}
// For every tag in keywordsList, scan taglist to check if tag already exists.
for (QStringList::const_iterator kwd = keywordsList.constBegin();
kwd != keywordsList.constEnd(); ++kwd )
{
// split full tag "url" into list of single tag names
QStringList tagHierarchy = (*kwd).split('/', QString::SkipEmptyParts);
if (tagHierarchy.isEmpty())
continue;
// last entry in list is the actual tag name
bool foundTag = false;
QString tagName = tagHierarchy.back();
tagHierarchy.pop_back();
for (TagInfo::List::const_iterator tag = currentTagsList.constBegin();
tag != currentTagsList.constEnd(); ++tag )
{
// There might be multiple tags with the same name, but in different
// hierarchies. We must check them all until we find the correct hierarchy
if ((*tag).name == tagName)
{
int parentID = (*tag).pid;
// Check hierarchy, from bottom to top
bool foundParentTag = true;
QStringList::iterator parentTagName = tagHierarchy.end();
while (foundParentTag && parentTagName != tagHierarchy.begin())
{
--parentTagName;
foundParentTag = false;
for (TagInfo::List::const_iterator parentTag = currentTagsList.constBegin();
parentTag != currentTagsList.constEnd(); ++parentTag )
{
// check if name is the same, and if ID is identical
// to the parent ID we got from the child tag
if ( (*parentTag).id == parentID &&
(*parentTag).name == (*parentTagName) )
{
parentID = (*parentTag).pid;
foundParentTag = true;
break;
}
}
// If we traversed the list without a match,
// foundParentTag will be false, the while loop breaks.
}
// If we managed to traverse the full hierarchy,
// we have our tag.
if (foundParentTag)
{
// add to result list
tagIDs.append((*tag).id);
foundTag = true;
break;
}
}
}
if (!foundTag)
keywordsList2Create.append(*kwd);
}
// If tags do not exist in database, create them.
if (create && !keywordsList2Create.isEmpty())
{
for (QStringList::const_iterator kwd = keywordsList2Create.constBegin();
kwd != keywordsList2Create.constEnd(); ++kwd )
{
// split full tag "url" into list of single tag names
QStringList tagHierarchy = (*kwd).split('/', QString::SkipEmptyParts);
if (tagHierarchy.isEmpty())
continue;
int parentTagID = 0;
int tagID = 0;
bool parentTagExisted = true;
// Traverse hierarchy from top to bottom
for (QStringList::const_iterator tagName = tagHierarchy.constBegin();
tagName != tagHierarchy.constEnd(); ++tagName)
{
tagID = 0;
// if the parent tag did not exist, we need not check if the child exists
if (parentTagExisted)
{
for (TagInfo::List::const_iterator tag = currentTagsList.constBegin();
tag != currentTagsList.constEnd(); ++tag )
{
// find the tag with tag name according to tagHierarchy,
// and parent ID identical to the ID of the tag we found in
// the previous run.
if ((*tag).name == (*tagName) && (*tag).pid == parentTagID)
{
tagID = (*tag).id;
break;
}
}
}
if (tagID != 0)
{
// tag already found in DB
parentTagID = tagID;
continue;
}
// Tag does not yet exist in DB, add it
tagID = addTag(parentTagID, (*tagName), QString(), 0);
if (tagID == -1)
{
// Something is wrong in database. Abort.
break;
}
// append to our list of existing tags (for following keywords)
TagInfo info;
info.id = tagID;
info.pid = parentTagID;
info.name = (*tagName);
currentTagsList.append(info);
parentTagID = tagID;
parentTagExisted = false;
}
// add to result list
tagIDs.append(tagID);
}
}
return tagIDs;
}
*/
int CoreDB::getItemAlbum(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT album FROM Images WHERE id=?;"),
imageID, &values);
if (!values.isEmpty())
{
return values.first().toInt();
}
else
{
return 1;
}
}
QString CoreDB::getItemName(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT name FROM Images WHERE id=?;"),
imageID, &values);
if (!values.isEmpty())
{
return values.first().toString();
}
else
{
return QString();
}
}
/*
bool CoreDB::setItemDate(qlonglong imageID,
const QDateTime& datetime)
{
d->db->execSql ( QString::fromUtf8("UPDATE Images SET datetime=?"
"WHERE id=?;"),
datetime.toString(Qt::ISODate),
imageID );
CoreDbAccess::attributesWatch()
->sendImageFieldChanged(imageID, DatabaseAttributesWatch::ImageDate);
return true;
}
bool CoreDB::setItemDate(int albumID, const QString& name,
const QDateTime& datetime)
{
/ *
d->db->execSql ( QString::fromUtf8("UPDATE Images SET datetime=?"
"WHERE dirid=? AND name=?;")
.datetime.toString(Qt::ISODate,
QString::number(albumID),
(name)) );
return true;
* /
// easier because of attributes watch
return setItemDate(getImageId(albumID, name), datetime);
}
void CoreDB::setItemRating(qlonglong imageID, int rating)
{
d->db->execSql ( QString::fromUtf8("REPLACE INTO ImageProperties "
"(imageid, property, value) "
"VALUES(?, ?, ?);"),
imageID,
QString("Rating"),
rating );
CoreDbAccess::attributesWatch()
->sendImageFieldChanged(imageID, DatabaseAttributesWatch::ImageRating);
}
int CoreDB::getItemRating(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8(SELECT value FROM ImageProperties "
"WHERE imageid=? and property=?;"),
imageID,
QString("Rating"),
&values);
if (!values.isEmpty())
return values.first().toInt();
else
return 0;
}
*/
QStringList CoreDB::getItemURLsInAlbum(int albumID, ItemSortOrder sortOrder)
{
QList<QVariant> values;
int albumRootId = getAlbumRootId(albumID);
if (albumRootId == -1)
{
return QStringList();
}
QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId);
if (albumRootPath.isNull())
{
return QStringList();
}
QMap<QString, QVariant> bindingMap;
bindingMap.insert(QString::fromUtf8(":albumID"), albumID);
switch (sortOrder)
{
case ByItemName:
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemName")), bindingMap, &values);
break;
case ByItemPath:
// Don't collate on the path - this is to maintain the same behavior
// that happens when sort order is "By Path"
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemPath")), bindingMap, &values);
break;
case ByItemDate:
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemDate")), bindingMap, &values);
break;
case ByItemRating:
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumByItemRating")), bindingMap, &values);
break;
case NoItemSorting:
default:
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemURLsInAlbumNoItemSorting")), bindingMap, &values);
break;
}
QStringList urls;
QString relativePath, name;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
relativePath = (*it).toString();
++it;
name = (*it).toString();
++it;
if (relativePath == QLatin1String("/"))
{
urls << albumRootPath + relativePath + name;
}
else
{
urls << albumRootPath + relativePath + QLatin1Char('/') + name;
}
}
return urls;
}
QList<qlonglong> CoreDB::getItemIDsInAlbum(int albumID)
{
QList<qlonglong> itemIDs;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images WHERE album=?;"),
albumID, &values);
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
itemIDs << (*it).toLongLong();
}
return itemIDs;
}
QMap<qlonglong, QString> CoreDB::getItemIDsAndURLsInAlbum(int albumID)
{
int albumRootId = getAlbumRootId(albumID);
if (albumRootId == -1)
{
return QMap<qlonglong, QString>();
}
QString albumRootPath = CollectionManager::instance()->albumRootPath(albumRootId);
if (albumRootPath.isNull())
{
return QMap<qlonglong, QString>();
}
QMap<qlonglong, QString> itemsMap;
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT Images.id, Albums.relativePath, Images.name "
"FROM Images JOIN Albums ON Albums.id=Images.album "
"WHERE Albums.id=?;"),
albumID, &values);
QString path;
qlonglong id;
QString relativePath, name;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
id = (*it).toLongLong();
++it;
relativePath = (*it).toString();
++it;
name = (*it).toString();
++it;
if (relativePath == QLatin1String("/"))
{
path = albumRootPath + relativePath + name;
}
else
{
path = albumRootPath + relativePath + QLatin1Char('/') + name;
}
itemsMap.insert(id, path);
};
return itemsMap;
}
QList<qlonglong> CoreDB::getAllItems()
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id FROM Images;"),
&values);
QList<qlonglong> items;
foreach(const QVariant& item, values)
{
items << item.toLongLong();
}
return items;
}
QList<ItemScanInfo> CoreDB::getItemScanInfos(int albumID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash "
"FROM Images WHERE album=?;"),
albumID,
&values);
QList<ItemScanInfo> list;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
ItemScanInfo info;
info.id = (*it).toLongLong();
++it;
info.albumID = (*it).toInt();
++it;
info.itemName = (*it).toString();
++it;
info.status = (DatabaseItem::Status)(*it).toInt();
++it;
info.category = (DatabaseItem::Category)(*it).toInt();
++it;
info.modificationDate = (*it).toDateTime();
++it;
info.fileSize = (*it).toLongLong();
++it;
info.uniqueHash = (*it).toString();
++it;
list << info;
}
return list;
}
ItemScanInfo CoreDB::getItemScanInfo(qlonglong imageID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT id, album, name, status, category, modificationDate, fileSize, uniqueHash "
"FROM Images WHERE id=?;"),
imageID,
&values);
ItemScanInfo info;
if (!values.isEmpty())
{
QList<QVariant>::const_iterator it = values.constBegin();
info.id = (*it).toLongLong();
++it;
info.albumID = (*it).toInt();
++it;
info.itemName = (*it).toString();
++it;
info.status = (DatabaseItem::Status)(*it).toInt();
++it;
info.category = (DatabaseItem::Category)(*it).toInt();
++it;
info.modificationDate = (*it).toDateTime();
++it;
info.fileSize = (*it).toLongLong();
++it;
info.uniqueHash = (*it).toString();
++it;
}
return info;
}
QStringList CoreDB::getItemURLsInTag(int tagID, bool recursive)
{
QList<QVariant> values;
QMap<QString, QVariant> bindingMap;
bindingMap.insert(QString::fromUtf8(":tagID"), tagID);
bindingMap.insert(QString::fromUtf8(":tagID2"), tagID);
if (recursive)
{
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("GetItemURLsInTagRecursive")), bindingMap, &values);
}
else
{
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("GetItemURLsInTag")), bindingMap, &values);
}
QStringList urls;
QString albumRootPath, relativePath, name;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ;)
{
albumRootPath = CollectionManager::instance()->albumRootPath((*it).toInt());
++it;
relativePath = (*it).toString();
++it;
name = (*it).toString();
++it;
if (relativePath == QLatin1String("/"))
{
urls << albumRootPath + relativePath + name;
}
else
{
urls << albumRootPath + relativePath + QLatin1Char('/') + name;
}
}
return urls;
}
QList<qlonglong> CoreDB::getItemIDsInTag(int tagID, bool recursive)
{
QList<qlonglong> itemIDs;
QList<QVariant> values;
QMap<QString, QVariant> parameters;
parameters.insert(QString::fromUtf8(":tagPID"), tagID);
parameters.insert(QString::fromUtf8(":tagID"), tagID);
if (recursive)
{
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemIDsInTagRecursive")), parameters, &values);
}
else
{
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("getItemIDsInTag")), parameters, &values);
}
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
itemIDs << (*it).toLongLong();
}
return itemIDs;
}
/*
QString CoreDB::getAlbumPath(int albumID)
{
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT AlbumRoots.absolutePath||Albums.relativePath "
"FROM Albums, AlbumRoots WHERE Albums.id=? AND AlbumRoots.id=Albums.albumRoot; "),
albumID, &values);
if (!values.isEmpty())
return values.first().toString();
else
return QString();
}
*/
QString CoreDB::getAlbumRelativePath(int albumID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT relativePath from Albums WHERE id=?;"),
albumID, &values);
if (!values.isEmpty())
{
return values.first().toString();
}
else
{
return QString();
}
}
int CoreDB::getAlbumRootId(int albumID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT albumRoot FROM Albums WHERE id=?;"),
albumID, &values);
if (!values.isEmpty())
{
return values.first().toInt();
}
else
{
return -1;
}
}
QDate CoreDB::getAlbumLowestDate(int albumID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT MIN(creationDate) FROM ImageInformation "
" INNER JOIN Images ON Images.id=ImageInformation.imageid "
" WHERE Images.album=? GROUP BY Images.album;"),
albumID, &values);
if (!values.isEmpty())
{
return values.first().toDate();
}
else
{
return QDate();
}
}
QDate CoreDB::getAlbumHighestDate(int albumID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT MAX(creationDate) FROM ImageInformation "
" INNER JOIN Images ON Images.id=ImageInformation.imageid "
" WHERE Images.album=? GROUP BY Images.album;"),
albumID , &values);
if (!values.isEmpty())
{
return values.first().toDate();
}
else
{
return QDate();
}
}
QDate CoreDB::getAlbumAverageDate(int albumID)
{
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT creationDate FROM ImageInformation "
" INNER JOIN Images ON Images.id=ImageInformation.imageid "
" WHERE Images.album=?;"),
albumID , &values);
QList<QDate> dates;
for (QList<QVariant>::const_iterator it = values.constBegin() ; it != values.constEnd() ; ++it)
{
QDateTime itemDateTime = (*it).toDateTime();
if (itemDateTime.isValid())
{
dates << itemDateTime.date();
}
}
if (dates.isEmpty())
{
return QDate();
}
qint64 julianDays = 0;
foreach(const QDate& date, dates)
{
julianDays += date.toJulianDay();
}
return QDate::fromJulianDay(julianDays / dates.size());
}
void CoreDB::deleteItem(int albumID, const QString& file)
{
qlonglong imageId = getImageId(albumID, file);
if (imageId == -1)
{
return;
}
d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE id=?;"),
imageId);
d->db->recordChangeset(CollectionImageChangeset(imageId, albumID, CollectionImageChangeset::Deleted));
}
void CoreDB::deleteItem(qlonglong imageId)
{
d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE id=? AND album IS NULL;"),
imageId);
}
void CoreDB::removeItemsFromAlbum(int albumID, const QList<qlonglong>& ids_forInformation)
{
d->db->execSql(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE album=?;"),
(int)DatabaseItem::Trashed, albumID);
d->db->recordChangeset(CollectionImageChangeset(ids_forInformation, albumID, CollectionImageChangeset::RemovedAll));
}
void CoreDB::removeItems(QList<qlonglong> itemIDs, const QList<int>& albumIDs)
{
DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE id=?;"));
QVariantList imageIds;
QVariantList status;
foreach(const qlonglong& id, itemIDs)
{
status << (int)DatabaseItem::Trashed;
imageIds << id;
}
query.addBindValue(status);
query.addBindValue(imageIds);
d->db->execBatch(query);
d->db->recordChangeset(CollectionImageChangeset(itemIDs, albumIDs, CollectionImageChangeset::Removed));
}
void CoreDB::removeItemsPermanently(QList<qlonglong> itemIDs, const QList<int>& albumIDs)
{
DbEngineSqlQuery query = d->db->prepareQuery(QString::fromUtf8("UPDATE Images SET status=?, album=NULL WHERE id=?;"));
QVariantList imageIds;
QVariantList status;
foreach(const qlonglong& id, itemIDs)
{
status << (int)DatabaseItem::Obsolete;
imageIds << id;
}
query.addBindValue(status);
query.addBindValue(imageIds);
d->db->execBatch(query);
d->db->recordChangeset(CollectionImageChangeset(itemIDs, albumIDs, CollectionImageChangeset::Removed));
}
void CoreDB::deleteRemovedItems()
{
d->db->execSql(QString::fromUtf8("DELETE FROM Images WHERE status=?;"),
(int)DatabaseItem::Obsolete);
d->db->recordChangeset(CollectionImageChangeset(QList<qlonglong>(), QList<int>(), CollectionImageChangeset::RemovedDeleted));
}
/*
// This method is probably nonsense because a remove image no longer has an associated album
void CoreDB::deleteRemovedItems(QList<int> albumIds)
{
DbEngineSqlQuery query = d->db->prepareQuery( QString::fromUtf8("DELETE FROM Images WHERE status=? AND album=?;") );
QVariantList albumBindIds;
QVariantList status;
foreach(int albumId, albumIds)
{
status << (int)DatabaseItem::Removed;
albumBindIds << albumId;
}
query.addBindValue(status);
query.addBindValue(albumBindIds);
d->db->execBatch(query);
d->db->recordChangeset(CollectionImageChangeset(QList<qlonglong>(), albumIds, CollectionImageChangeset::RemovedDeleted));
}
*/
void CoreDB::renameAlbum(int albumID, int newAlbumRoot, const QString& newRelativePath)
{
int albumRoot = getAlbumRootId(albumID);
QString relativePath = getAlbumRelativePath(albumID);
if (relativePath == newRelativePath && albumRoot == newAlbumRoot)
{
return;
}
// first delete any stale albums left behind at the destination of renaming
QMap<QString, QVariant> parameters;
parameters.insert(QString::fromUtf8(":albumRoot"), newAlbumRoot);
parameters.insert(QString::fromUtf8(":relativePath"), newRelativePath);
if (BdEngineBackend::NoErrors != d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("deleteAlbumRootPath")), parameters))
{
return;
}
// now update the album
d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=?, relativePath=? WHERE id=? AND albumRoot=?;"),
newAlbumRoot, newRelativePath, albumID, albumRoot);
d->db->recordChangeset(AlbumChangeset(albumID, AlbumChangeset::Renamed));
/*
if (renameSubalbums)
{
// now find the list of all subalbums which need to be updated
QList<QVariant> values;
d->db->execSql( QString::fromUtf8("SELECT id, relativePath FROM Albums WHERE albumRoot=? AND relativePath LIKE ?;"),
albumRoot, oldUrl + "/%", &values );
// and update their url
QString newChildURL;
for (QList<QVariant>::iterator it = values.begin(); it != values.end(); )
{
int childAlbumId = (*it).toInt();
++it;
newChildURL = (*it).toString();
++it;
newChildURL.replace(oldUrl, newRelativePath);
d->db->execSql(QString::fromUtf8("UPDATE Albums SET albumRoot=?, relativePath=? WHERE albumRoot=? AND relativePath=?"),
newAlbumRoot, newChildURL, albumRoot, (*it) );
d->db->recordChangeset(AlbumChangeset(childAlbumId, AlbumChangeset::Renamed));
}
}
*/
}
void CoreDB::setTagName(int tagID, const QString& name)
{
d->db->execSql(QString::fromUtf8("UPDATE Tags SET name=? WHERE id=?;"),
name, tagID);
d->db->recordChangeset(TagChangeset(tagID, TagChangeset::Renamed));
}
void CoreDB::moveItem(int srcAlbumID, const QString& srcName,
int dstAlbumID, const QString& dstName)
{
// find id of src image
qlonglong imageId = getImageId(srcAlbumID, srcName);
if (imageId == -1)
{
return;
}
// first delete any stale database entries (for destination) if any
deleteItem(dstAlbumID, dstName);
d->db->execSql(QString::fromUtf8("UPDATE Images SET album=?, name=? "
"WHERE id=?;"),
dstAlbumID, dstName, imageId);
d->db->recordChangeset(CollectionImageChangeset(imageId, srcAlbumID, CollectionImageChangeset::Moved));
d->db->recordChangeset(CollectionImageChangeset(imageId, srcAlbumID, CollectionImageChangeset::Removed));
d->db->recordChangeset(CollectionImageChangeset(imageId, dstAlbumID, CollectionImageChangeset::Added));
}
int CoreDB::copyItem(int srcAlbumID, const QString& srcName,
int dstAlbumID, const QString& dstName)
{
// find id of src image
qlonglong srcId = getImageId(srcAlbumID, srcName);
if (srcId == -1 || dstAlbumID == -1 || dstName.isEmpty())
{
return -1;
}
// check for src == dest
if (srcAlbumID == dstAlbumID && srcName == dstName)
{
return srcId;
}
// first delete any stale database entries if any
deleteItem(dstAlbumID, dstName);
// copy entry in Images table
QVariant id;
d->db->execSql(QString::fromUtf8("INSERT INTO Images "
" ( album, name, status, category, modificationDate, fileSize, uniqueHash ) "
" SELECT ?, ?, status, category, modificationDate, fileSize, uniqueHash "
" FROM Images WHERE id=?;"),
dstAlbumID, dstName, srcId,
0, &id);
if (id.isNull())
{
return -1;
}
d->db->recordChangeset(ImageChangeset(id.toLongLong(), DatabaseFields::ImagesAll));
d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), srcAlbumID, CollectionImageChangeset::Copied));
d->db->recordChangeset(CollectionImageChangeset(id.toLongLong(), dstAlbumID, CollectionImageChangeset::Added));
// copy all other tables
copyImageAttributes(srcId, id.toLongLong());
return id.toLongLong();
}
void CoreDB::copyImageAttributes(qlonglong srcId, qlonglong dstId)
{
// Go through all image-specific tables and copy the entries
DatabaseFields::Set fields;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageInformation "
" (imageid, rating, creationDate, digitizationDate, orientation, "
" width, height, format, colorDepth, colorModel) "
"SELECT ?, rating, creationDate, digitizationDate, orientation, "
" width, height, format, colorDepth, colorModel "
"FROM ImageInformation WHERE imageid=?;"),
dstId, srcId);
fields |= DatabaseFields::ImageInformationAll;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageMetadata "
" (imageid, make, model, lens, aperture, focalLength, focalLength35, "
" exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, "
" whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory) "
"SELECT ?, make, model, lens, aperture, focalLength, focalLength35, "
" exposureTime, exposureProgram, exposureMode, sensitivity, flash, whiteBalance, "
" whiteBalanceColorTemperature, meteringMode, subjectDistance, subjectDistanceCategory "
"FROM ImageMetadata WHERE imageid=?;"),
dstId, srcId);
fields |= DatabaseFields::ImageMetadataAll;
d->db->execSql(QString::fromUtf8("REPLACE INTO VideoMetadata "
" (imageid, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, "
" videoCodec) "
"SELECT ?, aspectRatio, audioBitRate, audioChannelType, audioCompressor, duration, frameRate, "
" videoCodec "
"FROM VideoMetadata WHERE imageid=?;"),
dstId, srcId);
fields |= DatabaseFields::VideoMetadataAll;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImagePositions "
" (imageid, latitude, latitudeNumber, longitude, longitudeNumber, "
" altitude, orientation, tilt, roll, accuracy, description) "
"SELECT ?, latitude, latitudeNumber, longitude, longitudeNumber, "
" altitude, orientation, tilt, roll, accuracy, description "
"FROM ImagePositions WHERE imageid=?;"),
dstId, srcId);
fields |= DatabaseFields::ImagePositionsAll;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageComments "
" (imageid, type, language, author, date, comment) "
"SELECT ?, type, language, author, date, comment "
"FROM ImageComments WHERE imageid=?;"),
dstId, srcId);
fields |= DatabaseFields::ImageCommentsAll;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageCopyright "
" (imageid, property, value, extraValue) "
"SELECT ?, property, value, extraValue "
"FROM ImageCopyright WHERE imageid=?;"),
dstId, srcId);
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageHistory "
" (imageid, uuid, history) "
"SELECT ?, uuid, history "
"FROM ImageHistory WHERE imageid=?;"),
dstId, srcId);
fields |= DatabaseFields::ImageHistoryInfoAll;
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations "
" (subject, object, type) "
"SELECT ?, object, type "
"FROM ImageRelations WHERE subject=?;"),
dstId, srcId);
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageRelations "
" (subject, object, type) "
"SELECT subject, ?, type "
"FROM ImageRelations WHERE object=?;"),
dstId, srcId);
fields |= DatabaseFields::ImageRelations;
d->db->recordChangeset(ImageChangeset(dstId, fields));
copyImageTags(srcId, dstId);
copyImageProperties(srcId, dstId);
}
void CoreDB::copyImageProperties(qlonglong srcId, qlonglong dstId)
{
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageProperties "
" (imageid, property, value) "
"SELECT ?, property, value "
"FROM ImageProperties WHERE imageid=?;"),
dstId, srcId);
}
void CoreDB::copyImageTags(qlonglong srcId, qlonglong dstId)
{
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTags "
" (imageid, tagid) "
"SELECT ?, tagid "
"FROM ImageTags WHERE imageid=?;"),
dstId, srcId);
d->db->execSql(QString::fromUtf8("REPLACE INTO ImageTagProperties "
" (imageid, tagid, property, value) "
"SELECT ?, tagid, property, value "
"FROM ImageTagProperties WHERE imageid=?;"),
dstId, srcId);
// leave empty tag list for now
d->db->recordChangeset(ImageTagChangeset(dstId, QList<int>(),
ImageTagChangeset::Added));
d->db->recordChangeset(ImageTagChangeset(dstId, QList<int>(),
ImageTagChangeset::PropertiesChanged));
}
bool CoreDB::copyAlbumProperties(int srcAlbumID, int dstAlbumID)
{
if (srcAlbumID == dstAlbumID)
{
return true;
}
QList<QVariant> values;
d->db->execSql(QString::fromUtf8("SELECT date, caption, collection, icon "
"FROM Albums WHERE id=?;"),
srcAlbumID,
&values);
if (values.isEmpty())
{
qCWarning(DIGIKAM_DATABASE_LOG) << " src album ID " << srcAlbumID << " does not exist";
return false;
}
QList<QVariant> boundValues;
boundValues << values.at(0) << values.at(1) << values.at(2) << values.at(3);
boundValues << dstAlbumID;
d->db->execSql(QString::fromUtf8("UPDATE Albums SET date=?, caption=?, "
"collection=?, icon=? WHERE id=?;"),
boundValues);
return true;
}
QList<QVariant> CoreDB::getImageIdsFromArea(qreal lat1, qreal lat2, qreal lng1, qreal lng2, int /*sortMode*/,
const QString& /*sortBy*/)
{
QList<QVariant> values;
QList<QVariant> boundValues;
boundValues << lat1 << lat2 << lng1 << lng2;
//CoreDbAccess access;
d->db->execSql(QString::fromUtf8("Select ImageInformation.imageid, ImageInformation.rating, ImagePositions.latitudeNumber, ImagePositions.longitudeNumber"
" FROM ImageInformation INNER JOIN ImagePositions"
" ON ImageInformation.imageid = ImagePositions.imageid"
" WHERE (ImagePositions.latitudeNumber>? AND ImagePositions.latitudeNumber<?)"
" AND (ImagePositions.longitudeNumber>? AND ImagePositions.longitudeNumber<?);"),
boundValues, &values);
return values;
}
void CoreDB::clearMetadataFromImage(qlonglong imageID)
{
DatabaseFields::Set fields;
qCDebug(DIGIKAM_DATABASE_LOG) << "Clean up the image information, the "
"file will be scanned again";
d->db->execSql(QString::fromUtf8("DELETE FROM ImagePositions WHERE imageid=?;"),
imageID);
fields |= DatabaseFields::ImagePositionsAll;
d->db->execSql(QString::fromUtf8("DELETE FROM ImageCopyright WHERE imageid=?;"),
imageID);
d->db->execSql(QString::fromUtf8("DELETE FROM ImageComments WHERE imageid=?;"),
imageID);
fields |= DatabaseFields::ImageCommentsAll;
d->db->execSql(QString::fromUtf8("DELETE FROM ImageMetadata WHERE imageid=?;"),
imageID);
fields |= DatabaseFields::ImageMetadataAll;
d->db->execSql(QString::fromUtf8("DELETE FROM VideoMetadata WHERE imageid=?;"),
imageID);
fields |= DatabaseFields::VideoMetadataAll;
d->db->recordChangeset(ImageChangeset(imageID, fields));
QList<int> tagIds = getItemTagIDs(imageID);
if (!tagIds.isEmpty())
{
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTags WHERE imageid=?;"),
imageID);
d->db->recordChangeset(ImageTagChangeset(imageID, tagIds, ImageTagChangeset::RemovedAll));
}
QList<ImageTagProperty> properties = getImageTagProperties(imageID);
if (!properties.isEmpty())
{
QList<int> tagIds;
foreach(const ImageTagProperty& property, properties)
{
tagIds << property.imageId;
}
d->db->execSql(QString::fromUtf8("DELETE FROM ImageTagProperties WHERE imageid=?;"),
imageID);
d->db->recordChangeset(ImageTagChangeset(imageID, tagIds, ImageTagChangeset::PropertiesChanged));
}
}
bool CoreDB::integrityCheck()
{
QList<QVariant> values;
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("checkCoreDbIntegrity")), &values);
switch (d->db->databaseType())
{
case BdEngineBackend::DbType::SQLite:
// For SQLite the integrity check returns a single row with one string column "ok" on success and multiple rows on error.
return values.size() == 1 && values.first().toString().toLower().compare(QLatin1String("ok")) == 0;
case BdEngineBackend::DbType::MySQL:
// For MySQL, for every checked table, the table name, operation (check), message type (status) and the message text (ok on success)
// are returned. So we check if there are four elements and if yes, whether the fourth element is "ok".
//qCDebug(DIGIKAM_DATABASE_LOG) << "MySQL check returned " << values.size() << " rows";
if ((values.size() % 4) != 0)
{
return false;
}
for (QList<QVariant>::iterator it = values.begin() ; it != values.end() ;)
{
QString tableName = (*it).toString();
++it;
QString operation = (*it).toString();
++it;
QString messageType = (*it).toString();
++it;
QString messageText = (*it).toString();
++it;
if (messageText.toLower().compare(QLatin1String("ok")) != 0)
{
qCDebug(DIGIKAM_DATABASE_LOG) << "Failed integrity check for table " << tableName << ". Reason:" << messageText;
return false;
}
else
{
//qCDebug(DIGIKAM_DATABASE_LOG) << "Passed integrity check for table " << tableName;
}
}
// No error conditions. Db passed the integrity check.
return true;
default:
return false;
}
}
void CoreDB::vacuum()
{
d->db->execDBAction(d->db->getDBAction(QString::fromUtf8("vacuumCoreDB")));
}
void CoreDB::readSettings()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
d->recentlyAssignedTags = group.readEntry(d->configRecentlyUsedTags, QList<int>());
}
void CoreDB::writeSettings()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
group.writeEntry(d->configRecentlyUsedTags, d->recentlyAssignedTags);
}
} // namespace Digikam
diff --git a/core/libs/database/engine/dbenginebackend.h b/core/libs/database/engine/dbenginebackend.h
index 8485189f96..507faee954 100644
--- a/core/libs/database/engine/dbenginebackend.h
+++ b/core/libs/database/engine/dbenginebackend.h
@@ -1,508 +1,508 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-06-07
* Description : Database engine abstract database backend
*
* Copyright (C) 2007-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DB_ENGINE_BACKEND_H
#define DIGIKAM_DB_ENGINE_BACKEND_H
// Qt includes
#include <QMap>
#include <QMutex>
#include <QObject>
#include <QString>
#include <QStringList>
#include <QSqlQuery>
#include <QSqlError>
// Local includes
#include "digikam_export.h"
#include "dbengineparameters.h"
#include "dbenginesqlquery.h"
namespace Digikam
{
class DbEngineConfigSettings;
class BdEngineBackendPrivate;
class DbEngineErrorHandler;
class DIGIKAM_EXPORT DbEngineLocking
{
public:
explicit DbEngineLocking();
public:
QMutex mutex;
int lockCount;
};
// -----------------------------------------------------------------
class DIGIKAM_EXPORT BdEngineBackend : public QObject
{
Q_OBJECT
public:
enum QueryStateEnum
{
/**
* No errors occurred while executing the query.
*/
NoErrors,
/**
* An SQLError has occurred while executing the query.
*/
SQLError,
/**
* An connection error has occurred while executing the query.
*/
ConnectionError
};
enum Status
{
/**
* The database is not available, because it has not been
* opened yet or because of an error condition.
*/
Unavailable,
/**
* The database is open. It has not been verified that
* the schema is up to date.
* This status is sufficient for use in a context where it
* can be assumed that the necessary schema check has been carried out
* by a master process.
*/
Open,
/**
* The database is open, and it has been verified that the schema is up to
* date, or the schema has been updated.
*/
OpenSchemaChecked
};
enum QueryOperationStatus
{
ExecuteNormal,
Wait,
AbortQueries
};
enum DbType
{
SQLite,
MySQL
};
public:
/**
* Creates a database backend. The backend name is an arbitrary string that
* shall be unique for this backend object.
* It will be used to create unique connection names per backend and thread.
*/
explicit BdEngineBackend(const QString& backendName, DbEngineLocking* const locking);
BdEngineBackend(const QString& backendName, DbEngineLocking* const locking, BdEngineBackendPrivate& dd);
~BdEngineBackend();
/**
* Checks if the parameters can be used for this database backend.
*/
bool isCompatible(const DbEngineParameters& parameters);
/**
* Open the database connection.
* @returns true on success
*/
bool open(const DbEngineParameters& parameters);
/**
* Close the database connection.
* Shall only be called from the thread that called open().
*/
void close();
public:
class QueryState
{
public:
QueryState()
: value(BdEngineBackend::NoErrors)
{
}
explicit QueryState(const QueryStateEnum value) // krazy:exclude=explicit
: value(value)
{
}
operator QueryStateEnum() const
{
return value;
}
operator bool() const
{
return value == BdEngineBackend::NoErrors;
}
private:
QueryStateEnum value;
};
public:
/**
* Returns the current status of the database backend
*/
Status status() const;
bool isOpen() const
{
return (status() > Unavailable);
}
bool isReady() const
{
return (status() == OpenSchemaChecked);
}
/**
* Add a DbEngineErrorHandler. This object must be created in the main thread.
* If a database error occurs, this object can handle problem solving and user interaction.
*/
void setDbEngineErrorHandler(DbEngineErrorHandler* const handler);
/**
* Return config read from XML,
* corresponding to this backend's database type.
*/
DbEngineConfigSettings configElement() const;
/**
* Return the database type.
*/
DbType databaseType() const;
/**
* Returns a database action with name, specified in actionName,
* for the current database.
*/
DbEngineAction getDBAction(const QString& actionName) const;
/**
* Performs the database action on the current database.
* Queries by the specified parameters mustn't have named parameters.
* The result values (if any) are stored within the values list.
*/
QueryState execDBAction(const DbEngineAction& action, QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execDBAction(const QString& action, QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
/**
* Performs the database action on the current database.
* Queries by the specified parameters can have named parameters which are
- * substituded with values from the bindingMap parameter.
+ * substituted with values from the bindingMap parameter.
* The result values (if any) are stored within the values list.
*/
QueryState execDBAction(const DbEngineAction& action, const QMap<QString, QVariant>& bindingMap,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execDBAction(const QString& action, const QMap<QString, QVariant>& bindingMap,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
/**
* Performs a special DBAction that is usually needed to "INSERT or UPDATE" entries in a table.
* The corresponding DBAction must contain exactly the named parameters :id, :fieldValueList,
* :fieldList and :valueList.
* You pass the value to be bound to the ":id" field, then two lists of the same size:
* The first containing the field names, the second one
* containing the values as QVariants ready for binding.
*/
QueryState execUpsertDBAction(const DbEngineAction& action, const QVariant& id,
const QStringList fieldNames, const QList<QVariant>& values);
QueryState execUpsertDBAction(const QString& action, const QVariant& id,
const QStringList fieldNames, const QList<QVariant>& values);
/**
* Performs the database action on the current database.
* Queries by the specified parameters can have named parameters which are
- * substituded with values from the bindingMap parameter.
+ * substituted with values from the bindingMap parameter.
* The result values (if any) are stored within the values list.
* This method returns the last query, which is used to handle special cases.
*/
QSqlQuery execDBActionQuery(const DbEngineAction& action, const QMap<QString, QVariant>& bindingMap);
QSqlQuery execDBActionQuery(const QString& action, const QMap<QString, QVariant>& bindingMap);
/**
* Executes the SQL statement, and write the returned data into the values list.
* If you are not interested in the returned data, set values to 0.
* Methods are provided for up to four bound values (positional binding), or for a list of bound values.
* If you want the last inserted id (and your query is suitable), set lastInsertId to the address of a QVariant.
* Additionally, methods are provided for prepared statements.
*/
QueryState execSql(const QString& sql, QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(const QString& sql, const QVariant& boundValue1,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(const QString& sql,
const QVariant& boundValue1, const QVariant& boundValue2,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(const QString& sql,
const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(const QString& sql,
const QVariant& boundValue1, const QVariant& boundValue2,
const QVariant& boundValue3, const QVariant& boundValue4,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(const QString& sql, const QList<QVariant>& boundValues,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(DbEngineSqlQuery& preparedQuery, QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(DbEngineSqlQuery& preparedQuery, const QVariant& boundValue1,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(DbEngineSqlQuery& preparedQuery,
const QVariant& boundValue1, const QVariant& boundValue2,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(DbEngineSqlQuery& preparedQuery,
const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(DbEngineSqlQuery& preparedQuery,
const QVariant& boundValue1, const QVariant& boundValue2,
const QVariant& boundValue3, const QVariant& boundValue4,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
QueryState execSql(DbEngineSqlQuery& preparedQuery, const QList<QVariant>& boundValues,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
/**
* Checks if there was a connection error. If so BdEngineBackend::ConnectionError is returned.
* If not, the values are extracted from the query and inserted in the values list,
* the last insertion id is taken from the query
* and BdEngineBackend::NoErrors is returned.
*/
QueryState handleQueryResult(DbEngineSqlQuery& query, QList<QVariant>* const values, QVariant* const lastInsertId);
/**
* Method which accepts a map for named binding.
* For special cases it's also possible to add a DbEngineActionType which wraps another
* data object (also lists or maps) which can be used as field entry or as value
* (where it's prepared with positional binding). See more on DbEngineActionType class.
* If the wrapped data object is an instance of list, then the elements are
* separated by comma.
* If the wrapped data object is an instance of map, then the elements are
* inserted in the following way: key1=value1, key2=value2,...,keyN=valueN.
*/
QueryState execSql(const QString& sql, const QMap<QString, QVariant>& bindingMap,
QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
/**
* Calls exec on the query, and handles debug output if something went wrong.
* The query is not prepared, which can be fail in certain situations
* (e.g. trigger statements on QMYSQL).
*/
QueryState execDirectSql(const QString& query);
/**
* Calls exec on the query, and handles debug output if something went wrong.
* The query is not prepared, which can be fail in certain situations
* (e.g. trigger statements on QMYSQL).
*/
QueryState execDirectSqlWithResult(const QString& query, QList<QVariant>* const values = 0, QVariant* const lastInsertId = 0);
/**
* Executes the statement and returns the query object.
* Methods are provided for up to four bound values (positional binding), or for a list of bound values.
*/
DbEngineSqlQuery execQuery(const QString& sql);
DbEngineSqlQuery execQuery(const QString& sql, const QVariant& boundValue1);
DbEngineSqlQuery execQuery(const QString& sql,
const QVariant& boundValue1, const QVariant& boundValue2);
DbEngineSqlQuery execQuery(const QString& sql,
const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3);
DbEngineSqlQuery execQuery(const QString& sql,
const QVariant& boundValue1, const QVariant& boundValue2,
const QVariant& boundValue3, const QVariant& boundValue4);
DbEngineSqlQuery execQuery(const QString& sql, const QList<QVariant>& boundValues);
/**
* Binds the values and executes the prepared query.
*/
void execQuery(DbEngineSqlQuery& preparedQuery, const QVariant& boundValue1);
void execQuery(DbEngineSqlQuery& preparedQuery,
const QVariant& boundValue1, const QVariant& boundValue2);
void execQuery(DbEngineSqlQuery& preparedQuery,
const QVariant& boundValue1, const QVariant& boundValue2, const QVariant& boundValue3);
void execQuery(DbEngineSqlQuery& preparedQuery,
const QVariant& boundValue1, const QVariant& boundValue2,
const QVariant& boundValue3, const QVariant& boundValue4);
void execQuery(DbEngineSqlQuery& preparedQuery, const QList<QVariant>& boundValues);
/**
* Method which accept a hashmap with key, values which are used for named binding
*/
DbEngineSqlQuery execQuery(const QString& sql, const QMap<QString, QVariant>& bindingMap);
/**
* Calls exec/execBatch on the query, and handles debug output if something went wrong
*/
bool exec(DbEngineSqlQuery& query);
bool execBatch(DbEngineSqlQuery& query);
/**
* Creates a query object prepared with the statement, waiting for bound values
*/
DbEngineSqlQuery prepareQuery(const QString& sql);
/**
* Creates an empty query object waiting for the statement
*/
DbEngineSqlQuery getQuery();
/**
* Creates a faithful copy of the passed query, with the current db connection.
*/
DbEngineSqlQuery copyQuery(const DbEngineSqlQuery& old);
/**
* Called with a failed query. Handles certain known errors and debug output.
* If it returns true, reexecute the query; if it returns false, return it as failed.
* Pass the number of retries already done for this query to help with some decisions.
*/
bool queryErrorHandling(DbEngineSqlQuery& query, int retries);
bool transactionErrorHandling(const QSqlError& lastError, int retries);
/**
* Called when an attempted connection to the database failed.
* If it returns true, retry; if it returns false, bail out.
* Pass the number of connection retries to help with some decisions.
*/
bool connectionErrorHandling(int retries);
/**
* Reads data of returned result set into a list which is returned.
* The read process is column wise, which means
* all data elements of a row is read, then the resultset is switched
* to the next row.
*/
QList<QVariant> readToList(DbEngineSqlQuery& query);
/**
* Begin a database transaction
*/
BdEngineBackend::QueryState beginTransaction();
/**
* Commit the current database transaction
*/
BdEngineBackend::QueryState commitTransaction();
/**
* Rollback the current database transaction
*/
void rollbackTransaction();
/**
* Returns if the database is in a different thread in a transaction.
* Note that a transaction does not require holding CoreDbAccess.
* Note that this does not give information about other processes
* locking the database.
*/
bool isInTransaction() const;
/**
* Returns a list with the names of tables in the database.
*/
QStringList tables();
/**
* Returns a description of the last error that occurred on this database.
* Use CoreDbAccess::lastError for errors presented to the user.
* This error will be included in that message.
* It may be empty.
*/
QString lastError();
/**
* Returns the last error that occurred on this database.
* Use CoreDbAccess::lastError for errors presented to the user.
* It may be empty.
*/
QSqlError lastSQLError();
/**
* Returns the maximum number of bound parameters allowed per query.
* This value depends on the database engine.
*/
int maximumBoundValues() const;
/**
* Enables or disables FOREIGN_KEY_CHECKS for the database.
* This function depends on the database engine.
*/
void setForeignKeyChecks(bool check);
/*
Qt SQL driver supported features
SQLITE3:
BLOB
Transactions
Unicode
LastInsertId
PreparedQueries
PositionalPlaceholders
SimpleLocking
MySQL:
Transactions (3.?)
QuerySize
BLOB
LastInsertId
Unicode
PreparedQueries (4.1)
PositionalPlaceholders (4.1)
Postgresql:
Transactions
QuerySize
LastInsertId
*/
protected:
BdEngineBackendPrivate* const d_ptr;
private:
Q_DECLARE_PRIVATE(BdEngineBackend)
};
} // namespace Digikam
Q_DECLARE_METATYPE(QSqlError)
#endif // DIGIKAM_DB_ENGINE_BACKEND_H
diff --git a/core/libs/database/engine/dbengineparameters.cpp b/core/libs/database/engine/dbengineparameters.cpp
index 3815cd1386..bf6ca3b3ae 100644
--- a/core/libs/database/engine/dbengineparameters.cpp
+++ b/core/libs/database/engine/dbengineparameters.cpp
@@ -1,811 +1,811 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-03-18
* Description : Database Engine storage container for connection parameters.
*
* Copyright (C) 2007-2008 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010 by Holger Foerster <hamsi2k at freenet dot de>
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2018 by Mario Frank <mario dot frank at uni minus potsdam dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dbengineparameters.h"
// Qt includes
#include <QDir>
#include <QUrlQuery>
#include <QFile>
#include <QCryptographicHash>
#include <QStandardPaths>
// KDE includes
#include <kconfiggroup.h>
// Local includes
#include "digikam_config.h"
#include "digikam_debug.h"
namespace
{
static const char* configGroupDatabase = "Database Settings";
static const char* configInternalDatabaseServer = "Internal Database Server";
static const char* configInternalDatabaseServerPath = "Internal Database Server Path";
static const char* configInternalDatabaseServerMysqlServCmd = "Internal Database Server Mysql Server Command";
static const char* configInternalDatabaseServerMysqlInitCmd = "Internal Database Server Mysql Init Command";
static const char* configDatabaseType = "Database Type";
static const char* configDatabaseName = "Database Name"; // For Sqlite the DB file path, for Mysql the DB name
static const char* configDatabaseNameThumbnails = "Database Name Thumbnails"; // For Sqlite the DB file path, for Mysql the DB name
static const char* configDatabaseNameFace = "Database Name Face"; // For Sqlite the DB file path, for Mysql the DB name
static const char* configDatabaseNameSimilarity = "Database Name Similarity"; // For Sqlite the DB file path, for Mysql the DB name
static const char* configDatabaseHostName = "Database Hostname";
static const char* configDatabasePort = "Database Port";
static const char* configDatabaseUsername = "Database Username";
static const char* configDatabasePassword = "Database Password";
static const char* configDatabaseConnectOptions = "Database Connectoptions";
// Legacy for older versions.
static const char* configDatabaseFilePathEntry = "Database File Path";
static const char* configAlbumPathEntry = "Album Path";
// Sqlite DB file names
static const char* digikam4db = "digikam4.db";
static const char* thumbnails_digikamdb = "thumbnails-digikam.db";
static const char* face_digikamdb = "recognition.db";
static const char* similarity_digikamdb = "similarity.db";
}
namespace Digikam
{
QString DbEngineParameters::internalServerPrivatePath()
{
return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
QLatin1String("/digikam/");
}
DbEngineParameters::DbEngineParameters()
: port(-1),
internalServer(false)
{
}
DbEngineParameters::DbEngineParameters(const QString& _type,
const QString& _databaseNameCore,
const QString& _connectOptions,
const QString& _hostName,
int _port,
bool _internalServer,
const QString& _userName,
const QString& _password,
const QString& _databaseNameThumbnails,
const QString& _databaseNameFace,
const QString& _databaseNameSimilarity,
const QString& _internalServerDBPath,
const QString& _internalServerMysqlServCmd,
const QString& _internalServerMysqlInitCmd
)
: databaseType(_type),
databaseNameCore(_databaseNameCore),
connectOptions(_connectOptions),
hostName(_hostName),
port(_port),
internalServer(_internalServer),
userName(_userName),
password(_password),
databaseNameThumbnails(_databaseNameThumbnails),
databaseNameFace(_databaseNameFace),
databaseNameSimilarity(_databaseNameSimilarity),
internalServerDBPath(_internalServerDBPath),
internalServerMysqlServCmd(_internalServerMysqlServCmd),
internalServerMysqlInitCmd(_internalServerMysqlInitCmd)
{
}
// Note no need to
DbEngineParameters::DbEngineParameters(const QUrl& url)
: port(-1),
internalServer(false)
{
databaseType = QUrlQuery(url).queryItemValue(QLatin1String("databaseType"));
databaseNameCore = QUrlQuery(url).queryItemValue(QLatin1String("databaseNameCore"));
databaseNameThumbnails = QUrlQuery(url).queryItemValue(QLatin1String("databaseNameThumbnails"));
databaseNameFace = QUrlQuery(url).queryItemValue(QLatin1String("databaseNameFace"));
databaseNameSimilarity = QUrlQuery(url).queryItemValue(QLatin1String("databaseNameSimilarity"));
connectOptions = QUrlQuery(url).queryItemValue(QLatin1String("connectOptions"));
hostName = QUrlQuery(url).queryItemValue(QLatin1String("hostName"));
QString queryPort = QUrlQuery(url).queryItemValue(QLatin1String("port"));
if (!queryPort.isNull())
{
port = queryPort.toInt();
}
#if defined(HAVE_MYSQLSUPPORT) && defined(HAVE_INTERNALMYSQL)
QString queryServer = QUrlQuery(url).queryItemValue(QLatin1String("internalServer"));
if (!queryServer.isNull())
{
internalServer = (queryServer == QLatin1String("true"));
}
queryServer = QUrlQuery(url).queryItemValue(QLatin1String("internalServerPath"));
if (!queryServer.isNull())
{
internalServerDBPath = QUrlQuery(url).queryItemValue(QLatin1String("internalServerPath"));
}
else
{
internalServerDBPath = internalServerPrivatePath();
}
internalServerMysqlServCmd = QUrlQuery(url).queryItemValue(QLatin1String("internalServerMysqlServCmd"));
internalServerMysqlInitCmd = QUrlQuery(url).queryItemValue(QLatin1String("internalServerMysqlInitCmd"));
#else
internalServer = false;
#endif
userName = QUrlQuery(url).queryItemValue(QLatin1String("userName"));
password = QUrlQuery(url).queryItemValue(QLatin1String("password"));
}
void DbEngineParameters::insertInUrl(QUrl& url) const
{
removeFromUrl(url);
QUrlQuery q(url);
q.addQueryItem(QLatin1String("databaseType"), databaseType);
q.addQueryItem(QLatin1String("databaseNameCore"), databaseNameCore);
q.addQueryItem(QLatin1String("databaseNameThumbnails"), databaseNameThumbnails);
q.addQueryItem(QLatin1String("databaseNameFace"), databaseNameFace);
q.addQueryItem(QLatin1String("databaseNameSimilarity"), databaseNameSimilarity);
if (!connectOptions.isNull())
{
q.addQueryItem(QLatin1String("connectOptions"), connectOptions);
}
if (!hostName.isNull())
{
q.addQueryItem(QLatin1String("hostName"), hostName);
}
if (port != -1)
{
q.addQueryItem(QLatin1String("port"), QString::number(port));
}
if (internalServer)
{
q.addQueryItem(QLatin1String("internalServer"), QLatin1String("true"));
q.addQueryItem(QLatin1String("internalServerPath"), internalServerDBPath);
q.addQueryItem(QLatin1String("internalServerMysqlServCmd"), internalServerMysqlServCmd);
q.addQueryItem(QLatin1String("internalServerMysqlInitCmd"), internalServerMysqlInitCmd);
}
if (!userName.isNull())
{
q.addQueryItem(QLatin1String("userName"), userName);
}
if (!password.isNull())
{
q.addQueryItem(QLatin1String("password"), password);
}
url.setQuery(q);
}
void DbEngineParameters::removeFromUrl(QUrl& url)
{
QUrlQuery q(url);
q.removeQueryItem(QLatin1String("databaseType"));
q.removeQueryItem(QLatin1String("databaseNameCore"));
q.removeQueryItem(QLatin1String("databaseNameThumbnails"));
q.removeQueryItem(QLatin1String("databaseNameFace"));
q.removeQueryItem(QLatin1String("databaseNameSimilarity"));
q.removeQueryItem(QLatin1String("connectOptions"));
q.removeQueryItem(QLatin1String("hostName"));
q.removeQueryItem(QLatin1String("port"));
q.removeQueryItem(QLatin1String("internalServer"));
q.removeQueryItem(QLatin1String("internalServerPath"));
q.removeQueryItem(QLatin1String("internalServerMysqlServCmd"));
q.removeQueryItem(QLatin1String("internalServerMysqlInitCmd"));
q.removeQueryItem(QLatin1String("userName"));
q.removeQueryItem(QLatin1String("password"));
url.setQuery(q);
}
bool DbEngineParameters::operator==(const DbEngineParameters& other) const
{
return(databaseType == other.databaseType &&
databaseNameCore == other.databaseNameCore &&
databaseNameThumbnails == other.databaseNameThumbnails &&
databaseNameFace == other.databaseNameFace &&
databaseNameSimilarity == other.databaseNameSimilarity &&
connectOptions == other.connectOptions &&
hostName == other.hostName &&
port == other.port &&
internalServer == other.internalServer &&
internalServerDBPath == other.internalServerDBPath &&
internalServerMysqlServCmd == other.internalServerMysqlServCmd &&
internalServerMysqlInitCmd == other.internalServerMysqlInitCmd &&
userName == other.userName &&
password == other.password);
}
bool DbEngineParameters::operator!=(const DbEngineParameters& other) const
{
return (!operator == (other));
}
bool DbEngineParameters::isValid() const
{
if (isSQLite())
{
return !databaseNameCore.isEmpty();
}
return false;
}
bool DbEngineParameters::isSQLite() const
{
return (databaseType == QLatin1String("QSQLITE"));
}
bool DbEngineParameters::isMySQL() const
{
return (databaseType == QLatin1String("QMYSQL"));
}
QString DbEngineParameters::SQLiteDatabaseType()
{
return QLatin1String("QSQLITE");
}
QString DbEngineParameters::MySQLDatabaseType()
{
return QLatin1String("QMYSQL");
}
QString DbEngineParameters::SQLiteDatabaseFile() const
{
if (isSQLite())
{
return databaseNameCore;
}
return QString();
}
QByteArray DbEngineParameters::hash() const
{
QCryptographicHash md5(QCryptographicHash::Md5);
md5.addData(databaseType.toUtf8());
md5.addData(databaseNameCore.toUtf8());
md5.addData(databaseNameThumbnails.toUtf8());
md5.addData(databaseNameFace.toUtf8());
md5.addData(databaseNameSimilarity.toUtf8());
md5.addData(connectOptions.toUtf8());
md5.addData(hostName.toUtf8());
md5.addData((const char*)&port, sizeof(int));
md5.addData(userName.toUtf8());
md5.addData(password.toUtf8());
md5.addData((const char*)&internalServer, sizeof(bool));
md5.addData(internalServerDBPath.toUtf8());
return md5.result().toHex();
}
DbEngineParameters DbEngineParameters::parametersFromConfig(KSharedConfig::Ptr config, const QString& configGroup)
{
DbEngineParameters parameters;
parameters.readFromConfig(config, configGroup);
return parameters;
}
void DbEngineParameters::readFromConfig(KSharedConfig::Ptr config, const QString& configGroup)
{
KConfigGroup group;
if (configGroup.isNull())
{
group = config->group(configGroupDatabase);
}
else
{
group = config->group(configGroup);
}
databaseType = group.readEntry(configDatabaseType, QString());
if (isSQLite()) // see bug #267131
{
databaseNameCore = group.readPathEntry(configDatabaseName, QString());
databaseNameThumbnails = group.readPathEntry(configDatabaseNameThumbnails, QString());
databaseNameFace = group.readPathEntry(configDatabaseNameFace, QString());
databaseNameSimilarity = group.readPathEntry(configDatabaseNameSimilarity, QString());
}
else
{
databaseNameCore = group.readEntry(configDatabaseName, QString());
databaseNameThumbnails = group.readEntry(configDatabaseNameThumbnails, QString());
databaseNameFace = group.readEntry(configDatabaseNameFace, QString());
databaseNameSimilarity = group.readEntry(configDatabaseNameSimilarity, QString());
}
hostName = group.readEntry(configDatabaseHostName, QString());
port = group.readEntry(configDatabasePort, -1);
userName = group.readEntry(configDatabaseUsername, QString());
password = group.readEntry(configDatabasePassword, QString());
connectOptions = group.readEntry(configDatabaseConnectOptions, QString());
#if defined(HAVE_MYSQLSUPPORT) && defined(HAVE_INTERNALMYSQL)
internalServer = group.readEntry(configInternalDatabaseServer, false);
internalServerDBPath = group.readEntry(configInternalDatabaseServerPath, internalServerPrivatePath());
internalServerMysqlServCmd = group.readEntry(configInternalDatabaseServerMysqlServCmd, defaultMysqlServerCmd());
internalServerMysqlInitCmd = group.readEntry(configInternalDatabaseServerMysqlInitCmd, defaultMysqlInitCmd());
#else
internalServer = false;
#endif
if (isSQLite() && !databaseNameCore.isNull())
{
QString orgName = databaseNameCore;
setCoreDatabasePath(orgName);
setThumbsDatabasePath(orgName);
setFaceDatabasePath(orgName);
setSimilarityDatabasePath(orgName);
}
}
void DbEngineParameters::setInternalServerPath(const QString& path)
{
internalServerDBPath = path;
}
QString DbEngineParameters::internalServerPath() const
{
QFileInfo fileInfo(internalServerDBPath);
return QDir::cleanPath(fileInfo.filePath());
}
void DbEngineParameters::setCoreDatabasePath(const QString& folderOrFileOrName)
{
if (isSQLite())
{
databaseNameCore = coreDatabaseFileSQLite(folderOrFileOrName);
}
else
{
databaseNameCore = folderOrFileOrName;
}
}
void DbEngineParameters::setThumbsDatabasePath(const QString& folderOrFileOrName)
{
if (isSQLite())
{
databaseNameThumbnails = thumbnailDatabaseFileSQLite(folderOrFileOrName);
}
else
{
databaseNameThumbnails = folderOrFileOrName;
}
}
void DbEngineParameters::setFaceDatabasePath(const QString& folderOrFileOrName)
{
if (isSQLite())
{
databaseNameFace = faceDatabaseFileSQLite(folderOrFileOrName);
}
else
{
databaseNameFace = folderOrFileOrName;
}
}
void DbEngineParameters::setSimilarityDatabasePath(const QString& folderOrFileOrName)
{
if (isSQLite())
{
databaseNameSimilarity = similarityDatabaseFileSQLite(folderOrFileOrName);
}
else
{
databaseNameSimilarity = folderOrFileOrName;
}
}
QString DbEngineParameters::coreDatabaseFileSQLite(const QString& folderOrFile)
{
QFileInfo fileInfo(folderOrFile);
if (fileInfo.isDir())
{
return QDir::cleanPath(fileInfo.filePath() + QLatin1Char('/') + QLatin1String(digikam4db));
}
return QDir::cleanPath(folderOrFile);
}
QString DbEngineParameters::thumbnailDatabaseFileSQLite(const QString& folderOrFile)
{
QFileInfo fileInfo(folderOrFile);
if (fileInfo.isDir())
{
return QDir::cleanPath(fileInfo.filePath() + QLatin1Char('/') + QLatin1String(thumbnails_digikamdb));
}
return QDir::cleanPath(folderOrFile);
}
QString DbEngineParameters::faceDatabaseFileSQLite(const QString& folderOrFile)
{
QFileInfo fileInfo(folderOrFile);
if (fileInfo.isDir())
{
return QDir::cleanPath(fileInfo.filePath() + QLatin1Char('/') + QLatin1String(face_digikamdb));
}
return QDir::cleanPath(folderOrFile);
}
QString DbEngineParameters::similarityDatabaseFileSQLite(const QString& folderOrFile)
{
QFileInfo fileInfo(folderOrFile);
if (fileInfo.isDir())
{
return QDir::cleanPath(fileInfo.filePath() + QLatin1Char('/') + QLatin1String(similarity_digikamdb));
}
return QDir::cleanPath(folderOrFile);
}
void DbEngineParameters::legacyAndDefaultChecks(const QString& suggestedPath, KSharedConfig::Ptr config)
{
// Additional semantic checks for the database section.
// If the internal server should be started, then the connection options must be reset
if (databaseType == QLatin1String("QMYSQL") && internalServer)
{
const QString miscDir = internalServerPrivatePath() + QLatin1String("db_misc");
databaseNameCore = QLatin1String("digikam");
databaseNameThumbnails = QLatin1String("digikam");
databaseNameFace = QLatin1String("digikam");
databaseNameSimilarity = QLatin1String("digikam");
internalServer = true;
userName = QLatin1String("root");
password.clear();
#ifdef Q_OS_WIN
hostName = QLatin1String("localhost");
port = 3307;
connectOptions.clear();
#else
hostName.clear();
port = -1;
connectOptions = QString::fromLatin1("UNIX_SOCKET=%1/mysql.socket").arg(miscDir);
#endif
}
if (databaseType.isEmpty())
{
// Empty 1.3 config: migration from older versions
KConfigGroup group = config->group("Album Settings");
QString databaseFilePath;
if (group.hasKey(configDatabaseFilePathEntry))
{
// 1.0 - 1.2 style database file path?
databaseFilePath = group.readEntry(configDatabaseFilePathEntry, QString());
}
else if (group.hasKey(configAlbumPathEntry))
{
// <= 0.9 style album path entry?
databaseFilePath = group.readEntry(configAlbumPathEntry, QString());
}
else if (!suggestedPath.isNull())
{
databaseFilePath = suggestedPath;
}
if (!databaseFilePath.isEmpty())
{
*this = parametersForSQLite(coreDatabaseFileSQLite(databaseFilePath));
}
// Be aware that schema updating from version <= 0.9 requires reading the "Album Path", so do not remove it here
}
}
void DbEngineParameters::removeLegacyConfig(KSharedConfig::Ptr config)
{
KConfigGroup group = config->group("Album Settings");
if (group.hasKey(configDatabaseFilePathEntry))
{
group.deleteEntry(configDatabaseFilePathEntry);
}
if (group.hasKey(configAlbumPathEntry))
{
group.deleteEntry(configAlbumPathEntry);
}
}
void DbEngineParameters::writeToConfig(KSharedConfig::Ptr config, const QString& configGroup) const
{
KConfigGroup group;
if (configGroup.isNull())
{
group = config->group(configGroupDatabase);
}
else
{
group = config->group(configGroup);
}
QString dbName = getCoreDatabaseNameOrDir();
QString dbNameThumbs = getThumbsDatabaseNameOrDir();
QString dbNameFace = getFaceDatabaseNameOrDir();
QString dbNameSimilarity = getSimilarityDatabaseNameOrDir();
group.writeEntry(configDatabaseType, databaseType);
group.writeEntry(configDatabaseName, dbName);
group.writeEntry(configDatabaseNameThumbnails, dbNameThumbs);
group.writeEntry(configDatabaseNameFace, dbNameFace);
group.writeEntry(configDatabaseNameSimilarity, dbNameSimilarity);
group.writeEntry(configDatabaseHostName, hostName);
group.writeEntry(configDatabasePort, port);
group.writeEntry(configDatabaseUsername, userName);
group.writeEntry(configDatabasePassword, password);
group.writeEntry(configDatabaseConnectOptions, connectOptions);
group.writeEntry(configInternalDatabaseServer, internalServer);
group.writeEntry(configInternalDatabaseServerPath, internalServerDBPath);
group.writeEntry(configInternalDatabaseServerMysqlServCmd, internalServerMysqlServCmd);
group.writeEntry(configInternalDatabaseServerMysqlInitCmd, internalServerMysqlInitCmd);
}
QString DbEngineParameters::getCoreDatabaseNameOrDir() const
{
if (isSQLite())
{
return coreDatabaseDirectorySQLite(databaseNameCore);
}
return databaseNameCore;
}
QString DbEngineParameters::getThumbsDatabaseNameOrDir() const
{
if (isSQLite())
{
return thumbnailDatabaseDirectorySQLite(databaseNameThumbnails);
}
return databaseNameThumbnails;
}
QString DbEngineParameters::getFaceDatabaseNameOrDir() const
{
if (isSQLite())
{
return faceDatabaseDirectorySQLite(databaseNameFace);
}
return databaseNameFace;
}
QString DbEngineParameters::getSimilarityDatabaseNameOrDir() const
{
if (isSQLite())
{
return similarityDatabaseDirectorySQLite(databaseNameSimilarity);
}
return databaseNameSimilarity;
}
QString DbEngineParameters::coreDatabaseDirectorySQLite(const QString& path)
{
if (path.endsWith(QLatin1String(digikam4db)))
{
QString chopped(path);
chopped.chop(QString(QLatin1String(digikam4db)).length());
return chopped;
}
return path;
}
QString DbEngineParameters::thumbnailDatabaseDirectorySQLite(const QString& path)
{
if (path.endsWith(QLatin1String(thumbnails_digikamdb)))
{
QString chopped(path);
chopped.chop(QString(QLatin1String(thumbnails_digikamdb)).length());
return chopped;
}
return path;
}
QString DbEngineParameters::faceDatabaseDirectorySQLite(const QString& path)
{
if (path.endsWith(QLatin1String(face_digikamdb)))
{
QString chopped(path);
chopped.chop(QString(QLatin1String(face_digikamdb)).length());
return chopped;
}
return path;
}
QString DbEngineParameters::similarityDatabaseDirectorySQLite(const QString& path)
{
if (path.endsWith(QLatin1String(similarity_digikamdb)))
{
QString chopped(path);
chopped.chop(QString(QLatin1String(similarity_digikamdb)).length());
return chopped;
}
return path;
}
DbEngineParameters DbEngineParameters::defaultParameters(const QString& databaseType)
{
DbEngineParameters parameters;
// only the database name is needed
DbEngineConfigSettings config = DbEngineConfig::element(databaseType);
parameters.databaseType = databaseType;
parameters.databaseNameCore = config.databaseName;
parameters.databaseNameThumbnails = config.databaseName;
parameters.databaseNameFace = config.databaseName;
parameters.databaseNameSimilarity = config.databaseName;
parameters.userName = config.userName;
parameters.password = config.password;
parameters.internalServer = (databaseType == QLatin1String("QMYSQL"));
parameters.internalServerDBPath = (databaseType == QLatin1String("QMYSQL")) ? internalServerPrivatePath() : QString();
parameters.internalServerMysqlServCmd = (databaseType == QLatin1String("QMYSQL")) ? defaultMysqlServerCmd() : QString();
parameters.internalServerMysqlInitCmd = (databaseType == QLatin1String("QMYSQL")) ? defaultMysqlInitCmd() : QString();
QString hostName = config.hostName;
QString port = config.port;
QString connectOptions = config.connectOptions;
#ifdef Q_OS_WIN
hostName.replace(QLatin1String("$$DBHOSTNAME$$"), (databaseType == QLatin1String("QMYSQL"))
? QLatin1String("localhost")
: QString());
port.replace(QLatin1String("$$DBPORT$$"), (databaseType == QLatin1String("QMYSQL"))
? QLatin1String("3307")
: QLatin1String("-1"));
connectOptions.replace(QLatin1String("$$DBOPTIONS$$"), QString());
#else
hostName.replace(QLatin1String("$$DBHOSTNAME$$"), QString());
port.replace(QLatin1String("$$DBPORT$$"), QLatin1String("-1"));
const QString miscDir = internalServerPrivatePath() + QLatin1String("db_misc");
connectOptions.replace(QLatin1String("$$DBOPTIONS$$"), (databaseType == QLatin1String("QMYSQL"))
? QString::fromLatin1("UNIX_SOCKET=%1/mysql.socket").arg(miscDir)
: QString());
#endif
parameters.hostName = hostName;
parameters.port = port.toInt();
parameters.connectOptions = connectOptions;
qCDebug(DIGIKAM_DBENGINE_LOG) << "ConnectOptions " << parameters.connectOptions;
return parameters;
}
DbEngineParameters DbEngineParameters::thumbnailParameters() const
{
DbEngineParameters params = *this;
params.databaseNameCore = databaseNameThumbnails;
return params;
}
DbEngineParameters DbEngineParameters::faceParameters() const
{
DbEngineParameters params = *this;
params.databaseNameCore = databaseNameFace;
return params;
}
DbEngineParameters DbEngineParameters::similarityParameters() const
{
DbEngineParameters params = *this;
params.databaseNameCore = databaseNameSimilarity;
return params;
}
DbEngineParameters DbEngineParameters::parametersForSQLite(const QString& databaseFile)
{
// only the database name is needed
DbEngineParameters params(QLatin1String("QSQLITE"), databaseFile);
params.setCoreDatabasePath(databaseFile);
params.setThumbsDatabasePath(params.getCoreDatabaseNameOrDir());
params.setFaceDatabasePath(params.getCoreDatabaseNameOrDir());
params.setSimilarityDatabasePath(params.getCoreDatabaseNameOrDir());
return params;
}
DbEngineParameters DbEngineParameters::parametersForSQLiteDefaultFile(const QString& directory)
{
return parametersForSQLite(QDir::cleanPath(directory + QLatin1Char('/') + QLatin1String(digikam4db)));
}
QString DbEngineParameters::defaultMysqlServerCmd()
{
return QLatin1String("mysqld"); // For Linux, Windows and OSX
}
QString DbEngineParameters::defaultMysqlInitCmd()
{
return QLatin1String("mysql_install_db"); // For Linux, Windows and OSX
}
// --------------------------------
QDebug operator<<(QDebug dbg, const DbEngineParameters& p)
{
dbg.nospace() << "Database Parameters:" << endl;
dbg.nospace() << " Type: " << p.databaseType << endl;
dbg.nospace() << " DB Core Name: " << p.databaseNameCore << endl;
dbg.nospace() << " DB Thumbs Name: " << p.databaseNameThumbnails << endl;
dbg.nospace() << " DB Face Name: " << p.databaseNameFace << endl;
- dbg.nospace() << " DB Similyritiy Name: " << p.databaseNameSimilarity << endl;
+ dbg.nospace() << " DB Similarity Name: " << p.databaseNameSimilarity << endl;
dbg.nospace() << " Connect Options: " << p.connectOptions << endl;
dbg.nospace() << " Host Name: " << p.hostName << endl;
dbg.nospace() << " Host port: " << p.port << endl;
dbg.nospace() << " Internal Server: " << p.internalServer << endl;
dbg.nospace() << " Internal Server Path: " << p.internalServerDBPath << endl;
dbg.nospace() << " Internal Server Serv Cmd: " << p.internalServerMysqlServCmd << endl;
dbg.nospace() << " Internal Server Init Cmd: " << p.internalServerMysqlInitCmd << endl;
dbg.nospace() << " Username: " << p.userName << endl;
dbg.nospace() << " Password: " << QString().fill(QLatin1Char('X'), p.password.size()) << endl;
return dbg.space();
}
} // namespace Digikam
diff --git a/core/libs/database/imgqsort/imgqsort.cpp b/core/libs/database/imgqsort/imgqsort.cpp
index 2a5c48e916..da7886a257 100644
--- a/core/libs/database/imgqsort/imgqsort.cpp
+++ b/core/libs/database/imgqsort/imgqsort.cpp
@@ -1,833 +1,833 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 25/08/2013
* Description : Image Quality Sorter
*
* Copyright (C) 2013-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2013-2014 by Gowtham Ashok <gwty93 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "imgqsort.h"
// C++ includes
#include <cmath>
#include <cfloat>
#include <cstdio>
// Qt includes
#include <QTextStream>
#include <QFile>
// Local includes
#include "digikam_opencv.h"
#include "digikam_debug.h"
#include "mixerfilter.h"
#include "nrfilter.h"
#include "nrestimate.h"
// To switch on/off log trace file.
// #define TRACE 1
using namespace cv;
namespace Digikam
{
class Q_DECL_HIDDEN ImgQSort::Private
{
public:
explicit Private() :
clusterCount(30), //used for k-means clustering algorithm in noise detection
size(512)
{
for (int c = 0 ; c < 3 ; c++)
{
fimg[c] = 0;
}
// Setting the default values
edgeThresh = 1;
lowThreshold = 0.4;
ratio = 3;
kernel_size = 3;
blurrejected = 0.0;
blur = 0.0;
acceptedThreshold = 0.0;
pendingThreshold = 0.0;
rejectedThreshold = 0.0;
label = 0;
running = true;
}
float* fimg[3];
const uint clusterCount;
const uint size; // Size of squared original image.
Mat src; // Matrix of the original source image
Mat src_gray; // Matrix of the grayscaled source image
Mat detected_edges; // Matrix containing only edges in the image
int edgeThresh; // threshold above which we say that edges are present at a point
int ratio; // lower:upper threshold for canny edge detector algorithm
int kernel_size;
// kernel size for the Sobel operations to be performed internally
// by the edge detector
double lowThreshold;
DImg image; // original image
DImg neimage; // noise estimation image[ for color]
ImageQualitySettings imq;
double blurrejected;
double blur;
double acceptedThreshold;
double pendingThreshold;
double rejectedThreshold;
QString path; // Path to host result file
PickLabel* label;
volatile bool running;
};
ImgQSort::ImgQSort(const DImg& img, const ImageQualitySettings& imq, PickLabel* const label)
: d(new Private)
{
// Reading settings from GUI
d->imq.detectBlur = imq.detectBlur;
d->imq.detectNoise = imq.detectNoise;
d->imq.detectCompression = imq.detectCompression;
d->imq.detectOverexposure = imq.detectOverexposure;
d->imq.lowQRejected = imq.lowQRejected;
d->imq.mediumQPending = imq.mediumQPending;
d->imq.highQAccepted = imq.highQAccepted;
d->imq.speed = imq.speed;
d->imq.rejectedThreshold = imq.rejectedThreshold;
d->imq.pendingThreshold = imq.pendingThreshold;
d->imq.acceptedThreshold = imq.acceptedThreshold;
d->imq.blurWeight = imq.blurWeight;
d->imq.noiseWeight = imq.noiseWeight;
d->imq.compressionWeight = imq.compressionWeight;
d->image = img;
d->neimage = img;
d->label = label;
}
ImgQSort::~ImgQSort()
{
delete d;
}
void ImgQSort::startAnalyse()
{
// For Noise Estimation
// Use the Top/Left corner of 256x256 pixels to analyze noise contents from image.
// This will speed-up computation time with OpenCV.
readImage();
double blur = 0.0;
short blur2 = 0;
double noise = 0.0;
int compressionlevel = 0;
float finalquality = 0.0;
int exposurelevel = 0;
// If blur option is selected in settings, run the blur detection algorithms
if (d->running && d->imq.detectBlur)
{
// Returns blur value between 0 and 1.
// If NaN is returned just assign NoPickLabel
blur = blurdetector();
qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of Blur present in image is : " << blur;
// Returns blur value between 1 and 32767.
// If 1 is returned just assign NoPickLabel
blur2 = blurdetector2();
qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of Blur present in image [using LoG Filter] is : " << blur2;
}
if (d->running && d->imq.detectNoise)
{
// Some images give very low noise value. Assign NoPickLabel in that case.
// Returns noise value between 0 and 1.
noise = noisedetector();
qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of Noise present in image is : " << noise;
}
if (d->running && d->imq.detectCompression)
{
// Returns number of blocks in the image.
compressionlevel = compressiondetector();
qCDebug(DIGIKAM_DATABASE_LOG) << "Amount of compression artifacts present in image is : " << compressionlevel;
}
if (d->running && d->imq.detectOverexposure)
{
// Returns if there is overexposure in the image
exposurelevel = exposureamount();
qCDebug(DIGIKAM_DATABASE_LOG) << "Exposure level present in image is : " << exposurelevel;
}
#ifdef TRACE
QFile filems("imgqsortresult.txt");
if (filems.open(QIODevice::Append | QIODevice::Text))
{
QTextStream oms(&filems);
oms << "File:" << d->image.originalFilePath() << endl;
if (d->imq.detectBlur)
{
oms << "Blur Present:" << blur << endl;
oms << "Blur Present(using LoG filter):"<< blur2 << endl;
}
if (d->imq.detectNoise)
{
oms << "Noise Present:" << noise << endl;
}
if (d->imq.detectCompression)
{
oms << "Compression Present:" << compressionlevel << endl;
}
if (d->imq.detectOverexposure)
{
oms << "Exposure Present:" << exposurelevel << endl;
}
}
#endif // TRACE
// Calculating finalquality
// All the results to have a range of 1 to 100.
if (d->running)
{
float finalblur = (blur*100) + ((blur2/32767)*100);
float finalnoise = noise*100;
float finalcompression = (compressionlevel / 1024) * 100; // we are processing 1024 pixels size image
finalquality = finalblur*d->imq.blurWeight +
finalnoise*d->imq.noiseWeight +
finalcompression*d->imq.compressionWeight;
finalquality = finalquality / 100;
// Assigning PickLabels
if (finalquality == 0.0)
{
// Algorithms have not been run. So return noPickLabel
*d->label = NoPickLabel;
}
else if (finalquality < d->imq.rejectedThreshold)
{
*d->label = RejectedLabel;
}
else if (finalquality > d->imq.rejectedThreshold && finalquality < d->imq.acceptedThreshold)
{
*d->label = PendingLabel;
}
else
{
*d->label = AcceptedLabel;
}
}
else
{
*d->label = NoPickLabel;
}
}
void ImgQSort::cancelAnalyse()
{
d->running = false;
}
void ImgQSort::readImage() const
{
MixerContainer settings;
settings.bMonochrome = true;
MixerFilter mixer(&d->image, 0L, settings);
mixer.startFilterDirectly();
d->image.putImageData(mixer.getTargetImage().bits());
d->src = Mat(d->image.numPixels(), 3, CV_8UC3); // Create a matrix containing the pixel values of original image
d->src_gray = Mat(d->image.numPixels(), 1, CV_8UC1); // Create a matrix containing the pixel values of grayscaled image
if (d->imq.detectNoise)
{
DColor col;
for (int c = 0; d->running && (c < 3); c++)
{
d->fimg[c] = new float[d->neimage.numPixels()];
}
int j = 0;
for (uint y = 0; d->running && (y < d->neimage.height()); y++)
{
for (uint x = 0; d->running && (x < d->neimage.width()); x++)
{
col = d->neimage.getPixelColor(x, y);
d->fimg[0][j] = col.red();
d->fimg[1][j] = col.green();
d->fimg[2][j] = col.blue();
j++;
}
}
}
}
void ImgQSort::CannyThreshold(int, void*) const
{
// Reduce noise with a kernel 3x3.
blur(d->src_gray, d->detected_edges, Size(3,3));
// Canny detector.
Canny(d->detected_edges, d->detected_edges, d->lowThreshold, d->lowThreshold*d->ratio,d-> kernel_size);
}
double ImgQSort::blurdetector() const
{
d->lowThreshold = 0.4;
d->ratio = 3;
double maxval = 0.0;
ImgQSort::CannyThreshold(0, 0);
double average = mean(d->detected_edges)[0];
int* const maxIdx = new int[sizeof(d->detected_edges)];
minMaxIdx(d->detected_edges, 0, &maxval, 0, maxIdx);
double blurresult = average / maxval;
qCDebug(DIGIKAM_DATABASE_LOG) << "The average of the edge intensity is " << average;
qCDebug(DIGIKAM_DATABASE_LOG) << "The maximum of the edge intensity is " << maxval;
qCDebug(DIGIKAM_DATABASE_LOG) << "The result of the edge intensity is " << blurresult;
delete [] maxIdx;
return blurresult;
}
short ImgQSort::blurdetector2() const
{
// Algorithm using Laplacian of Gaussian Filter to detect blur.
Mat out;
Mat noise_free;
qCDebug(DIGIKAM_DATABASE_LOG) << "Algorithm using LoG Filter started";
// To remove noise from the image
GaussianBlur(d->src_gray, noise_free, Size(3,3), 0, 0, BORDER_DEFAULT);
// Aperture size of 1 corresponds to the correct matrix
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
Laplacian(noise_free, out, ddepth, kernel_size, scale, delta, BORDER_DEFAULT);
// noise_free: The input image without noise.
// out: Destination (output) image
// ddepth: Depth of the destination image. Since our input is CV_8U we define ddepth = CV_16S to avoid overflow
// kernel_size: The kernel size of the Sobel operator to be applied internally. We use 3 ihere
short maxLap = -32767;
for (int i = 0; i < out.rows; i++)
{
for (int j = 0; j < out.cols; j++)
{
short value = out.at<short>(i,j);
if (value > maxLap)
{
maxLap = value ;
}
}
}
return maxLap;
}
double ImgQSort::noisedetector() const
{
double noiseresult = 0.0;
//--convert fimg to CvMat*-------------------------------------------------------------------------------
// Convert the image into YCrCb color model.
NRFilter::srgb2ycbcr(d->fimg, d->neimage.numPixels());
- // One dimentional CvMat which stores the image.
+ // One dimensional CvMat which stores the image.
CvMat* points = cvCreateMat(d->neimage.numPixels(), 3, CV_32FC1);
// Matrix to store the index of the clusters.
CvMat* clusters = cvCreateMat(d->neimage.numPixels(), 1, CV_32SC1);
// Pointer variable to handle the CvMat* points (the image in CvMat format).
float* pointsPtr = reinterpret_cast<float*>(points->data.ptr);
for (uint x=0 ; d->running && (x < d->neimage.numPixels()) ; x++)
{
for (int y=0 ; d->running && (y < 3) ; y++)
{
*pointsPtr++ = (float)d->fimg[y][x];
}
}
// Array to store the centers of the clusters.
CvArr* centers = 0;
qCDebug(DIGIKAM_DATABASE_LOG) << "Everything ready for the cvKmeans2 or as it seems to";
//-- KMEANS ---------------------------------------------------------------------------------------------
if (d->running)
{
cvKMeans2(points, d->clusterCount, clusters,
cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 3, 0, 0, centers, 0);
}
qCDebug(DIGIKAM_DATABASE_LOG) << "cvKmeans2 successfully run";
//-- Divide into cluster->columns, sample->rows, in matrix standard deviation ---------------------------
QScopedArrayPointer<int> rowPosition(new int[d->clusterCount]);
// The row position array would just make the hold the number of elements in each cluster.
for (uint i=0 ; d->running && (i < d->clusterCount) ; i++)
{
// Initializing the cluster count array.
rowPosition[i] = 0;
}
int rowIndex, columnIndex;
for (uint i=0 ; d->running && (i < d->neimage.numPixels()) ; i++)
{
columnIndex = clusters->data.i[i];
rowPosition[columnIndex]++;
}
qCDebug(DIGIKAM_DATABASE_LOG) << "array indexed, and ready to find maximum";
//-- Finding maximum of the rowPosition array ------------------------------------------------------------
int max = rowPosition[0];
for (uint i=1 ; d->running && (i < d->clusterCount) ; i++)
{
if (rowPosition[i] > max)
{
max = rowPosition[i];
}
}
QString maxString;
maxString.append(QString::number(max));
qCDebug(DIGIKAM_DATABASE_LOG) << QString::fromUtf8("maximum declared = %1").arg(maxString);
//-- Divide and conquer ---------------------------------------------------------------------------------
CvMat* sd = 0;
if (d->running)
{
sd = cvCreateMat(max, (d->clusterCount * points->cols), CV_32FC1);
}
//-- Initialize the rowPosition array -------------------------------------------------------------------
QScopedArrayPointer<int> rPosition(new int[d->clusterCount]);
for (uint i=0 ; d->running && (i < d->clusterCount) ; i++)
{
rPosition[i] = 0;
}
float* ptr = 0;
qCDebug(DIGIKAM_DATABASE_LOG) << "The rowPosition array is ready!";
for (uint i=0 ; d->running && (i < d->neimage.numPixels()) ; i++)
{
columnIndex = clusters->data.i[i];
rowIndex = rPosition[columnIndex];
// Moving to the right row.
ptr = reinterpret_cast<float*>(sd->data.ptr + rowIndex*(sd->step));
// Moving to the right column.
for (int j=0 ; d->running && (j < columnIndex) ; j++)
{
for (int z=0 ; d->running && (z < (points->cols)) ; z++)
{
ptr++;
}
}
for (int z=0 ; d->running && (z < (points->cols)) ; z++)
{
*ptr++ = cvGet2D(points, i, z).val[0];
}
rPosition[columnIndex] = rPosition[columnIndex] + 1;
}
qCDebug(DIGIKAM_DATABASE_LOG) << "sd matrix creation over!";
//-- This part of the code would involve the sd matrix and make the mean and the std of the data -------------
CvScalar std;
CvScalar mean;
CvMat* meanStore = 0;
CvMat* stdStore = 0;
float* meanStorePtr = 0;
float* stdStorePtr = 0;
int totalcount = 0; // Number of non-empty clusters.
if (d->running)
{
meanStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1);
stdStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1);
meanStorePtr = reinterpret_cast<float*>(meanStore->data.ptr);
stdStorePtr = reinterpret_cast<float*>(stdStore->data.ptr);
}
for (int i=0 ; d->running && (i < sd->cols) ; i++)
{
if (d->running && (rowPosition[(i/points->cols)] >= 1))
{
CvMat* workingArr = cvCreateMat(rowPosition[(i / points->cols)], 1, CV_32FC1);
ptr = reinterpret_cast<float*>(workingArr->data.ptr);
for (int j=0 ; d->running && (j < rowPosition[(i / (points->cols))]) ; j++)
{
*ptr++ = cvGet2D(sd, j, i).val[0];
}
cvAvgSdv(workingArr, &mean, &std);
*meanStorePtr++ = (float)mean.val[0];
*stdStorePtr++ = (float)std.val[0];
totalcount++;
cvReleaseMat(&workingArr);
}
}
qCDebug(DIGIKAM_DATABASE_LOG) << "Make the mean and the std of the data";
// -----------------------------------------------------------------------------------------------------------------
if (d->running)
{
meanStorePtr = reinterpret_cast<float*>(meanStore->data.ptr);
stdStorePtr = reinterpret_cast<float*>(stdStore->data.ptr);
}
qCDebug(DIGIKAM_DATABASE_LOG) << "Done with the basic work of storing the mean and the std";
//-- Calculating weighted mean, and weighted std -----------------------------------------------------------
QString info;
float weightedMean = 0.0F;
float weightedStd = 0.0F;
float datasd[3] = {0.0F, 0.0F, 0.0F};
for (int j=0 ; d->running && (j < points->cols) ; j++)
{
meanStorePtr = reinterpret_cast<float*>(meanStore->data.ptr);
stdStorePtr = reinterpret_cast<float*>(stdStore->data.ptr);
for (int moveToChannel=0 ; moveToChannel <= j ; moveToChannel++)
{
meanStorePtr++;
stdStorePtr++;
}
for (uint i=0 ; i < d->clusterCount ; i++)
{
if (rowPosition[i] >= 1)
{
weightedMean += (*meanStorePtr) * rowPosition[i];
weightedStd += (*stdStorePtr) * rowPosition[i];
meanStorePtr += points->cols;
stdStorePtr += points->cols;
}
}
weightedMean = weightedMean / (d->neimage.numPixels());
weightedStd = weightedStd / (d->neimage.numPixels());
datasd[j] = weightedStd;
info.append(QLatin1String("\n\nChannel: "));
info.append(QString::number(j));
info.append(QLatin1String("\nWeighted Mean: "));
info.append(QString::number(weightedMean));
info.append(QLatin1String("\nWeighted Standard Deviation: "));
info.append(QString::number(weightedStd));
}
qCDebug(DIGIKAM_DATABASE_LOG) << "Info : " << info;
// -- adaptation ---------------------------------------------------------------------------------------
if (d->running)
{
// For 16 bits images only.
if (d->neimage.sixteenBit())
{
for (int i=0 ; i < points->cols ; i++)
{
datasd[i] = datasd[i] / 256;
}
}
noiseresult = ((datasd[0]/2)+(datasd[1]/2)+(datasd[2]/2))/3;
qCDebug(DIGIKAM_DATABASE_LOG) << "All is completed";
//-- releasing matrices and closing files ----------------------------------------------------------------------
cvReleaseMat(&sd);
cvReleaseMat(&stdStore);
cvReleaseMat(&meanStore);
cvReleaseMat(&points);
cvReleaseMat(&clusters);
for (uint i = 0; i < 3; i++)
{
delete [] d->fimg[i];
}
}
return noiseresult;
}
int ImgQSort::compressiondetector() const
{
//FIXME: set threshold value to an acceptable standard to get the number of blocking artifacts
const int THRESHOLD = 30;
const int block_size = 8;
int countblocks = 0;
int number_of_blocks = 0;
int sum = 0;
std::vector<int> average_bottom, average_middle, average_top;
// Go through 8 blocks at a time horizontally
// iterating through columns.
for (int i = 0; d->running && i < d->src_gray.rows; i++)
{
// Calculating intensity of top column.
for (int j = 0; j < d->src_gray.cols; j+=8)
{
sum = 0;
for (int k = j; k < block_size; k++)
{
sum += (int)d->src_gray.at<uchar>(i, j);
}
average_top.push_back(sum/8);
}
// Calculating intensity of middle column.
for (int j = 0; j < d->src_gray.cols; j+=8)
{
sum = 0;
for (int k = j; k < block_size; k++)
{
sum += (int)d->src_gray.at<uchar>(i+1, j);
}
average_middle.push_back(sum/8);
}
// Calculating intensity of bottom column.
countblocks = 0;
for (int j = 0; j < d->src_gray.cols; j+=8)
{
sum = 0;
for (int k = j; k < block_size; k++)
{
sum += (int)d->src_gray.at<uchar>(i+2, j);
}
average_bottom.push_back(sum/8);
countblocks++;
}
// Check if the average intensity of 8 blocks in the top, middle and bottom rows are equal.
// If so increment number_of_blocks.
for (int j = 0; j < countblocks; j++)
{
if ((average_middle[j] == (average_top[j]+average_bottom[j])/2) &&
average_middle[j] > THRESHOLD)
{
number_of_blocks++;
}
}
}
average_bottom.clear();
average_middle.clear();
average_top.clear();
// Iterating through rows.
for (int j = 0; d->running && j < d->src_gray.cols; j++)
{
// Calculating intensity of top row.
for (int i = 0; i < d->src_gray.rows; i+=8)
{
sum = 0;
for (int k = i; k < block_size; k++)
{
sum += (int)d->src_gray.at<uchar>(i, j);
}
average_top.push_back(sum/8);
}
// Calculating intensity of middle row.
for (int i= 0; i < d->src_gray.rows; i+=8)
{
sum = 0;
for (int k = i; k < block_size; k++)
{
sum += (int)d->src_gray.at<uchar>(i, j+1);
}
average_middle.push_back(sum/8);
}
// Calculating intensity of bottom row.
countblocks = 0;
for (int i = 0; i < d->src_gray.rows; i+=8)
{
sum = 0;
for (int k = i; k < block_size; k++)
{
sum += (int)d->src_gray.at<uchar>(i, j+2);
}
average_bottom.push_back(sum/8);
countblocks++;
}
// Check if the average intensity of 8 blocks in the top, middle and bottom rows are equal.
// If so increment number_of_blocks.
for (int i = 0; i < countblocks; i++)
{
if ((average_middle[i] == (average_top[i]+average_bottom[i])/2) &&
average_middle[i] > THRESHOLD)
{
number_of_blocks++;
}
}
}
return number_of_blocks;
}
int ImgQSort::exposureamount() const
{
/// Separate the image in 3 places ( B, G and R )
std::vector<Mat> bgr_planes;
split(d->src, bgr_planes);
/// Establish the number of bins
int histSize = 256;
/// Set the ranges ( for B,G,R) )
float range[] = { 0, 256 } ;
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat b_hist, g_hist, r_hist;
/// Compute the histograms
calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
// Draw the histograms for B, G and R
int hist_w = 512;
int hist_h = 400;
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
/// Normalize the histograms:
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
/// Sum the histograms
Scalar rmean,gmean,bmean;
rmean = mean(r_hist);
gmean = mean(g_hist);
bmean = mean(b_hist);
int exposurelevel = (rmean[0] + gmean[0] + bmean[0]) / 3;
return exposurelevel;
}
} // namespace Digikam
diff --git a/core/libs/database/item/imageinfo.h b/core/libs/database/item/imageinfo.h
index d60c235973..70fbf936b1 100644
--- a/core/libs/database/item/imageinfo.h
+++ b/core/libs/database/item/imageinfo.h
@@ -1,541 +1,541 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-04-21
* Description : Handling accesses to one image and associated data
*
* Copyright (C) 2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2007-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2013 by Michael G. Hansen <mike at mghansen dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_IMAGE_INFO_H
#define DIGIKAM_IMAGE_INFO_H
// Qt includes
#include <QString>
#include <QDateTime>
#include <QList>
#include <QSize>
#include <QUrl>
// Local includes
#include "coredbalbuminfo.h"
#include "digikam_export.h"
#include "dshareddata.h"
#include "coredburl.h"
#include "imageinfolist.h"
#include "coredbfields.h"
namespace Digikam
{
class DImageHistory;
class HistoryImageId;
class ImageComments;
class ImageCommonContainer;
class ImageCopyright;
class ImageExtendedProperties;
class ImageInfoData;
class ImageListerRecord;
class ImageMetadataContainer;
class VideoMetadataContainer;
class ImagePosition;
class ImageTagPair;
class PhotoInfoContainer;
class VideoInfoContainer;
class Template;
class ThumbnailIdentifier;
class ThumbnailInfo;
/**
* The ImageInfo class contains provides access to the database for a single image.
* The properties can be read and written. Information will be cached.
*/
class DIGIKAM_DATABASE_EXPORT ImageInfo
{
public:
/**
* Constructor
* Creates a null image info
*/
ImageInfo();
/**
* Constructor. Creates an ImageInfo object without any cached data initially.
* @param ID unique ID for this image
*/
explicit ImageInfo(qlonglong ID);
/**
* Constructor. Creates an ImageInfo object where the provided information
* will initially be available cached, without database access.
*/
explicit ImageInfo(const ImageListerRecord& record);
ImageInfo(const ImageInfo& info);
/**
* Creates an ImageInfo object from a file url.
*/
static ImageInfo fromLocalFile(const QString& path);
static ImageInfo fromUrl(const QUrl& url);
/**
* Create an ImageInfo object from the given combination, which
* must be cleaned and corresponding to the values in the database
*/
static ImageInfo fromLocationAlbumAndName(int locationId, const QString& album, const QString& name);
/**
* Destructor
*/
~ImageInfo();
ImageInfo& operator=(const ImageInfo& info);
bool operator==(const ImageInfo& info) const;
bool operator!=(const ImageInfo& info) const
{
return !operator==(info);
}
bool operator<(const ImageInfo& info) const;
uint hash() const;
/**
* Returns if this objects contains valid data
*/
bool isNull() const;
/**
* @return the name of the image
*/
QString name() const;
/**
* @return the datetime of the image
*/
QDateTime dateTime() const;
/**
* @return the modification datetime of the image
*/
QDateTime modDateTime() const;
/**
* @return the filesize of the image
*/
qlonglong fileSize() const;
/**
* @return the unique hash of the image.
*/
QString uniqueHash() const;
/**
* @return the dimensions of the image (valid only if dimensions
* have been requested)
*/
QSize dimensions() const;
/**
* Returns the file:// url.
* This is equivalent to QUrl::fromLocalFile(filePath())
*/
QUrl fileUrl() const;
/**
* Returns the file path to the image
*/
QString filePath() const;
/**
* @return the unique image id for this item
*/
qlonglong id() const;
/**
* @return the id of the PAlbum to which this item belongs
*/
int albumId() const;
/**
* The album root id
*/
int albumRootId() const;
/**
* @return the default title for this item
*/
QString title() const;
/**
* @return the default comment for this item
*/
QString comment() const;
/**
* @return the id of the Aspect Ratio for this item
*/
double aspectRatio() const;
/**
* Returns the Pick Label Id (see PickLabel values in globals.h)
*/
int pickLabel() const;
/**
* Returns the Color Label Id (see ColorLabel values in globals.h)
*/
int colorLabel() const;
/**
* Returns the rating
*/
int rating() const;
/**
* Returns the manual sort order
*/
qlonglong manualOrder() const;
/**
* Returns the category of the item: Image, Audio, Video
*/
DatabaseItem::Category category() const;
/** Returns the image format / mimetype as a standardized
* string (see project/documents/DBSCHEMA.ODS).
*/
QString format() const;
/**
* @return a list of IDs of tags assigned to this item
* @see tagNames
* @see tagPaths
* @see Album::id()
*/
QList<int> tagIds() const;
/**
* Returns true if the image is marked as visible in the database.
*/
bool isVisible() const;
/**
* Returns true if the corresponding file was not deleted.
*/
bool isRemoved() const;
/**
* Returns the orientation of the image,
* (MetaEngine::ImageOrientation, EXIF standard)
*/
int orientation() const;
/**
* Retrieve the ImageComments object for this item.
* This object allows full read and write access to all comments
* and their properties.
* You need to hold CoreDbAccess to ensure the validity.
* For simple, cached read access see comment().
*/
ImageComments imageComments(CoreDbAccess& access) const;
/**
* Retrieve the ImageCopyright object for this item.
* This object allows full read and write access to all copyright
* values.
*/
ImageCopyright imageCopyright() const;
/**
* Retrieve the ImageExtendedProperties object for this item.
* This object allows full read and write access to all extended properties
* values.
*/
ImageExtendedProperties imageExtendedProperties() const;
/**
* Retrieve the ImagePosition object for this item.
*/
ImagePosition imagePosition() const;
/**
* Retrieves the coordinates and the altitude.
* Returns 0 if hasCoordinates(), or hasAltitude resp, is false.
*/
double longitudeNumber() const;
double latitudeNumber() const;
double altitudeNumber() const;
bool hasCoordinates() const;
bool hasAltitude() const;
/**
* Retrieve an ImageTagPair object for a single tag, or for all
* image/tag pairs for which properties are available
* (not necessarily the assigned tags)
*/
ImageTagPair imageTagPair(int tagId) const;
QList<ImageTagPair> availableImageTagPairs() const;
/**
* Retrieves and sets the image history from the database.
* Note: The image history retrieved here does typically include all
* steps from the original to this image, but does not reference this image
* itself.
*
*/
DImageHistory imageHistory() const;
void setImageHistory(const DImageHistory& history);
bool hasImageHistory() const;
/**
* Retrieves and sets this' images UUID
*/
QString uuid() const;
void setUuid(const QString& uuid);
/**
* Constructs a HistoryImageId with all available information for this image.
*/
HistoryImageId historyImageId() const;
/**
* Retrieve information about images from which this image
* is derived (ancestorImages) and images that have been derived
* from this images (derivedImages).
*/
bool hasDerivedImages() const;
bool hasAncestorImages() const;
QList<ImageInfo> derivedImages() const;
QList<ImageInfo> ancestorImages() const;
/**
* Returns the cloud of all directly or indirectly related images,
* derived images or ancestors, in from of "a derived from b" pairs.
*/
QList<QPair<qlonglong, qlonglong> > relationCloud() const;
/**
* Add a relation to the database:
* This image is derived from the ancestorImage.
*/
void markDerivedFrom(const ImageInfo& ancestorImage);
/**
* The image is grouped in the group of another (leading) image.
*/
bool isGrouped() const;
/**
* The image is the leading image of a group,
* there are other images grouped behind this one.
*/
bool hasGroupedImages() const;
int numberOfGroupedImages() const;
/**
* Returns the leading image of the group.
* Returns a null image if this image is not grouped (isGrouped())
*/
ImageInfo groupImage() const;
qlonglong groupImageId() const;
/**
* Returns the list of images grouped behind this image (not including this
* image itself) and an empty list if there is none.
*/
QList<ImageInfo> groupedImages() const;
/**
* Group this image behind the given image
*/
void addToGroup(const ImageInfo& info);
/**
* This image is grouped behind another image:
* Remove this image from its group
*/
void removeFromGroup();
/**
* This image hasGroupedImages(): Split up the group,
* remove all groupedImages() from this image's group.
*/
void clearGroup();
/**
* Retrieve information about the image,
* in form of numbers and user presentable strings,
* for certain defined fields of information (see databaseinfocontainers.h)
*/
ImageCommonContainer imageCommonContainer() const;
ImageMetadataContainer imageMetadataContainer() const;
VideoMetadataContainer videoMetadataContainer() const;
PhotoInfoContainer photoInfoContainer() const;
VideoInfoContainer videoInfoContainer() const;
typedef DatabaseFields::Hash<QVariant> DatabaseFieldsHashRaw;
/**
* @todo Supports only VideoMetadataField and ImageMetadataField values for now.
*/
DatabaseFieldsHashRaw getDatabaseFieldsRaw(const DatabaseFields::Set& requestedSet) const;
QVariant getDatabaseFieldRaw(const DatabaseFields::Set& requestedField) const;
/**
* Retrieve metadata template information about the image.
*/
Template metadataTemplate() const;
/**
* Set metadata template information (write it to database)
* @param t the new template data.
*/
void setMetadataTemplate(const Template& t);
/**
* Remove all template info about the image from database.
*/
void removeMetadataTemplate();
/**
* Set the name (write it to database)
* @param newName the new name.
*/
void setName(const QString& newName);
/**
* Set the date and time (write it to database)
* @param dateTime the new date and time.
*/
void setDateTime(const QDateTime& dateTime);
/**
* Adds a tag to the item (writes it to database)
* @param tagID the ID of the tag to add
*/
void setTag(int tagID);
/**
* Adds tags in the list to the item.
* Tags are created if they do not yet exist
*/
void addTagPaths(const QStringList& tagPaths);
/**
* Remove a tag from the item (removes it from database)
* @param tagID the ID of the tag to remove
*/
void removeTag(int tagID);
/**
* Remove all tags from the item (removes it from database)
*/
void removeAllTags();
/** Set the pick Label Id for the item (see PickLabel values from globals.h)
*/
void setPickLabel(int value);
/**
* Set the color Label Id for the item (see ColorLabel values from globals.h)
*/
void setColorLabel(int value);
/**
* Set the rating for the item
*/
void setRating(int value);
/**
- * Set the manul sorting order for the item
+ * Set the manual sorting order for the item
*/
void setManualOrder(qlonglong value);
/**
* Set the orientation for the item
*/
void setOrientation(int value);
/**
* Set the visibility flag - triggers between Visible and Hidden
*/
void setVisible(bool isVisible);
/**
* Copy database information of this item to a newly created item
* @param dstAlbumID destination album id
* @param dstFileName new filename
* @return an ImageInfo object of the new item
*/
//TODO: Move to album?
ImageInfo copyItem(int dstAlbumID, const QString& dstFileName);
/**
* Returns true if this is a valid ImageInfo,
* and the location of the image is currently available
* (information freshly obtained from CollectionManager)
*/
bool isLocationAvailable() const;
/**
* Scans the database for items with the given signature.
*/
QList<ImageInfo> fromUniqueHash(const QString& uniqueHash, qlonglong fileSize);
/**
* Fills a ThumbnailIdentifier / ThumbnailInfo from this ImageInfo
*/
ThumbnailIdentifier thumbnailIdentifier() const;
ThumbnailInfo thumbnailInfo() const;
static ThumbnailIdentifier thumbnailIdentifier(qlonglong id);
double similarityTo(const qlonglong imageId) const;
double currentSimilarity() const;
/**
* Returns the id of the current fuzzy search reference image.
*/
qlonglong currentReferenceImage() const;
private:
friend class ImageInfoCache;
friend class ImageInfoList;
DSharedDataPointer<ImageInfoData> m_data;
};
inline uint qHash(const ImageInfo& info)
{
return info.hash();
}
//! qDebug() stream operator. Writes property @a info to the debug output in a nicely formatted way.
DIGIKAM_DATABASE_EXPORT QDebug operator<<(QDebug stream, const ImageInfo& info);
} // namespace Digikam
Q_DECLARE_TYPEINFO(Digikam::ImageInfo, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(Digikam::ImageInfo)
#endif // DIGIKAM_IMAGE_INFO_H
diff --git a/core/libs/database/item/imagetagpair.h b/core/libs/database/item/imagetagpair.h
index 9236343959..7078e8960c 100644
--- a/core/libs/database/item/imagetagpair.h
+++ b/core/libs/database/item/imagetagpair.h
@@ -1,130 +1,130 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-07-05
* Description : Access to the properties of an Image / Tag pair, i.e., a tag associated to an image
*
* Copyright (C) 2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_IMAGE_TAG_PAIR_H
#define DIGIKAM_IMAGE_TAG_PAIR_H
// Qt includes
#include <QString>
#include <QStringList>
#include <QList>
#include <QExplicitlySharedDataPointer>
#include <QMap>
// Local includes
#include "digikam_export.h"
namespace Digikam
{
class ImageInfo;
class ImageTagPairPriv;
class DIGIKAM_DATABASE_EXPORT ImageTagPair
{
public:
/** This class provides a wrapper over the Database methods
- * to access the properties of tag / image assocation. It is meant to be a
+ * to access the properties of tag / image association. It is meant to be a
* short-lived object, it does not listen to external database changes.
*/
/// Creates a null pair
ImageTagPair();
/** Access the properties of the given image - tag pair */
ImageTagPair(qlonglong imageId, int tagId);
ImageTagPair(const ImageInfo& info, int tagId);
~ImageTagPair();
ImageTagPair(const ImageTagPair& other);
ImageTagPair& operator=(const ImageTagPair& other);
bool isNull() const;
/**
* Return all pairs for the given image for which entries exist.
* This list of tags may not be identical to the tags assigned to the image.
*/
static QList<ImageTagPair> availablePairs(qlonglong imageId);
static QList<ImageTagPair> availablePairs(const ImageInfo& info);
qlonglong imageId() const;
int tagId() const;
/** Returns if the tag is assigned to the image
*/
bool isAssigned() const;
/** Assigns the tag to the image
*/
void assignTag();
/** Removes the tag from the image
*/
void unAssignTag();
/// Returns true if the property is set
bool hasProperty(const QString& key) const;
/// Returns true if any of the properties is set
bool hasAnyProperty(const QStringList& keys) const;
/// Returns true of the given property and value is set
bool hasValue(const QString& key, const QString& value) const;
/// Returns the value of the given property, or a null string if not set
QString value(const QString& key) const;
/// Returns value() concatenated for all given keys
QStringList allValues(const QStringList& keys) const;
/// Returns a list of values with the given property
QStringList values(const QString& key) const;
/// Returns all set property keys
QStringList propertyKeys() const;
/// Returns a map of all key->value pairs
QMap<QString, QString> properties() const;
/// Set the given property. Replaces all previous occurrences of this property.
void setProperty(const QString& key, const QString& value);
/** Adds the given property. Does not change any previous occurrences of this property,
* allowing multiple properties with the same key.
* (duplicates of same key _and_ value are not added, though)
*/
void addProperty(const QString& key, const QString& value);
/// Remove all occurrences of the property
void removeProperty(const QString& key, const QString& value);
/// Remove all occurrences of the property
void removeProperties(const QString& key);
/// Removes all properties
void clearProperties();
private:
QExplicitlySharedDataPointer<ImageTagPairPriv> d;
};
} // namespace
#endif // DIGIKAM_IMAGE_TAG_PAIR_H
diff --git a/core/libs/database/models/imagesortsettings.cpp b/core/libs/database/models/imagesortsettings.cpp
index d0fd243d4c..6ef33d9113 100644
--- a/core/libs/database/models/imagesortsettings.cpp
+++ b/core/libs/database/models/imagesortsettings.cpp
@@ -1,428 +1,428 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-03-05
* Description : Filter values for use with ImageFilterModel
*
* Copyright (C) 2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2014 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "imagesortsettings.h"
// Qt includes
#include <QDateTime>
#include <QRectF>
// Local includes
#include "coredbfields.h"
#include "imageinfo.h"
namespace Digikam
{
ImageSortSettings::ImageSortSettings()
{
categorizationMode = NoCategories;
categorizationSortOrder = DefaultOrder;
categorizationCaseSensitivity = Qt::CaseSensitive;
sortRole = SortByFileName;
sortOrder = DefaultOrder;
strTypeNatural = true;
sortCaseSensitivity = Qt::CaseSensitive;
currentCategorizationSortOrder = Qt::AscendingOrder;
currentSortOrder = Qt::AscendingOrder;
}
bool ImageSortSettings::operator==(const ImageSortSettings& other) const
{
return
categorizationMode == other.categorizationMode &&
categorizationSortOrder == other.categorizationSortOrder &&
categorizationCaseSensitivity == other.categorizationCaseSensitivity &&
sortRole == other.sortRole &&
sortOrder == other.sortOrder &&
sortCaseSensitivity == other.sortCaseSensitivity;
}
void ImageSortSettings::setCategorizationMode(CategorizationMode mode)
{
categorizationMode = mode;
if (categorizationSortOrder == DefaultOrder)
{
currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode);
}
}
void ImageSortSettings::setCategorizationSortOrder(SortOrder order)
{
categorizationSortOrder = order;
if (categorizationSortOrder == DefaultOrder)
{
currentCategorizationSortOrder = defaultSortOrderForCategorizationMode(categorizationMode);
}
else
{
currentCategorizationSortOrder = (Qt::SortOrder)categorizationSortOrder;
}
}
void ImageSortSettings::setSortRole(SortRole role)
{
sortRole = role;
if (sortOrder == DefaultOrder)
{
currentSortOrder = defaultSortOrderForSortRole(sortRole);
}
}
void ImageSortSettings::setSortOrder(SortOrder order)
{
sortOrder = order;
if (sortOrder == DefaultOrder)
{
currentSortOrder = defaultSortOrderForSortRole(sortRole);
}
else
{
currentSortOrder = (Qt::SortOrder)order;
}
}
void ImageSortSettings::setStringTypeNatural(bool natural)
{
strTypeNatural = natural;
}
Qt::SortOrder ImageSortSettings::defaultSortOrderForCategorizationMode(CategorizationMode mode)
{
switch (mode)
{
case NoCategories:
case OneCategory:
case CategoryByAlbum:
case CategoryByFormat:
case CategoryByMonth:
default:
return Qt::AscendingOrder;
}
}
Qt::SortOrder ImageSortSettings::defaultSortOrderForSortRole(SortRole role)
{
switch (role)
{
case SortByFileName:
case SortByFilePath:
return Qt::AscendingOrder;
case SortByFileSize:
return Qt::DescendingOrder;
case SortByModificationDate:
case SortByCreationDate:
return Qt::AscendingOrder;
case SortByRating:
case SortByImageSize:
return Qt::DescendingOrder;
case SortByAspectRatio:
return Qt::DescendingOrder;
case SortBySimilarity:
return Qt::DescendingOrder;
default:
return Qt::AscendingOrder;
}
}
int ImageSortSettings::compareCategories(const ImageInfo& left, const ImageInfo& right) const
{
switch (categorizationMode)
{
case NoCategories:
case OneCategory:
return 0;
case CategoryByAlbum:
{
int leftAlbum = left.albumId();
int rightAlbum = right.albumId();
- // return comparation result
+ // return comparison result
if (leftAlbum == rightAlbum)
{
return 0;
}
else if (lessThanByOrder(leftAlbum, rightAlbum,
currentCategorizationSortOrder))
{
return -1;
}
else
{
return 1;
}
}
case CategoryByFormat:
{
return naturalCompare(left.format(), right.format(),
currentCategorizationSortOrder,
categorizationCaseSensitivity, strTypeNatural);
}
case CategoryByMonth:
{
return compareByOrder(left.dateTime().date(),
right.dateTime().date(),
currentCategorizationSortOrder);
}
default:
return 0;
}
}
bool ImageSortSettings::lessThan(const ImageInfo& left, const ImageInfo& right) const
{
int result = compare(left, right, sortRole);
if (result != 0)
{
return result < 0;
}
// are they identical?
if (left == right)
{
return false;
}
// If left and right equal for first sort order, use a hierarchy of all sort orders
if ( (result = compare(left, right, SortByFileName)) != 0)
{
return result < 0;
}
if ( (result = compare(left, right, SortByCreationDate)) != 0)
{
return result < 0;
}
if ( (result = compare(left, right, SortByModificationDate)) != 0)
{
return result < 0;
}
if ( (result = compare(left, right, SortByFilePath)) != 0)
{
return result < 0;
}
if ( (result = compare(left, right, SortByFileSize)) != 0)
{
return result < 0;
}
if ( (result = compare(left, right, SortBySimilarity)) != 0)
{
return result < 0;
}
if ( (result = compare(left, right, SortByManualOrder)) != 0)
{
return result < 0;
}
return false;
}
int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right) const
{
return compare(left, right, sortRole);
}
int ImageSortSettings::compare(const ImageInfo& left, const ImageInfo& right, SortRole role) const
{
switch (role)
{
case SortByFileName:
{
bool versioning = (left.name().contains(QLatin1String("_v"), Qt::CaseInsensitive) ||
right.name().contains(QLatin1String("_v"), Qt::CaseInsensitive));
return naturalCompare(left.name(), right.name(), currentSortOrder, sortCaseSensitivity, strTypeNatural, versioning);
}
case SortByFilePath:
return naturalCompare(left.filePath(), right.filePath(), currentSortOrder, sortCaseSensitivity, strTypeNatural);
case SortByFileSize:
return compareByOrder(left.fileSize(), right.fileSize(), currentSortOrder);
case SortByModificationDate:
return compareByOrder(left.modDateTime(), right.modDateTime(), currentSortOrder);
case SortByCreationDate:
return compareByOrder(left.dateTime(), right.dateTime(), currentSortOrder);
case SortByRating:
// I have the feeling that inverting the sort order for rating is the natural order
return - compareByOrder(left.rating(), right.rating(), currentSortOrder);
case SortByImageSize:
{
QSize leftSize = left.dimensions();
QSize rightSize = right.dimensions();
int leftPixels = leftSize.width() * leftSize.height();
int rightPixels = rightSize.width() * rightSize.height();
return compareByOrder(leftPixels, rightPixels, currentSortOrder);
}
case SortByAspectRatio:
{
QSize leftSize = left.dimensions();
QSize rightSize = right.dimensions();
int leftAR = (double(leftSize.width()) / double(leftSize.height())) * 1000000;
int rightAR = (double(rightSize.width()) / double(rightSize.height())) * 1000000;
return compareByOrder(leftAR, rightAR, currentSortOrder);
}
case SortBySimilarity:
{
qlonglong leftReferenceImageId = left.currentReferenceImage();
qlonglong rightReferenceImageId = right.currentReferenceImage();
// make sure that the original image has always the highest similarity.
double leftSimilarity = left.id() == leftReferenceImageId ? 1.1 : left.currentSimilarity();
double rightSimilarity = right.id() == rightReferenceImageId ? 1.1 : right.currentSimilarity();
return compareByOrder(leftSimilarity, rightSimilarity, currentSortOrder);
}
case SortByManualOrder:
return compareByOrder(left.manualOrder(), right.manualOrder(), currentSortOrder);
default:
return 1;
}
}
bool ImageSortSettings::lessThan(const QVariant& left, const QVariant& right) const
{
if (left.type() != right.type())
{
return false;
}
switch (left.type())
{
case QVariant::Int:
return compareByOrder(left.toInt(), right.toInt(), currentSortOrder);
case QVariant::UInt:
return compareByOrder(left.toUInt(), right.toUInt(), currentSortOrder);
case QVariant::LongLong:
return compareByOrder(left.toLongLong(), right.toLongLong(), currentSortOrder);
case QVariant::ULongLong:
return compareByOrder(left.toULongLong(), right.toULongLong(), currentSortOrder);
case QVariant::Double:
return compareByOrder(left.toDouble(), right.toDouble(), currentSortOrder);
case QVariant::Date:
return compareByOrder(left.toDate(), right.toDate(), currentSortOrder);
case QVariant::DateTime:
return compareByOrder(left.toDateTime(), right.toDateTime(), currentSortOrder);
case QVariant::Time:
return compareByOrder(left.toTime(), right.toTime(), currentSortOrder);
case QVariant::Rect:
case QVariant::RectF:
{
QRectF rectLeft = left.toRectF();
QRectF rectRight = right.toRectF();
int result;
if ((result = compareByOrder(rectLeft.top(), rectRight.top(), currentSortOrder)) != 0)
{
return result < 0;
}
if ((result = compareByOrder(rectLeft.left(), rectRight.left(), currentSortOrder)) != 0)
{
return result < 0;
}
QSizeF sizeLeft = rectLeft.size();
QSizeF sizeRight = rectRight.size();
if ((result = compareByOrder(sizeLeft.width()*sizeLeft.height(), sizeRight.width()*sizeRight.height(), currentSortOrder)) != 0)
{
return result < 0;
}
#if __GNUC__ >= 7 // krazy:exclude=cpp
[[fallthrough]];
#endif
}
default:
{
return naturalCompare(left.toString(), right.toString(), currentSortOrder, sortCaseSensitivity, strTypeNatural);
}
}
}
DatabaseFields::Set ImageSortSettings::watchFlags() const
{
DatabaseFields::Set set;
switch (sortRole)
{
case SortByFileName:
set |= DatabaseFields::Name;
break;
case SortByFilePath:
set |= DatabaseFields::Name;
break;
case SortByFileSize:
set |= DatabaseFields::FileSize;
break;
case SortByModificationDate:
set |= DatabaseFields::ModificationDate;
break;
case SortByCreationDate:
set |= DatabaseFields::CreationDate;
break;
case SortByRating:
set |= DatabaseFields::Rating;
break;
case SortByImageSize:
set |= DatabaseFields::Width | DatabaseFields::Height;
break;
case SortByAspectRatio:
set |= DatabaseFields::Width | DatabaseFields::Height;
break;
case SortBySimilarity:
// TODO: Not sure what to do here....
set |= DatabaseFields::Name;
break;
case SortByManualOrder:
set |= DatabaseFields::ManualOrder;
break;
}
switch (categorizationMode)
{
case NoCategories:
case OneCategory:
case CategoryByAlbum:
break;
case CategoryByFormat:
set |= DatabaseFields::Format;
break;
case CategoryByMonth:
set |= DatabaseFields::CreationDate;
break;
}
return set;
}
} // namespace Digikam
diff --git a/core/libs/database/tags/tagregion.cpp b/core/libs/database/tags/tagregion.cpp
index 18f4ef4760..a25836cb68 100644
--- a/core/libs/database/tags/tagregion.cpp
+++ b/core/libs/database/tags/tagregion.cpp
@@ -1,313 +1,313 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-08-26
* Description : Tag region formatting
*
* Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "tagregion.h"
// Qt includes
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
// Local includes
#include "dimg.h"
namespace Digikam
{
TagRegion::TagRegion()
: m_type(Invalid)
{
}
TagRegion::TagRegion(const QString& descriptor)
: m_value(descriptor),
m_type(Invalid)
{
QString xmlStartDocument = QLatin1String("<?xml version=\"1.0\"?>");
QXmlStreamReader reader(xmlStartDocument + descriptor);
if (reader.readNextStartElement())
{
if (reader.name() == QLatin1String("rect"))
{
QRect r(reader.attributes().value(QLatin1String("x")).toString().toInt(),
reader.attributes().value(QLatin1String("y")).toString().toInt(),
reader.attributes().value(QLatin1String("width")).toString().toInt(),
reader.attributes().value(QLatin1String("height")).toString().toInt());
if (r.isValid())
{
m_value = r;
m_type = Rect;
}
}
}
}
TagRegion::TagRegion(const QRect& rect)
: m_value(rect),
m_type(Rect)
{
}
TagRegion::Type TagRegion::type() const
{
return m_type;
}
bool TagRegion::isValid() const
{
return m_type != Invalid;
}
bool TagRegion::operator==(const TagRegion& other) const
{
return ((m_type == other.m_type) &&
(m_value == other.m_value));
}
QString TagRegion::toXml() const
{
if (m_type == Invalid)
{
return QString();
}
QString output;
QXmlStreamWriter writer(&output);
writer.writeStartDocument();
int lengthOfHeader = output.length();
if (m_type == Rect)
{
QRect rect = m_value.toRect();
writer.writeStartElement(QLatin1String("rect"));
writer.writeAttribute(QLatin1String("x"), QString::number(rect.x()));
writer.writeAttribute(QLatin1String("y"), QString::number(rect.y()));
writer.writeAttribute(QLatin1String("width"), QString::number(rect.width()));
writer.writeAttribute(QLatin1String("height"), QString::number(rect.height()));
writer.writeEndElement();
}
// cut off the <?xml> tag at start of document
return output.mid(lengthOfHeader);
}
QRect TagRegion::toRect() const
{
if (m_type == Rect)
{
return m_value.toRect();
}
return QRect();
}
QVariant TagRegion::toVariant() const
{
return m_value;
}
TagRegion TagRegion::fromVariant(const QVariant& var)
{
switch (var.type())
{
case QVariant::Rect:
return TagRegion(var.toRect());
case QVariant::String:
return TagRegion(var.toString());
default:
return TagRegion();
}
}
bool TagRegion::intersects(const TagRegion& other, double fraction)
{
if (m_type == Invalid || other.m_type == Invalid)
{
return false;
}
if (m_type == Rect)
{
QRect r = toRect();
if (other.m_type == Rect)
{
QRect r2 = other.toRect();
if (fraction == 0)
{
return r.intersects(r2);
}
else if (fraction == 1)
{
return r.contains(r2);
}
else
{
QRect i = r.intersected(r2);
return ( (double(i.width() * i.height()) / double(r.width() * r.height())) > fraction );
}
}
}
return false;
}
QRect TagRegion::mapToOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& reducedSizeDetail)
{
if (fullImageSize == reducedImageSize)
{
return reducedSizeDetail;
}
double ratioWidth = double(fullImageSize.width()) / double(reducedImageSize.width());
double ratioHeight = double(fullImageSize.height()) / double(reducedImageSize.height());
return QRectF(reducedSizeDetail.x() * ratioWidth,
reducedSizeDetail.y() * ratioHeight,
reducedSizeDetail.width() * ratioWidth,
reducedSizeDetail.height() * ratioHeight).toRect();
}
QRect TagRegion::mapFromOriginalSize(const QSize& fullImageSize, const QSize& reducedImageSize, const QRect& fullSizeDetail)
{
if (fullImageSize == reducedImageSize)
{
return fullSizeDetail;
}
double ratioWidth = double(reducedImageSize.width()) / double(fullImageSize.width());
double ratioHeight = double(reducedImageSize.height()) / double(fullImageSize.height());
return QRectF(fullSizeDetail.x() * ratioWidth,
fullSizeDetail.y() * ratioHeight,
fullSizeDetail.width() * ratioWidth,
fullSizeDetail.height() * ratioHeight).toRect();
}
QRect TagRegion::mapToOriginalSize(const DImg& reducedSizeImage, const QRect& reducedSizeDetail)
{
return mapToOriginalSize(reducedSizeImage.originalSize(), reducedSizeImage.size(), reducedSizeDetail);
}
QRect TagRegion::mapFromOriginalSize(const DImg& reducedSizeImage, const QRect& fullSizeDetail)
{
return mapFromOriginalSize(reducedSizeImage.originalSize(), reducedSizeImage.size(), fullSizeDetail);
}
QRect TagRegion::relativeToAbsolute(const QRectF& region, const QSize& fullSize)
{
return QRectF(region.x() * fullSize.width(),
region.y() * fullSize.height(),
region.width() * fullSize.width(),
region.height() * fullSize.height()).toRect();
}
QRect TagRegion::relativeToAbsolute(const QRectF& region, const DImg& reducedSizeImage)
{
return relativeToAbsolute(region, reducedSizeImage.originalSize());
}
QRectF TagRegion::absoluteToRelative(const QRect& region, const QSize& fullSize)
{
return QRectF((qreal)region.x() / (qreal)fullSize.width(),
(qreal)region.y() / (qreal)fullSize.height(),
(qreal)region.width() / (qreal)fullSize.width(),
(qreal)region.height() / (qreal)fullSize.height());
}
QRect TagRegion::ajustToRotatedImg(const QRect& region, const QSize &fullSize, int rotation)
{
int x, y, w, h;
region.getRect(&x, &y, &w, &h);
int newx, newy, neww, newh;
- if (rotation == 0) // Rotate right 90 degress
+ if (rotation == 0) // Rotate right 90 degrees
{
newx = fullSize.height() - y -h;
newy = x;
neww = h;
newh = w;
}
- else // Rotate left 90 degress
+ else // Rotate left 90 degrees
{
newx = y;
newy = fullSize.width() - x -w;
neww = h;
newh = w;
}
return QRect(newx, newy, neww, newh);
}
QRect TagRegion::ajustToFlippedImg(const QRect& region, const QSize& fullSize, int flip)
{
int x, y, w, h;
region.getRect(&x, &y, &w, &h);
int newx, newy, neww, newh;
if (flip == 0) // Flip horizontally
{
newx = fullSize.width() - x -w;
newy = y;
neww = w;
newh = h;
}
else // Flip vertically
{
newx = x;
newy = fullSize.height() - y -h;
neww = w;
newh = h;
}
return QRect(newx, newy, neww, newh);
}
QDebug operator<<(QDebug dbg, const TagRegion& r)
{
QVariant var = r.toVariant();
switch (var.type())
{
case QVariant::Rect:
dbg.nospace() << var.toRect();
break;
case QVariant::String:
dbg.nospace() << var.toString();
break;
default:
dbg.nospace() << var;
break;
}
return dbg;
}
} // namespace Digikam
diff --git a/core/libs/dialogs/dmessagebox.h b/core/libs/dialogs/dmessagebox.h
index 9883720c71..a64ee1cbb4 100644
--- a/core/libs/dialogs/dmessagebox.h
+++ b/core/libs/dialogs/dmessagebox.h
@@ -1,169 +1,169 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2015-01-19
* Description : message box notification settings
*
* Copyright (C) 2015-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DMESSAGE_BOX_H
#define DIGIKAM_DMESSAGE_BOX_H
// Qt includes
#include <QWidget>
#include <QString>
#include <QMessageBox>
#include <QListWidget>
// Local includes
#include "digikam_export.h"
class QDialog;
class QDialogButtonBox;
namespace Digikam
{
class DIGIKAM_EXPORT DMessageBox
{
public:
/**
* @return true if the corresponding message box should be shown.
* @param dontShowAgainName the name that identify the message box. If
* empty, this method return false.
* @param result is set to the result that was chosen the last
* time the message box was shown.
*/
static bool readMsgBoxShouldBeShown(const QString& dontShowAgainName);
/**
* Save the fact that the message box should not be shown again.
* @param dontShowAgainName the name that identify the message box. If
* empty, this method does nothing.
* @param value the value chosen in the message box to show it again next time.
*/
static void saveMsgBoxShouldBeShown(const QString& dontShowAgainName, bool value);
public:
/** Show List of items into an informative message box.
*/
static void showInformationList(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
const QStringList& items,
const QString& dontShowAgainName = QString());
/** Show widget into an informative message box.
*/
static void showInformationWidget(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
QWidget* const listWidget,
const QString& dontShowAgainName);
public:
/** Show a message box with Continue and Cancel buttons, and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::Cancel.
*/
static int showContinueCancel(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
const QString& dontAskAgainName = QString());
- /** Show List of items to processs into a message box with Continue and Cancel buttons,
+ /** Show List of items to process into a message box with Continue and Cancel buttons,
* and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::Cancel.
*/
static int showContinueCancelList(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
const QStringList& items,
const QString& dontAskAgainName = QString());
/** Show widget into a message box with Continue and Cancel buttons,
* and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::Cancel.
*/
static int showContinueCancelWidget(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
QWidget* const listWidget,
const QString& dontAskAgainName);
public:
/** Show a message box with Yes and No buttons, and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::No.
*/
static int showYesNo(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
const QString& dontAskAgainName = QString());
- /** Show List of items to processs into a message box with Yes and No buttons,
+ /** Show List of items to process into a message box with Yes and No buttons,
* and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::No.
*/
static int showYesNoList(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
const QStringList& items,
const QString& dontAskAgainName = QString());
/** Show widget into a message box with Yes and No buttons,
* and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::No.
*/
static int showYesNoWidget(QMessageBox::Icon icon,
QWidget* const parent,
const QString& caption,
const QString& text,
QWidget* const listWidget,
const QString& dontAskAgainName = QString());
private:
static int createMessageBox(QDialog* const dialog,
QDialogButtonBox* const buttons,
const QIcon& icon,
const QString& text,
QWidget* const listWidget,
const QString& ask,
bool* checkboxReturn);
static QIcon createIcon(QMessageBox::Icon icon);
static QListWidget* createWidgetList(const QStringList& items);
};
} // namespace Digikam
#endif // DIGIKAM_DMESSAGE_BOX_H
diff --git a/core/libs/dialogs/dsplashscreen.cpp b/core/libs/dialogs/dsplashscreen.cpp
index 53b78b902b..4099638caa 100644
--- a/core/libs/dialogs/dsplashscreen.cpp
+++ b/core/libs/dialogs/dsplashscreen.cpp
@@ -1,236 +1,236 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2003-02-10
* Description : a widget to display splash with progress bar
*
* Copyright (C) 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dsplashscreen.h"
// Qt includes
#include <QApplication>
#include <QTimer>
#include <QFont>
#include <QString>
#include <QColor>
#include <QTime>
#include <QTextDocument>
#include <QFontDatabase>
#include <QStandardPaths>
// Local includes
#include "digikam_debug.h"
#include "daboutdata.h"
#include "digikam_version.h"
namespace Digikam
{
class Q_DECL_HIDDEN DSplashScreen::Private
{
public:
explicit Private()
{
state = 0;
progressBarSize = 3;
state = 0;
messageAlign = Qt::AlignLeft;
version = QLatin1String(digikam_version_short);
versionColor = Qt::white;
messageColor = Qt::white;
lastStateUpdateTime = QTime::currentTime();
}
int state;
int progressBarSize;
int messageAlign;
QString message;
QString version;
QColor messageColor;
QColor versionColor;
QTime lastStateUpdateTime;
};
DSplashScreen::DSplashScreen()
: QSplashScreen(QPixmap()),
d(new Private)
{
QPixmap splash;
if (QApplication::applicationName() == QLatin1String("digikam"))
{
splash = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/splash-digikam.png"));
}
else
{
splash = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("showfoto/data/splash-showfoto.png"));
}
// Under Linux, only test versions has Beta stage.
// cppcheck-suppress redundantAssignment
bool isBeta = !QString::fromUtf8(digikam_version_suffix).isEmpty();
#if defined Q_OS_WIN
isBeta = true; // Windows version is always beta for the moment.
#elif defined Q_OS_OSX
isBeta = true; // MAC version is always beta for the moment.
#endif
if (isBeta)
{
QPainter p(&splash);
p.drawPixmap(380, 27, QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/logo-beta.png")));
p.end();
}
setPixmap(splash);
QTimer* const timer = new QTimer(this);
connect(timer, SIGNAL(timeout()),
this, SLOT(slotAnimate()));
timer->start(150);
}
DSplashScreen::~DSplashScreen()
{
delete d;
}
void DSplashScreen::setColor(const QColor& color)
{
d->messageColor = color;
}
void DSplashScreen::setAlignment(int alignment)
{
d->messageAlign = alignment;
}
void DSplashScreen::setMessage(const QString& message)
{
d->message = message;
QSplashScreen::showMessage(d->message, d->messageAlign, d->messageColor); // krazy:exclude=qclasses
slotAnimate();
qApp->processEvents();
}
void DSplashScreen::slotAnimate()
{
QTime currentTime = QTime::currentTime();
if (d->lastStateUpdateTime.msecsTo(currentTime) > 100)
{
d->state = ((d->state + 1) % (2 * d->progressBarSize - 1));
d->lastStateUpdateTime = currentTime;
}
update();
}
void DSplashScreen::drawContents(QPainter* p)
{
int position = 0;
QColor basecolor(155, 192, 231);
// -- Draw background circles ------------------------------------
QPainter::RenderHints hints = p->renderHints();
p->setRenderHints(QPainter::Antialiasing);
p->setPen(Qt::NoPen);
p->setBrush(QColor(225, 234, 231));
p->drawEllipse(21, 6, 9, 9);
p->drawEllipse(32, 6, 9, 9);
p->drawEllipse(43, 6, 9, 9);
p->setRenderHints(hints);
// -- Draw animated circles --------------------------------------
// Increments are chosen to get close to background's color
// (didn't work well with QColor::light function)
for (int i = 0 ; i < d->progressBarSize ; i++)
{
position = (d->state + i) % (2 * d->progressBarSize - 1);
if (position < 3)
{
p->setBrush(QColor(basecolor.red() - 18*i,
basecolor.green() - 28*i,
basecolor.blue() - 10*i));
p->drawEllipse(21 + position*11, 6, 9, 9);
}
}
- // We use a device dependant font with a fixed size.
+ // We use a device dependent font with a fixed size.
QFont fnt(QFontDatabase::systemFont(QFontDatabase::GeneralFont));
fnt.setPixelSize(10);
fnt.setBold(false);
p->setFont(fnt);
QRect r = rect();
r.setCoords(r.x() + 60, r.y() + 4, r.width() - 10, r.height() - 10);
// -- Draw message --------------------------------------------------------
// Message is draw at given position, limited to 49 chars
// If message is too long, string is truncated
if (d->message.length() > 50)
{
d->message.truncate(49);
}
p->setPen(d->messageColor);
p->drawText(r, d->messageAlign, d->message);
// -- Draw version string -------------------------------------------------
QFontMetrics fontMt(fnt);
QRect r2 = fontMt.boundingRect(rect(), 0, d->version);
r2.moveTopLeft(QPoint(width()-r2.width()-10, r.y()));
p->setPen(d->versionColor);
p->drawText(r2, Qt::AlignLeft, d->version);
// -- Draw slogan ----------------------------------------------------------
// NOTE: splashscreen size is 469*288 pixels
r = rect();
r.setCoords(r.x() + 210, r.y() + 225, r.x() + 462, r.y() + 275);
p->translate(r.x(), r.y());
QTextDocument slogan;
slogan.setDefaultTextOption(QTextOption(Qt::AlignRight | Qt::AlignVCenter));
slogan.setHtml(DAboutData::digiKamSloganFormated());
slogan.setPageSize(r.size());
slogan.setDefaultFont(fnt);
slogan.drawContents(p, QRect(0, 0, r.width(), r.height()));
}
} // namespace Digikam
diff --git a/core/libs/dimg/dimg_p.h b/core/libs/dimg/dimg_p.h
index e693825ec2..561c7a61ec 100644
--- a/core/libs/dimg/dimg_p.h
+++ b/core/libs/dimg/dimg_p.h
@@ -1,151 +1,151 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-06-15
* Description : DImg private data members
*
* Copyright (C) 2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DIMG_PRIVATE_H
#define DIGIKAM_DIMG_PRIVATE_H
// Qt includes
#include <QString>
#include <QByteArray>
#include <QVariant>
#include <QMap>
// Local includes
#include "digikam_export.h"
#include "dmetadata.h"
#include "dshareddata.h"
#include "dimagehistory.h"
#include "iccprofile.h"
/** Lanczos kernel is precomputed in a table with this resolution
The value below seems to be enough for HQ upscaling up to eight times
*/
#define LANCZOS_TABLE_RES 256
/** A support of 3 gives an overall sharper looking image, but
- it is a) slower b) gives more sharpening artefacts
+ it is a) slower b) gives more sharpening artifacts
*/
#define LANCZOS_SUPPORT 2
/** Define this to use a floating-point implementation of Lanczos interpolation.
The integer implementation is a little bit less accurate, but MUCH faster
(even on machines with FPU - ~2.5 times faster on Core2); besides, it will
run a hell lot faster on computers without a FPU (e.g. PDAs).
*/
//#define LANCZOS_DATA_FLOAT
#ifdef LANCZOS_DATA_FLOAT
#define LANCZOS_DATA_TYPE float
#define LANCZOS_DATA_ONE 1.0
#else
#define LANCZOS_DATA_TYPE int
#define LANCZOS_DATA_ONE 4096
#endif
namespace Digikam
{
class DIGIKAM_EXPORT DImg::Private : public DSharedData
{
public:
explicit Private()
{
null = true;
width = 0;
height = 0;
data = 0;
lanczos_func = 0;
alpha = false;
sixteenBit = false;
}
~Private()
{
delete [] data;
delete [] lanczos_func;
}
static QStringList fileOriginAttributes()
{
QStringList list;
list << QLatin1String("format")
<< QLatin1String("isreadonly")
<< QLatin1String("originalFilePath")
<< QLatin1String("originalSize")
<< QLatin1String("originalImageHistory")
<< QLatin1String("rawDecodingSettings")
<< QLatin1String("rawDecodingFilterAction")
<< QLatin1String("uniqueHash")
<< QLatin1String("uniqueHashV2");
return list;
}
/**
* x,y, w x h is a section of the image. The image size is width x height.
* Clips the section to the bounds of the image.
* Returns if the (clipped) section is a valid rectangle.
*/
static bool clipped(int& x, int& y, int& w, int& h, uint width, uint height)
{
QRect inner(x, y, w, h);
QRect outer(0, 0, width, height);
if (!outer.contains(inner))
{
QRect clipped = inner.intersected(outer);
x = clipped.x();
y = clipped.y();
w = clipped.width();
h = clipped.height();
return clipped.isValid();
}
return inner.isValid();
}
public:
bool null;
bool alpha;
bool sixteenBit;
unsigned int width;
unsigned int height;
unsigned char* data;
LANCZOS_DATA_TYPE* lanczos_func;
MetaEngineData metaData;
QMap<QString, QVariant> attributes;
QMap<QString, QString> embeddedText;
IccProfile iccProfile;
DImageHistory imageHistory;
};
} // namespace Digikam
#endif // DIGIKAM_DIMG_PRIVATE_H
diff --git a/core/libs/dimg/filters/dimgthreadedfilter.h b/core/libs/dimg/filters/dimgthreadedfilter.h
index dc0bcc8d6c..68776fe408 100644
--- a/core/libs/dimg/filters/dimgthreadedfilter.h
+++ b/core/libs/dimg/filters/dimgthreadedfilter.h
@@ -1,300 +1,300 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-05-25
* Description : threaded image filter class.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2007-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DIMG_THREADED_FILTER_H
#define DIGIKAM_DIMG_THREADED_FILTER_H
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_export.h"
#include "dimg.h"
#include "dynamicthread.h"
#include "filteraction.h"
class QObject;
namespace Digikam
{
class DIGIKAM_EXPORT DImgThreadedFilter : public DynamicThread
{
Q_OBJECT
public:
/** Constructs a filter without argument.
* You need to call setupFilter() and startFilter()
* to start the threaded computation.
* To run filter without to use multithreading, call startFilterDirectly().
*/
explicit DImgThreadedFilter(QObject* const parent=0, const QString& name = QString());
/** Constructs a filter with all arguments (ready to use).
* The given original image will be copied.
* You need to call startFilter() to start the threaded computation.
* To run filter without to use multithreading, call startFilterDirectly().
*/
DImgThreadedFilter(DImg* const orgImage, QObject* const parent,
const QString& name = QString());
~DImgThreadedFilter();
/** You need to call this and then start filter of you used
* the constructor not setting an original image.
* The original image's data will not be copied.
*/
void setupFilter(const DImg& orgImage);
/** Initializes the filter for use as a slave and directly starts computation (in-thread)
*/
void setupAndStartDirectly(const DImg& orgImage, DImgThreadedFilter* const master,
int progressBegin = 0, int progressEnd = 100);
void setOriginalImage(const DImg& orgImage);
void setFilterName(const QString& name);
DImg getTargetImage()
{
return m_destImage;
};
const QString& filterName()
{
return m_name;
};
/** This method return a list of steps to process parallelized operation in filter using QtConcurrents API.
- * Ususally, start and stop are rows or columns from image to process. By defaut whole image will be processed
+ * Usually, start and stop are rows or columns from image to process. By default, whole image will be processed
* and start value is 0. In this case stop will be last row or column to process.
- * Between range [start,stop], this method will divide by egal steps depending of number of CPU cores available.
- * To be sure that all vlaues will be processed, in case of CPU core division give rest, the last step compensate
+ * Between range [start,stop], this method will divide by equal steps depending of number of CPU cores available.
+ * To be sure that all values will be processed, in case of CPU core division give rest, the last step compensate
* the difference.
- * See Blur filter loop implementation for exemple to see how to use this method with QtConcurrents API.
+ * See Blur filter loop implementation for example to see how to use this method with QtConcurrents API.
*/
QList<int> multithreadedSteps(int stop, int start=0) const;
/** Start the threaded computation.
*/
virtual void startFilter();
/** Cancel the threaded computation.
*/
virtual void cancelFilter();
/** Start computation of this filter, directly in this thread.
*/
virtual void startFilterDirectly();
/** Returns the action description corresponding to currently set options.
*/
virtual FilterAction filterAction() = 0;
virtual void readParameters(const FilterAction&) = 0;
/** Return the identifier for this filter in the image history.
*/
virtual QString filterIdentifier() const = 0;
virtual QList<int> supportedVersions() const;
/**
* Replaying a filter action:
* Set the filter version. A filter may implement different versions, to preserve
* image history when the algorithm is changed.
* Any value set here must be contained in supportedVersions, otherwise
* this call will be ignored. Default value is 1.
* (Note: If you intend to _record_ a filter action, please look at FilterAction's m_version)
*/
void setFilterVersion(int version);
int filterVersion() const;
/**
* Optional: error handling for readParameters.
* When readParameters() has been called, this method will return true
* if the call was successful, and false if not.
* If returning false, readParametersError() will give an error message.
* The default implementation always returns success. You only need to reimplement
* when a filter is likely to fail in a different environment, e.g.
* depending on availability of installed files.
* These methods have an undefined return value if readParameters() was not called
* previously.
*/
virtual bool parametersSuccessfullyRead() const;
virtual QString readParametersError(const FilterAction& actionThatFailed) const;
Q_SIGNALS:
/** This signal is emitted when image data is available and the computation has started.
*/
void started();
/** Emitted when progress info from the calculation is available.
*/
void progress(int progress);
/** Emitted when the computation has completed.
@param success True if computation finished without interruption on valid data
False if the thread was canceled, or no data is available.
*/
void finished(bool success);
protected:
/** Start filter operation before threaded method. Must be called by your constructor.
*/
virtual void initFilter();
/** List of threaded operations by filter.
*/
virtual void run();
/** Main image filter method. Override in subclass.
*/
virtual void filterImage() = 0;
/** Clean up filter data if necessary, called by stopComputation() method.
Override in subclass.
*/
virtual void cleanupFilter() {};
/** Emit progress info.
*/
void postProgress(int progress);
protected:
/**
Support for chaining two filters as master and thread.
Do not call startFilter() or startFilterDirectly() on this.
The computation will be started from initFilter() which you must
call from the derived class constructor.
Constructor for slave mode:
Constructs a new slave filter with the specified master.
The filter will be executed in the current thread.
orgImage and destImage will not be copied.
Note that the slave is still free to reallocate his destImage.
progressBegin and progressEnd can indicate the progress span
that the slave filter uses in the parent filter's progress.
Any derived filter class that is publicly available to other filters
should implement an additional constructor using this constructor.
*/
DImgThreadedFilter(DImgThreadedFilter* const master, const DImg& orgImage, const DImg& destImage,
int progressBegin=0, int progressEnd=100, const QString& name=QString());
/** Initialize the filter for use as a slave - reroutes progress info to master.
* Note: Computation will be started from setupFilter().
*/
void initSlave(DImgThreadedFilter* const master, int progressBegin = 0, int progressEnd = 100);
/** Inform the master that there is currently a slave. At destruction of the slave, call with slave=0.
*/
void setSlave(DImgThreadedFilter* const slave);
/** This method modulates the progress value from the 0..100 span to the span of this slave.
* Called by postProgress if master is not null.
*/
virtual int modulateProgress(int progress);
void initMaster();
virtual void prepareDestImage();
/**
* Convenience class to spare the few repeating lines of code
*/
template <class Filter>
class DefaultFilterAction : public FilterAction
{
public:
explicit DefaultFilterAction(FilterAction::Category category = FilterAction::ReproducibleFilter)
: FilterAction(Filter::FilterIdentifier(), Filter::CurrentVersion(), category)
{
setDisplayableName(Filter::DisplayableName());
}
explicit DefaultFilterAction(bool isReproducible)
: FilterAction(Filter::FilterIdentifier(), Filter::CurrentVersion(),
isReproducible ? FilterAction::ReproducibleFilter : FilterAction::ComplexFilter)
{
setDisplayableName(Filter::DisplayableName());
}
/**
* Preserve backwards compatibility:
* If a given condition (some new feature is not used) is true,
* decrease the version so that older digikam versions can still replay the action
*/
void supportOlderVersionIf(int version, bool condition)
{
if (condition && version <= m_version)
{
m_version = version;
}
}
};
protected:
int m_version;
bool m_wasCancelled;
/** The progress span that a slave filter uses in the parent filter's progress.
*/
int m_progressBegin;
int m_progressSpan;
int m_progressCurrent; // To prevent signals bombarding with progress indicator value in postProgress().
/** Filter name.
*/
QString m_name;
/** Copy of original Image data.
*/
DImg m_orgImage;
/** Output image data.
*/
DImg m_destImage;
/** The current slave. Any filter might want to use another filter while processing.
*/
DImgThreadedFilter* m_slave;
/** The master of this slave filter. Progress info will be routed to this one.
*/
DImgThreadedFilter* m_master;
};
} // namespace Digikam
#endif // DIGIKAM_DIMG_THREADED_FILTER_H
diff --git a/core/libs/dimg/filters/fx/colorfxfilter.cpp b/core/libs/dimg/filters/fx/colorfxfilter.cpp
index 56b90b8378..fddd049d67 100644
--- a/core/libs/dimg/filters/fx/colorfxfilter.cpp
+++ b/core/libs/dimg/filters/fx/colorfxfilter.cpp
@@ -1,645 +1,645 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-02-14
* Description : Color FX threaded image filter.
*
* Copyright 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
* Copyright 2015 by Andrej Krutak <dev at andree dot sk>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "colorfxfilter.h"
// C++ includes
#include <cstdio>
#include <cmath>
// Qt includes
#include <QFileInfo>
#include <QImage>
#include <QtMath>
// Local includes
#include "digikam_debug.h"
#include "curvesfilter.h"
#include "mixerfilter.h"
#include "dimg.h"
namespace Digikam
{
ColorFXFilter::ColorFXFilter(QObject* const parent)
: DImgThreadedFilter(parent),
m_lutTable(0),
m_lutTableSize(0)
{
initFilter();
}
ColorFXFilter::ColorFXFilter(DImg* const orgImage,
QObject* const parent,
const ColorFXContainer& settings)
: DImgThreadedFilter(orgImage, parent, QLatin1String("ColorFX")),
m_settings(settings),
m_lutTable(0),
m_lutTableSize(0)
{
loadLut3D(m_settings.path);
initFilter();
}
ColorFXFilter::~ColorFXFilter()
{
cancelFilter();
if (m_lutTable)
{
delete [] m_lutTable;
}
}
void ColorFXFilter::filterImage()
{
switch (m_settings.colorFXType)
{
case Solarize:
solarize(&m_orgImage, &m_destImage, m_settings.level);
break;
case Vivid:
vivid(&m_orgImage, &m_destImage, m_settings.level);
break;
case Neon:
neon(&m_orgImage, &m_destImage, m_settings.level, m_settings.iterations);
break;
case FindEdges:
findEdges(&m_orgImage, &m_destImage, m_settings.level, m_settings.iterations);
break;
case Lut3D:
applyLut3D();
m_destImage = m_orgImage;
break;
}
}
void ColorFXFilter::solarize(DImg* const orgImage, DImg* const destImage, int factor)
{
bool stretch = true;
int w = orgImage->width();
int h = orgImage->height();
const uchar* data = orgImage->bits();
bool sb = orgImage->sixteenBit();
uchar* pResBits = destImage->bits();
if (!sb) // 8 bits image.
{
uint threshold = (uint)((100 - factor) * (255 + 1) / 100);
threshold = qMax((uint)1, threshold);
const uchar* ptr = data;
uchar* dst = pResBits;
uchar a, r, g, b;
for (int x = 0 ; x < w * h ; ++x)
{
b = ptr[0];
g = ptr[1];
r = ptr[2];
a = ptr[3];
if (stretch)
{
r = (r > threshold) ? (255 - r) * 255 / (255 - threshold) : r * 255 / threshold;
g = (g > threshold) ? (255 - g) * 255 / (255 - threshold) : g * 255 / threshold;
b = (b > threshold) ? (255 - b) * 255 / (255 - threshold) : b * 255 / threshold;
}
else
{
if (r > threshold)
{
r = (255 - r);
}
if (g > threshold)
{
g = (255 - g);
}
if (b > threshold)
{
b = (255 - b);
}
}
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
ptr += 4;
dst += 4;
}
}
else // 16 bits image.
{
uint threshold = (uint)((100 - factor) * (65535 + 1) / 100);
threshold = qMax((uint)1, threshold);
const unsigned short* ptr = reinterpret_cast<const unsigned short*>(data);
unsigned short* dst = reinterpret_cast<unsigned short*>(pResBits);
unsigned short a, r, g, b;
for (int x = 0 ; x < w * h ; ++x)
{
b = ptr[0];
g = ptr[1];
r = ptr[2];
a = ptr[3];
if (stretch)
{
r = (r > threshold) ? (65535 - r) * 65535 / (65535 - threshold) : r * 65535 / threshold;
g = (g > threshold) ? (65535 - g) * 65535 / (65535 - threshold) : g * 65535 / threshold;
b = (b > threshold) ? (65535 - b) * 65535 / (65535 - threshold) : b * 65535 / threshold;
}
else
{
if (r > threshold)
{
r = (65535 - r);
}
if (g > threshold)
{
g = (65535 - g);
}
if (b > threshold)
{
b = (65535 - b);
}
}
dst[0] = b;
dst[1] = g;
dst[2] = r;
dst[3] = a;
ptr += 4;
dst += 4;
}
}
}
void ColorFXFilter::vivid(DImg* const orgImage, DImg* const destImage, int factor)
{
float amount = factor / 100.0;
// Apply Channel Mixer adjustments.
MixerContainer settings;
settings.redRedGain = 1.0 + amount + amount;
settings.redGreenGain = (-1.0) * amount;
settings.redBlueGain = (-1.0) * amount;
settings.greenRedGain = (-1.0) * amount;
settings.greenGreenGain = 1.0 + amount + amount;
settings.greenBlueGain = (-1.0) * amount;
settings.blueRedGain = (-1.0) * amount;
settings.blueGreenGain = (-1.0) * amount;
settings.blueBlueGain = 1.0 + amount + amount;
MixerFilter mixer(orgImage, 0L, settings);
mixer.startFilterDirectly();
DImg mixed = mixer.getTargetImage();
// And now apply the curve correction.
CurvesContainer prm(ImageCurves::CURVE_SMOOTH, orgImage->sixteenBit());
prm.initialize();
if (!orgImage->sixteenBit()) // 8 bits image.
{
prm.values[LuminosityChannel].setPoint(0, QPoint(0, 0));
prm.values[LuminosityChannel].setPoint(5, QPoint(63, 60));
prm.values[LuminosityChannel].setPoint(10, QPoint(191, 194));
prm.values[LuminosityChannel].setPoint(16, QPoint(255, 255));
}
else // 16 bits image.
{
prm.values[LuminosityChannel].setPoint(0, QPoint(0, 0));
prm.values[LuminosityChannel].setPoint(5, QPoint(16128, 15360));
prm.values[LuminosityChannel].setPoint(10, QPoint(48896, 49664));
prm.values[LuminosityChannel].setPoint(16, QPoint(65535, 65535));
}
CurvesFilter curves(&mixed, 0L, prm);
curves.startFilterDirectly();
*destImage = curves.getTargetImage();
}
/* Function to apply the Neon effect
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* Intensity => Intensity value
* BW => Border Width
*
* Theory => Wow, this is a great effect, you've never seen a Neon effect
* like this on PSC. Is very similar to Growing Edges (photoshop)
* Some pictures will be very interesting
*/
void ColorFXFilter::neon(DImg* const orgImage, DImg* const destImage, int Intensity, int BW)
{
neonFindEdges(orgImage, destImage, true, Intensity, BW);
}
/* Function to apply the Find Edges effect
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* Intensity => Intensity value
* BW => Border Width
*
* Theory => Wow, another Photoshop filter (FindEdges). Do you understand
- * Neon effect ? This is the same engine, but is inversed with
+ * Neon effect ? This is the same engine, but is inverted with
* 255 - color.
*/
void ColorFXFilter::findEdges(DImg* const orgImage, DImg* const destImage, int Intensity, int BW)
{
neonFindEdges(orgImage, destImage, false, Intensity, BW);
}
static inline int getOffset(int Width, int X, int Y, int bytesDepth)
{
return (Y * Width * bytesDepth) + (X * bytesDepth);
}
static inline int Lim_Max(int Now, int Up, int Max)
{
--Max;
while (Now > Max - Up)
{
--Up;
}
return (Up);
}
// Implementation of neon and FindEdges. They share 99% of their code.
void ColorFXFilter::neonFindEdges(DImg* const orgImage, DImg* const destImage, bool neon, int Intensity, int BW)
{
int Width = orgImage->width();
int Height = orgImage->height();
const uchar* data = orgImage->bits();
bool sixteenBit = orgImage->sixteenBit();
int bytesDepth = orgImage->bytesDepth();
uchar* pResBits = destImage->bits();
Intensity = (Intensity < 0) ? 0 : (Intensity > 5) ? 5 : Intensity;
BW = (BW < 1) ? 1 : (BW > 5) ? 5 : BW;
uchar* ptr=0, *ptr1=0, *ptr2=0;
// these must be uint, we need full 2^32 range for 16 bit
uint color_1, color_2, colorPoint, colorOther1, colorOther2;
// initial copy
memcpy(pResBits, data, Width * Height * bytesDepth);
double intensityFactor = qSqrt(1 << Intensity);
for (int h = 0 ; h < Height ; ++h)
{
for (int w = 0 ; w < Width ; ++w)
{
ptr = pResBits + getOffset(Width, w, h, bytesDepth);
ptr1 = pResBits + getOffset(Width, w + Lim_Max(w, BW, Width), h, bytesDepth);
ptr2 = pResBits + getOffset(Width, w, h + Lim_Max(h, BW, Height), bytesDepth);
if (sixteenBit)
{
for (int k = 0 ; k <= 2 ; ++k)
{
colorPoint = reinterpret_cast<unsigned short*>(ptr)[k];
colorOther1 = reinterpret_cast<unsigned short*>(ptr1)[k];
colorOther2 = reinterpret_cast<unsigned short*>(ptr2)[k];
color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
// old algorithm was
// sqrt ((color_1 + color_2) << Intensity)
// As (a << I) = a * (1 << I) = a * (2^I), and we can split the square root
if (neon)
{
reinterpret_cast<unsigned short*>(ptr)[k] = CLAMP065535((int)(sqrt((double)color_1 + color_2) * intensityFactor));
}
else
{
reinterpret_cast<unsigned short*>(ptr)[k] = 65535 - CLAMP065535((int)(sqrt((double)color_1 + color_2) * intensityFactor));
}
}
}
else
{
for (int k = 0 ; k <= 2 ; ++k)
{
colorPoint = ptr[k];
colorOther1 = ptr1[k];
colorOther2 = ptr2[k];
color_1 = (colorPoint - colorOther1) * (colorPoint - colorOther1);
color_2 = (colorPoint - colorOther2) * (colorPoint - colorOther2);
if (neon)
{
ptr[k] = CLAMP0255((int)(qSqrt((double)color_1 + color_2) * intensityFactor));
}
else
{
ptr[k] = 255 - CLAMP0255((int)(qSqrt((double)color_1 + color_2) * intensityFactor));
}
}
}
}
}
}
#define Lut3DSetPixel(table, w, x, y, p) \
table[((y) * (w) + (x)) * 4 + 0] = qRed(p) * 65535 / 255; \
table[((y) * (w) + (x)) * 4 + 1] = qGreen(p) * 65535 / 255; \
table[((y) * (w) + (x)) * 4 + 2] = qBlue(p) * 65535 / 255;
void ColorFXFilter::loadLut3D(const QString& path)
{
QFileInfo fi(path);
m_lutTable = NULL;
if (fi.suffix().toLower() == QLatin1String("cube"))
{
qCDebug(DIGIKAM_DIMG_LOG) << "Ca not yet process Cube LUTs";
// TODO: Adobe Cube LUT http://wwwimages.adobe.com/content/dam/Adobe/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf
}
else
{
QImage img(path);
if (img.isNull())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Format of " << path << " unknown!";
return;
}
if (img.width() == img.height())
{
// HALD LUT (Like imagemagick creates)
int w = img.width();
m_lutTableSize = int(pow(pow(w, 1 / 3.0), 2) + 0.1);
if ((w / m_lutTableSize) * m_lutTableSize != w)
{
qCDebug(DIGIKAM_DIMG_LOG) << "Format of " << path << " unknown (bad dimensions)!";
return;
}
/* Rearrange the square-table to SxS rectangles, arranged alongside:
*
* Input:
* 11 12 13 14 15 16 17 18
* 21 22 23 24 25 26 27 28
* 31 32 33 34 ...
*
* Output:
* 11 12 13 14|31 32 33 34|...
* 15 16 17 18|...
* 21 22 23 24|
* 25 26 27 28|
*/
m_lutTable = new quint16[(m_lutTableSize * m_lutTableSize) * m_lutTableSize * 4];
int x;
int xOff;
int oz, y1, y2;
int iRow = 0, iCol;
const int zSize = w / m_lutTableSize;
for (oz = 0 ; oz < m_lutTableSize ; ++oz)
{
xOff = oz * m_lutTableSize;
for (y1 = 0 ; y1 < zSize ; ++y1)
{
iCol = 0;
for (y2 = 0 ; y2 < zSize ; ++y2)
{
for (x = 0 ; x < m_lutTableSize ; ++x, ++iCol)
{
QRgb p = img.pixel(iCol, iRow);
Lut3DSetPixel(m_lutTable,
m_lutTableSize * m_lutTableSize,
xOff + x,
y1 * zSize + y2,
p);
}
}
++iRow;
}
}
}
else if (img.width() / img.height() == img.height())
{
int x, y, w;
// LUT (like Android's Gallery2 uses)
m_lutTableSize = img.height();
m_lutTable = new quint16[img.width() * img.height() * 4];
w = img.width();
for (y = 0 ; y < m_lutTableSize ; ++y)
{
for (x = 0 ; x < w ; ++x)
{
QRgb p = img.pixel(x, y);
Lut3DSetPixel(m_lutTable, w, x, y, p);
}
}
}
else
{
qCDebug(DIGIKAM_DIMG_LOG) << "Format of " << path << " unknown (bad dimensions)!";
return;
}
}
}
/* TODO: using liblcms would be fancier... */
/* Tetrahedral interpolation, taken from AOSP Gallery2 app */
static __inline__ int interp(const quint16* src, int p, int* off ,float dr, float dg, float db)
{
float fr00 = (src[p+off[0]])*(1-dr)+(src[p+off[1]])*dr;
float fr01 = (src[p+off[2]])*(1-dr)+(src[p+off[3]])*dr;
float fr10 = (src[p+off[4]])*(1-dr)+(src[p+off[5]])*dr;
float fr11 = (src[p+off[6]])*(1-dr)+(src[p+off[7]])*dr;
float frb0 = fr00 * (1-db)+fr01*db;
float frb1 = fr10 * (1-db)+fr11*db;
float frbg = frb0 * (1-dg)+frb1*dg;
return (int)frbg;
}
#define unlikely(x) __builtin_expect(!!(x), 0)
static __inline__ int clamp(int from, int maxVal)
{
if (unlikely(from < 0))
from = 0;
else if (unlikely(from > 65535))
from = 65535;
if (maxVal == 65535)
return from;
else
return from * maxVal / 65535;
}
template<typename T>
static void ImageFilterFx(const quint16* lutrgb, int lutTableSize,
T* rgb, uint start, uint end, int maxVal, int intensity)
{
int lutdim_r = lutTableSize;
int lutdim_g = lutTableSize;
int lutdim_b = lutTableSize;
int STEP = 4;
const int RED = 2, GREEN = 1, BLUE = 0;
int off[8] =
{
0,
STEP*1,
STEP*lutdim_r,
STEP*(lutdim_r + 1),
STEP*(lutdim_r*lutdim_b),
STEP*(lutdim_r*lutdim_b+1),
STEP*(lutdim_r*lutdim_b+lutdim_r),
STEP*(lutdim_r*lutdim_b+lutdim_r + 1)
};
float scale_R = (lutdim_r-1.f)/(maxVal + 1);
float scale_G = (lutdim_g-1.f)/(maxVal + 1);
float scale_B = (lutdim_b-1.f)/(maxVal + 1);
rgb += 4 * start;
for (uint i = start ; i < end ; ++i)
{
int r = rgb[RED], rn;
int g = rgb[GREEN], gn;
int b = rgb[BLUE], bn;
float fb = b*scale_B;
float fg = g*scale_G;
float fr = r*scale_R;
int lut_b = (int)fb;
int lut_g = (int)fg;
int lut_r = (int)fr;
int p = lut_r+lut_b*lutdim_r+lut_g*lutdim_r*lutdim_b;
p *= STEP;
float dr = fr-lut_r;
float dg = fg-lut_g;
float db = fb-lut_b;
rn = clamp(interp(lutrgb,p ,off,dr,dg,db), maxVal);
gn = clamp(interp(lutrgb,p+1,off,dr,dg,db), maxVal);
bn = clamp(interp(lutrgb,p+2,off,dr,dg,db), maxVal);
rgb[RED] = (T)(((100-intensity) * r + intensity * rn) / 100);
rgb[GREEN] = (T)(((100-intensity) * g + intensity * gn) / 100);
rgb[BLUE] = (T)(((100-intensity) * b + intensity * bn) / 100);
rgb += 4;
}
}
#define min(a, b) ((a) < (b) ? (a) : (b))
void ColorFXFilter::applyLut3D()
{
uint i, stepI, maxI;
int progress;
const int steps = 10;
if (!m_lutTable)
return;
maxI = m_orgImage.width() * m_orgImage.height();
stepI = maxI / steps;
for (progress = 0, i = 0 ;
runningFlag() && (i < maxI) ;
i += stepI, progress += (100 / steps))
{
if (!m_orgImage.sixteenBit())
{
ImageFilterFx(m_lutTable, m_lutTableSize, m_orgImage.bits(),
i, min(i + stepI, maxI),
255, m_settings.intensity);
}
else
{
ImageFilterFx(m_lutTable, m_lutTableSize, reinterpret_cast<unsigned short*>(m_orgImage.bits()),
i, min(i + stepI, maxI),
65535, m_settings.intensity);
}
postProgress(progress);
}
}
FilterAction ColorFXFilter::filterAction()
{
FilterAction action(FilterIdentifier(), CurrentVersion());
action.setDisplayableName(DisplayableName());
action.addParameter(QLatin1String("type"), m_settings.colorFXType);
action.addParameter(QLatin1String("iteration"), m_settings.iterations);
action.addParameter(QLatin1String("level"), m_settings.level);
action.addParameter(QLatin1String("path"), m_settings.path);
action.addParameter(QLatin1String("intensity"), m_settings.intensity);
return action;
}
void ColorFXFilter::readParameters(const FilterAction& action)
{
m_settings.colorFXType = action.parameter(QLatin1String("type")).toInt();
m_settings.iterations = action.parameter(QLatin1String("iteration")).toInt();
m_settings.level = action.parameter(QLatin1String("level")).toInt();
m_settings.path = action.parameter(QLatin1String("path")).toString();
m_settings.intensity = action.parameter(QLatin1String("intensity")).toInt();
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/fx/distortionfxfilter.cpp b/core/libs/dimg/filters/fx/distortionfxfilter.cpp
index 4bb5046765..ff7fc48279 100644
--- a/core/libs/dimg/filters/fx/distortionfxfilter.cpp
+++ b/core/libs/dimg/filters/fx/distortionfxfilter.cpp
@@ -1,1279 +1,1279 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-07-18
* Description : Distortion FX threaded image filter.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
*
* Original Distortion algorithms copyrighted 2004-2005 by
* Pieter Z. Voloshyn <pieter dot voloshyn at gmail dot com>.
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#define ANGLE_RATIO 0.017453292519943295769236907685
#include "distortionfxfilter.h"
// C++ includes
#include <cstdlib>
// Qt includes
#include <QDateTime>
#include <QSize>
#include <QMutex>
#include <QtConcurrent> // krazy:exclude=includes
#include <QtMath>
// Local includes
#include "dimg.h"
#include "pixelsaliasfilter.h"
#include "randomnumbergenerator.h"
namespace Digikam
{
class Q_DECL_HIDDEN DistortionFXFilter::Private
{
public:
explicit Private()
{
antiAlias = true;
level = 0;
iteration = 0;
effectType = 0;
randomSeed = 0;
globalProgress = 0;
}
bool antiAlias;
int level;
int iteration;
int effectType;
quint32 randomSeed;
RandomNumberGenerator generator;
int globalProgress;
QMutex lock;
QMutex lock2; // RandomNumberGenerator is not re-entrant (dixit Boost lib)
};
DistortionFXFilter::DistortionFXFilter(QObject* const parent)
: DImgThreadedFilter(parent),
d(new Private)
{
initFilter();
}
DistortionFXFilter::DistortionFXFilter(DImg* const orgImage, QObject* const parent, int effectType,
int level, int iteration, bool antialiaqSing)
: DImgThreadedFilter(orgImage, parent, QLatin1String("DistortionFX")),
d(new Private)
{
d->effectType = effectType;
d->level = level;
d->iteration = iteration;
d->antiAlias = antialiaqSing;
d->randomSeed = RandomNumberGenerator::timeSeed();
initFilter();
}
DistortionFXFilter::~DistortionFXFilter()
{
cancelFilter();
delete d;
}
void DistortionFXFilter::filterImage()
{
int w = m_orgImage.width();
int h = m_orgImage.height();
int l = d->level;
int f = d->iteration;
switch (d->effectType)
{
case FishEye:
fisheye(&m_orgImage, &m_destImage, (double)(l / 5.0), d->antiAlias);
break;
case Twirl:
twirl(&m_orgImage, &m_destImage, l, d->antiAlias);
break;
case CilindricalHor:
cilindrical(&m_orgImage, &m_destImage, (double)l, true, false, d->antiAlias);
break;
case CilindricalVert:
cilindrical(&m_orgImage, &m_destImage, (double)l, false, true, d->antiAlias);
break;
case CilindricalHV:
cilindrical(&m_orgImage, &m_destImage, (double)l, true, true, d->antiAlias);
break;
case Caricature:
fisheye(&m_orgImage, &m_destImage, (double)(-l / 5.0), d->antiAlias);
break;
case MultipleCorners:
multipleCorners(&m_orgImage, &m_destImage, l, d->antiAlias);
break;
case WavesHorizontal:
waves(&m_orgImage, &m_destImage, l, f, true, true);
break;
case WavesVertical:
waves(&m_orgImage, &m_destImage, l, f, true, false);
break;
case BlockWaves1:
blockWaves(&m_orgImage, &m_destImage, l, f, false);
break;
case BlockWaves2:
blockWaves(&m_orgImage, &m_destImage, l, f, true);
break;
case CircularWaves1:
circularWaves(&m_orgImage, &m_destImage, w / 2, h / 2, (double)l, (double)f, 0.0, false, d->antiAlias);
break;
case CircularWaves2:
circularWaves(&m_orgImage, &m_destImage, w / 2, h / 2, (double)l, (double)f, 25.0, true, d->antiAlias);
break;
case PolarCoordinates:
polarCoordinates(&m_orgImage, &m_destImage, true, d->antiAlias);
break;
case UnpolarCoordinates:
polarCoordinates(&m_orgImage, &m_destImage, false, d->antiAlias);
break;
case Tile:
tile(&m_orgImage, &m_destImage, 210 - f, 210 - f, l);
break;
}
}
void DistortionFXFilter::fisheyeMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
double nh, nw, tw;
DColor color;
int offset;
int nHalfW = Width / 2;
int nHalfH = Height / 2;
double lfXScale = 1.0;
double lfYScale = 1.0;
double lfCoeffStep = prm.Coeff / 1000.0;
double lfRadius, lfAngle;
if (Width > Height)
{
lfYScale = (double)Width / (double)Height;
}
else if (Height > Width)
{
lfXScale = (double)Height / (double)Width;
}
double lfRadMax = (double)qMax(Height, Width) / 2.0;
double lfCoeff = lfRadMax / qLn(qFabs(lfCoeffStep) * lfRadMax + 1.0);
double th = lfYScale * (double)(prm.h - nHalfH);
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
tw = lfXScale * (double)(w - nHalfW);
// we find the distance from the center
lfRadius = qSqrt(th * th + tw * tw);
if (lfRadius < lfRadMax)
{
lfAngle = qAtan2(th, tw);
if (prm.Coeff > 0.0)
{
lfRadius = (qExp(lfRadius / lfCoeff) - 1.0) / lfCoeffStep;
}
else
{
lfRadius = lfCoeff * qLn(1.0 + (-1.0 * lfCoeffStep) * lfRadius);
}
nw = (double)nHalfW + (lfRadius / lfXScale) * qCos(lfAngle);
nh = (double)nHalfH + (lfRadius / lfYScale) * qSin(lfAngle);
setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
}
else
{
// copy pixel
offset = getOffset(Width, w, prm.h, bytesDepth);
color.setColor(data + offset, sixteenBit);
color.setPixel(pResBits + offset);
}
}
}
/* Function to apply the fisheye effect backported from ImageProcesqSing version 2
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* Coeff => Distortion effect coeff. Positive value render 'Fish Eyes' effect,
* and negative values render 'Caricature' effect.
* Antialias => Smart blurring result.
*
* Theory => This is a great effect if you take employee photos
* Its pure trigonometry. I think if you study hard the code you
* understand very well.
*/
void DistortionFXFilter::fisheye(DImg* orgImage, DImg* destImage, double Coeff, bool AntiAlias)
{
if (Coeff == 0.0)
{
return;
}
int progress;
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Coeff = Coeff;
prm.AntiAlias = AntiAlias;
// main loop
for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.h = h;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::fisheyeMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)(h) * 100.0) / orgImage->height());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::twirlMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
DColor color;
int offset;
int nHalfW = Width / 2;
int nHalfH = Height / 2;
double lfXScale = 1.0;
double lfYScale = 1.0;
double lfAngle, lfNewAngle, lfAngleSum, lfCurrentRadius;
double tw, nh, nw;
if (Width > Height)
{
lfYScale = (double)Width / (double)Height;
}
else if (Height > Width)
{
lfXScale = (double)Height / (double)Width;
}
// the angle step is dist divided by 10000
double lfAngleStep = prm.dist / 10000.0;
// now, we get the minimum radius
double lfRadMax = (double)qMax(Width, Height) / 2.0;
double th = lfYScale * (double)(prm.h - nHalfH);
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
tw = lfXScale * (double)(w - nHalfW);
// now, we get the distance
lfCurrentRadius = qSqrt(th * th + tw * tw);
// if distance is less than maximum radius...
if (lfCurrentRadius < lfRadMax)
{
// we find the angle from the center
lfAngle = qAtan2(th, tw);
- // we get the accumuled angle
+ // we get the accumulated angle
lfAngleSum = lfAngleStep * (-1.0 * (lfCurrentRadius - lfRadMax));
- // ok, we sum angle with accumuled to find a new angle
+ // ok, we sum angle with accumulated to find a new angle
lfNewAngle = lfAngle + lfAngleSum;
// now we find the exact position's x and y
nw = (double)nHalfW + qCos(lfNewAngle) * (lfCurrentRadius / lfXScale);
nh = (double)nHalfH + qSin(lfNewAngle) * (lfCurrentRadius / lfYScale);
setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
}
else
{
// copy pixel
offset = getOffset(Width, w, prm.h, bytesDepth);
color.setColor(data + offset, sixteenBit);
color.setPixel(pResBits + offset);
}
}
}
/* Function to apply the twirl effect backported from ImageProcesqSing version 2
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* dist => Distance value.
* Antialias => Smart blurring result.
*
* Theory => Take spiral studies, you will understand better, I'm studying
* hard on this effect, because it is not too fast.
*/
void DistortionFXFilter::twirl(DImg* orgImage, DImg* destImage, int dist, bool AntiAlias)
{
// if dist value is zero, we do nothing
if (dist == 0)
{
return;
}
int progress;
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.dist = dist;
prm.AntiAlias = AntiAlias;
// main loop
for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.h = h;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::twirlMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)h * 100.0) / orgImage->height());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::cilindricalMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
double nh, nw;
int nHalfW = Width / 2;
int nHalfH = Height / 2;
double lfCoeffX = 1.0;
double lfCoeffY = 1.0;
double lfCoeffStep = prm.Coeff / 1000.0;
if (prm.Horizontal)
{
lfCoeffX = (double)nHalfW / qLn(qFabs(lfCoeffStep) * nHalfW + 1.0);
}
if (prm.Vertical)
{
lfCoeffY = (double)nHalfH / qLn(qFabs(lfCoeffStep) * nHalfH + 1.0);
}
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
// we find the distance from the center
nh = qFabs((double)(prm.h - nHalfH));
nw = qFabs((double)(w - nHalfW));
if (prm.Horizontal)
{
if (prm.Coeff > 0.0)
{
nw = (qExp(nw / lfCoeffX) - 1.0) / lfCoeffStep;
}
else
{
nw = lfCoeffX * qLn(1.0 + (-1.0 * lfCoeffStep) * nw);
}
}
if (prm.Vertical)
{
if (prm.Coeff > 0.0)
{
nh = (qExp(nh / lfCoeffY) - 1.0) / lfCoeffStep;
}
else
{
nh = lfCoeffY * qLn(1.0 + (-1.0 * lfCoeffStep) * nh);
}
}
nw = (double)nHalfW + ((w >= nHalfW) ? nw : -nw);
nh = (double)nHalfH + ((prm.h >= nHalfH) ? nh : -nh);
setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
}
}
-/* Function to apply the Cilindrical effect backported from ImageProcessing version 2
+/* Function to apply the Cylindrical effect backported from ImageProcessing version 2
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
- * Coeff => Cilindrical value.
+ * Coeff => Cylindrical value.
* Horizontal => Apply horizontally.
* Vertical => Apply vertically.
* Antialias => Smart blurring result.
*
* Theory => This is a great effect, similar to Spherize (Photoshop).
- * If you understand FishEye, you will understand Cilindrical
+ * If you understand FishEye, you will understand Cylindrical
* FishEye apply a logarithm function using a sphere radius,
* Spherize use the same function but in a rectangular
* environment.
*/
void DistortionFXFilter::cilindrical(DImg* orgImage, DImg* destImage, double Coeff,
bool Horizontal, bool Vertical, bool AntiAlias)
{
if ((Coeff == 0.0) || (!(Horizontal || Vertical)))
{
return;
}
int progress;
// initial copy
memcpy(destImage->bits(), orgImage->bits(), orgImage->numBytes());
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Coeff = Coeff;
prm.Horizontal = Horizontal;
prm.Vertical = Vertical;
prm.AntiAlias = AntiAlias;
// main loop
for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.h = h;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::cilindricalMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)h * 100.0) / orgImage->height());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::multipleCornersMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
double nh, nw;
int nHalfW = Width / 2;
int nHalfH = Height / 2;
double lfRadMax = qSqrt(Height * Height + Width * Width) / 2.0;
double lfAngle, lfNewRadius, lfCurrentRadius;
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
// we find the distance from the center
nh = nHalfH - prm.h;
nw = nHalfW - w;
// now, we get the distance
lfCurrentRadius = qSqrt(nh * nh + nw * nw);
// we find the angle from the center
lfAngle = qAtan2(nh, nw) * (double)prm.Factor;
- // ok, we sum angle with accumuled to find a new angle
+ // ok, we sum angle with accumulated to find a new angle
lfNewRadius = lfCurrentRadius * lfCurrentRadius / lfRadMax;
// now we find the exact position's x and y
nw = (double)nHalfW - (qCos(lfAngle) * lfNewRadius);
nh = (double)nHalfH - (qSin(lfAngle) * lfNewRadius);
setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
}
}
/* Function to apply the Multiple Corners effect backported from ImageProcessing version 2
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* Factor => nb corners.
* Antialias => Smart blurring result.
*
* Theory => This is an amazing function, you've never seen this before.
* I was testing some trigonometric functions, and I saw that if
* I multiply the angle by 2, the result is an image like this
* If we multiply by 3, we can create the SixCorners effect.
*/
void DistortionFXFilter::multipleCorners(DImg* orgImage, DImg* destImage, int Factor, bool AntiAlias)
{
if (Factor == 0)
{
return;
}
int progress;
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Factor = Factor;
prm.AntiAlias = AntiAlias;
// main loop
for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.h = h;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::multipleCornersMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)h * 100.0) / orgImage->height());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::wavesHorizontalMultithreaded(const Args& prm)
{
int oldProgress=0, progress=0, tx;
for (int h = prm.start; runningFlag() && (h < prm.stop); ++h)
{
tx = lround(prm.Amplitude * qSin((prm.Frequency * 2) * h * (M_PI / 180)));
prm.destImage->bitBltImage(prm.orgImage, 0, h, prm.orgImage->width(), 1, tx, h);
if (prm.FillSides)
{
prm.destImage->bitBltImage(prm.orgImage, prm.orgImage->width() - tx, h, tx, 1, 0, h);
prm.destImage->bitBltImage(prm.orgImage, 0, h, prm.orgImage->width() - (prm.orgImage->width() - 2 * prm.Amplitude + tx), 1, prm.orgImage->width() + tx, h);
}
// Update the progress bar in dialog.
progress = (int)( ( (double)h * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start));
if ((progress % 5 == 0) && (progress > oldProgress))
{
d->lock.lock();
oldProgress = progress;
d->globalProgress += 5;
postProgress(d->globalProgress);
d->lock.unlock();
}
}
}
void DistortionFXFilter::wavesVerticalMultithreaded(const Args& prm)
{
int oldProgress=0, progress=0, ty;
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
ty = lround(prm.Amplitude * qSin((prm.Frequency * 2) * w * (M_PI / 180)));
prm.destImage->bitBltImage(prm.orgImage, w, 0, 1, prm.orgImage->height(), w, ty);
if (prm.FillSides)
{
prm.destImage->bitBltImage(prm.orgImage, w, prm.orgImage->height() - ty, 1, ty, w, 0);
prm.destImage->bitBltImage(prm.orgImage, w, 0, 1, prm.orgImage->height() - (prm.orgImage->height() - 2 * prm.Amplitude + ty), w, prm.orgImage->height() + ty);
}
// Update the progress bar in dialog.
progress = (int)( ( (double)w * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start));
if ((progress % 5 == 0) && (progress > oldProgress))
{
d->lock.lock();
oldProgress = progress;
d->globalProgress += 5;
postProgress(d->globalProgress);
d->lock.unlock();
}
}
}
/* Function to apply the waves effect
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
- * Amplitude => Sinoidal maximum height.
+ * Amplitude => Sinusoidal maximum height.
* Frequency => Frequency value.
* FillSides => Like a boolean variable.
* Direction => Vertical or horizontal flag.
*
* Theory => This is an amazing effect, very funny, and very simple to
* understand. You just need understand how qSin and qCos works.
*/
void DistortionFXFilter::waves(DImg* orgImage, DImg* destImage,
int Amplitude, int Frequency,
bool FillSides, bool Direction)
{
if (Amplitude < 0)
{
Amplitude = 0;
}
if (Frequency < 0)
{
Frequency = 0;
}
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Amplitude = Amplitude;
prm.Frequency = Frequency;
prm.FillSides = FillSides;
if (Direction) // Horizontal
{
QList<int> vals = multithreadedSteps(orgImage->height());
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::wavesHorizontalMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
else
{
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::wavesVerticalMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
}
void DistortionFXFilter::blockWavesMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
int nw, nh;
DColor color;
int offset, offsetOther;
int nHalfW = Width / 2;
int nHalfH = Height / 2;
for (int h = prm.start; runningFlag() && (h < prm.stop); ++h)
{
nw = nHalfW - prm.w;
nh = nHalfH - h;
if (prm.Mode)
{
nw = (int)(prm.w + prm.Amplitude * qSin(prm.Frequency * nw * (M_PI / 180)));
nh = (int)(h + prm.Amplitude * qCos(prm.Frequency * nh * (M_PI / 180)));
}
else
{
nw = (int)(prm.w + prm.Amplitude * qSin(prm.Frequency * prm.w * (M_PI / 180)));
nh = (int)(h + prm.Amplitude * qCos(prm.Frequency * h * (M_PI / 180)));
}
offset = getOffset(Width, prm.w, h, bytesDepth);
offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth);
// read color
color.setColor(data + offsetOther, sixteenBit);
// write color to destination
color.setPixel(pResBits + offset);
}
}
/* Function to apply the block waves effect
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
- * Amplitude => Sinoidal maximum height
+ * Amplitude => Sinusoidal maximum height
* Frequency => Frequency value
* Mode => The mode to be applied.
*
* Theory => This is an amazing effect, very funny when amplitude and
* frequency are small values.
*/
void DistortionFXFilter::blockWaves(DImg* orgImage, DImg* destImage,
int Amplitude, int Frequency, bool Mode)
{
if (Amplitude < 0)
{
Amplitude = 0;
}
if (Frequency < 0)
{
Frequency = 0;
}
int progress;
QList<int> vals = multithreadedSteps(orgImage->height());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Mode = Mode;
prm.Frequency = Frequency;
prm.Amplitude = Amplitude;
for (int w = 0; runningFlag() && (w < (int)orgImage->width()); ++w)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.w = w;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::blockWavesMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)w * 100.0) / orgImage->width());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::circularWavesMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
double nh, nw;
double lfRadius, lfRadMax;
double lfNewAmp = prm.Amplitude;
double lfFreqAngle = prm.Frequency * ANGLE_RATIO;
double phase = prm.Phase * ANGLE_RATIO;
lfRadMax = qSqrt(Height * Height + Width * Width);
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
nw = prm.X - w;
nh = prm.Y - prm.h;
lfRadius = qSqrt(nw * nw + nh * nh);
if (prm.WavesType)
{
lfNewAmp = prm.Amplitude * lfRadius / lfRadMax;
}
nw = (double)w + lfNewAmp * qSin(lfFreqAngle * lfRadius + phase);
nh = (double)prm.h + lfNewAmp * qCos(lfFreqAngle * lfRadius + phase);
setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
}
}
/* Function to apply the circular waves effect backported from ImageProcesqSing version 2
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* X, Y => Position of circle center on the image.
- * Amplitude => Sinoidal maximum height
+ * Amplitude => Sinusoidal maximum height
* Frequency => Frequency value.
* Phase => Phase value.
* WavesType => If true the amplitude is proportional to radius.
- * Antialias => Smart bluring result.
+ * Antialias => Smart blurring result.
*
- * Theory => Similar to Waves effect, but here I apply a senoidal function
+ * Theory => Similar to Waves effect, but here I apply a sinusoidal function
* with the angle point.
*/
void DistortionFXFilter::circularWaves(DImg* orgImage, DImg* destImage, int X, int Y, double Amplitude,
double Frequency, double Phase, bool WavesType, bool AntiAlias)
{
if (Amplitude < 0.0)
{
Amplitude = 0.0;
}
if (Frequency < 0.0)
{
Frequency = 0.0;
}
int progress;
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Phase = Phase;
prm.Frequency = Frequency;
prm.Amplitude = Amplitude;
prm.WavesType = WavesType;
prm.X = X;
prm.Y = Y;
prm.AntiAlias = AntiAlias;
for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.h = h;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::circularWavesMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)h * 100.0) / orgImage->height());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::polarCoordinatesMultithreaded(const Args& prm)
{
int Width = prm.orgImage->width();
int Height = prm.orgImage->height();
uchar* data = prm.orgImage->bits();
bool sixteenBit = prm.orgImage->sixteenBit();
int bytesDepth = prm.orgImage->bytesDepth();
uchar* pResBits = prm.destImage->bits();
int nHalfW = Width / 2;
int nHalfH = Height / 2;
double lfXScale = 1.0;
double lfYScale = 1.0;
double lfAngle, lfRadius, lfRadMax;
double nh, nw, tw;
if (Width > Height)
{
lfYScale = (double)Width / (double)Height;
}
else if (Height > Width)
{
lfXScale = (double)Height / (double)Width;
}
lfRadMax = (double)qMax(Height, Width) / 2.0;
double th = lfYScale * (double)(prm.h - nHalfH);
for (int w = prm.start; runningFlag() && (w < prm.stop); ++w)
{
tw = lfXScale * (double)(w - nHalfW);
if (prm.Type)
{
// now, we get the distance
lfRadius = qSqrt(th * th + tw * tw);
// we find the angle from the center
lfAngle = qAtan2(tw, th);
// now we find the exact position's x and y
nh = lfRadius * (double) Height / lfRadMax;
nw = lfAngle * (double) Width / (2 * M_PI);
nw = (double)nHalfW + nw;
}
else
{
lfRadius = (double)(prm.h) * lfRadMax / (double)Height;
lfAngle = (double)(w) * (2 * M_PI) / (double) Width;
nw = (double)nHalfW - (lfRadius / lfXScale) * qSin(lfAngle);
nh = (double)nHalfH - (lfRadius / lfYScale) * qCos(lfAngle);
}
setPixelFromOther(Width, Height, sixteenBit, bytesDepth, data, pResBits, w, prm.h, nw, nh, prm.AntiAlias);
}
}
/* Function to apply the Polar Coordinates effect backported from ImageProcesqSing version 2
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* Type => if true Polar Coordinate to Polar else inverse.
* Antialias => Smart blurring result.
*
* Theory => Similar to PolarCoordinates from Photoshop. We apply the polar
* transformation in a proportional (Height and Width) radius.
*/
void DistortionFXFilter::polarCoordinates(DImg* orgImage, DImg* destImage, bool Type, bool AntiAlias)
{
int progress;
QList<int> vals = multithreadedSteps(orgImage->width());
QList <QFuture<void> > tasks;
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.Type = Type;
prm.AntiAlias = AntiAlias;
// main loop
for (int h = 0; runningFlag() && (h < (int)orgImage->height()); ++h)
{
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
prm.h = h;
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::polarCoordinatesMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
// Update the progress bar in dialog.
progress = (int)(((double)h * 100.0) / orgImage->height());
if (progress % 5 == 0)
{
postProgress(progress);
}
}
}
void DistortionFXFilter::tileMultithreaded(const Args& prm)
{
int tx, ty, progress=0, oldProgress=0;
for (int h = prm.start; runningFlag() && (h < prm.stop); h += prm.HSize)
{
for (int w = 0; runningFlag() && (w < (int)prm.orgImage->width()); w += prm.WSize)
{
d->lock2.lock();
tx = d->generator.number(-prm.Random / 2, prm.Random / 2);
ty = d->generator.number(-prm.Random / 2, prm.Random / 2);
d->lock2.unlock();
prm.destImage->bitBltImage(prm.orgImage, w, h, prm.WSize, prm.HSize, w + tx, h + ty);
}
// Update the progress bar in dialog.
progress = (int)( ( (double)h * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (prm.stop - prm.start));
if ((progress % 5 == 0) && (progress > oldProgress))
{
d->lock.lock();
oldProgress = progress;
d->globalProgress += 5;
postProgress(d->globalProgress);
d->lock.unlock();
}
}
}
/* Function to apply the tile effect
*
* data => The image data in RGBA mode.
* Width => Width of image.
* Height => Height of image.
* WSize => Tile Width
* HSize => Tile Height
* Random => Maximum random value
*
* Theory => Similar to Tile effect from Photoshop and very easy to
* understand. We get a rectangular area uqSing WSize and HSize and
* replace in a position with a random distance from the original
* position.
*/
void DistortionFXFilter::tile(DImg* orgImage, DImg* destImage,
int WSize, int HSize, int Random)
{
if (WSize < 1)
{
WSize = 1;
}
if (HSize < 1)
{
HSize = 1;
}
if (Random < 1)
{
Random = 1;
}
Args prm;
prm.orgImage = orgImage;
prm.destImage = destImage;
prm.WSize = WSize;
prm.HSize = HSize;
prm.Random = Random;
d->generator.seed(d->randomSeed);
QList<int> vals = multithreadedSteps(orgImage->height());
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
prm.start = vals[j];
prm.stop = vals[j+1];
tasks.append(QtConcurrent::run(this,
&DistortionFXFilter::tileMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
/*
This code is shared by six methods.
Write value of pixel w|h in data to pixel nw|nh in pResBits.
Antialias if requested.
*/
void DistortionFXFilter::setPixelFromOther(int Width, int Height, bool sixteenBit, int bytesDepth,
uchar* data, uchar* pResBits,
int w, int h, double nw, double nh, bool AntiAlias)
{
DColor color;
int offset = getOffset(Width, w, h, bytesDepth);
if (AntiAlias)
{
uchar* const ptr = pResBits + offset;
if (sixteenBit)
{
unsigned short* ptr16 = reinterpret_cast<unsigned short*>(ptr);
PixelsAliasFilter().pixelAntiAliasing16(reinterpret_cast<unsigned short*>(data), Width, Height, nw, nh,
ptr16 + 3, ptr16 + 2, ptr16 + 1, ptr16);
}
else
{
PixelsAliasFilter().pixelAntiAliasing(data, Width, Height, nw, nh,
ptr + 3, ptr + 2, ptr + 1, ptr);
}
}
else
{
// we get the position adjusted
int offsetOther = getOffsetAdjusted(Width, Height, (int)nw, (int)nh, bytesDepth);
// read color
color.setColor(data + offsetOther, sixteenBit);
// write color to destination
color.setPixel(pResBits + offset);
}
}
FilterAction DistortionFXFilter::filterAction()
{
FilterAction action(FilterIdentifier(), CurrentVersion());
action.setDisplayableName(DisplayableName());
action.addParameter(QLatin1String("antiAlias"), d->antiAlias);
action.addParameter(QLatin1String("type"), d->effectType);
action.addParameter(QLatin1String("iteration"), d->iteration);
action.addParameter(QLatin1String("level"), d->level);
if (d->effectType == Tile)
{
action.addParameter(QLatin1String("randomSeed"), d->randomSeed);
}
return action;
}
void DistortionFXFilter::readParameters(const FilterAction& action)
{
d->antiAlias = action.parameter(QLatin1String("antiAlias")).toBool();
d->effectType = action.parameter(QLatin1String("type")).toInt();
d->iteration = action.parameter(QLatin1String("iteration")).toInt();
d->level = action.parameter(QLatin1String("level")).toInt();
if (d->effectType == Tile)
{
d->randomSeed = action.parameter(QLatin1String("randomSeed")).toUInt();
}
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/fx/filmgrainfilter.cpp b/core/libs/dimg/filters/fx/filmgrainfilter.cpp
index be2027b187..9bf608a1a2 100644
--- a/core/libs/dimg/filters/fx/filmgrainfilter.cpp
+++ b/core/libs/dimg/filters/fx/filmgrainfilter.cpp
@@ -1,443 +1,443 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-05-25
* Description : filter to add Film Grain to image.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2005-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010 by Julien Narboux <julien at narboux dot fr>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "filmgrainfilter.h"
// C++ includes
#include <cstdlib>
#include <cmath>
// Qt includes
#include <QtConcurrent> // krazy:exclude=includes
#include <QMutex>
// Local includes
#include "dimg.h"
#include "digikam_globals.h"
#include "randomnumbergenerator.h"
namespace Digikam
{
class Q_DECL_HIDDEN FilmGrainFilter::Private
{
public:
explicit Private()
: div(0.0),
leadLumaNoise(1.0),
leadChromaBlueNoise(1.0),
leadChromaRedNoise(1.0),
globalProgress(0)
{
}
enum YUVChannel
{
Luma = 0,
ChromaBlue,
ChromaRed
};
double div;
double leadLumaNoise;
double leadChromaBlueNoise;
double leadChromaRedNoise;
FilmGrainContainer settings;
RandomNumberGenerator generator;
int globalProgress;
QMutex lock;
QMutex lock2; // RandomNumberGenerator is not re-entrant (dixit Boost lib)
};
FilmGrainFilter::FilmGrainFilter(QObject* const parent)
: DImgThreadedFilter(parent),
d(new Private)
{
initFilter();
}
FilmGrainFilter::FilmGrainFilter(DImg* const orgImage, QObject* const parent, const FilmGrainContainer& settings)
: DImgThreadedFilter(orgImage, parent, QLatin1String("FilmGrain")),
d(new Private)
{
d->settings = settings;
initFilter();
}
FilmGrainFilter::FilmGrainFilter(DImgThreadedFilter* const parentFilter,
const DImg& orgImage, const DImg& destImage,
int progressBegin, int progressEnd,
const FilmGrainContainer& settings)
: DImgThreadedFilter(parentFilter, orgImage, destImage, progressBegin, progressEnd,
parentFilter->filterName() + QLatin1String(": FilmGrain")),
d(new Private)
{
d->settings = settings;
filterImage();
}
FilmGrainFilter::~FilmGrainFilter()
{
cancelFilter();
delete d;
}
void FilmGrainFilter::filmgrainMultithreaded(uint start, uint stop)
{
// To emulate grain size we use a matrix [grainSize x grainSize].
// We will parse whole image using grainSize step. Color from a reference point located
// on the top left corner of matrix will be used to apply noise on whole matrix.
// In first, for each matrix processed over the image, we compute the lead noise value
// using Uniform noise generator.
- // In second time, all others points from the matrix are process to add suplemental noise
+ // In second time, all others points from the matrix are process to add supplemental noise
// generated with Gaussian or Poisson noise generator.
DColor refCol, matCol;
uint progress=0, oldProgress=0, posX, posY;
- // Reference point noise adjustements.
+ // Reference point noise adjustments.
double refLumaNoise = 0.0, refLumaRange = 0.0;
double refChromaBlueNoise = 0.0, refChromaBlueRange = 0.0;
double refChromaRedNoise = 0.0, refChromaRedRange = 0.0;
- // Current matrix point noise adjustements.
+ // Current matrix point noise adjustments.
double matLumaNoise = 0.0, matLumaRange = 0.0;
double matChromaBlueNoise = 0.0, matChromaBlueRange = 0.0;
double matChromaRedNoise = 0.0, matChromaRedRange = 0.0;
uint width = m_orgImage.width();
uint height = m_orgImage.height();
for (uint x = start ; runningFlag() && (x < stop) ; x += d->settings.grainSize)
{
for (uint y = 0; runningFlag() && y < height; y += d->settings.grainSize)
{
refCol = m_orgImage.getPixelColor(x, y);
computeNoiseSettings(refCol,
refLumaRange, refLumaNoise,
refChromaBlueRange, refChromaBlueNoise,
refChromaRedRange, refChromaRedNoise);
// Grain size matrix processing.
for (int zx = 0; runningFlag() && zx < d->settings.grainSize; ++zx)
{
for (int zy = 0; runningFlag() && zy < d->settings.grainSize; ++zy)
{
posX = x + zx;
posY = y + zy;
if (posX < width && posY < height)
{
matCol = m_orgImage.getPixelColor(posX, posY);
computeNoiseSettings(matCol,
matLumaRange, matLumaNoise,
matChromaBlueRange, matChromaBlueNoise,
matChromaRedRange, matChromaRedNoise);
if (d->settings.addLuminanceNoise)
{
if (((refLumaRange - matLumaRange) / refLumaRange) > 0.1)
{
adjustYCbCr(matCol, matLumaRange, matLumaNoise, Private::Luma);
}
else
{
adjustYCbCr(matCol, refLumaRange, refLumaNoise, Private::Luma);
}
}
if (d->settings.addChrominanceBlueNoise)
{
if (((refChromaBlueRange - matChromaBlueRange) / refChromaBlueRange) > 0.1)
{
adjustYCbCr(matCol, matChromaBlueRange, matChromaBlueNoise, Private::ChromaBlue);
}
else
{
adjustYCbCr(matCol, refChromaBlueRange, refChromaBlueNoise, Private::ChromaBlue);
}
}
if (d->settings.addChrominanceRedNoise)
{
if (((refChromaRedRange - matChromaRedRange) / refChromaRedRange) > 0.1)
{
adjustYCbCr(matCol, matChromaRedRange, matChromaRedNoise, Private::ChromaBlue);
}
else
{
adjustYCbCr(matCol, refChromaRedRange, refChromaRedNoise, Private::ChromaRed);
}
}
m_destImage.setPixelColor(posX, posY, matCol);
}
}
}
}
progress = (int)( ( (double)x * (100.0 / QThreadPool::globalInstance()->maxThreadCount()) ) / (stop-start));
if ((progress % 5 == 0) && (progress > oldProgress))
{
d->lock.lock();
oldProgress = progress;
d->globalProgress += 5;
postProgress(d->globalProgress);
d->lock.unlock();
}
}
}
/** This method have been implemented following this report in bugzilla :
https://bugs.kde.org/show_bug.cgi?id=148540
We use YCbCr color space to perform noise addition. Please follow this url for
details about this color space :
http://en.allexperts.com/e/y/yc/ycbcr.htm
*/
void FilmGrainFilter::filterImage()
{
if (d->settings.lumaIntensity <= 0 ||
d->settings.chromaBlueIntensity <= 0 ||
d->settings.chromaRedIntensity <= 0 ||
!d->settings.isDirty())
{
m_destImage = m_orgImage;
return;
}
d->div = m_orgImage.sixteenBit() ? 65535.0 : 255.0;
d->leadLumaNoise = d->settings.lumaIntensity * (m_orgImage.sixteenBit() ? 256.0 : 1.0);
d->leadChromaBlueNoise = d->settings.chromaBlueIntensity * (m_orgImage.sixteenBit() ? 256.0 : 1.0);
d->leadChromaRedNoise = d->settings.chromaRedIntensity * (m_orgImage.sixteenBit() ? 256.0 : 1.0);
d->generator.seed(1); // noise will always be the same
QList<int> vals = multithreadedSteps(m_orgImage.width());
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&FilmGrainFilter::filmgrainMultithreaded,
vals[j],
vals[j+1]
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
-/** This method compute lead noise of reference matrix point used to similate graininess size
+/** This method compute lead noise of reference matrix point used to simulate graininess size
*/
void FilmGrainFilter::computeNoiseSettings(const DColor& col,
double& luRange, double& luNoise,
double& cbRange, double& cbNoise,
double& crRange, double& crNoise)
{
if (d->settings.addLuminanceNoise)
{
luRange = interpolate(d->settings.lumaShadows, d->settings.lumaMidtones,
d->settings.lumaHighlights, col) * d->leadLumaNoise + 1.0;
luNoise = randomizeUniform(luRange);
}
if (d->settings.addChrominanceBlueNoise)
{
cbRange = interpolate(d->settings.chromaBlueShadows, d->settings.chromaBlueMidtones,
d->settings.chromaBlueHighlights, col) * d->leadChromaBlueNoise + 1.0;
cbNoise = randomizeUniform(cbRange);
}
if (d->settings.addChrominanceRedNoise)
{
crRange = interpolate(d->settings.chromaRedShadows, d->settings.chromaRedMidtones,
d->settings.chromaRedHighlights, col) * d->leadChromaRedNoise + 1.0;
crNoise = randomizeUniform(crRange);
}
}
-/** This method apply grain adjustement on a pixel color channel from YCrCb color space.
+/** This method apply grain adjustment on a pixel color channel from YCrCb color space.
NRand is the lead uniform noise set from matrix used to scan whole image step by step.
- Additionally noise is applied on pixel using Poisson or Gausian distribution.
+ Additionally noise is applied on pixel using Poisson or Gaussian distribution.
*/
void FilmGrainFilter::adjustYCbCr(DColor& col, double range, double nRand, int channel)
{
double y, cb, cr, n2;
col.getYCbCr(&y, &cb, &cr);
if (d->settings.photoDistribution)
{
n2 = randomizePoisson((d->settings.grainSize / 2.0) * (range / 1.414));
}
else
{
n2 = randomizeGauss((d->settings.grainSize / 2.0) * (range / 1.414));
}
switch (channel)
{
case Private::Luma:
y = CLAMP(y + (nRand + n2) / d->div, 0.0, 1.0);
break;
case Private::ChromaBlue:
cb = CLAMP(cb + (nRand + n2) / d->div, 0.0, 1.0);
break;
default: // ChromaRed
cr = CLAMP(cr + (nRand + n2) / d->div, 0.0, 1.0);
break;
}
col.setYCbCr(y, cb, cr, col.sixteenBit());
}
/** This method compute uniform noise value used to randomize matrix reference point.
This value is lead noise apply to image.
*/
double FilmGrainFilter::randomizeUniform(double range)
{
d->lock2.lock();
double val = d->generator.number(- range / 2, range / 2);
d->lock2.unlock();
return val;
}
-/** This method compute Guaussian noise value used to randomize all matrix points.
+/** This method compute Gaussian noise value used to randomize all matrix points.
This value is added to lead noise value.
*/
double FilmGrainFilter::randomizeGauss(double sigma)
{
d->lock2.lock();
double u = - d->generator.number(-1.0, 0.0); // exclude 0
double v = d->generator.number(0.0, 1.0);
d->lock2.unlock();
return (sigma * sqrt(-2 * log(u)) * cos(2 * M_PI * v));
}
/** This method compute Poisson noise value used to randomize all matrix points.
This value is added to lead noise value.
Poisson noise is more realist to simulate photon noise apply on analog film.
NOTE: see approximation of Poisson noise using Gauss algorithm from noise.c code take from :
http://registry.gimp.org/node/13016
This method is very fast compared to real Poisson noise generator.
*/
double FilmGrainFilter::randomizePoisson(double lambda)
{
return (randomizeGauss(sqrt(lambda * d->settings.grainSize * d->settings.grainSize)));
}
-/** This method interpolate gain adjustements to apply grain on shadows, midtones and highlights colors.
+/** This method interpolate gain adjustments to apply grain on shadows, midtones and highlights colors.
The output value is a coefficient computed between 0.0 and 1.0.
*/
double FilmGrainFilter::interpolate(int shadows, int midtones, int highlights, const DColor& col)
{
double s = (shadows + 100.0) / 200.0;
double m = (midtones + 100.0) / 200.0;
double h = (highlights + 100.0) / 200.0;
double y, cb, cr;
col.getYCbCr(&y, &cb, &cr);
if (y >= 0.0 && y <= 0.5)
{
return (s + 2 * (m - s) * y);
}
else if (y >= 0.5 && y <= 1.0)
{
return (2 * (h - m) * y + 2 * m - h);
}
else
{
return 1.0;
}
}
FilterAction FilmGrainFilter::filterAction()
{
FilterAction action(FilterIdentifier(), CurrentVersion());
action.setDisplayableName(DisplayableName());
action.addParameter(QLatin1String("grainSize"), d->settings.grainSize);
action.addParameter(QLatin1String("photoDistribution"), d->settings.photoDistribution);
action.addParameter(QLatin1String("addLuminanceNoise"), d->settings.addLuminanceNoise);
action.addParameter(QLatin1String("lumaIntensity"), d->settings.lumaIntensity);
action.addParameter(QLatin1String("lumaShadows"), d->settings.lumaShadows);
action.addParameter(QLatin1String("lumaMidtones"), d->settings.lumaMidtones);
action.addParameter(QLatin1String("lumaHighlights"), d->settings.lumaHighlights);
action.addParameter(QLatin1String("addChrominanceBlueNoise"), d->settings.addChrominanceBlueNoise);
action.addParameter(QLatin1String("chromaBlueIntensity"), d->settings.chromaBlueIntensity);
action.addParameter(QLatin1String("chromaBlueShadows"), d->settings.chromaBlueShadows);
action.addParameter(QLatin1String("chromaBlueMidtones"), d->settings.chromaBlueMidtones);
action.addParameter(QLatin1String("chromaBlueHighlights"), d->settings.chromaBlueHighlights);
action.addParameter(QLatin1String("addChrominanceRedNoise"), d->settings.addChrominanceRedNoise);
action.addParameter(QLatin1String("chromaRedIntensity"), d->settings.chromaRedIntensity);
action.addParameter(QLatin1String("chromaRedShadows"), d->settings.chromaRedShadows);
action.addParameter(QLatin1String("chromaRedMidtones"), d->settings.chromaRedMidtones);
action.addParameter(QLatin1String("chromaRedHighlights"), d->settings.chromaRedHighlights);
return action;
}
void FilmGrainFilter::readParameters(const Digikam::FilterAction& action)
{
d->settings.grainSize = action.parameter(QLatin1String("grainSize")).toInt();
d->settings.photoDistribution = action.parameter(QLatin1String("photoDistribution")).toBool();
d->settings.addLuminanceNoise = action.parameter(QLatin1String("addLuminanceNoise")).toBool();
d->settings.lumaIntensity = action.parameter(QLatin1String("lumaIntensity")).toInt();
d->settings.lumaShadows = action.parameter(QLatin1String("lumaShadows")).toInt();
d->settings.lumaMidtones = action.parameter(QLatin1String("lumaMidtones")).toInt();
d->settings.lumaHighlights = action.parameter(QLatin1String("lumaHighlights")).toInt();
d->settings.addChrominanceBlueNoise = action.parameter(QLatin1String("addChrominanceBlueNoise")).toBool();
d->settings.chromaBlueIntensity = action.parameter(QLatin1String("chromaBlueIntensity")).toInt();
d->settings.chromaBlueShadows = action.parameter(QLatin1String("chromaBlueShadows")).toInt();
d->settings.chromaBlueMidtones = action.parameter(QLatin1String("chromaBlueMidtones")).toInt();
d->settings.chromaBlueHighlights = action.parameter(QLatin1String("chromaBlueHighlights")).toInt();
d->settings.addChrominanceRedNoise = action.parameter(QLatin1String("addChrominanceRedNoise")).toBool();
d->settings.chromaRedIntensity = action.parameter(QLatin1String("chromaRedIntensity")).toInt();
d->settings.chromaRedShadows = action.parameter(QLatin1String("chromaRedShadows")).toInt();
d->settings.chromaRedMidtones = action.parameter(QLatin1String("chromaRedMidtones")).toInt();
d->settings.chromaRedHighlights = action.parameter(QLatin1String("chromaRedHighlights")).toInt();
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/greycstoration/cimg/CImg.h b/core/libs/dimg/filters/greycstoration/cimg/CImg.h
index 76477d6806..1640115f7c 100644
--- a/core/libs/dimg/filters/greycstoration/cimg/CImg.h
+++ b/core/libs/dimg/filters/greycstoration/cimg/CImg.h
@@ -1,36844 +1,36844 @@
/*
#
# File : CImg.h
# ( C++ header file )
#
# Description : The C++ Template Image Processing Library.
# This file is the main part of the CImg Library project.
# ( http://cimg.sourceforge.net )
#
# Project manager : David Tschumperle.
# ( http://www.greyc.ensicaen.fr/~dtschump/ )
#
# The complete contributor list can be seen in the 'README.txt' file.
#
# Licenses : This file is "dual-licensed", you have to choose one
# of the two licenses below to apply on this file.
#
# CeCILL-C
# The CeCILL-C license is close to the GNU LGPL.
# ( http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.html )
#
# or CeCILL v2.0
# The CeCILL license is compatible with the GNU GPL.
# ( http://www.cecill.info/licences/Licence_CeCILL_V2-en.html )
#
# This software is governed either by the CeCILL or the CeCILL-C license
# under French law and abiding by the rules of distribution of free software.
# You can use, modify and or redistribute the software under the terms of
# the CeCILL or CeCILL-C licenses as circulated by CEA, CNRS and INRIA
# at the following URL : "http://www.cecill.info".
#
# As a counterpart to the access to the source code and rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty and the software's author, the holder of the
# economic rights, and the successive licensors have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading, using, modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean that it is complicated to manipulate, and that also
# therefore means that it is reserved for developers and experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and, more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL and CeCILL-C licenses and that you accept its terms.
#
*/
// Define version number of the current file.
//
#ifndef cimg_version
#define cimg_version 130
/*-----------------------------------------------------------
#
# Test/auto-set CImg configuration variables
# and include required headers.
#
# If you find that default configuration variables are
# not adapted, you can override their values before including
# the header file "CImg.h" (using the #define directive).
#
------------------------------------------------------------*/
// Include required standard C++ headers.
//
#include <cstdio>
#include <cstdlib>
#include <cstdarg>
#include <cstring>
#include <cmath>
#include <ctime>
// Operating system configuration.
//
-// Define 'cimg_OS' to : 0 for an unknown OS (will try to minize library dependancies).
+// Define 'cimg_OS' to : 0 for an unknown OS (will try to minimize library dependencies).
// 1 for a Unix-like OS (Linux, Solaris, BSD, MacOSX, Irix, ...).
// 2 for Microsoft Windows.
//
#ifndef cimg_OS
#if defined(unix) || defined(__unix) || defined(__unix__) \
|| defined(linux) || defined(__linux) || defined(__linux__) \
|| defined(sun) || defined(__sun) \
|| defined(BSD) || defined(__OpenBSD__) || defined(__NetBSD__) \
|| defined(__FreeBSD__) || defined __DragonFly__ \
|| defined(sgi) || defined(__sgi) \
|| defined(__MACOSX__) || defined(__APPLE__) \
|| defined(__CYGWIN__)
#define cimg_OS 1
#elif defined(_MSC_VER) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) \
|| defined(WIN64) || defined(_WIN64) || defined(__WIN64__)
#define cimg_OS 2
#else
#define cimg_OS 0
#endif
#elif !(cimg_OS==0 || cimg_OS==1 || cimg_OS==2)
#error CImg Library : Configuration variable 'cimg_OS' is badly defined.
#error (valid values are '0=unknown OS', '1=Unix-like OS', '2=Microsoft Windows').
#endif
// Compiler configuration.
//
// Try to detect Microsoft VC++ compilers.
// (lot of workarounds are needed afterwards to
// make CImg working, particularly with VC++ 6.0).
//
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4311)
#pragma warning(disable:4312)
#pragma warning(disable:4800)
#pragma warning(disable:4804)
#pragma warning(disable:4996)
#ifndef _CRT_SECURE_NO_DEPRECATE
# define _CRT_SECURE_NO_DEPRECATE 1
#endif
#ifndef _CRT_NONSTDC_NO_DEPRECATE
# define _CRT_NONSTDC_NO_DEPRECATE 1
#endif
#if _MSC_VER<1300
#define cimg_use_visualcpp6
#define cimg_std
#define _WIN32_WINNT 0x0500
#endif
#endif
// Include OS-specific headers.
//
#if cimg_OS==1
#include <sys/time.h>
#include <unistd.h>
#elif cimg_OS==2
#include <windows.h>
#ifndef _WIN32_IE
#define _WIN32_IE 0x0400
#endif
#include <shlobj.h>
#endif
-// Define defaut pipe for output messages
+// Define default pipe for output messages
//
// Define 'cimg_stdout' to : stdout to print CImg messages on the standard output.
// stderr to print CImg messages on the standart error output (default behavior).
//
#ifndef cimg_std
#define cimg_std std
#endif
#ifndef cimg_stdout
#define cimg_stdout stderr
#endif
// Output messages configuration.
//
// Define 'cimg_debug' to : 0 to hide debug messages (quiet mode, but exceptions are still thrown).
// 1 to display debug messages on the console.
// 2 to display debug messages with dialog windows (default behavior).
// 3 to do as 1 + add extra warnings (may slow down the code !).
// 4 to do as 2 + add extra warnings (may slow down the code !).
//
// Define 'cimg_strict_warnings' to replace warning messages by exception throwns.
//
// Define 'cimg_use_vt100' to allow output of color messages (require VT100-compatible terminal).
//
#ifndef cimg_debug
#define cimg_debug 2
#elif !(cimg_debug==0 || cimg_debug==1 || cimg_debug==2 || cimg_debug==3 || cimg_debug==4)
#error CImg Library : Configuration variable 'cimg_debug' is badly defined.
#error (valid values are '0=quiet', '1=console', '2=dialog', '3=console+warnings', '4=dialog+warnings').
#endif
// Display framework configuration.
//
// Define 'cimg_display' to : 0 to disable display capabilities.
// 1 to use X-Window framework (X11).
// 2 to use Microsoft GDI32 framework.
// 3 to use Apple Carbon framework.
//
#ifndef cimg_display
#if cimg_OS==0
#define cimg_display 0
#elif cimg_OS==1
#if defined(__MACOSX__) || defined(__APPLE__)
#define cimg_display 1
#else
#define cimg_display 1
#endif
#elif cimg_OS==2
#define cimg_display 2
#endif
#elif !(cimg_display==0 || cimg_display==1 || cimg_display==2 || cimg_display==3)
#error CImg Library : Configuration variable 'cimg_display' is badly defined.
#error (valid values are '0=disable', '1=X-Window (X11)', '2=Microsoft GDI32', '3=Apple Carbon').
#endif
// Include display-specific headers.
//
#if cimg_display==1
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <pthread.h>
#ifdef cimg_use_xshm
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif
#ifdef cimg_use_xrandr
#include <X11/extensions/Xrandr.h>
#endif
#elif cimg_display==3
#include <Carbon/Carbon.h>
#include <pthread.h>
#endif
// OpenMP configuration.
// (http://www.openmp.org)
//
// Define 'cimg_use_openmp' to enable OpenMP support.
//
// OpenMP directives can be used in few CImg functions to get
// advantages of multi-core CPUs. Using OpenMP is not mandatory.
//
#ifdef cimg_use_openmp
#include "omp.h"
#endif
// LibPNG configuration.
// (http://www.libpng.org)
//
// Define 'cimg_use_png' to enable LibPNG support.
//
// LibPNG can be used in functions 'CImg<T>::{load,save}_png()'
// to get a builtin support of PNG files. Using LibPNG is not mandatory.
//
#ifdef cimg_use_png
extern "C" {
#include "png.h"
}
#endif
// LibJPEG configuration.
// (http://en.wikipedia.org/wiki/Libjpeg)
//
// Define 'cimg_use_jpeg' to enable LibJPEG support.
//
// LibJPEG can be used in functions 'CImg<T>::{load,save}_jpeg()'
// to get a builtin support of JPEG files. Using LibJPEG is not mandatory.
//
#ifdef cimg_use_jpeg
extern "C" {
#include "jpeglib.h"
}
#endif
// LibTIFF configuration.
// (http://www.libtiff.org)
//
// Define 'cimg_use_tiff' to enable LibTIFF support.
//
// LibTIFF can be used in functions 'CImg[List]<T>::{load,save}_tiff()'
// to get a builtin support of TIFF files. Using LibTIFF is not mandatory.
//
#ifdef cimg_use_tiff
extern "C" {
#include "tiffio.h"
}
#endif
// FFMPEG Avcodec and Avformat libraries configuration.
// (http://www.ffmpeg.org)
//
// Define 'cimg_use_ffmpeg' to enable FFMPEG lib support.
//
// Avcodec and Avformat libraries can be used in functions
// 'CImg[List]<T>::load_ffmpeg()' to get a builtin
// support of various image sequences files.
// Using FFMPEG libraries is not mandatory.
//
#ifdef cimg_use_ffmpeg
extern "C" {
#include "avformat.h"
#include "avcodec.h"
#include "swscale.h"
}
#endif
// Zlib configuration
// (http://www.zlib.net)
//
// Define 'cimg_use_zlib' to enable Zlib support.
//
// Zlib can be used in functions 'CImg[List]<T>::{load,save}_cimg()'
// to allow compressed data in '.cimg' files. Using Zlib is not mandatory.
//
#ifdef cimg_use_zlib
extern "C" {
#include "zlib.h"
}
#endif
// Magick++ configuration.
// (http://www.imagemagick.org/Magick++)
//
// Define 'cimg_use_magick' to enable Magick++ support.
//
// Magick++ library can be used in functions 'CImg<T>::{load,save}()'
// to get a builtin support of various image formats (PNG,JPEG,TIFF,...).
// Using Magick++ is not mandatory.
//
#ifdef cimg_use_magick
#include "Magick++.h"
#endif
// FFTW3 configuration.
// (http://www.fftw.org)
//
// Define 'cimg_use_fftw3' to enable libFFTW3 support.
//
// FFTW3 library can be used in functions 'CImg[List]<T>::FFT()' to
// efficiently compile the Fast Fourier Transform of image data.
//
#ifdef cimg_use_fftw3
extern "C" {
#include "fftw3.h"
}
#endif
// Board configuration.
// (http://libboard.sourceforge.net/)
//
// Define 'cimg_use_board' to enable Board support.
//
// Board library can be used in functions 'CImg<T>::draw_object3d()'
// to draw objects 3D in vector-graphics canvas that can be saved
// as .PS or .SVG files afterwards.
//
#ifdef cimg_use_board
#include "Board.h"
#endif
// Lapack configuration.
// (http://www.netlib.org/lapack)
//
// Define 'cimg_use_lapack' to enable LAPACK support.
//
// Lapack can be used in various CImg functions dealing with
// matrix computation and algorithms (eigenvalues, inverse, ...).
// Using Lapack is not mandatory.
//
#ifdef cimg_use_lapack
extern "C" {
extern void sgetrf_(int*, int*, float*, int*, int*, int*);
extern void sgetri_(int*, float*, int*, int*, float*, int*, int*);
extern void sgetrs_(char*, int*, int*, float*, int*, int*, float*, int*, int*);
extern void sgesvd_(char*, char*, int*, int*, float*, int*, float*, float*, int*, float*, int*, float*, int*, int*);
extern void ssyev_(char*, char*, int*, float*, int*, float*, float*, int*, int*);
extern void dgetrf_(int*, int*, double*, int*, int*, int*);
extern void dgetri_(int*, double*, int*, int*, double*, int*, int*);
extern void dgetrs_(char*, int*, int*, double*, int*, int*, double*, int*, int*);
extern void dgesvd_(char*, char*, int*, int*, double*, int*, double*, double*, int*, double*, int*, double*, int*, int*);
extern void dsyev_(char*, char*, int*, double*, int*, double*, double*, int*, int*);
}
#endif
// Check if min/max macros are defined.
//
// CImg does not compile if macros 'min' or 'max' are defined,
// because min() and max() functions are also defined in the cimg:: namespace.
// so it '#undef' these macros if necessary, and restore them to reasonable
// values at the end of the file.
//
#ifdef min
#undef min
#define _cimg_redefine_min
#endif
#ifdef max
#undef max
#define _cimg_redefine_max
#endif
// Set the current working directory for native MacOSX bundled applications.
//
// By default, MacOS bundled applications set the cwd at the root directory '/',
// the code below allows to set it to the current exec directory instead when
// a CImg-based program is executed.
//
#if cimg_OS==1 && cimg_display==3
static struct _cimg_macosx_setcwd {
_cimg_macosx_setcwd() {
FSRef location;
ProcessSerialNumber psn;
char filePath[512];
if (GetCurrentProcess(&psn)!=noErr) return;
if (GetProcessBundleLocation(&psn,&location)!=noErr) return;
FSRefMakePath(&location,(UInt8*)filePath,sizeof(filePath)-1);
int p = cimg_std::strlen(filePath);
while (filePath[p] != '/') --p;
filePath[p] = 0;
chdir(filePath);
}
} cimg_macosx_setcwd;
#endif
/*------------------------------------------------------------------------------
#
# Define user-friendly macros.
#
# User macros are prefixed by 'cimg_' and can be used in your own code.
# They are particularly useful for option parsing, and image loops creation.
#
------------------------------------------------------------------------------*/
// Define the program usage, and retrieve command line arguments.
//
#define cimg_usage(usage) cimg_library::cimg::option((char*)0,argc,argv,(char*)0,usage)
#define cimg_help(str) cimg_library::cimg::option((char*)0,argc,argv,str,(char*)0)
#define cimg_option(name,defaut,usage) cimg_library::cimg::option(name,argc,argv,defaut,usage)
#define cimg_argument(pos) cimg_library::cimg::argument(pos,argc,argv)
#define cimg_argument1(pos,s0) cimg_library::cimg::argument(pos,argc,argv,1,s0)
#define cimg_argument2(pos,s0,s1) cimg_library::cimg::argument(pos,argc,argv,2,s0,s1)
#define cimg_argument3(pos,s0,s1,s2) cimg_library::cimg::argument(pos,argc,argv,3,s0,s1,s2)
#define cimg_argument4(pos,s0,s1,s2,s3) cimg_library::cimg::argument(pos,argc,argv,4,s0,s1,s2,s3)
#define cimg_argument5(pos,s0,s1,s2,s3,s4) cimg_library::cimg::argument(pos,argc,argv,5,s0,s1,s2,s3,s4)
#define cimg_argument6(pos,s0,s1,s2,s3,s4,s5) cimg_library::cimg::argument(pos,argc,argv,6,s0,s1,s2,s3,s4,s5)
#define cimg_argument7(pos,s0,s1,s2,s3,s4,s5,s6) cimg_library::cimg::argument(pos,argc,argv,7,s0,s1,s2,s3,s4,s5,s6)
#define cimg_argument8(pos,s0,s1,s2,s3,s4,s5,s6,s7) cimg_library::cimg::argument(pos,argc,argv,8,s0,s1,s2,s3,s4,s5,s6,s7)
#define cimg_argument9(pos,s0,s1,s2,s3,s4,s5,s6,s7,s8) cimg_library::cimg::argument(pos,argc,argv,9,s0,s1,s2,s3,s4,s5,s6,s7,s8)
// Define and manipulate local neighborhoods.
//
#define CImg_2x2(I,T) T I[4]; \
T& I##cc = I[0]; T& I##nc = I[1]; \
T& I##cn = I[2]; T& I##nn = I[3]; \
I##cc = I##nc = \
I##cn = I##nn = 0
#define CImg_3x3(I,T) T I[9]; \
T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; \
T& I##pc = I[3]; T& I##cc = I[4]; T& I##nc = I[5]; \
T& I##pn = I[6]; T& I##cn = I[7]; T& I##nn = I[8]; \
I##pp = I##cp = I##np = \
I##pc = I##cc = I##nc = \
I##pn = I##cn = I##nn = 0
#define CImg_4x4(I,T) T I[16]; \
T& I##pp = I[0]; T& I##cp = I[1]; T& I##np = I[2]; T& I##ap = I[3]; \
T& I##pc = I[4]; T& I##cc = I[5]; T& I##nc = I[6]; T& I##ac = I[7]; \
T& I##pn = I[8]; T& I##cn = I[9]; T& I##nn = I[10]; T& I##an = I[11]; \
T& I##pa = I[12]; T& I##ca = I[13]; T& I##na = I[14]; T& I##aa = I[15]; \
I##pp = I##cp = I##np = I##ap = \
I##pc = I##cc = I##nc = I##ac = \
I##pn = I##cn = I##nn = I##an = \
I##pa = I##ca = I##na = I##aa = 0
#define CImg_5x5(I,T) T I[25]; \
T& I##bb = I[0]; T& I##pb = I[1]; T& I##cb = I[2]; T& I##nb = I[3]; T& I##ab = I[4]; \
T& I##bp = I[5]; T& I##pp = I[6]; T& I##cp = I[7]; T& I##np = I[8]; T& I##ap = I[9]; \
T& I##bc = I[10]; T& I##pc = I[11]; T& I##cc = I[12]; T& I##nc = I[13]; T& I##ac = I[14]; \
T& I##bn = I[15]; T& I##pn = I[16]; T& I##cn = I[17]; T& I##nn = I[18]; T& I##an = I[19]; \
T& I##ba = I[20]; T& I##pa = I[21]; T& I##ca = I[22]; T& I##na = I[23]; T& I##aa = I[24]; \
I##bb = I##pb = I##cb = I##nb = I##ab = \
I##bp = I##pp = I##cp = I##np = I##ap = \
I##bc = I##pc = I##cc = I##nc = I##ac = \
I##bn = I##pn = I##cn = I##nn = I##an = \
I##ba = I##pa = I##ca = I##na = I##aa = 0
#define CImg_2x2x2(I,T) T I[8]; \
T& I##ccc = I[0]; T& I##ncc = I[1]; \
T& I##cnc = I[2]; T& I##nnc = I[3]; \
T& I##ccn = I[4]; T& I##ncn = I[5]; \
T& I##cnn = I[6]; T& I##nnn = I[7]; \
I##ccc = I##ncc = \
I##cnc = I##nnc = \
I##ccn = I##ncn = \
I##cnn = I##nnn = 0
#define CImg_3x3x3(I,T) T I[27]; \
T& I##ppp = I[0]; T& I##cpp = I[1]; T& I##npp = I[2]; \
T& I##pcp = I[3]; T& I##ccp = I[4]; T& I##ncp = I[5]; \
T& I##pnp = I[6]; T& I##cnp = I[7]; T& I##nnp = I[8]; \
T& I##ppc = I[9]; T& I##cpc = I[10]; T& I##npc = I[11]; \
T& I##pcc = I[12]; T& I##ccc = I[13]; T& I##ncc = I[14]; \
T& I##pnc = I[15]; T& I##cnc = I[16]; T& I##nnc = I[17]; \
T& I##ppn = I[18]; T& I##cpn = I[19]; T& I##npn = I[20]; \
T& I##pcn = I[21]; T& I##ccn = I[22]; T& I##ncn = I[23]; \
T& I##pnn = I[24]; T& I##cnn = I[25]; T& I##nnn = I[26]; \
I##ppp = I##cpp = I##npp = \
I##pcp = I##ccp = I##ncp = \
I##pnp = I##cnp = I##nnp = \
I##ppc = I##cpc = I##npc = \
I##pcc = I##ccc = I##ncc = \
I##pnc = I##cnc = I##nnc = \
I##ppn = I##cpn = I##npn = \
I##pcn = I##ccn = I##ncn = \
I##pnn = I##cnn = I##nnn = 0
#define cimg_get2x2(img,x,y,z,v,I) \
I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v)
#define cimg_get3x3(img,x,y,z,v,I) \
I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_p1##x,y,z,v), \
I[4] = (img)(x,y,z,v), I[5] = (img)(_n1##x,y,z,v), I[6] = (img)(_p1##x,_n1##y,z,v), I[7] = (img)(x,_n1##y,z,v), \
I[8] = (img)(_n1##x,_n1##y,z,v)
#define cimg_get4x4(img,x,y,z,v,I) \
I[0] = (img)(_p1##x,_p1##y,z,v), I[1] = (img)(x,_p1##y,z,v), I[2] = (img)(_n1##x,_p1##y,z,v), I[3] = (img)(_n2##x,_p1##y,z,v), \
I[4] = (img)(_p1##x,y,z,v), I[5] = (img)(x,y,z,v), I[6] = (img)(_n1##x,y,z,v), I[7] = (img)(_n2##x,y,z,v), \
I[8] = (img)(_p1##x,_n1##y,z,v), I[9] = (img)(x,_n1##y,z,v), I[10] = (img)(_n1##x,_n1##y,z,v), I[11] = (img)(_n2##x,_n1##y,z,v), \
I[12] = (img)(_p1##x,_n2##y,z,v), I[13] = (img)(x,_n2##y,z,v), I[14] = (img)(_n1##x,_n2##y,z,v), I[15] = (img)(_n2##x,_n2##y,z,v)
#define cimg_get5x5(img,x,y,z,v,I) \
I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \
I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_p2##x,_p1##y,z,v), I[6] = (img)(_p1##x,_p1##y,z,v), I[7] = (img)(x,_p1##y,z,v), \
I[8] = (img)(_n1##x,_p1##y,z,v), I[9] = (img)(_n2##x,_p1##y,z,v), I[10] = (img)(_p2##x,y,z,v), I[11] = (img)(_p1##x,y,z,v), \
I[12] = (img)(x,y,z,v), I[13] = (img)(_n1##x,y,z,v), I[14] = (img)(_n2##x,y,z,v), I[15] = (img)(_p2##x,_n1##y,z,v), \
I[16] = (img)(_p1##x,_n1##y,z,v), I[17] = (img)(x,_n1##y,z,v), I[18] = (img)(_n1##x,_n1##y,z,v), I[19] = (img)(_n2##x,_n1##y,z,v), \
I[20] = (img)(_p2##x,_n2##y,z,v), I[21] = (img)(_p1##x,_n2##y,z,v), I[22] = (img)(x,_n2##y,z,v), I[23] = (img)(_n1##x,_n2##y,z,v), \
I[24] = (img)(_n2##x,_n2##y,z,v)
#define cimg_get6x6(img,x,y,z,v,I) \
I[0] = (img)(_p2##x,_p2##y,z,v), I[1] = (img)(_p1##x,_p2##y,z,v), I[2] = (img)(x,_p2##y,z,v), I[3] = (img)(_n1##x,_p2##y,z,v), \
I[4] = (img)(_n2##x,_p2##y,z,v), I[5] = (img)(_n3##x,_p2##y,z,v), I[6] = (img)(_p2##x,_p1##y,z,v), I[7] = (img)(_p1##x,_p1##y,z,v), \
I[8] = (img)(x,_p1##y,z,v), I[9] = (img)(_n1##x,_p1##y,z,v), I[10] = (img)(_n2##x,_p1##y,z,v), I[11] = (img)(_n3##x,_p1##y,z,v), \
I[12] = (img)(_p2##x,y,z,v), I[13] = (img)(_p1##x,y,z,v), I[14] = (img)(x,y,z,v), I[15] = (img)(_n1##x,y,z,v), \
I[16] = (img)(_n2##x,y,z,v), I[17] = (img)(_n3##x,y,z,v), I[18] = (img)(_p2##x,_n1##y,z,v), I[19] = (img)(_p1##x,_n1##y,z,v), \
I[20] = (img)(x,_n1##y,z,v), I[21] = (img)(_n1##x,_n1##y,z,v), I[22] = (img)(_n2##x,_n1##y,z,v), I[23] = (img)(_n3##x,_n1##y,z,v), \
I[24] = (img)(_p2##x,_n2##y,z,v), I[25] = (img)(_p1##x,_n2##y,z,v), I[26] = (img)(x,_n2##y,z,v), I[27] = (img)(_n1##x,_n2##y,z,v), \
I[28] = (img)(_n2##x,_n2##y,z,v), I[29] = (img)(_n3##x,_n2##y,z,v), I[30] = (img)(_p2##x,_n3##y,z,v), I[31] = (img)(_p1##x,_n3##y,z,v), \
I[32] = (img)(x,_n3##y,z,v), I[33] = (img)(_n1##x,_n3##y,z,v), I[34] = (img)(_n2##x,_n3##y,z,v), I[35] = (img)(_n3##x,_n3##y,z,v)
#define cimg_get7x7(img,x,y,z,v,I) \
I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \
I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_p3##x,_p2##y,z,v), \
I[8] = (img)(_p2##x,_p2##y,z,v), I[9] = (img)(_p1##x,_p2##y,z,v), I[10] = (img)(x,_p2##y,z,v), I[11] = (img)(_n1##x,_p2##y,z,v), \
I[12] = (img)(_n2##x,_p2##y,z,v), I[13] = (img)(_n3##x,_p2##y,z,v), I[14] = (img)(_p3##x,_p1##y,z,v), I[15] = (img)(_p2##x,_p1##y,z,v), \
I[16] = (img)(_p1##x,_p1##y,z,v), I[17] = (img)(x,_p1##y,z,v), I[18] = (img)(_n1##x,_p1##y,z,v), I[19] = (img)(_n2##x,_p1##y,z,v), \
I[20] = (img)(_n3##x,_p1##y,z,v), I[21] = (img)(_p3##x,y,z,v), I[22] = (img)(_p2##x,y,z,v), I[23] = (img)(_p1##x,y,z,v), \
I[24] = (img)(x,y,z,v), I[25] = (img)(_n1##x,y,z,v), I[26] = (img)(_n2##x,y,z,v), I[27] = (img)(_n3##x,y,z,v), \
I[28] = (img)(_p3##x,_n1##y,z,v), I[29] = (img)(_p2##x,_n1##y,z,v), I[30] = (img)(_p1##x,_n1##y,z,v), I[31] = (img)(x,_n1##y,z,v), \
I[32] = (img)(_n1##x,_n1##y,z,v), I[33] = (img)(_n2##x,_n1##y,z,v), I[34] = (img)(_n3##x,_n1##y,z,v), I[35] = (img)(_p3##x,_n2##y,z,v), \
I[36] = (img)(_p2##x,_n2##y,z,v), I[37] = (img)(_p1##x,_n2##y,z,v), I[38] = (img)(x,_n2##y,z,v), I[39] = (img)(_n1##x,_n2##y,z,v), \
I[40] = (img)(_n2##x,_n2##y,z,v), I[41] = (img)(_n3##x,_n2##y,z,v), I[42] = (img)(_p3##x,_n3##y,z,v), I[43] = (img)(_p2##x,_n3##y,z,v), \
I[44] = (img)(_p1##x,_n3##y,z,v), I[45] = (img)(x,_n3##y,z,v), I[46] = (img)(_n1##x,_n3##y,z,v), I[47] = (img)(_n2##x,_n3##y,z,v), \
I[48] = (img)(_n3##x,_n3##y,z,v)
#define cimg_get8x8(img,x,y,z,v,I) \
I[0] = (img)(_p3##x,_p3##y,z,v), I[1] = (img)(_p2##x,_p3##y,z,v), I[2] = (img)(_p1##x,_p3##y,z,v), I[3] = (img)(x,_p3##y,z,v), \
I[4] = (img)(_n1##x,_p3##y,z,v), I[5] = (img)(_n2##x,_p3##y,z,v), I[6] = (img)(_n3##x,_p3##y,z,v), I[7] = (img)(_n4##x,_p3##y,z,v), \
I[8] = (img)(_p3##x,_p2##y,z,v), I[9] = (img)(_p2##x,_p2##y,z,v), I[10] = (img)(_p1##x,_p2##y,z,v), I[11] = (img)(x,_p2##y,z,v), \
I[12] = (img)(_n1##x,_p2##y,z,v), I[13] = (img)(_n2##x,_p2##y,z,v), I[14] = (img)(_n3##x,_p2##y,z,v), I[15] = (img)(_n4##x,_p2##y,z,v), \
I[16] = (img)(_p3##x,_p1##y,z,v), I[17] = (img)(_p2##x,_p1##y,z,v), I[18] = (img)(_p1##x,_p1##y,z,v), I[19] = (img)(x,_p1##y,z,v), \
I[20] = (img)(_n1##x,_p1##y,z,v), I[21] = (img)(_n2##x,_p1##y,z,v), I[22] = (img)(_n3##x,_p1##y,z,v), I[23] = (img)(_n4##x,_p1##y,z,v), \
I[24] = (img)(_p3##x,y,z,v), I[25] = (img)(_p2##x,y,z,v), I[26] = (img)(_p1##x,y,z,v), I[27] = (img)(x,y,z,v), \
I[28] = (img)(_n1##x,y,z,v), I[29] = (img)(_n2##x,y,z,v), I[30] = (img)(_n3##x,y,z,v), I[31] = (img)(_n4##x,y,z,v), \
I[32] = (img)(_p3##x,_n1##y,z,v), I[33] = (img)(_p2##x,_n1##y,z,v), I[34] = (img)(_p1##x,_n1##y,z,v), I[35] = (img)(x,_n1##y,z,v), \
I[36] = (img)(_n1##x,_n1##y,z,v), I[37] = (img)(_n2##x,_n1##y,z,v), I[38] = (img)(_n3##x,_n1##y,z,v), I[39] = (img)(_n4##x,_n1##y,z,v), \
I[40] = (img)(_p3##x,_n2##y,z,v), I[41] = (img)(_p2##x,_n2##y,z,v), I[42] = (img)(_p1##x,_n2##y,z,v), I[43] = (img)(x,_n2##y,z,v), \
I[44] = (img)(_n1##x,_n2##y,z,v), I[45] = (img)(_n2##x,_n2##y,z,v), I[46] = (img)(_n3##x,_n2##y,z,v), I[47] = (img)(_n4##x,_n2##y,z,v), \
I[48] = (img)(_p3##x,_n3##y,z,v), I[49] = (img)(_p2##x,_n3##y,z,v), I[50] = (img)(_p1##x,_n3##y,z,v), I[51] = (img)(x,_n3##y,z,v), \
I[52] = (img)(_n1##x,_n3##y,z,v), I[53] = (img)(_n2##x,_n3##y,z,v), I[54] = (img)(_n3##x,_n3##y,z,v), I[55] = (img)(_n4##x,_n3##y,z,v), \
I[56] = (img)(_p3##x,_n4##y,z,v), I[57] = (img)(_p2##x,_n4##y,z,v), I[58] = (img)(_p1##x,_n4##y,z,v), I[59] = (img)(x,_n4##y,z,v), \
I[60] = (img)(_n1##x,_n4##y,z,v), I[61] = (img)(_n2##x,_n4##y,z,v), I[62] = (img)(_n3##x,_n4##y,z,v), I[63] = (img)(_n4##x,_n4##y,z,v);
#define cimg_get9x9(img,x,y,z,v,I) \
I[0] = (img)(_p4##x,_p4##y,z,v), I[1] = (img)(_p3##x,_p4##y,z,v), I[2] = (img)(_p2##x,_p4##y,z,v), I[3] = (img)(_p1##x,_p4##y,z,v), \
I[4] = (img)(x,_p4##y,z,v), I[5] = (img)(_n1##x,_p4##y,z,v), I[6] = (img)(_n2##x,_p4##y,z,v), I[7] = (img)(_n3##x,_p4##y,z,v), \
I[8] = (img)(_n4##x,_p4##y,z,v), I[9] = (img)(_p4##x,_p3##y,z,v), I[10] = (img)(_p3##x,_p3##y,z,v), I[11] = (img)(_p2##x,_p3##y,z,v), \
I[12] = (img)(_p1##x,_p3##y,z,v), I[13] = (img)(x,_p3##y,z,v), I[14] = (img)(_n1##x,_p3##y,z,v), I[15] = (img)(_n2##x,_p3##y,z,v), \
I[16] = (img)(_n3##x,_p3##y,z,v), I[17] = (img)(_n4##x,_p3##y,z,v), I[18] = (img)(_p4##x,_p2##y,z,v), I[19] = (img)(_p3##x,_p2##y,z,v), \
I[20] = (img)(_p2##x,_p2##y,z,v), I[21] = (img)(_p1##x,_p2##y,z,v), I[22] = (img)(x,_p2##y,z,v), I[23] = (img)(_n1##x,_p2##y,z,v), \
I[24] = (img)(_n2##x,_p2##y,z,v), I[25] = (img)(_n3##x,_p2##y,z,v), I[26] = (img)(_n4##x,_p2##y,z,v), I[27] = (img)(_p4##x,_p1##y,z,v), \
I[28] = (img)(_p3##x,_p1##y,z,v), I[29] = (img)(_p2##x,_p1##y,z,v), I[30] = (img)(_p1##x,_p1##y,z,v), I[31] = (img)(x,_p1##y,z,v), \
I[32] = (img)(_n1##x,_p1##y,z,v), I[33] = (img)(_n2##x,_p1##y,z,v), I[34] = (img)(_n3##x,_p1##y,z,v), I[35] = (img)(_n4##x,_p1##y,z,v), \
I[36] = (img)(_p4##x,y,z,v), I[37] = (img)(_p3##x,y,z,v), I[38] = (img)(_p2##x,y,z,v), I[39] = (img)(_p1##x,y,z,v), \
I[40] = (img)(x,y,z,v), I[41] = (img)(_n1##x,y,z,v), I[42] = (img)(_n2##x,y,z,v), I[43] = (img)(_n3##x,y,z,v), \
I[44] = (img)(_n4##x,y,z,v), I[45] = (img)(_p4##x,_n1##y,z,v), I[46] = (img)(_p3##x,_n1##y,z,v), I[47] = (img)(_p2##x,_n1##y,z,v), \
I[48] = (img)(_p1##x,_n1##y,z,v), I[49] = (img)(x,_n1##y,z,v), I[50] = (img)(_n1##x,_n1##y,z,v), I[51] = (img)(_n2##x,_n1##y,z,v), \
I[52] = (img)(_n3##x,_n1##y,z,v), I[53] = (img)(_n4##x,_n1##y,z,v), I[54] = (img)(_p4##x,_n2##y,z,v), I[55] = (img)(_p3##x,_n2##y,z,v), \
I[56] = (img)(_p2##x,_n2##y,z,v), I[57] = (img)(_p1##x,_n2##y,z,v), I[58] = (img)(x,_n2##y,z,v), I[59] = (img)(_n1##x,_n2##y,z,v), \
I[60] = (img)(_n2##x,_n2##y,z,v), I[61] = (img)(_n3##x,_n2##y,z,v), I[62] = (img)(_n4##x,_n2##y,z,v), I[63] = (img)(_p4##x,_n3##y,z,v), \
I[64] = (img)(_p3##x,_n3##y,z,v), I[65] = (img)(_p2##x,_n3##y,z,v), I[66] = (img)(_p1##x,_n3##y,z,v), I[67] = (img)(x,_n3##y,z,v), \
I[68] = (img)(_n1##x,_n3##y,z,v), I[69] = (img)(_n2##x,_n3##y,z,v), I[70] = (img)(_n3##x,_n3##y,z,v), I[71] = (img)(_n4##x,_n3##y,z,v), \
I[72] = (img)(_p4##x,_n4##y,z,v), I[73] = (img)(_p3##x,_n4##y,z,v), I[74] = (img)(_p2##x,_n4##y,z,v), I[75] = (img)(_p1##x,_n4##y,z,v), \
I[76] = (img)(x,_n4##y,z,v), I[77] = (img)(_n1##x,_n4##y,z,v), I[78] = (img)(_n2##x,_n4##y,z,v), I[79] = (img)(_n3##x,_n4##y,z,v), \
I[80] = (img)(_n4##x,_n4##y,z,v)
#define cimg_get2x2x2(img,x,y,z,v,I) \
I[0] = (img)(x,y,z,v), I[1] = (img)(_n1##x,y,z,v), I[2] = (img)(x,_n1##y,z,v), I[3] = (img)(_n1##x,_n1##y,z,v), \
I[4] = (img)(x,y,_n1##z,v), I[5] = (img)(_n1##x,y,_n1##z,v), I[6] = (img)(x,_n1##y,_n1##z,v), I[7] = (img)(_n1##x,_n1##y,_n1##z,v)
#define cimg_get3x3x3(img,x,y,z,v,I) \
I[0] = (img)(_p1##x,_p1##y,_p1##z,v), I[1] = (img)(x,_p1##y,_p1##z,v), I[2] = (img)(_n1##x,_p1##y,_p1##z,v), \
I[3] = (img)(_p1##x,y,_p1##z,v), I[4] = (img)(x,y,_p1##z,v), I[5] = (img)(_n1##x,y,_p1##z,v), \
I[6] = (img)(_p1##x,_n1##y,_p1##z,v), I[7] = (img)(x,_n1##y,_p1##z,v), I[8] = (img)(_n1##x,_n1##y,_p1##z,v), \
I[9] = (img)(_p1##x,_p1##y,z,v), I[10] = (img)(x,_p1##y,z,v), I[11] = (img)(_n1##x,_p1##y,z,v), \
I[12] = (img)(_p1##x,y,z,v), I[13] = (img)(x,y,z,v), I[14] = (img)(_n1##x,y,z,v), \
I[15] = (img)(_p1##x,_n1##y,z,v), I[16] = (img)(x,_n1##y,z,v), I[17] = (img)(_n1##x,_n1##y,z,v), \
I[18] = (img)(_p1##x,_p1##y,_n1##z,v), I[19] = (img)(x,_p1##y,_n1##z,v), I[20] = (img)(_n1##x,_p1##y,_n1##z,v), \
I[21] = (img)(_p1##x,y,_n1##z,v), I[22] = (img)(x,y,_n1##z,v), I[23] = (img)(_n1##x,y,_n1##z,v), \
I[24] = (img)(_p1##x,_n1##y,_n1##z,v), I[25] = (img)(x,_n1##y,_n1##z,v), I[26] = (img)(_n1##x,_n1##y,_n1##z,v)
// Define various image loops.
//
// These macros generally avoid the use of iterators, but you are not forced to used them !
//
#define cimg_for(img,ptr,T_ptr) for (T_ptr *ptr = (img).data + (img).size(); (ptr--)>(img).data; )
#define cimg_foroff(img,off) for (unsigned int off = 0, _max##off = (unsigned int)(img).size(); off<_max##off; ++off)
#define cimglist_for(list,l) for (unsigned int l=0; l<(list).size; ++l)
#define cimglist_apply(list,fn) cimglist_for(list,__##fn) (list)[__##fn].fn
#define cimg_for1(bound,i) for (int i = 0; i<(int)(bound); ++i)
#define cimg_forX(img,x) cimg_for1((img).width,x)
#define cimg_forY(img,y) cimg_for1((img).height,y)
#define cimg_forZ(img,z) cimg_for1((img).depth,z)
#define cimg_forV(img,v) cimg_for1((img).dim,v)
#define cimg_forXY(img,x,y) cimg_forY(img,y) cimg_forX(img,x)
#define cimg_forXZ(img,x,z) cimg_forZ(img,z) cimg_forX(img,x)
#define cimg_forYZ(img,y,z) cimg_forZ(img,z) cimg_forY(img,y)
#define cimg_forXV(img,x,v) cimg_forV(img,v) cimg_forX(img,x)
#define cimg_forYV(img,y,v) cimg_forV(img,v) cimg_forY(img,y)
#define cimg_forZV(img,z,v) cimg_forV(img,v) cimg_forZ(img,z)
#define cimg_forXYZ(img,x,y,z) cimg_forZ(img,z) cimg_forXY(img,x,y)
#define cimg_forXYV(img,x,y,v) cimg_forV(img,v) cimg_forXY(img,x,y)
#define cimg_forXZV(img,x,z,v) cimg_forV(img,v) cimg_forXZ(img,x,z)
#define cimg_forYZV(img,y,z,v) cimg_forV(img,v) cimg_forYZ(img,y,z)
#define cimg_forXYZV(img,x,y,z,v) cimg_forV(img,v) cimg_forXYZ(img,x,y,z)
#define cimg_for_in1(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), _max##i = (int)(i1)<(int)(bound)?(int)(i1):(int)(bound)-1; i<=_max##i; ++i)
#define cimg_for_inX(img,x0,x1,x) cimg_for_in1((img).width,x0,x1,x)
#define cimg_for_inY(img,y0,y1,y) cimg_for_in1((img).height,y0,y1,y)
#define cimg_for_inZ(img,z0,z1,z) cimg_for_in1((img).depth,z0,z1,z)
#define cimg_for_inV(img,v0,v1,v) cimg_for_in1((img).dim,v0,v1,v)
#define cimg_for_inXY(img,x0,y0,x1,y1,x,y) cimg_for_inY(img,y0,y1,y) cimg_for_inX(img,x0,x1,x)
#define cimg_for_inXZ(img,x0,z0,x1,z1,x,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inX(img,x0,x1,x)
#define cimg_for_inXV(img,x0,v0,x1,v1,x,v) cimg_for_inV(img,v0,v1,v) cimg_for_inX(img,x0,x1,x)
#define cimg_for_inYZ(img,y0,z0,y1,z1,y,z) cimg_for_inZ(img,x0,z1,z) cimg_for_inY(img,y0,y1,y)
#define cimg_for_inYV(img,y0,v0,y1,v1,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inY(img,y0,y1,y)
#define cimg_for_inZV(img,z0,v0,z1,v1,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inZ(img,z0,z1,z)
#define cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_inZ(img,z0,z1,z) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
#define cimg_for_inXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXY(img,x0,y0,x1,y1,x,y)
#define cimg_for_inXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXZ(img,x0,z0,x1,z1,x,z)
#define cimg_for_inYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inYZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_inXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_inV(img,v0,v1,v) cimg_for_inXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for_insideX(img,x,n) cimg_for_inX(img,n,(img).width-1-(n),x)
#define cimg_for_insideY(img,y,n) cimg_for_inY(img,n,(img).height-1-(n),y)
#define cimg_for_insideZ(img,z,n) cimg_for_inZ(img,n,(img).depth-1-(n),z)
#define cimg_for_insideV(img,v,n) cimg_for_inV(img,n,(img).dim-1-(n),v)
#define cimg_for_insideXY(img,x,y,n) cimg_for_inXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y)
#define cimg_for_insideXYZ(img,x,y,z,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
#define cimg_for_insideXYZV(img,x,y,z,v,n) cimg_for_inXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
#define cimg_for_out1(boundi,i0,i1,i) \
for (int i = (int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); ++i, i = i==(int)(i0)?(int)(i1)+1:i)
#define cimg_for_out2(boundi,boundj,i0,j0,i1,j1,i,j) \
for (int j = 0; j<(int)(boundj); ++j) \
for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
++i, i = _n1j?i:(i==(int)(i0)?(int)(i1)+1:i))
#define cimg_for_out3(boundi,boundj,boundk,i0,j0,k0,i1,j1,k1,i,j,k) \
for (int k = 0; k<(int)(boundk); ++k) \
for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
++i, i = _n1j || _n1k?i:(i==(int)(i0)?(int)(i1)+1:i))
#define cimg_for_out4(boundi,boundj,boundk,boundl,i0,j0,k0,l0,i1,j1,k1,l1,i,j,k,l) \
for (int l = 0; l<(int)(boundl); ++l) \
for (int _n1l = (int)(l<(int)(l0) || l>(int)(l1)), k = 0; k<(int)(boundk); ++k) \
for (int _n1k = (int)(k<(int)(k0) || k>(int)(k1)), j = 0; j<(int)(boundj); ++j) \
for (int _n1j = (int)(j<(int)(j0) || j>(int)(j1)), i = _n1j || _n1k || _n1l?0:(int)(i0)>0?0:(int)(i1)+1; i<(int)(boundi); \
++i, i = _n1j || _n1k || _n1l?i:(i==(int)(i0)?(int)(i1)+1:i))
#define cimg_for_outX(img,x0,x1,x) cimg_for_out1((img).width,x0,x1,x)
#define cimg_for_outY(img,y0,y1,y) cimg_for_out1((img).height,y0,y1,y)
#define cimg_for_outZ(img,z0,z1,z) cimg_for_out1((img).depth,z0,z1,z)
#define cimg_for_outV(img,v0,v1,v) cimg_for_out1((img).dim,v0,v1,v)
#define cimg_for_outXY(img,x0,y0,x1,y1,x,y) cimg_for_out2((img).width,(img).height,x0,y0,x1,y1,x,y)
#define cimg_for_outXZ(img,x0,z0,x1,z1,x,z) cimg_for_out2((img).width,(img).depth,x0,z0,x1,z1,x,z)
#define cimg_for_outXV(img,x0,v0,x1,v1,x,v) cimg_for_out2((img).width,(img).dim,x0,v0,x1,v1,x,v)
#define cimg_for_outYZ(img,y0,z0,y1,z1,y,z) cimg_for_out2((img).height,(img).depth,y0,z0,y1,z1,y,z)
#define cimg_for_outYV(img,y0,v0,y1,v1,y,v) cimg_for_out2((img).height,(img).dim,y0,v0,y1,v1,y,v)
#define cimg_for_outZV(img,z0,v0,z1,v1,z,v) cimg_for_out2((img).depth,(img).dim,z0,v0,z1,v1,z,v)
#define cimg_for_outXYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_out3((img).width,(img).height,(img).depth,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for_outXYV(img,x0,y0,v0,x1,y1,v1,x,y,v) cimg_for_out3((img).width,(img).height,(img).dim,x0,y0,v0,x1,y1,v1,x,y,v)
#define cimg_for_outXZV(img,x0,z0,v0,x1,z1,v1,x,z,v) cimg_for_out3((img).width,(img).depth,(img).dim,x0,z0,v0,x1,z1,v1,x,z,v)
#define cimg_for_outYZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_out3((img).height,(img).depth,(img).dim,y0,z0,v0,y1,z1,v1,y,z,v)
#define cimg_for_outXYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) \
cimg_for_out4((img).width,(img).height,(img).depth,(img).dim,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v)
#define cimg_for_borderX(img,x,n) cimg_for_outX(img,n,(img).width-1-(n),x)
#define cimg_for_borderY(img,y,n) cimg_for_outY(img,n,(img).height-1-(n),y)
#define cimg_for_borderZ(img,z,n) cimg_for_outZ(img,n,(img).depth-1-(n),z)
#define cimg_for_borderV(img,v,n) cimg_for_outV(img,n,(img).dim-1-(n),v)
#define cimg_for_borderXY(img,x,y,n) cimg_for_outXY(img,n,n,(img).width-1-(n),(img).height-1-(n),x,y)
#define cimg_for_borderXYZ(img,x,y,z,n) cimg_for_outXYZ(img,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),x,y,z)
#define cimg_for_borderXYZV(img,x,y,z,v,n) \
cimg_for_outXYZV(img,n,n,n,n,(img).width-1-(n),(img).height-1-(n),(img).depth-1-(n),(img).dim-1-(n),x,y,z,v)
#define cimg_for_spiralXY(img,x,y) \
for (int x = 0, y = 0, _n1##x = 1, _n1##y = (int)((img).width*(img).height); _n1##y; \
--_n1##y, _n1##x += (_n1##x>>2)-((!(_n1##x&3)?--y:((_n1##x&3)==1?(img).width-1-++x:((_n1##x&3)==2?(img).height-1-++y:--x))))?0:1)
#define cimg_for_lineXY(x,y,x0,y0,x1,y1) \
for (int x = (int)(x0), y = (int)(y0), _sx = 1, _sy = 1, _steep = 0, \
_dx=(x1)>(x0)?(int)(x1)-(int)(x0):(_sx=-1,(int)(x0)-(int)(x1)), \
_dy=(y1)>(y0)?(int)(y1)-(int)(y0):(_sy=-1,(int)(y0)-(int)(y1)), \
_counter = _dx, \
_err = _dx>_dy?(_dy>>1):((_steep=1),(_counter=_dy),(_dx>>1)); \
_counter>=0; \
--_counter, x+=_steep? \
(y+=_sy,(_err-=_dx)<0?_err+=_dy,_sx:0): \
(y+=(_err-=_dy)<0?_err+=_dx,_sy:0,_sx))
#define cimg_for2(bound,i) \
for (int i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1; \
_n1##i<(int)(bound) || i==--_n1##i; \
++i, ++_n1##i)
#define cimg_for2X(img,x) cimg_for2((img).width,x)
#define cimg_for2Y(img,y) cimg_for2((img).height,y)
#define cimg_for2Z(img,z) cimg_for2((img).depth,z)
#define cimg_for2V(img,v) cimg_for2((img).dim,v)
#define cimg_for2XY(img,x,y) cimg_for2Y(img,y) cimg_for2X(img,x)
#define cimg_for2XZ(img,x,z) cimg_for2Z(img,z) cimg_for2X(img,x)
#define cimg_for2XV(img,x,v) cimg_for2V(img,v) cimg_for2X(img,x)
#define cimg_for2YZ(img,y,z) cimg_for2Z(img,z) cimg_for2Y(img,y)
#define cimg_for2YV(img,y,v) cimg_for2V(img,v) cimg_for2Y(img,y)
#define cimg_for2ZV(img,z,v) cimg_for2V(img,v) cimg_for2Z(img,z)
#define cimg_for2XYZ(img,x,y,z) cimg_for2Z(img,z) cimg_for2XY(img,x,y)
#define cimg_for2XZV(img,x,z,v) cimg_for2V(img,v) cimg_for2XZ(img,x,z)
#define cimg_for2YZV(img,y,z,v) cimg_for2V(img,v) cimg_for2YZ(img,y,z)
#define cimg_for2XYZV(img,x,y,z,v) cimg_for2V(img,v) cimg_for2XYZ(img,x,y,z)
#define cimg_for_in2(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
++i, ++_n1##i)
#define cimg_for_in2X(img,x0,x1,x) cimg_for_in2((img).width,x0,x1,x)
#define cimg_for_in2Y(img,y0,y1,y) cimg_for_in2((img).height,y0,y1,y)
#define cimg_for_in2Z(img,z0,z1,z) cimg_for_in2((img).depth,z0,z1,z)
#define cimg_for_in2V(img,v0,v1,v) cimg_for_in2((img).dim,v0,v1,v)
#define cimg_for_in2XY(img,x0,y0,x1,y1,x,y) cimg_for_in2Y(img,y0,y1,y) cimg_for_in2X(img,x0,x1,x)
#define cimg_for_in2XZ(img,x0,z0,x1,z1,x,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2X(img,x0,x1,x)
#define cimg_for_in2XV(img,x0,v0,x1,v1,x,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2X(img,x0,x1,x)
#define cimg_for_in2YZ(img,y0,z0,y1,z1,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2Y(img,y0,y1,y)
#define cimg_for_in2YV(img,y0,v0,y1,v1,y,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Y(img,y0,y1,y)
#define cimg_for_in2ZV(img,z0,v0,z1,v1,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2Z(img,z0,z1,z)
#define cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in2Z(img,z0,z1,z) cimg_for_in2XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in2XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in2YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in2XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in2V(img,v0,v1,v) cimg_for_in2XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for3(bound,i) \
for (int i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1; \
_n1##i<(int)(bound) || i==--_n1##i; \
_p1##i = i++, ++_n1##i)
#define cimg_for3X(img,x) cimg_for3((img).width,x)
#define cimg_for3Y(img,y) cimg_for3((img).height,y)
#define cimg_for3Z(img,z) cimg_for3((img).depth,z)
#define cimg_for3V(img,v) cimg_for3((img).dim,v)
#define cimg_for3XY(img,x,y) cimg_for3Y(img,y) cimg_for3X(img,x)
#define cimg_for3XZ(img,x,z) cimg_for3Z(img,z) cimg_for3X(img,x)
#define cimg_for3XV(img,x,v) cimg_for3V(img,v) cimg_for3X(img,x)
#define cimg_for3YZ(img,y,z) cimg_for3Z(img,z) cimg_for3Y(img,y)
#define cimg_for3YV(img,y,v) cimg_for3V(img,v) cimg_for3Y(img,y)
#define cimg_for3ZV(img,z,v) cimg_for3V(img,v) cimg_for3Z(img,z)
#define cimg_for3XYZ(img,x,y,z) cimg_for3Z(img,z) cimg_for3XY(img,x,y)
#define cimg_for3XZV(img,x,z,v) cimg_for3V(img,v) cimg_for3XZ(img,x,z)
#define cimg_for3YZV(img,y,z,v) cimg_for3V(img,v) cimg_for3YZ(img,y,z)
#define cimg_for3XYZV(img,x,y,z,v) cimg_for3V(img,v) cimg_for3XYZ(img,x,y,z)
#define cimg_for_in3(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1; \
i<=(int)(i1) && (_n1##i<(int)(bound) || i==--_n1##i); \
_p1##i = i++, ++_n1##i)
#define cimg_for_in3X(img,x0,x1,x) cimg_for_in3((img).width,x0,x1,x)
#define cimg_for_in3Y(img,y0,y1,y) cimg_for_in3((img).height,y0,y1,y)
#define cimg_for_in3Z(img,z0,z1,z) cimg_for_in3((img).depth,z0,z1,z)
#define cimg_for_in3V(img,v0,v1,v) cimg_for_in3((img).dim,v0,v1,v)
#define cimg_for_in3XY(img,x0,y0,x1,y1,x,y) cimg_for_in3Y(img,y0,y1,y) cimg_for_in3X(img,x0,x1,x)
#define cimg_for_in3XZ(img,x0,z0,x1,z1,x,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3X(img,x0,x1,x)
#define cimg_for_in3XV(img,x0,v0,x1,v1,x,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3X(img,x0,x1,x)
#define cimg_for_in3YZ(img,y0,z0,y1,z1,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3Y(img,y0,y1,y)
#define cimg_for_in3YV(img,y0,v0,y1,v1,y,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Y(img,y0,y1,y)
#define cimg_for_in3ZV(img,z0,v0,z1,v1,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3Z(img,z0,z1,z)
#define cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in3Z(img,z0,z1,z) cimg_for_in3XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in3XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in3YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in3XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in3V(img,v0,v1,v) cimg_for_in3XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for4(bound,i) \
for (int i = 0, _p1##i = 0, _n1##i = 1>=(bound)?(int)(bound)-1:1, \
_n2##i = 2>=(bound)?(int)(bound)-1:2; \
_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
_p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for4X(img,x) cimg_for4((img).width,x)
#define cimg_for4Y(img,y) cimg_for4((img).height,y)
#define cimg_for4Z(img,z) cimg_for4((img).depth,z)
#define cimg_for4V(img,v) cimg_for4((img).dim,v)
#define cimg_for4XY(img,x,y) cimg_for4Y(img,y) cimg_for4X(img,x)
#define cimg_for4XZ(img,x,z) cimg_for4Z(img,z) cimg_for4X(img,x)
#define cimg_for4XV(img,x,v) cimg_for4V(img,v) cimg_for4X(img,x)
#define cimg_for4YZ(img,y,z) cimg_for4Z(img,z) cimg_for4Y(img,y)
#define cimg_for4YV(img,y,v) cimg_for4V(img,v) cimg_for4Y(img,y)
#define cimg_for4ZV(img,z,v) cimg_for4V(img,v) cimg_for4Z(img,z)
#define cimg_for4XYZ(img,x,y,z) cimg_for4Z(img,z) cimg_for4XY(img,x,y)
#define cimg_for4XZV(img,x,z,v) cimg_for4V(img,v) cimg_for4XZ(img,x,z)
#define cimg_for4YZV(img,y,z,v) cimg_for4V(img,v) cimg_for4YZ(img,y,z)
#define cimg_for4XYZV(img,x,y,z,v) cimg_for4V(img,v) cimg_for4XYZ(img,x,y,z)
#define cimg_for_in4(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
_n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
_p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for_in4X(img,x0,x1,x) cimg_for_in4((img).width,x0,x1,x)
#define cimg_for_in4Y(img,y0,y1,y) cimg_for_in4((img).height,y0,y1,y)
#define cimg_for_in4Z(img,z0,z1,z) cimg_for_in4((img).depth,z0,z1,z)
#define cimg_for_in4V(img,v0,v1,v) cimg_for_in4((img).dim,v0,v1,v)
#define cimg_for_in4XY(img,x0,y0,x1,y1,x,y) cimg_for_in4Y(img,y0,y1,y) cimg_for_in4X(img,x0,x1,x)
#define cimg_for_in4XZ(img,x0,z0,x1,z1,x,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4X(img,x0,x1,x)
#define cimg_for_in4XV(img,x0,v0,x1,v1,x,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4X(img,x0,x1,x)
#define cimg_for_in4YZ(img,y0,z0,y1,z1,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4Y(img,y0,y1,y)
#define cimg_for_in4YV(img,y0,v0,y1,v1,y,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Y(img,y0,y1,y)
#define cimg_for_in4ZV(img,z0,v0,z1,v1,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4Z(img,z0,z1,z)
#define cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in4Z(img,z0,z1,z) cimg_for_in4XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in4XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in4YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in4XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in4V(img,v0,v1,v) cimg_for_in4XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for5(bound,i) \
for (int i = 0, _p2##i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1, \
_n2##i = 2>=(bound)?(int)(bound)-1:2; \
_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i); \
_p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for5X(img,x) cimg_for5((img).width,x)
#define cimg_for5Y(img,y) cimg_for5((img).height,y)
#define cimg_for5Z(img,z) cimg_for5((img).depth,z)
#define cimg_for5V(img,v) cimg_for5((img).dim,v)
#define cimg_for5XY(img,x,y) cimg_for5Y(img,y) cimg_for5X(img,x)
#define cimg_for5XZ(img,x,z) cimg_for5Z(img,z) cimg_for5X(img,x)
#define cimg_for5XV(img,x,v) cimg_for5V(img,v) cimg_for5X(img,x)
#define cimg_for5YZ(img,y,z) cimg_for5Z(img,z) cimg_for5Y(img,y)
#define cimg_for5YV(img,y,v) cimg_for5V(img,v) cimg_for5Y(img,y)
#define cimg_for5ZV(img,z,v) cimg_for5V(img,v) cimg_for5Z(img,z)
#define cimg_for5XYZ(img,x,y,z) cimg_for5Z(img,z) cimg_for5XY(img,x,y)
#define cimg_for5XZV(img,x,z,v) cimg_for5V(img,v) cimg_for5XZ(img,x,z)
#define cimg_for5YZV(img,y,z,v) cimg_for5V(img,v) cimg_for5YZ(img,y,z)
#define cimg_for5XYZV(img,x,y,z,v) cimg_for5V(img,v) cimg_for5XYZ(img,x,y,z)
#define cimg_for_in5(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p2##i = i-2<0?0:i-2, \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
_n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2; \
i<=(int)(i1) && (_n2##i<(int)(bound) || _n1##i==--_n2##i || i==(_n2##i = --_n1##i)); \
_p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i)
#define cimg_for_in5X(img,x0,x1,x) cimg_for_in5((img).width,x0,x1,x)
#define cimg_for_in5Y(img,y0,y1,y) cimg_for_in5((img).height,y0,y1,y)
#define cimg_for_in5Z(img,z0,z1,z) cimg_for_in5((img).depth,z0,z1,z)
#define cimg_for_in5V(img,v0,v1,v) cimg_for_in5((img).dim,v0,v1,v)
#define cimg_for_in5XY(img,x0,y0,x1,y1,x,y) cimg_for_in5Y(img,y0,y1,y) cimg_for_in5X(img,x0,x1,x)
#define cimg_for_in5XZ(img,x0,z0,x1,z1,x,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5X(img,x0,x1,x)
#define cimg_for_in5XV(img,x0,v0,x1,v1,x,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5X(img,x0,x1,x)
#define cimg_for_in5YZ(img,y0,z0,y1,z1,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5Y(img,y0,y1,y)
#define cimg_for_in5YV(img,y0,v0,y1,v1,y,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Y(img,y0,y1,y)
#define cimg_for_in5ZV(img,z0,v0,z1,v1,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5Z(img,z0,z1,z)
#define cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in5Z(img,z0,z1,z) cimg_for_in5XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in5XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in5YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in5XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in5V(img,v0,v1,v) cimg_for_in5XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for6(bound,i) \
for (int i = 0, _p2##i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1, \
_n2##i = 2>=(bound)?(int)(bound)-1:2, \
_n3##i = 3>=(bound)?(int)(bound)-1:3; \
_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
_p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for6X(img,x) cimg_for6((img).width,x)
#define cimg_for6Y(img,y) cimg_for6((img).height,y)
#define cimg_for6Z(img,z) cimg_for6((img).depth,z)
#define cimg_for6V(img,v) cimg_for6((img).dim,v)
#define cimg_for6XY(img,x,y) cimg_for6Y(img,y) cimg_for6X(img,x)
#define cimg_for6XZ(img,x,z) cimg_for6Z(img,z) cimg_for6X(img,x)
#define cimg_for6XV(img,x,v) cimg_for6V(img,v) cimg_for6X(img,x)
#define cimg_for6YZ(img,y,z) cimg_for6Z(img,z) cimg_for6Y(img,y)
#define cimg_for6YV(img,y,v) cimg_for6V(img,v) cimg_for6Y(img,y)
#define cimg_for6ZV(img,z,v) cimg_for6V(img,v) cimg_for6Z(img,z)
#define cimg_for6XYZ(img,x,y,z) cimg_for6Z(img,z) cimg_for6XY(img,x,y)
#define cimg_for6XZV(img,x,z,v) cimg_for6V(img,v) cimg_for6XZ(img,x,z)
#define cimg_for6YZV(img,y,z,v) cimg_for6V(img,v) cimg_for6YZ(img,y,z)
#define cimg_for6XYZV(img,x,y,z,v) cimg_for6V(img,v) cimg_for6XYZ(img,x,y,z)
#define cimg_for_in6(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p2##i = i-2<0?0:i-2, \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
_n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
_n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
_p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for_in6X(img,x0,x1,x) cimg_for_in6((img).width,x0,x1,x)
#define cimg_for_in6Y(img,y0,y1,y) cimg_for_in6((img).height,y0,y1,y)
#define cimg_for_in6Z(img,z0,z1,z) cimg_for_in6((img).depth,z0,z1,z)
#define cimg_for_in6V(img,v0,v1,v) cimg_for_in6((img).dim,v0,v1,v)
#define cimg_for_in6XY(img,x0,y0,x1,y1,x,y) cimg_for_in6Y(img,y0,y1,y) cimg_for_in6X(img,x0,x1,x)
#define cimg_for_in6XZ(img,x0,z0,x1,z1,x,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6X(img,x0,x1,x)
#define cimg_for_in6XV(img,x0,v0,x1,v1,x,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6X(img,x0,x1,x)
#define cimg_for_in6YZ(img,y0,z0,y1,z1,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6Y(img,y0,y1,y)
#define cimg_for_in6YV(img,y0,v0,y1,v1,y,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Y(img,y0,y1,y)
#define cimg_for_in6ZV(img,z0,v0,z1,v1,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6Z(img,z0,z1,z)
#define cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in6Z(img,z0,z1,z) cimg_for_in6XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in6XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in6YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in6XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in6V(img,v0,v1,v) cimg_for_in6XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for7(bound,i) \
for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1, \
_n2##i = 2>=(bound)?(int)(bound)-1:2, \
_n3##i = 3>=(bound)?(int)(bound)-1:3; \
_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i); \
_p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for7X(img,x) cimg_for7((img).width,x)
#define cimg_for7Y(img,y) cimg_for7((img).height,y)
#define cimg_for7Z(img,z) cimg_for7((img).depth,z)
#define cimg_for7V(img,v) cimg_for7((img).dim,v)
#define cimg_for7XY(img,x,y) cimg_for7Y(img,y) cimg_for7X(img,x)
#define cimg_for7XZ(img,x,z) cimg_for7Z(img,z) cimg_for7X(img,x)
#define cimg_for7XV(img,x,v) cimg_for7V(img,v) cimg_for7X(img,x)
#define cimg_for7YZ(img,y,z) cimg_for7Z(img,z) cimg_for7Y(img,y)
#define cimg_for7YV(img,y,v) cimg_for7V(img,v) cimg_for7Y(img,y)
#define cimg_for7ZV(img,z,v) cimg_for7V(img,v) cimg_for7Z(img,z)
#define cimg_for7XYZ(img,x,y,z) cimg_for7Z(img,z) cimg_for7XY(img,x,y)
#define cimg_for7XZV(img,x,z,v) cimg_for7V(img,v) cimg_for7XZ(img,x,z)
#define cimg_for7YZV(img,y,z,v) cimg_for7V(img,v) cimg_for7YZ(img,y,z)
#define cimg_for7XYZV(img,x,y,z,v) cimg_for7V(img,v) cimg_for7XYZ(img,x,y,z)
#define cimg_for_in7(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p3##i = i-3<0?0:i-3, \
_p2##i = i-2<0?0:i-2, \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
_n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
_n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3; \
i<=(int)(i1) && (_n3##i<(int)(bound) || _n2##i==--_n3##i || _n1##i==--_n2##i || i==(_n3##i = _n2##i = --_n1##i)); \
_p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i)
#define cimg_for_in7X(img,x0,x1,x) cimg_for_in7((img).width,x0,x1,x)
#define cimg_for_in7Y(img,y0,y1,y) cimg_for_in7((img).height,y0,y1,y)
#define cimg_for_in7Z(img,z0,z1,z) cimg_for_in7((img).depth,z0,z1,z)
#define cimg_for_in7V(img,v0,v1,v) cimg_for_in7((img).dim,v0,v1,v)
#define cimg_for_in7XY(img,x0,y0,x1,y1,x,y) cimg_for_in7Y(img,y0,y1,y) cimg_for_in7X(img,x0,x1,x)
#define cimg_for_in7XZ(img,x0,z0,x1,z1,x,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7X(img,x0,x1,x)
#define cimg_for_in7XV(img,x0,v0,x1,v1,x,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7X(img,x0,x1,x)
#define cimg_for_in7YZ(img,y0,z0,y1,z1,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7Y(img,y0,y1,y)
#define cimg_for_in7YV(img,y0,v0,y1,v1,y,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Y(img,y0,y1,y)
#define cimg_for_in7ZV(img,z0,v0,z1,v1,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7Z(img,z0,z1,z)
#define cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in7Z(img,z0,z1,z) cimg_for_in7XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in7XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in7YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in7XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in7V(img,v0,v1,v) cimg_for_in7XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for8(bound,i) \
for (int i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1, \
_n2##i = 2>=(bound)?(int)(bound)-1:2, \
_n3##i = 3>=(bound)?(int)(bound)-1:3, \
_n4##i = 4>=(bound)?(int)(bound)-1:4; \
_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
_p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for8X(img,x) cimg_for8((img).width,x)
#define cimg_for8Y(img,y) cimg_for8((img).height,y)
#define cimg_for8Z(img,z) cimg_for8((img).depth,z)
#define cimg_for8V(img,v) cimg_for8((img).dim,v)
#define cimg_for8XY(img,x,y) cimg_for8Y(img,y) cimg_for8X(img,x)
#define cimg_for8XZ(img,x,z) cimg_for8Z(img,z) cimg_for8X(img,x)
#define cimg_for8XV(img,x,v) cimg_for8V(img,v) cimg_for8X(img,x)
#define cimg_for8YZ(img,y,z) cimg_for8Z(img,z) cimg_for8Y(img,y)
#define cimg_for8YV(img,y,v) cimg_for8V(img,v) cimg_for8Y(img,y)
#define cimg_for8ZV(img,z,v) cimg_for8V(img,v) cimg_for8Z(img,z)
#define cimg_for8XYZ(img,x,y,z) cimg_for8Z(img,z) cimg_for8XY(img,x,y)
#define cimg_for8XZV(img,x,z,v) cimg_for8V(img,v) cimg_for8XZ(img,x,z)
#define cimg_for8YZV(img,y,z,v) cimg_for8V(img,v) cimg_for8YZ(img,y,z)
#define cimg_for8XYZV(img,x,y,z,v) cimg_for8V(img,v) cimg_for8XYZ(img,x,y,z)
#define cimg_for_in8(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p3##i = i-3<0?0:i-3, \
_p2##i = i-2<0?0:i-2, \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
_n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
_n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
_n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
_p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for_in8X(img,x0,x1,x) cimg_for_in8((img).width,x0,x1,x)
#define cimg_for_in8Y(img,y0,y1,y) cimg_for_in8((img).height,y0,y1,y)
#define cimg_for_in8Z(img,z0,z1,z) cimg_for_in8((img).depth,z0,z1,z)
#define cimg_for_in8V(img,v0,v1,v) cimg_for_in8((img).dim,v0,v1,v)
#define cimg_for_in8XY(img,x0,y0,x1,y1,x,y) cimg_for_in8Y(img,y0,y1,y) cimg_for_in8X(img,x0,x1,x)
#define cimg_for_in8XZ(img,x0,z0,x1,z1,x,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8X(img,x0,x1,x)
#define cimg_for_in8XV(img,x0,v0,x1,v1,x,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8X(img,x0,x1,x)
#define cimg_for_in8YZ(img,y0,z0,y1,z1,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8Y(img,y0,y1,y)
#define cimg_for_in8YV(img,y0,v0,y1,v1,y,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Y(img,y0,y1,y)
#define cimg_for_in8ZV(img,z0,v0,z1,v1,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8Z(img,z0,z1,z)
#define cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in8Z(img,z0,z1,z) cimg_for_in8XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in8XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in8YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in8XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in8V(img,v0,v1,v) cimg_for_in8XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for9(bound,i) \
for (int i = 0, _p4##i = 0, _p3##i = 0, _p2##i = 0, _p1##i = 0, \
_n1##i = 1>=(int)(bound)?(int)(bound)-1:1, \
_n2##i = 2>=(int)(bound)?(int)(bound)-1:2, \
_n3##i = 3>=(int)(bound)?(int)(bound)-1:3, \
_n4##i = 4>=(int)(bound)?(int)(bound)-1:4; \
_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
i==(_n4##i = _n3##i = _n2##i = --_n1##i); \
_p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for9X(img,x) cimg_for9((img).width,x)
#define cimg_for9Y(img,y) cimg_for9((img).height,y)
#define cimg_for9Z(img,z) cimg_for9((img).depth,z)
#define cimg_for9V(img,v) cimg_for9((img).dim,v)
#define cimg_for9XY(img,x,y) cimg_for9Y(img,y) cimg_for9X(img,x)
#define cimg_for9XZ(img,x,z) cimg_for9Z(img,z) cimg_for9X(img,x)
#define cimg_for9XV(img,x,v) cimg_for9V(img,v) cimg_for9X(img,x)
#define cimg_for9YZ(img,y,z) cimg_for9Z(img,z) cimg_for9Y(img,y)
#define cimg_for9YV(img,y,v) cimg_for9V(img,v) cimg_for9Y(img,y)
#define cimg_for9ZV(img,z,v) cimg_for9V(img,v) cimg_for9Z(img,z)
#define cimg_for9XYZ(img,x,y,z) cimg_for9Z(img,z) cimg_for9XY(img,x,y)
#define cimg_for9XZV(img,x,z,v) cimg_for9V(img,v) cimg_for9XZ(img,x,z)
#define cimg_for9YZV(img,y,z,v) cimg_for9V(img,v) cimg_for9YZ(img,y,z)
#define cimg_for9XYZV(img,x,y,z,v) cimg_for9V(img,v) cimg_for9XYZ(img,x,y,z)
#define cimg_for_in9(bound,i0,i1,i) \
for (int i = (int)(i0)<0?0:(int)(i0), \
_p4##i = i-4<0?0:i-4, \
_p3##i = i-3<0?0:i-3, \
_p2##i = i-2<0?0:i-2, \
_p1##i = i-1<0?0:i-1, \
_n1##i = i+1>=(int)(bound)?(int)(bound)-1:i+1, \
_n2##i = i+2>=(int)(bound)?(int)(bound)-1:i+2, \
_n3##i = i+3>=(int)(bound)?(int)(bound)-1:i+3, \
_n4##i = i+4>=(int)(bound)?(int)(bound)-1:i+4; \
i<=(int)(i1) && (_n4##i<(int)(bound) || _n3##i==--_n4##i || _n2##i==--_n3##i || _n1##i==--_n2##i || \
i==(_n4##i = _n3##i = _n2##i = --_n1##i)); \
_p4##i = _p3##i, _p3##i = _p2##i, _p2##i = _p1##i, _p1##i = i++, ++_n1##i, ++_n2##i, ++_n3##i, ++_n4##i)
#define cimg_for_in9X(img,x0,x1,x) cimg_for_in9((img).width,x0,x1,x)
#define cimg_for_in9Y(img,y0,y1,y) cimg_for_in9((img).height,y0,y1,y)
#define cimg_for_in9Z(img,z0,z1,z) cimg_for_in9((img).depth,z0,z1,z)
#define cimg_for_in9V(img,v0,v1,v) cimg_for_in9((img).dim,v0,v1,v)
#define cimg_for_in9XY(img,x0,y0,x1,y1,x,y) cimg_for_in9Y(img,y0,y1,y) cimg_for_in9X(img,x0,x1,x)
#define cimg_for_in9XZ(img,x0,z0,x1,z1,x,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9X(img,x0,x1,x)
#define cimg_for_in9XV(img,x0,v0,x1,v1,x,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9X(img,x0,x1,x)
#define cimg_for_in9YZ(img,y0,z0,y1,z1,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9Y(img,y0,y1,y)
#define cimg_for_in9YV(img,y0,v0,y1,v1,y,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Y(img,y0,y1,y)
#define cimg_for_in9ZV(img,z0,v0,z1,v1,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9Z(img,z0,z1,z)
#define cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z) cimg_for_in9Z(img,z0,z1,z) cimg_for_in9XY(img,x0,y0,x1,y1,x,y)
#define cimg_for_in9XZV(img,x0,z0,v0,x1,y1,v1,x,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XZ(img,x0,y0,x1,y1,x,z)
#define cimg_for_in9YZV(img,y0,z0,v0,y1,z1,v1,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9YZ(img,y0,z0,y1,z1,y,z)
#define cimg_for_in9XYZV(img,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) cimg_for_in9V(img,v0,v1,v) cimg_for_in9XYZ(img,x0,y0,z0,x1,y1,z1,x,y,z)
#define cimg_for2x2(img,x,y,z,v,I) \
cimg_for2((img).height,y) for (int x = 0, \
_n1##x = (int)( \
(I[0] = (img)(0,y,z,v)), \
(I[2] = (img)(0,_n1##y,z,v)), \
1>=(img).width?(int)((img).width)-1:1); \
(_n1##x<(int)((img).width) && ( \
(I[1] = (img)(_n1##x,y,z,v)), \
(I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \
x==--_n1##x; \
I[0] = I[1], \
I[2] = I[3], \
++x, ++_n1##x)
#define cimg_for_in2x2(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_n1##x = (int)( \
(I[0] = (img)(x,y,z,v)), \
(I[2] = (img)(x,_n1##y,z,v)), \
x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
(I[1] = (img)(_n1##x,y,z,v)), \
(I[3] = (img)(_n1##x,_n1##y,z,v)),1)) || \
x==--_n1##x); \
I[0] = I[1], \
I[2] = I[3], \
++x, ++_n1##x)
#define cimg_for3x3(img,x,y,z,v,I) \
cimg_for3((img).height,y) for (int x = 0, \
_p1##x = 0, \
_n1##x = (int)( \
(I[0] = I[1] = (img)(0,_p1##y,z,v)), \
(I[3] = I[4] = (img)(0,y,z,v)), \
(I[6] = I[7] = (img)(0,_n1##y,z,v)), \
1>=(img).width?(int)((img).width)-1:1); \
(_n1##x<(int)((img).width) && ( \
(I[2] = (img)(_n1##x,_p1##y,z,v)), \
(I[5] = (img)(_n1##x,y,z,v)), \
(I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
x==--_n1##x; \
I[0] = I[1], I[1] = I[2], \
I[3] = I[4], I[4] = I[5], \
I[6] = I[7], I[7] = I[8], \
_p1##x = x++, ++_n1##x)
#define cimg_for_in3x3(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p1##x = x-1<0?0:x-1, \
_n1##x = (int)( \
(I[0] = (img)(_p1##x,_p1##y,z,v)), \
(I[3] = (img)(_p1##x,y,z,v)), \
(I[6] = (img)(_p1##x,_n1##y,z,v)), \
(I[1] = (img)(x,_p1##y,z,v)), \
(I[4] = (img)(x,y,z,v)), \
(I[7] = (img)(x,_n1##y,z,v)), \
x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
(I[2] = (img)(_n1##x,_p1##y,z,v)), \
(I[5] = (img)(_n1##x,y,z,v)), \
(I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
x==--_n1##x); \
I[0] = I[1], I[1] = I[2], \
I[3] = I[4], I[4] = I[5], \
I[6] = I[7], I[7] = I[8], \
_p1##x = x++, ++_n1##x)
#define cimg_for4x4(img,x,y,z,v,I) \
cimg_for4((img).height,y) for (int x = 0, \
_p1##x = 0, \
_n1##x = 1>=(img).width?(int)((img).width)-1:1, \
_n2##x = (int)( \
(I[0] = I[1] = (img)(0,_p1##y,z,v)), \
(I[4] = I[5] = (img)(0,y,z,v)), \
(I[8] = I[9] = (img)(0,_n1##y,z,v)), \
(I[12] = I[13] = (img)(0,_n2##y,z,v)), \
(I[2] = (img)(_n1##x,_p1##y,z,v)), \
(I[6] = (img)(_n1##x,y,z,v)), \
(I[10] = (img)(_n1##x,_n1##y,z,v)), \
(I[14] = (img)(_n1##x,_n2##y,z,v)), \
2>=(img).width?(int)((img).width)-1:2); \
(_n2##x<(int)((img).width) && ( \
(I[3] = (img)(_n2##x,_p1##y,z,v)), \
(I[7] = (img)(_n2##x,y,z,v)), \
(I[11] = (img)(_n2##x,_n1##y,z,v)), \
(I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \
_n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], \
I[4] = I[5], I[5] = I[6], I[6] = I[7], \
I[8] = I[9], I[9] = I[10], I[10] = I[11], \
I[12] = I[13], I[13] = I[14], I[14] = I[15], \
_p1##x = x++, ++_n1##x, ++_n2##x)
#define cimg_for_in4x4(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in4((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p1##x = x-1<0?0:x-1, \
_n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
_n2##x = (int)( \
(I[0] = (img)(_p1##x,_p1##y,z,v)), \
(I[4] = (img)(_p1##x,y,z,v)), \
(I[8] = (img)(_p1##x,_n1##y,z,v)), \
(I[12] = (img)(_p1##x,_n2##y,z,v)), \
(I[1] = (img)(x,_p1##y,z,v)), \
(I[5] = (img)(x,y,z,v)), \
(I[9] = (img)(x,_n1##y,z,v)), \
(I[13] = (img)(x,_n2##y,z,v)), \
(I[2] = (img)(_n1##x,_p1##y,z,v)), \
(I[6] = (img)(_n1##x,y,z,v)), \
(I[10] = (img)(_n1##x,_n1##y,z,v)), \
(I[14] = (img)(_n1##x,_n2##y,z,v)), \
x+2>=(int)(img).width?(int)((img).width)-1:x+2); \
x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \
(I[3] = (img)(_n2##x,_p1##y,z,v)), \
(I[7] = (img)(_n2##x,y,z,v)), \
(I[11] = (img)(_n2##x,_n1##y,z,v)), \
(I[15] = (img)(_n2##x,_n2##y,z,v)),1)) || \
_n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], \
I[4] = I[5], I[5] = I[6], I[6] = I[7], \
I[8] = I[9], I[9] = I[10], I[10] = I[11], \
I[12] = I[13], I[13] = I[14], I[14] = I[15], \
_p1##x = x++, ++_n1##x, ++_n2##x)
#define cimg_for5x5(img,x,y,z,v,I) \
cimg_for5((img).height,y) for (int x = 0, \
_p2##x = 0, _p1##x = 0, \
_n1##x = 1>=(img).width?(int)((img).width)-1:1, \
_n2##x = (int)( \
(I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \
(I[5] = I[6] = I[7] = (img)(0,_p1##y,z,v)), \
(I[10] = I[11] = I[12] = (img)(0,y,z,v)), \
(I[15] = I[16] = I[17] = (img)(0,_n1##y,z,v)), \
(I[20] = I[21] = I[22] = (img)(0,_n2##y,z,v)), \
(I[3] = (img)(_n1##x,_p2##y,z,v)), \
(I[8] = (img)(_n1##x,_p1##y,z,v)), \
(I[13] = (img)(_n1##x,y,z,v)), \
(I[18] = (img)(_n1##x,_n1##y,z,v)), \
(I[23] = (img)(_n1##x,_n2##y,z,v)), \
2>=(img).width?(int)((img).width)-1:2); \
(_n2##x<(int)((img).width) && ( \
(I[4] = (img)(_n2##x,_p2##y,z,v)), \
(I[9] = (img)(_n2##x,_p1##y,z,v)), \
(I[14] = (img)(_n2##x,y,z,v)), \
(I[19] = (img)(_n2##x,_n1##y,z,v)), \
(I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \
_n1##x==--_n2##x || x==(_n2##x = --_n1##x); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
_p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
#define cimg_for_in5x5(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in5((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p2##x = x-2<0?0:x-2, \
_p1##x = x-1<0?0:x-1, \
_n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
_n2##x = (int)( \
(I[0] = (img)(_p2##x,_p2##y,z,v)), \
(I[5] = (img)(_p2##x,_p1##y,z,v)), \
(I[10] = (img)(_p2##x,y,z,v)), \
(I[15] = (img)(_p2##x,_n1##y,z,v)), \
(I[20] = (img)(_p2##x,_n2##y,z,v)), \
(I[1] = (img)(_p1##x,_p2##y,z,v)), \
(I[6] = (img)(_p1##x,_p1##y,z,v)), \
(I[11] = (img)(_p1##x,y,z,v)), \
(I[16] = (img)(_p1##x,_n1##y,z,v)), \
(I[21] = (img)(_p1##x,_n2##y,z,v)), \
(I[2] = (img)(x,_p2##y,z,v)), \
(I[7] = (img)(x,_p1##y,z,v)), \
(I[12] = (img)(x,y,z,v)), \
(I[17] = (img)(x,_n1##y,z,v)), \
(I[22] = (img)(x,_n2##y,z,v)), \
(I[3] = (img)(_n1##x,_p2##y,z,v)), \
(I[8] = (img)(_n1##x,_p1##y,z,v)), \
(I[13] = (img)(_n1##x,y,z,v)), \
(I[18] = (img)(_n1##x,_n1##y,z,v)), \
(I[23] = (img)(_n1##x,_n2##y,z,v)), \
x+2>=(int)(img).width?(int)((img).width)-1:x+2); \
x<=(int)(x1) && ((_n2##x<(int)((img).width) && ( \
(I[4] = (img)(_n2##x,_p2##y,z,v)), \
(I[9] = (img)(_n2##x,_p1##y,z,v)), \
(I[14] = (img)(_n2##x,y,z,v)), \
(I[19] = (img)(_n2##x,_n1##y,z,v)), \
(I[24] = (img)(_n2##x,_n2##y,z,v)),1)) || \
_n1##x==--_n2##x || x==(_n2##x = --_n1##x)); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], \
I[5] = I[6], I[6] = I[7], I[7] = I[8], I[8] = I[9], \
I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], \
I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], \
I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], \
_p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x)
#define cimg_for6x6(img,x,y,z,v,I) \
cimg_for6((img).height,y) for (int x = 0, \
_p2##x = 0, _p1##x = 0, \
_n1##x = 1>=(img).width?(int)((img).width)-1:1, \
_n2##x = 2>=(img).width?(int)((img).width)-1:2, \
_n3##x = (int)( \
(I[0] = I[1] = I[2] = (img)(0,_p2##y,z,v)), \
(I[6] = I[7] = I[8] = (img)(0,_p1##y,z,v)), \
(I[12] = I[13] = I[14] = (img)(0,y,z,v)), \
(I[18] = I[19] = I[20] = (img)(0,_n1##y,z,v)), \
(I[24] = I[25] = I[26] = (img)(0,_n2##y,z,v)), \
(I[30] = I[31] = I[32] = (img)(0,_n3##y,z,v)), \
(I[3] = (img)(_n1##x,_p2##y,z,v)), \
(I[9] = (img)(_n1##x,_p1##y,z,v)), \
(I[15] = (img)(_n1##x,y,z,v)), \
(I[21] = (img)(_n1##x,_n1##y,z,v)), \
(I[27] = (img)(_n1##x,_n2##y,z,v)), \
(I[33] = (img)(_n1##x,_n3##y,z,v)), \
(I[4] = (img)(_n2##x,_p2##y,z,v)), \
(I[10] = (img)(_n2##x,_p1##y,z,v)), \
(I[16] = (img)(_n2##x,y,z,v)), \
(I[22] = (img)(_n2##x,_n1##y,z,v)), \
(I[28] = (img)(_n2##x,_n2##y,z,v)), \
(I[34] = (img)(_n2##x,_n3##y,z,v)), \
3>=(img).width?(int)((img).width)-1:3); \
(_n3##x<(int)((img).width) && ( \
(I[5] = (img)(_n3##x,_p2##y,z,v)), \
(I[11] = (img)(_n3##x,_p1##y,z,v)), \
(I[17] = (img)(_n3##x,y,z,v)), \
(I[23] = (img)(_n3##x,_n1##y,z,v)), \
(I[29] = (img)(_n3##x,_n2##y,z,v)), \
(I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \
_n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
_p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
#define cimg_for_in6x6(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in6((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)x0, \
_p2##x = x-2<0?0:x-2, \
_p1##x = x-1<0?0:x-1, \
_n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
_n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \
_n3##x = (int)( \
(I[0] = (img)(_p2##x,_p2##y,z,v)), \
(I[6] = (img)(_p2##x,_p1##y,z,v)), \
(I[12] = (img)(_p2##x,y,z,v)), \
(I[18] = (img)(_p2##x,_n1##y,z,v)), \
(I[24] = (img)(_p2##x,_n2##y,z,v)), \
(I[30] = (img)(_p2##x,_n3##y,z,v)), \
(I[1] = (img)(_p1##x,_p2##y,z,v)), \
(I[7] = (img)(_p1##x,_p1##y,z,v)), \
(I[13] = (img)(_p1##x,y,z,v)), \
(I[19] = (img)(_p1##x,_n1##y,z,v)), \
(I[25] = (img)(_p1##x,_n2##y,z,v)), \
(I[31] = (img)(_p1##x,_n3##y,z,v)), \
(I[2] = (img)(x,_p2##y,z,v)), \
(I[8] = (img)(x,_p1##y,z,v)), \
(I[14] = (img)(x,y,z,v)), \
(I[20] = (img)(x,_n1##y,z,v)), \
(I[26] = (img)(x,_n2##y,z,v)), \
(I[32] = (img)(x,_n3##y,z,v)), \
(I[3] = (img)(_n1##x,_p2##y,z,v)), \
(I[9] = (img)(_n1##x,_p1##y,z,v)), \
(I[15] = (img)(_n1##x,y,z,v)), \
(I[21] = (img)(_n1##x,_n1##y,z,v)), \
(I[27] = (img)(_n1##x,_n2##y,z,v)), \
(I[33] = (img)(_n1##x,_n3##y,z,v)), \
(I[4] = (img)(_n2##x,_p2##y,z,v)), \
(I[10] = (img)(_n2##x,_p1##y,z,v)), \
(I[16] = (img)(_n2##x,y,z,v)), \
(I[22] = (img)(_n2##x,_n1##y,z,v)), \
(I[28] = (img)(_n2##x,_n2##y,z,v)), \
(I[34] = (img)(_n2##x,_n3##y,z,v)), \
x+3>=(int)(img).width?(int)((img).width)-1:x+3); \
x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \
(I[5] = (img)(_n3##x,_p2##y,z,v)), \
(I[11] = (img)(_n3##x,_p1##y,z,v)), \
(I[17] = (img)(_n3##x,y,z,v)), \
(I[23] = (img)(_n3##x,_n1##y,z,v)), \
(I[29] = (img)(_n3##x,_n2##y,z,v)), \
(I[35] = (img)(_n3##x,_n3##y,z,v)),1)) || \
_n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3## x = _n2##x = --_n1##x)); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], \
I[6] = I[7], I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], \
I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], \
I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
_p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
#define cimg_for7x7(img,x,y,z,v,I) \
cimg_for7((img).height,y) for (int x = 0, \
_p3##x = 0, _p2##x = 0, _p1##x = 0, \
_n1##x = 1>=(img).width?(int)((img).width)-1:1, \
_n2##x = 2>=(img).width?(int)((img).width)-1:2, \
_n3##x = (int)( \
(I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \
(I[7] = I[8] = I[9] = I[10] = (img)(0,_p2##y,z,v)), \
(I[14] = I[15] = I[16] = I[17] = (img)(0,_p1##y,z,v)), \
(I[21] = I[22] = I[23] = I[24] = (img)(0,y,z,v)), \
(I[28] = I[29] = I[30] = I[31] = (img)(0,_n1##y,z,v)), \
(I[35] = I[36] = I[37] = I[38] = (img)(0,_n2##y,z,v)), \
(I[42] = I[43] = I[44] = I[45] = (img)(0,_n3##y,z,v)), \
(I[4] = (img)(_n1##x,_p3##y,z,v)), \
(I[11] = (img)(_n1##x,_p2##y,z,v)), \
(I[18] = (img)(_n1##x,_p1##y,z,v)), \
(I[25] = (img)(_n1##x,y,z,v)), \
(I[32] = (img)(_n1##x,_n1##y,z,v)), \
(I[39] = (img)(_n1##x,_n2##y,z,v)), \
(I[46] = (img)(_n1##x,_n3##y,z,v)), \
(I[5] = (img)(_n2##x,_p3##y,z,v)), \
(I[12] = (img)(_n2##x,_p2##y,z,v)), \
(I[19] = (img)(_n2##x,_p1##y,z,v)), \
(I[26] = (img)(_n2##x,y,z,v)), \
(I[33] = (img)(_n2##x,_n1##y,z,v)), \
(I[40] = (img)(_n2##x,_n2##y,z,v)), \
(I[47] = (img)(_n2##x,_n3##y,z,v)), \
3>=(img).width?(int)((img).width)-1:3); \
(_n3##x<(int)((img).width) && ( \
(I[6] = (img)(_n3##x,_p3##y,z,v)), \
(I[13] = (img)(_n3##x,_p2##y,z,v)), \
(I[20] = (img)(_n3##x,_p1##y,z,v)), \
(I[27] = (img)(_n3##x,y,z,v)), \
(I[34] = (img)(_n3##x,_n1##y,z,v)), \
(I[41] = (img)(_n3##x,_n2##y,z,v)), \
(I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \
_n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
_p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
#define cimg_for_in7x7(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in7((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p3##x = x-3<0?0:x-3, \
_p2##x = x-2<0?0:x-2, \
_p1##x = x-1<0?0:x-1, \
_n1##x = x+1>=(int)(img).width?(int)((img).width)-1:x+1, \
_n2##x = x+2>=(int)(img).width?(int)((img).width)-1:x+2, \
_n3##x = (int)( \
(I[0] = (img)(_p3##x,_p3##y,z,v)), \
(I[7] = (img)(_p3##x,_p2##y,z,v)), \
(I[14] = (img)(_p3##x,_p1##y,z,v)), \
(I[21] = (img)(_p3##x,y,z,v)), \
(I[28] = (img)(_p3##x,_n1##y,z,v)), \
(I[35] = (img)(_p3##x,_n2##y,z,v)), \
(I[42] = (img)(_p3##x,_n3##y,z,v)), \
(I[1] = (img)(_p2##x,_p3##y,z,v)), \
(I[8] = (img)(_p2##x,_p2##y,z,v)), \
(I[15] = (img)(_p2##x,_p1##y,z,v)), \
(I[22] = (img)(_p2##x,y,z,v)), \
(I[29] = (img)(_p2##x,_n1##y,z,v)), \
(I[36] = (img)(_p2##x,_n2##y,z,v)), \
(I[43] = (img)(_p2##x,_n3##y,z,v)), \
(I[2] = (img)(_p1##x,_p3##y,z,v)), \
(I[9] = (img)(_p1##x,_p2##y,z,v)), \
(I[16] = (img)(_p1##x,_p1##y,z,v)), \
(I[23] = (img)(_p1##x,y,z,v)), \
(I[30] = (img)(_p1##x,_n1##y,z,v)), \
(I[37] = (img)(_p1##x,_n2##y,z,v)), \
(I[44] = (img)(_p1##x,_n3##y,z,v)), \
(I[3] = (img)(x,_p3##y,z,v)), \
(I[10] = (img)(x,_p2##y,z,v)), \
(I[17] = (img)(x,_p1##y,z,v)), \
(I[24] = (img)(x,y,z,v)), \
(I[31] = (img)(x,_n1##y,z,v)), \
(I[38] = (img)(x,_n2##y,z,v)), \
(I[45] = (img)(x,_n3##y,z,v)), \
(I[4] = (img)(_n1##x,_p3##y,z,v)), \
(I[11] = (img)(_n1##x,_p2##y,z,v)), \
(I[18] = (img)(_n1##x,_p1##y,z,v)), \
(I[25] = (img)(_n1##x,y,z,v)), \
(I[32] = (img)(_n1##x,_n1##y,z,v)), \
(I[39] = (img)(_n1##x,_n2##y,z,v)), \
(I[46] = (img)(_n1##x,_n3##y,z,v)), \
(I[5] = (img)(_n2##x,_p3##y,z,v)), \
(I[12] = (img)(_n2##x,_p2##y,z,v)), \
(I[19] = (img)(_n2##x,_p1##y,z,v)), \
(I[26] = (img)(_n2##x,y,z,v)), \
(I[33] = (img)(_n2##x,_n1##y,z,v)), \
(I[40] = (img)(_n2##x,_n2##y,z,v)), \
(I[47] = (img)(_n2##x,_n3##y,z,v)), \
x+3>=(int)(img).width?(int)((img).width)-1:x+3); \
x<=(int)(x1) && ((_n3##x<(int)((img).width) && ( \
(I[6] = (img)(_n3##x,_p3##y,z,v)), \
(I[13] = (img)(_n3##x,_p2##y,z,v)), \
(I[20] = (img)(_n3##x,_p1##y,z,v)), \
(I[27] = (img)(_n3##x,y,z,v)), \
(I[34] = (img)(_n3##x,_n1##y,z,v)), \
(I[41] = (img)(_n3##x,_n2##y,z,v)), \
(I[48] = (img)(_n3##x,_n3##y,z,v)),1)) || \
_n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n3##x = _n2##x = --_n1##x)); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], \
I[7] = I[8], I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], \
I[14] = I[15], I[15] = I[16], I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], \
I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], I[26] = I[27], \
I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], \
I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], \
I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], I[47] = I[48], \
_p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x)
#define cimg_for8x8(img,x,y,z,v,I) \
cimg_for8((img).height,y) for (int x = 0, \
_p3##x = 0, _p2##x = 0, _p1##x = 0, \
_n1##x = 1>=((img).width)?(int)((img).width)-1:1, \
_n2##x = 2>=((img).width)?(int)((img).width)-1:2, \
_n3##x = 3>=((img).width)?(int)((img).width)-1:3, \
_n4##x = (int)( \
(I[0] = I[1] = I[2] = I[3] = (img)(0,_p3##y,z,v)), \
(I[8] = I[9] = I[10] = I[11] = (img)(0,_p2##y,z,v)), \
(I[16] = I[17] = I[18] = I[19] = (img)(0,_p1##y,z,v)), \
(I[24] = I[25] = I[26] = I[27] = (img)(0,y,z,v)), \
(I[32] = I[33] = I[34] = I[35] = (img)(0,_n1##y,z,v)), \
(I[40] = I[41] = I[42] = I[43] = (img)(0,_n2##y,z,v)), \
(I[48] = I[49] = I[50] = I[51] = (img)(0,_n3##y,z,v)), \
(I[56] = I[57] = I[58] = I[59] = (img)(0,_n4##y,z,v)), \
(I[4] = (img)(_n1##x,_p3##y,z,v)), \
(I[12] = (img)(_n1##x,_p2##y,z,v)), \
(I[20] = (img)(_n1##x,_p1##y,z,v)), \
(I[28] = (img)(_n1##x,y,z,v)), \
(I[36] = (img)(_n1##x,_n1##y,z,v)), \
(I[44] = (img)(_n1##x,_n2##y,z,v)), \
(I[52] = (img)(_n1##x,_n3##y,z,v)), \
(I[60] = (img)(_n1##x,_n4##y,z,v)), \
(I[5] = (img)(_n2##x,_p3##y,z,v)), \
(I[13] = (img)(_n2##x,_p2##y,z,v)), \
(I[21] = (img)(_n2##x,_p1##y,z,v)), \
(I[29] = (img)(_n2##x,y,z,v)), \
(I[37] = (img)(_n2##x,_n1##y,z,v)), \
(I[45] = (img)(_n2##x,_n2##y,z,v)), \
(I[53] = (img)(_n2##x,_n3##y,z,v)), \
(I[61] = (img)(_n2##x,_n4##y,z,v)), \
(I[6] = (img)(_n3##x,_p3##y,z,v)), \
(I[14] = (img)(_n3##x,_p2##y,z,v)), \
(I[22] = (img)(_n3##x,_p1##y,z,v)), \
(I[30] = (img)(_n3##x,y,z,v)), \
(I[38] = (img)(_n3##x,_n1##y,z,v)), \
(I[46] = (img)(_n3##x,_n2##y,z,v)), \
(I[54] = (img)(_n3##x,_n3##y,z,v)), \
(I[62] = (img)(_n3##x,_n4##y,z,v)), \
4>=((img).width)?(int)((img).width)-1:4); \
(_n4##x<(int)((img).width) && ( \
(I[7] = (img)(_n4##x,_p3##y,z,v)), \
(I[15] = (img)(_n4##x,_p2##y,z,v)), \
(I[23] = (img)(_n4##x,_p1##y,z,v)), \
(I[31] = (img)(_n4##x,y,z,v)), \
(I[39] = (img)(_n4##x,_n1##y,z,v)), \
(I[47] = (img)(_n4##x,_n2##y,z,v)), \
(I[55] = (img)(_n4##x,_n3##y,z,v)), \
(I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \
_n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
_p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
#define cimg_for_in8x8(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in8((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p3##x = x-3<0?0:x-3, \
_p2##x = x-2<0?0:x-2, \
_p1##x = x-1<0?0:x-1, \
_n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \
_n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \
_n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \
_n4##x = (int)( \
(I[0] = (img)(_p3##x,_p3##y,z,v)), \
(I[8] = (img)(_p3##x,_p2##y,z,v)), \
(I[16] = (img)(_p3##x,_p1##y,z,v)), \
(I[24] = (img)(_p3##x,y,z,v)), \
(I[32] = (img)(_p3##x,_n1##y,z,v)), \
(I[40] = (img)(_p3##x,_n2##y,z,v)), \
(I[48] = (img)(_p3##x,_n3##y,z,v)), \
(I[56] = (img)(_p3##x,_n4##y,z,v)), \
(I[1] = (img)(_p2##x,_p3##y,z,v)), \
(I[9] = (img)(_p2##x,_p2##y,z,v)), \
(I[17] = (img)(_p2##x,_p1##y,z,v)), \
(I[25] = (img)(_p2##x,y,z,v)), \
(I[33] = (img)(_p2##x,_n1##y,z,v)), \
(I[41] = (img)(_p2##x,_n2##y,z,v)), \
(I[49] = (img)(_p2##x,_n3##y,z,v)), \
(I[57] = (img)(_p2##x,_n4##y,z,v)), \
(I[2] = (img)(_p1##x,_p3##y,z,v)), \
(I[10] = (img)(_p1##x,_p2##y,z,v)), \
(I[18] = (img)(_p1##x,_p1##y,z,v)), \
(I[26] = (img)(_p1##x,y,z,v)), \
(I[34] = (img)(_p1##x,_n1##y,z,v)), \
(I[42] = (img)(_p1##x,_n2##y,z,v)), \
(I[50] = (img)(_p1##x,_n3##y,z,v)), \
(I[58] = (img)(_p1##x,_n4##y,z,v)), \
(I[3] = (img)(x,_p3##y,z,v)), \
(I[11] = (img)(x,_p2##y,z,v)), \
(I[19] = (img)(x,_p1##y,z,v)), \
(I[27] = (img)(x,y,z,v)), \
(I[35] = (img)(x,_n1##y,z,v)), \
(I[43] = (img)(x,_n2##y,z,v)), \
(I[51] = (img)(x,_n3##y,z,v)), \
(I[59] = (img)(x,_n4##y,z,v)), \
(I[4] = (img)(_n1##x,_p3##y,z,v)), \
(I[12] = (img)(_n1##x,_p2##y,z,v)), \
(I[20] = (img)(_n1##x,_p1##y,z,v)), \
(I[28] = (img)(_n1##x,y,z,v)), \
(I[36] = (img)(_n1##x,_n1##y,z,v)), \
(I[44] = (img)(_n1##x,_n2##y,z,v)), \
(I[52] = (img)(_n1##x,_n3##y,z,v)), \
(I[60] = (img)(_n1##x,_n4##y,z,v)), \
(I[5] = (img)(_n2##x,_p3##y,z,v)), \
(I[13] = (img)(_n2##x,_p2##y,z,v)), \
(I[21] = (img)(_n2##x,_p1##y,z,v)), \
(I[29] = (img)(_n2##x,y,z,v)), \
(I[37] = (img)(_n2##x,_n1##y,z,v)), \
(I[45] = (img)(_n2##x,_n2##y,z,v)), \
(I[53] = (img)(_n2##x,_n3##y,z,v)), \
(I[61] = (img)(_n2##x,_n4##y,z,v)), \
(I[6] = (img)(_n3##x,_p3##y,z,v)), \
(I[14] = (img)(_n3##x,_p2##y,z,v)), \
(I[22] = (img)(_n3##x,_p1##y,z,v)), \
(I[30] = (img)(_n3##x,y,z,v)), \
(I[38] = (img)(_n3##x,_n1##y,z,v)), \
(I[46] = (img)(_n3##x,_n2##y,z,v)), \
(I[54] = (img)(_n3##x,_n3##y,z,v)), \
(I[62] = (img)(_n3##x,_n4##y,z,v)), \
x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \
x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \
(I[7] = (img)(_n4##x,_p3##y,z,v)), \
(I[15] = (img)(_n4##x,_p2##y,z,v)), \
(I[23] = (img)(_n4##x,_p1##y,z,v)), \
(I[31] = (img)(_n4##x,y,z,v)), \
(I[39] = (img)(_n4##x,_n1##y,z,v)), \
(I[47] = (img)(_n4##x,_n2##y,z,v)), \
(I[55] = (img)(_n4##x,_n3##y,z,v)), \
(I[63] = (img)(_n4##x,_n4##y,z,v)),1)) || \
_n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], \
I[8] = I[9], I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], \
I[16] = I[17], I[17] = I[18], I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], \
I[24] = I[25], I[25] = I[26], I[26] = I[27], I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], \
I[32] = I[33], I[33] = I[34], I[34] = I[35], I[35] = I[36], I[36] = I[37], I[37] = I[38], I[38] = I[39], \
I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], I[44] = I[45], I[45] = I[46], I[46] = I[47], \
I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], I[53] = I[54], I[54] = I[55], \
I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], I[62] = I[63], \
_p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
#define cimg_for9x9(img,x,y,z,v,I) \
cimg_for9((img).height,y) for (int x = 0, \
_p4##x = 0, _p3##x = 0, _p2##x = 0, _p1##x = 0, \
_n1##x = 1>=((img).width)?(int)((img).width)-1:1, \
_n2##x = 2>=((img).width)?(int)((img).width)-1:2, \
_n3##x = 3>=((img).width)?(int)((img).width)-1:3, \
_n4##x = (int)( \
(I[0] = I[1] = I[2] = I[3] = I[4] = (img)(0,_p4##y,z,v)), \
(I[9] = I[10] = I[11] = I[12] = I[13] = (img)(0,_p3##y,z,v)), \
(I[18] = I[19] = I[20] = I[21] = I[22] = (img)(0,_p2##y,z,v)), \
(I[27] = I[28] = I[29] = I[30] = I[31] = (img)(0,_p1##y,z,v)), \
(I[36] = I[37] = I[38] = I[39] = I[40] = (img)(0,y,z,v)), \
(I[45] = I[46] = I[47] = I[48] = I[49] = (img)(0,_n1##y,z,v)), \
(I[54] = I[55] = I[56] = I[57] = I[58] = (img)(0,_n2##y,z,v)), \
(I[63] = I[64] = I[65] = I[66] = I[67] = (img)(0,_n3##y,z,v)), \
(I[72] = I[73] = I[74] = I[75] = I[76] = (img)(0,_n4##y,z,v)), \
(I[5] = (img)(_n1##x,_p4##y,z,v)), \
(I[14] = (img)(_n1##x,_p3##y,z,v)), \
(I[23] = (img)(_n1##x,_p2##y,z,v)), \
(I[32] = (img)(_n1##x,_p1##y,z,v)), \
(I[41] = (img)(_n1##x,y,z,v)), \
(I[50] = (img)(_n1##x,_n1##y,z,v)), \
(I[59] = (img)(_n1##x,_n2##y,z,v)), \
(I[68] = (img)(_n1##x,_n3##y,z,v)), \
(I[77] = (img)(_n1##x,_n4##y,z,v)), \
(I[6] = (img)(_n2##x,_p4##y,z,v)), \
(I[15] = (img)(_n2##x,_p3##y,z,v)), \
(I[24] = (img)(_n2##x,_p2##y,z,v)), \
(I[33] = (img)(_n2##x,_p1##y,z,v)), \
(I[42] = (img)(_n2##x,y,z,v)), \
(I[51] = (img)(_n2##x,_n1##y,z,v)), \
(I[60] = (img)(_n2##x,_n2##y,z,v)), \
(I[69] = (img)(_n2##x,_n3##y,z,v)), \
(I[78] = (img)(_n2##x,_n4##y,z,v)), \
(I[7] = (img)(_n3##x,_p4##y,z,v)), \
(I[16] = (img)(_n3##x,_p3##y,z,v)), \
(I[25] = (img)(_n3##x,_p2##y,z,v)), \
(I[34] = (img)(_n3##x,_p1##y,z,v)), \
(I[43] = (img)(_n3##x,y,z,v)), \
(I[52] = (img)(_n3##x,_n1##y,z,v)), \
(I[61] = (img)(_n3##x,_n2##y,z,v)), \
(I[70] = (img)(_n3##x,_n3##y,z,v)), \
(I[79] = (img)(_n3##x,_n4##y,z,v)), \
4>=((img).width)?(int)((img).width)-1:4); \
(_n4##x<(int)((img).width) && ( \
(I[8] = (img)(_n4##x,_p4##y,z,v)), \
(I[17] = (img)(_n4##x,_p3##y,z,v)), \
(I[26] = (img)(_n4##x,_p2##y,z,v)), \
(I[35] = (img)(_n4##x,_p1##y,z,v)), \
(I[44] = (img)(_n4##x,y,z,v)), \
(I[53] = (img)(_n4##x,_n1##y,z,v)), \
(I[62] = (img)(_n4##x,_n2##y,z,v)), \
(I[71] = (img)(_n4##x,_n3##y,z,v)), \
(I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \
_n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
_p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
#define cimg_for_in9x9(img,x0,y0,x1,y1,x,y,z,v,I) \
cimg_for_in9((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p4##x = x-4<0?0:x-4, \
_p3##x = x-3<0?0:x-3, \
_p2##x = x-2<0?0:x-2, \
_p1##x = x-1<0?0:x-1, \
_n1##x = x+1>=(int)((img).width)?(int)((img).width)-1:x+1, \
_n2##x = x+2>=(int)((img).width)?(int)((img).width)-1:x+2, \
_n3##x = x+3>=(int)((img).width)?(int)((img).width)-1:x+3, \
_n4##x = (int)( \
(I[0] = (img)(_p4##x,_p4##y,z,v)), \
(I[9] = (img)(_p4##x,_p3##y,z,v)), \
(I[18] = (img)(_p4##x,_p2##y,z,v)), \
(I[27] = (img)(_p4##x,_p1##y,z,v)), \
(I[36] = (img)(_p4##x,y,z,v)), \
(I[45] = (img)(_p4##x,_n1##y,z,v)), \
(I[54] = (img)(_p4##x,_n2##y,z,v)), \
(I[63] = (img)(_p4##x,_n3##y,z,v)), \
(I[72] = (img)(_p4##x,_n4##y,z,v)), \
(I[1] = (img)(_p3##x,_p4##y,z,v)), \
(I[10] = (img)(_p3##x,_p3##y,z,v)), \
(I[19] = (img)(_p3##x,_p2##y,z,v)), \
(I[28] = (img)(_p3##x,_p1##y,z,v)), \
(I[37] = (img)(_p3##x,y,z,v)), \
(I[46] = (img)(_p3##x,_n1##y,z,v)), \
(I[55] = (img)(_p3##x,_n2##y,z,v)), \
(I[64] = (img)(_p3##x,_n3##y,z,v)), \
(I[73] = (img)(_p3##x,_n4##y,z,v)), \
(I[2] = (img)(_p2##x,_p4##y,z,v)), \
(I[11] = (img)(_p2##x,_p3##y,z,v)), \
(I[20] = (img)(_p2##x,_p2##y,z,v)), \
(I[29] = (img)(_p2##x,_p1##y,z,v)), \
(I[38] = (img)(_p2##x,y,z,v)), \
(I[47] = (img)(_p2##x,_n1##y,z,v)), \
(I[56] = (img)(_p2##x,_n2##y,z,v)), \
(I[65] = (img)(_p2##x,_n3##y,z,v)), \
(I[74] = (img)(_p2##x,_n4##y,z,v)), \
(I[3] = (img)(_p1##x,_p4##y,z,v)), \
(I[12] = (img)(_p1##x,_p3##y,z,v)), \
(I[21] = (img)(_p1##x,_p2##y,z,v)), \
(I[30] = (img)(_p1##x,_p1##y,z,v)), \
(I[39] = (img)(_p1##x,y,z,v)), \
(I[48] = (img)(_p1##x,_n1##y,z,v)), \
(I[57] = (img)(_p1##x,_n2##y,z,v)), \
(I[66] = (img)(_p1##x,_n3##y,z,v)), \
(I[75] = (img)(_p1##x,_n4##y,z,v)), \
(I[4] = (img)(x,_p4##y,z,v)), \
(I[13] = (img)(x,_p3##y,z,v)), \
(I[22] = (img)(x,_p2##y,z,v)), \
(I[31] = (img)(x,_p1##y,z,v)), \
(I[40] = (img)(x,y,z,v)), \
(I[49] = (img)(x,_n1##y,z,v)), \
(I[58] = (img)(x,_n2##y,z,v)), \
(I[67] = (img)(x,_n3##y,z,v)), \
(I[76] = (img)(x,_n4##y,z,v)), \
(I[5] = (img)(_n1##x,_p4##y,z,v)), \
(I[14] = (img)(_n1##x,_p3##y,z,v)), \
(I[23] = (img)(_n1##x,_p2##y,z,v)), \
(I[32] = (img)(_n1##x,_p1##y,z,v)), \
(I[41] = (img)(_n1##x,y,z,v)), \
(I[50] = (img)(_n1##x,_n1##y,z,v)), \
(I[59] = (img)(_n1##x,_n2##y,z,v)), \
(I[68] = (img)(_n1##x,_n3##y,z,v)), \
(I[77] = (img)(_n1##x,_n4##y,z,v)), \
(I[6] = (img)(_n2##x,_p4##y,z,v)), \
(I[15] = (img)(_n2##x,_p3##y,z,v)), \
(I[24] = (img)(_n2##x,_p2##y,z,v)), \
(I[33] = (img)(_n2##x,_p1##y,z,v)), \
(I[42] = (img)(_n2##x,y,z,v)), \
(I[51] = (img)(_n2##x,_n1##y,z,v)), \
(I[60] = (img)(_n2##x,_n2##y,z,v)), \
(I[69] = (img)(_n2##x,_n3##y,z,v)), \
(I[78] = (img)(_n2##x,_n4##y,z,v)), \
(I[7] = (img)(_n3##x,_p4##y,z,v)), \
(I[16] = (img)(_n3##x,_p3##y,z,v)), \
(I[25] = (img)(_n3##x,_p2##y,z,v)), \
(I[34] = (img)(_n3##x,_p1##y,z,v)), \
(I[43] = (img)(_n3##x,y,z,v)), \
(I[52] = (img)(_n3##x,_n1##y,z,v)), \
(I[61] = (img)(_n3##x,_n2##y,z,v)), \
(I[70] = (img)(_n3##x,_n3##y,z,v)), \
(I[79] = (img)(_n3##x,_n4##y,z,v)), \
x+4>=(int)((img).width)?(int)((img).width)-1:x+4); \
x<=(int)(x1) && ((_n4##x<(int)((img).width) && ( \
(I[8] = (img)(_n4##x,_p4##y,z,v)), \
(I[17] = (img)(_n4##x,_p3##y,z,v)), \
(I[26] = (img)(_n4##x,_p2##y,z,v)), \
(I[35] = (img)(_n4##x,_p1##y,z,v)), \
(I[44] = (img)(_n4##x,y,z,v)), \
(I[53] = (img)(_n4##x,_n1##y,z,v)), \
(I[62] = (img)(_n4##x,_n2##y,z,v)), \
(I[71] = (img)(_n4##x,_n3##y,z,v)), \
(I[80] = (img)(_n4##x,_n4##y,z,v)),1)) || \
_n3##x==--_n4##x || _n2##x==--_n3##x || _n1##x==--_n2##x || x==(_n4##x = _n3##x = _n2##x = --_n1##x)); \
I[0] = I[1], I[1] = I[2], I[2] = I[3], I[3] = I[4], I[4] = I[5], I[5] = I[6], I[6] = I[7], I[7] = I[8], \
I[9] = I[10], I[10] = I[11], I[11] = I[12], I[12] = I[13], I[13] = I[14], I[14] = I[15], I[15] = I[16], I[16] = I[17], \
I[18] = I[19], I[19] = I[20], I[20] = I[21], I[21] = I[22], I[22] = I[23], I[23] = I[24], I[24] = I[25], I[25] = I[26], \
I[27] = I[28], I[28] = I[29], I[29] = I[30], I[30] = I[31], I[31] = I[32], I[32] = I[33], I[33] = I[34], I[34] = I[35], \
I[36] = I[37], I[37] = I[38], I[38] = I[39], I[39] = I[40], I[40] = I[41], I[41] = I[42], I[42] = I[43], I[43] = I[44], \
I[45] = I[46], I[46] = I[47], I[47] = I[48], I[48] = I[49], I[49] = I[50], I[50] = I[51], I[51] = I[52], I[52] = I[53], \
I[54] = I[55], I[55] = I[56], I[56] = I[57], I[57] = I[58], I[58] = I[59], I[59] = I[60], I[60] = I[61], I[61] = I[62], \
I[63] = I[64], I[64] = I[65], I[65] = I[66], I[66] = I[67], I[67] = I[68], I[68] = I[69], I[69] = I[70], I[70] = I[71], \
I[72] = I[73], I[73] = I[74], I[74] = I[75], I[75] = I[76], I[76] = I[77], I[77] = I[78], I[78] = I[79], I[79] = I[80], \
_p4##x = _p3##x, _p3##x = _p2##x, _p2##x = _p1##x, _p1##x = x++, ++_n1##x, ++_n2##x, ++_n3##x, ++_n4##x)
#define cimg_for2x2x2(img,x,y,z,v,I) \
cimg_for2((img).depth,z) cimg_for2((img).height,y) for (int x = 0, \
_n1##x = (int)( \
(I[0] = (img)(0,y,z,v)), \
(I[2] = (img)(0,_n1##y,z,v)), \
(I[4] = (img)(0,y,_n1##z,v)), \
(I[6] = (img)(0,_n1##y,_n1##z,v)), \
1>=(img).width?(int)((img).width)-1:1); \
(_n1##x<(int)((img).width) && ( \
(I[1] = (img)(_n1##x,y,z,v)), \
(I[3] = (img)(_n1##x,_n1##y,z,v)), \
(I[5] = (img)(_n1##x,y,_n1##z,v)), \
(I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
x==--_n1##x; \
I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
++x, ++_n1##x)
#define cimg_for_in2x2x2(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \
cimg_for_in2((img).depth,z0,z1,z) cimg_for_in2((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_n1##x = (int)( \
(I[0] = (img)(x,y,z,v)), \
(I[2] = (img)(x,_n1##y,z,v)), \
(I[4] = (img)(x,y,_n1##z,v)), \
(I[6] = (img)(x,_n1##y,_n1##z,v)), \
x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
(I[1] = (img)(_n1##x,y,z,v)), \
(I[3] = (img)(_n1##x,_n1##y,z,v)), \
(I[5] = (img)(_n1##x,y,_n1##z,v)), \
(I[7] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
x==--_n1##x); \
I[0] = I[1], I[2] = I[3], I[4] = I[5], I[6] = I[7], \
++x, ++_n1##x)
#define cimg_for3x3x3(img,x,y,z,v,I) \
cimg_for3((img).depth,z) cimg_for3((img).height,y) for (int x = 0, \
_p1##x = 0, \
_n1##x = (int)( \
(I[0] = I[1] = (img)(0,_p1##y,_p1##z,v)), \
(I[3] = I[4] = (img)(0,y,_p1##z,v)), \
(I[6] = I[7] = (img)(0,_n1##y,_p1##z,v)), \
(I[9] = I[10] = (img)(0,_p1##y,z,v)), \
(I[12] = I[13] = (img)(0,y,z,v)), \
(I[15] = I[16] = (img)(0,_n1##y,z,v)), \
(I[18] = I[19] = (img)(0,_p1##y,_n1##z,v)), \
(I[21] = I[22] = (img)(0,y,_n1##z,v)), \
(I[24] = I[25] = (img)(0,_n1##y,_n1##z,v)), \
1>=(img).width?(int)((img).width)-1:1); \
(_n1##x<(int)((img).width) && ( \
(I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \
(I[5] = (img)(_n1##x,y,_p1##z,v)), \
(I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \
(I[11] = (img)(_n1##x,_p1##y,z,v)), \
(I[14] = (img)(_n1##x,y,z,v)), \
(I[17] = (img)(_n1##x,_n1##y,z,v)), \
(I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \
(I[23] = (img)(_n1##x,y,_n1##z,v)), \
(I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
x==--_n1##x; \
I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
_p1##x = x++, ++_n1##x)
#define cimg_for_in3x3x3(img,x0,y0,z0,x1,y1,z1,x,y,z,v,I) \
cimg_for_in3((img).depth,z0,z1,z) cimg_for_in3((img).height,y0,y1,y) for (int x = (int)(x0)<0?0:(int)(x0), \
_p1##x = x-1<0?0:x-1, \
_n1##x = (int)( \
(I[0] = (img)(_p1##x,_p1##y,_p1##z,v)), \
(I[3] = (img)(_p1##x,y,_p1##z,v)), \
(I[6] = (img)(_p1##x,_n1##y,_p1##z,v)), \
(I[9] = (img)(_p1##x,_p1##y,z,v)), \
(I[12] = (img)(_p1##x,y,z,v)), \
(I[15] = (img)(_p1##x,_n1##y,z,v)), \
(I[18] = (img)(_p1##x,_p1##y,_n1##z,v)), \
(I[21] = (img)(_p1##x,y,_n1##z,v)), \
(I[24] = (img)(_p1##x,_n1##y,_n1##z,v)), \
(I[1] = (img)(x,_p1##y,_p1##z,v)), \
(I[4] = (img)(x,y,_p1##z,v)), \
(I[7] = (img)(x,_n1##y,_p1##z,v)), \
(I[10] = (img)(x,_p1##y,z,v)), \
(I[13] = (img)(x,y,z,v)), \
(I[16] = (img)(x,_n1##y,z,v)), \
(I[19] = (img)(x,_p1##y,_n1##z,v)), \
(I[22] = (img)(x,y,_n1##z,v)), \
(I[25] = (img)(x,_n1##y,_n1##z,v)), \
x+1>=(int)(img).width?(int)((img).width)-1:x+1); \
x<=(int)(x1) && ((_n1##x<(int)((img).width) && ( \
(I[2] = (img)(_n1##x,_p1##y,_p1##z,v)), \
(I[5] = (img)(_n1##x,y,_p1##z,v)), \
(I[8] = (img)(_n1##x,_n1##y,_p1##z,v)), \
(I[11] = (img)(_n1##x,_p1##y,z,v)), \
(I[14] = (img)(_n1##x,y,z,v)), \
(I[17] = (img)(_n1##x,_n1##y,z,v)), \
(I[20] = (img)(_n1##x,_p1##y,_n1##z,v)), \
(I[23] = (img)(_n1##x,y,_n1##z,v)), \
(I[26] = (img)(_n1##x,_n1##y,_n1##z,v)),1)) || \
x==--_n1##x); \
I[0] = I[1], I[1] = I[2], I[3] = I[4], I[4] = I[5], I[6] = I[7], I[7] = I[8], \
I[9] = I[10], I[10] = I[11], I[12] = I[13], I[13] = I[14], I[15] = I[16], I[16] = I[17], \
I[18] = I[19], I[19] = I[20], I[21] = I[22], I[22] = I[23], I[24] = I[25], I[25] = I[26], \
_p1##x = x++, ++_n1##x)
/*------------------------------------------------
#
#
# Definition of the cimg_library:: namespace
#
#
-------------------------------------------------*/
//! This namespace encompasses all classes and functions of the %CImg library.
/**
This namespace is defined to avoid functions and class names collisions
that could happen with the include of other C++ header files.
- Anyway, it should not happen often and you should reasonnably start most of your
+ Anyway, it should not happen often and you should reasonably start most of your
%CImg-based programs with
\code
#include "CImg.h"
using namespace cimg_library;
\endcode
to simplify the declaration of %CImg Library variables afterwards.
**/
namespace cimg_library {
// Declare the only four classes of the CImg Library.
//
template<typename T=float> struct CImg;
template<typename T=float> struct CImgList;
struct CImgDisplay;
struct CImgException;
// (Pre)declare the cimg namespace.
// This is not the complete namespace declaration. It only contains some
// necessary stuffs to ensure a correct declaration order of classes and functions
// defined afterwards.
//
namespace cimg {
#ifdef cimg_use_vt100
const char t_normal[] = { 0x1b,'[','0',';','0',';','0','m','\0' };
const char t_red[] = { 0x1b,'[','4',';','3','1',';','5','9','m','\0' };
const char t_bold[] = { 0x1b,'[','1','m','\0' };
const char t_purple[] = { 0x1b,'[','0',';','3','5',';','5','9','m','\0' };
const char t_green[] = { 0x1b,'[','0',';','3','2',';','5','9','m','\0' };
#else
const char t_normal[] = { '\0' };
const char *const t_red = cimg::t_normal, *const t_bold = cimg::t_normal,
*const t_purple = cimg::t_normal, *const t_green = cimg::t_normal;
#endif
inline void info();
//! Get/set the current CImg exception mode.
/**
The way error messages are handled by CImg can be changed dynamically, using this function.
Possible values are :
- 0 to hide debug messages (quiet mode, but exceptions are still thrown).
- 1 to display debug messages on standard error (console).
- 2 to display debug messages in modal windows (default behavior).
- 3 to do as 1 + add extra warnings (may slow down the code !).
- 4 to do as 2 + add extra warnings (may slow down the code !).
**/
inline unsigned int& exception_mode() { static unsigned int mode = cimg_debug; return mode; }
inline int dialog(const char *title, const char *msg, const char *button1_txt="OK",
const char *button2_txt=0, const char *button3_txt=0,
const char *button4_txt=0, const char *button5_txt=0,
const char *button6_txt=0, const bool centering=false);
}
/*----------------------------------------------
#
# Definition of the CImgException structures
#
----------------------------------------------*/
//! Instances of this class are thrown when errors occur during a %CImg library function call.
/**
\section ex1 Overview
CImgException is the base class of %CImg exceptions.
- Exceptions are thrown by the %CImg Library when an error occured in a %CImg library function call.
+ Exceptions are thrown by the %CImg Library when an error occurred in a %CImg library function call.
CImgException is seldom thrown itself. Children classes that specify the kind of error encountered
are generally used instead. These sub-classes are :
- \b CImgInstanceException : Thrown when the instance associated to the called %CImg function is not
correctly defined. Generally, this exception is thrown when one tries to process \a empty images. The example
below will throw a \a CImgInstanceException.
\code
CImg<float> img; // Construct an empty image.
img.blur(10); // Try to blur the image.
\endcode
- \b CImgArgumentException : Thrown when one of the arguments given to the called %CImg function is not correct.
Generally, this exception is thrown when arguments passed to the function are outside an admissible range of values.
The example below will throw a \a CImgArgumentException.
\code
CImg<float> img(100,100,1,3); // Define a 100x100 color image with float pixels.
img = 0; // Try to fill pixels from the 0 pointer (invalid argument to operator=() ).
\endcode
- - \b CImgIOException : Thrown when an error occured when trying to load or save image files.
+ - \b CImgIOException : Thrown when an error occurred when trying to load or save image files.
The example below will throw a \a CImgIOException.
\code
CImg<float> img("file_doesnt_exist.jpg"); // Try to load a file that doesn't exist.
\endcode
- - \b CImgDisplayException : Thrown when an error occured when trying to display an image in a window.
+ - \b CImgDisplayException : Thrown when an error occurred when trying to display an image in a window.
This exception is thrown when image display request cannot be satisfied.
The parent class CImgException may be thrown itself when errors that cannot be classified in one of
the above type occur. It is recommended not to throw CImgExceptions yourself, since there are normally
reserved to %CImg Library functions.
\b CImgInstanceException, \b CImgArgumentException, \b CImgIOException and \b CImgDisplayException are simple
- subclasses of CImgException and are thus not detailled more in this reference documentation.
+ subclasses of CImgException and are thus not detailed more in this reference documentation.
\section ex2 Exception handling
When an error occurs, the %CImg Library first displays the error in a modal window.
Then, it throws an instance of the corresponding exception class, generally leading the program to stop
(this is the default behavior).
You can bypass this default behavior by handling the exceptions yourself,
using a code block <tt>try { ... } catch() { ... }</tt>.
In this case, you can avoid the apparition of the modal window, by
defining the environment variable <tt>cimg_debug</tt> to 0 before including the %CImg header file.
The example below shows how to cleanly handle %CImg Library exceptions :
\code
#define cimg_debug 0 // Disable modal window in CImg exceptions.
#define "CImg.h"
int main() {
try {
...; // Here, do what you want.
}
catch (CImgInstanceException &e) {
std::fprintf(stderr,"CImg Library Error : %s",e.message); // Display your own error message
... // Do what you want now.
}
}
\endcode
**/
struct CImgException {
#define _cimg_exception_err(etype,disp_flag) \
cimg_std::va_list ap; va_start(ap,format); cimg_std::vsprintf(message,format,ap); va_end(ap); \
switch (cimg::exception_mode()) { \
case 0 : break; \
case 2 : case 4 : try { cimg::dialog(etype,message,"Abort"); } catch (CImgException&) { \
cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \
} break; \
default : cimg_std::fprintf(cimg_stdout,"\n%s# %s%s :\n%s\n\n",cimg::t_red,etype,cimg::t_normal,message); \
} \
if (cimg::exception_mode()>=3) cimg_library::cimg::info();
char message[1024]; //!< Message associated with the error that thrown the exception.
CImgException() { message[0]='\0'; }
CImgException(const char *format, ...) { _cimg_exception_err("CImgException",true); }
};
// The \ref CImgInstanceException class is used to throw an exception related
// to a non suitable instance encountered in a library function call.
struct CImgInstanceException: public CImgException {
CImgInstanceException(const char *format, ...) { _cimg_exception_err("CImgInstanceException",true); }
};
// The \ref CImgArgumentException class is used to throw an exception related
// to invalid arguments encountered in a library function call.
struct CImgArgumentException: public CImgException {
CImgArgumentException(const char *format, ...) { _cimg_exception_err("CImgArgumentException",true); }
};
// The \ref CImgIOException class is used to throw an exception related
// to Input/Output file problems encountered in a library function call.
struct CImgIOException: public CImgException {
CImgIOException(const char *format, ...) { _cimg_exception_err("CImgIOException",true); }
};
// The CImgDisplayException class is used to throw an exception related to display problems
// encountered in a library function call.
struct CImgDisplayException: public CImgException {
CImgDisplayException(const char *format, ...) { _cimg_exception_err("CImgDisplayException",false); }
};
// The CImgWarningException class is used to throw an exception for warnings
// encountered in a library function call.
struct CImgWarningException: public CImgException {
CImgWarningException(const char *format, ...) { _cimg_exception_err("CImgWarningException",false); }
};
/*-------------------------------------
#
# Definition of the namespace 'cimg'
#
--------------------------------------*/
//! Namespace that encompasses \a low-level functions and variables of the %CImg Library.
/**
Most of the functions and variables within this namespace are used by the library for low-level processing.
Nevertheless, documented variables and functions of this namespace may be used safely in your own source code.
\warning Never write <tt>using namespace cimg_library::cimg;</tt> in your source code, since a lot of functions of the
<tt>cimg::</tt> namespace have prototypes similar to standard C functions that could defined in the global namespace <tt>::</tt>.
**/
namespace cimg {
// Define the traits that will be used to determine the best data type to work with.
//
template<typename T> struct type {
static const char* string() {
static const char* s[] = { "unknown", "unknown8", "unknown16", "unknown24",
"unknown32", "unknown40", "unknown48", "unknown56",
"unknown64", "unknown72", "unknown80", "unknown88",
"unknown96", "unknown104", "unknown112", "unknown120",
"unknown128" };
return s[(sizeof(T)<17)?sizeof(T):0];
}
static bool is_float() { return false; }
static T min() { return (T)-1>0?(T)0:(T)-1<<(8*sizeof(T)-1); }
static T max() { return (T)-1>0?(T)-1:~((T)-1<<(8*sizeof(T)-1)); }
static const char* format() { return "%s"; }
static const char* format(const T val) { static const char *s = "unknown"; return s; }
};
template<> struct type<bool> {
static const char* string() { static const char *const s = "bool"; return s; }
static bool is_float() { return false; }
static bool min() { return false; }
static bool max() { return true; }
static const char* format() { return "%s"; }
static const char* format(const bool val) { static const char* s[] = { "false", "true" }; return s[val?1:0]; }
};
template<> struct type<unsigned char> {
static const char* string() { static const char *const s = "unsigned char"; return s; }
static bool is_float() { return false; }
static unsigned char min() { return 0; }
static unsigned char max() { return (unsigned char)~0U; }
static const char* format() { return "%u"; }
static unsigned int format(const unsigned char val) { return (unsigned int)val; }
};
template<> struct type<char> {
static const char* string() { static const char *const s = "char"; return s; }
static bool is_float() { return false; }
static char min() { return (char)(-1L<<(8*sizeof(char)-1)); }
static char max() { return ~((char)(-1L<<(8*sizeof(char)-1))); }
static const char* format() { return "%d"; }
static int format(const char val) { return (int)val; }
};
template<> struct type<signed char> {
static const char* string() { static const char *const s = "signed char"; return s; }
static bool is_float() { return false; }
static signed char min() { return (signed char)(-1L<<(8*sizeof(signed char)-1)); }
static signed char max() { return ~((signed char)(-1L<<(8*sizeof(signed char)-1))); }
static const char* format() { return "%d"; }
static unsigned int format(const signed char val) { return (int)val; }
};
template<> struct type<unsigned short> {
static const char* string() { static const char *const s = "unsigned short"; return s; }
static bool is_float() { return false; }
static unsigned short min() { return 0; }
static unsigned short max() { return (unsigned short)~0U; }
static const char* format() { return "%u"; }
static unsigned int format(const unsigned short val) { return (unsigned int)val; }
};
template<> struct type<short> {
static const char* string() { static const char *const s = "short"; return s; }
static bool is_float() { return false; }
static short min() { return (short)(-1L<<(8*sizeof(short)-1)); }
static short max() { return ~((short)(-1L<<(8*sizeof(short)-1))); }
static const char* format() { return "%d"; }
static int format(const short val) { return (int)val; }
};
template<> struct type<unsigned int> {
static const char* string() { static const char *const s = "unsigned int"; return s; }
static bool is_float() { return false; }
static unsigned int min() { return 0; }
static unsigned int max() { return (unsigned int)~0U; }
static const char* format() { return "%u"; }
static unsigned int format(const unsigned int val) { return val; }
};
template<> struct type<int> {
static const char* string() { static const char *const s = "int"; return s; }
static bool is_float() { return false; }
static int min() { return (int)(-1L<<(8*sizeof(int)-1)); }
static int max() { return ~((int)(-1L<<(8*sizeof(int)-1))); }
static const char* format() { return "%d"; }
static int format(const int val) { return val; }
};
template<> struct type<unsigned long> {
static const char* string() { static const char *const s = "unsigned long"; return s; }
static bool is_float() { return false; }
static unsigned long min() { return 0; }
static unsigned long max() { return (unsigned long)~0UL; }
static const char* format() { return "%lu"; }
static unsigned long format(const unsigned long val) { return val; }
};
template<> struct type<long> {
static const char* string() { static const char *const s = "long"; return s; }
static bool is_float() { return false; }
static long min() { return (long)(-1L<<(8*sizeof(long)-1)); }
static long max() { return ~((long)(-1L<<(8*sizeof(long)-1))); }
static const char* format() { return "%ld"; }
static long format(const long val) { return val; }
};
template<> struct type<float> {
static const char* string() { static const char *const s = "float"; return s; }
static bool is_float() { return true; }
static float min() { return -3.4E38f; }
static float max() { return 3.4E38f; }
static const char* format() { return "%g"; }
static double format(const float val) { return (double)val; }
};
template<> struct type<double> {
static const char* string() { static const char *const s = "double"; return s; }
static bool is_float() { return true; }
static double min() { return -1.7E308; }
static double max() { return 1.7E308; }
static const char* format() { return "%g"; }
static double format(const double val) { return val; }
};
template<typename T, typename t> struct superset { typedef T type; };
template<> struct superset<bool,unsigned char> { typedef unsigned char type; };
template<> struct superset<bool,char> { typedef char type; };
template<> struct superset<bool,signed char> { typedef signed char type; };
template<> struct superset<bool,unsigned short> { typedef unsigned short type; };
template<> struct superset<bool,short> { typedef short type; };
template<> struct superset<bool,unsigned int> { typedef unsigned int type; };
template<> struct superset<bool,int> { typedef int type; };
template<> struct superset<bool,unsigned long> { typedef unsigned long type; };
template<> struct superset<bool,long> { typedef long type; };
template<> struct superset<bool,float> { typedef float type; };
template<> struct superset<bool,double> { typedef double type; };
template<> struct superset<unsigned char,char> { typedef short type; };
template<> struct superset<unsigned char,signed char> { typedef short type; };
template<> struct superset<unsigned char,unsigned short> { typedef unsigned short type; };
template<> struct superset<unsigned char,short> { typedef short type; };
template<> struct superset<unsigned char,unsigned int> { typedef unsigned int type; };
template<> struct superset<unsigned char,int> { typedef int type; };
template<> struct superset<unsigned char,unsigned long> { typedef unsigned long type; };
template<> struct superset<unsigned char,long> { typedef long type; };
template<> struct superset<unsigned char,float> { typedef float type; };
template<> struct superset<unsigned char,double> { typedef double type; };
template<> struct superset<signed char,unsigned char> { typedef short type; };
template<> struct superset<signed char,char> { typedef short type; };
template<> struct superset<signed char,unsigned short> { typedef int type; };
template<> struct superset<signed char,short> { typedef short type; };
template<> struct superset<signed char,unsigned int> { typedef long type; };
template<> struct superset<signed char,int> { typedef int type; };
template<> struct superset<signed char,unsigned long> { typedef long type; };
template<> struct superset<signed char,long> { typedef long type; };
template<> struct superset<signed char,float> { typedef float type; };
template<> struct superset<signed char,double> { typedef double type; };
template<> struct superset<char,unsigned char> { typedef short type; };
template<> struct superset<char,signed char> { typedef short type; };
template<> struct superset<char,unsigned short> { typedef int type; };
template<> struct superset<char,short> { typedef short type; };
template<> struct superset<char,unsigned int> { typedef long type; };
template<> struct superset<char,int> { typedef int type; };
template<> struct superset<char,unsigned long> { typedef long type; };
template<> struct superset<char,long> { typedef long type; };
template<> struct superset<char,float> { typedef float type; };
template<> struct superset<char,double> { typedef double type; };
template<> struct superset<unsigned short,char> { typedef int type; };
template<> struct superset<unsigned short,signed char> { typedef int type; };
template<> struct superset<unsigned short,short> { typedef int type; };
template<> struct superset<unsigned short,unsigned int> { typedef unsigned int type; };
template<> struct superset<unsigned short,int> { typedef int type; };
template<> struct superset<unsigned short,unsigned long> { typedef unsigned long type; };
template<> struct superset<unsigned short,long> { typedef long type; };
template<> struct superset<unsigned short,float> { typedef float type; };
template<> struct superset<unsigned short,double> { typedef double type; };
template<> struct superset<short,unsigned short> { typedef int type; };
template<> struct superset<short,unsigned int> { typedef long type; };
template<> struct superset<short,int> { typedef int type; };
template<> struct superset<short,unsigned long> { typedef long type; };
template<> struct superset<short,long> { typedef long type; };
template<> struct superset<short,float> { typedef float type; };
template<> struct superset<short,double> { typedef double type; };
template<> struct superset<unsigned int,char> { typedef long type; };
template<> struct superset<unsigned int,signed char> { typedef long type; };
template<> struct superset<unsigned int,short> { typedef long type; };
template<> struct superset<unsigned int,int> { typedef long type; };
template<> struct superset<unsigned int,unsigned long> { typedef unsigned long type; };
template<> struct superset<unsigned int,long> { typedef long type; };
template<> struct superset<unsigned int,float> { typedef float type; };
template<> struct superset<unsigned int,double> { typedef double type; };
template<> struct superset<int,unsigned int> { typedef long type; };
template<> struct superset<int,unsigned long> { typedef long type; };
template<> struct superset<int,long> { typedef long type; };
template<> struct superset<int,float> { typedef float type; };
template<> struct superset<int,double> { typedef double type; };
template<> struct superset<unsigned long,char> { typedef long type; };
template<> struct superset<unsigned long,signed char> { typedef long type; };
template<> struct superset<unsigned long,short> { typedef long type; };
template<> struct superset<unsigned long,int> { typedef long type; };
template<> struct superset<unsigned long,long> { typedef long type; };
template<> struct superset<unsigned long,float> { typedef float type; };
template<> struct superset<unsigned long,double> { typedef double type; };
template<> struct superset<long,float> { typedef float type; };
template<> struct superset<long,double> { typedef double type; };
template<> struct superset<float,double> { typedef double type; };
template<typename t1, typename t2, typename t3> struct superset2 {
typedef typename superset<t1, typename superset<t2,t3>::type>::type type;
};
template<typename t1, typename t2, typename t3, typename t4> struct superset3 {
typedef typename superset<t1, typename superset2<t2,t3,t4>::type>::type type;
};
template<typename t1, typename t2> struct last { typedef t2 type; };
#define _cimg_Tuchar typename cimg::superset<T,unsigned char>::type
#define _cimg_Tint typename cimg::superset<T,int>::type
#define _cimg_Tfloat typename cimg::superset<T,float>::type
#define _cimg_Tdouble typename cimg::superset<T,double>::type
#define _cimg_Tt typename cimg::superset<T,t>::type
// Define internal library variables.
//
#if cimg_display==1
struct X11info {
volatile unsigned int nb_wins;
pthread_t* event_thread;
CImgDisplay* wins[1024];
Display* display;
unsigned int nb_bits;
GC* gc;
bool blue_first;
bool byte_order;
bool shm_enabled;
#ifdef cimg_use_xrandr
XRRScreenSize *resolutions;
Rotation curr_rotation;
unsigned int curr_resolution;
unsigned int nb_resolutions;
#endif
X11info():nb_wins(0),event_thread(0),display(0),
nb_bits(0),gc(0),blue_first(false),byte_order(false),shm_enabled(false) {
#ifdef cimg_use_xrandr
resolutions = 0;
curr_rotation = 0;
curr_resolution = nb_resolutions = 0;
#endif
}
};
#if defined(cimg_module)
X11info& X11attr();
#elif defined(cimg_main)
X11info& X11attr() { static X11info val; return val; }
#else
inline X11info& X11attr() { static X11info val; return val; }
#endif
#elif cimg_display==2
struct Win32info {
HANDLE wait_event;
Win32info() { wait_event = CreateEvent(0,FALSE,FALSE,0); }
};
#if defined(cimg_module)
Win32info& Win32attr();
#elif defined(cimg_main)
Win32info& Win32attr() { static Win32info val; return val; }
#else
inline Win32info& Win32attr() { static Win32info val; return val; }
#endif
#elif cimg_display==3
struct CarbonInfo {
MPCriticalRegionID windowListCR; // Protects access to the list of windows
int windowCount; // Count of displays used on the screen
pthread_t event_thread; // The background event thread
MPSemaphoreID sync_event; // Event used to perform tasks synchronizations
- MPSemaphoreID wait_event; // Event used to notify that new events occured on the display
+ MPSemaphoreID wait_event; // Event used to notify that new events occurred on the display
MPQueueID com_queue; // The message queue
CarbonInfo(): windowCount(0),event_thread(0),sync_event(0),com_queue(0) {
if (MPCreateCriticalRegion(&windowListCR) != noErr) // Create the critical region
throw CImgDisplayException("MPCreateCriticalRegion failed.");
if (MPCreateSemaphore(1, 0, &sync_event) != noErr) // Create the inter-thread sync object
throw CImgDisplayException("MPCreateSemaphore failed.");
if (MPCreateSemaphore(1, 0, &wait_event) != noErr) // Create the event sync object
throw CImgDisplayException("MPCreateSemaphore failed.");
if (MPCreateQueue(&com_queue) != noErr) // Create the shared queue
throw CImgDisplayException("MPCreateQueue failed.");
}
~CarbonInfo() {
if (event_thread != 0) { // Terminates the resident thread, if needed
pthread_cancel(event_thread);
pthread_join(event_thread, NULL);
event_thread = 0;
}
if (MPDeleteCriticalRegion(windowListCR) != noErr) // Delete the critical region
throw CImgDisplayException("MPDeleteCriticalRegion failed.");
if (MPDeleteSemaphore(wait_event) != noErr) // Delete the event sync event
throw CImgDisplayException("MPDeleteEvent failed.");
if (MPDeleteSemaphore(sync_event) != noErr) // Delete the inter-thread sync event
throw CImgDisplayException("MPDeleteEvent failed.");
if (MPDeleteQueue(com_queue) != noErr) // Delete the shared queue
throw CImgDisplayException("MPDeleteQueue failed.");
}
};
#if defined(cimg_module)
CarbonInfo& CarbonAttr();
#elif defined(cimg_main)
CarbonInfo CarbonAttr() { static CarbonInfo val; return val; }
#else
inline CarbonInfo& CarbonAttr() { static CarbonInfo val; return val; }
#endif
#endif
#if cimg_display==1
// Keycodes for X11-based graphical systems.
//
const unsigned int keyESC = XK_Escape;
const unsigned int keyF1 = XK_F1;
const unsigned int keyF2 = XK_F2;
const unsigned int keyF3 = XK_F3;
const unsigned int keyF4 = XK_F4;
const unsigned int keyF5 = XK_F5;
const unsigned int keyF6 = XK_F6;
const unsigned int keyF7 = XK_F7;
const unsigned int keyF8 = XK_F8;
const unsigned int keyF9 = XK_F9;
const unsigned int keyF10 = XK_F10;
const unsigned int keyF11 = XK_F11;
const unsigned int keyF12 = XK_F12;
const unsigned int keyPAUSE = XK_Pause;
const unsigned int key1 = XK_1;
const unsigned int key2 = XK_2;
const unsigned int key3 = XK_3;
const unsigned int key4 = XK_4;
const unsigned int key5 = XK_5;
const unsigned int key6 = XK_6;
const unsigned int key7 = XK_7;
const unsigned int key8 = XK_8;
const unsigned int key9 = XK_9;
const unsigned int key0 = XK_0;
const unsigned int keyBACKSPACE = XK_BackSpace;
const unsigned int keyINSERT = XK_Insert;
const unsigned int keyHOME = XK_Home;
const unsigned int keyPAGEUP = XK_Page_Up;
const unsigned int keyTAB = XK_Tab;
const unsigned int keyQ = XK_q;
const unsigned int keyW = XK_w;
const unsigned int keyE = XK_e;
const unsigned int keyR = XK_r;
const unsigned int keyT = XK_t;
const unsigned int keyY = XK_y;
const unsigned int keyU = XK_u;
const unsigned int keyI = XK_i;
const unsigned int keyO = XK_o;
const unsigned int keyP = XK_p;
const unsigned int keyDELETE = XK_Delete;
const unsigned int keyEND = XK_End;
const unsigned int keyPAGEDOWN = XK_Page_Down;
const unsigned int keyCAPSLOCK = XK_Caps_Lock;
const unsigned int keyA = XK_a;
const unsigned int keyS = XK_s;
const unsigned int keyD = XK_d;
const unsigned int keyF = XK_f;
const unsigned int keyG = XK_g;
const unsigned int keyH = XK_h;
const unsigned int keyJ = XK_j;
const unsigned int keyK = XK_k;
const unsigned int keyL = XK_l;
const unsigned int keyENTER = XK_Return;
const unsigned int keySHIFTLEFT = XK_Shift_L;
const unsigned int keyZ = XK_z;
const unsigned int keyX = XK_x;
const unsigned int keyC = XK_c;
const unsigned int keyV = XK_v;
const unsigned int keyB = XK_b;
const unsigned int keyN = XK_n;
const unsigned int keyM = XK_m;
const unsigned int keySHIFTRIGHT = XK_Shift_R;
const unsigned int keyARROWUP = XK_Up;
const unsigned int keyCTRLLEFT = XK_Control_L;
const unsigned int keyAPPLEFT = XK_Super_L;
const unsigned int keyALT = XK_Alt_L;
const unsigned int keySPACE = XK_space;
const unsigned int keyALTGR = XK_Alt_R;
const unsigned int keyAPPRIGHT = XK_Super_R;
const unsigned int keyMENU = XK_Menu;
const unsigned int keyCTRLRIGHT = XK_Control_R;
const unsigned int keyARROWLEFT = XK_Left;
const unsigned int keyARROWDOWN = XK_Down;
const unsigned int keyARROWRIGHT = XK_Right;
const unsigned int keyPAD0 = XK_KP_0;
const unsigned int keyPAD1 = XK_KP_1;
const unsigned int keyPAD2 = XK_KP_2;
const unsigned int keyPAD3 = XK_KP_3;
const unsigned int keyPAD4 = XK_KP_4;
const unsigned int keyPAD5 = XK_KP_5;
const unsigned int keyPAD6 = XK_KP_6;
const unsigned int keyPAD7 = XK_KP_7;
const unsigned int keyPAD8 = XK_KP_8;
const unsigned int keyPAD9 = XK_KP_9;
const unsigned int keyPADADD = XK_KP_Add;
const unsigned int keyPADSUB = XK_KP_Subtract;
const unsigned int keyPADMUL = XK_KP_Multiply;
const unsigned int keyPADDIV = XK_KP_Divide;
#elif cimg_display==2
// Keycodes for Windows.
//
const unsigned int keyESC = VK_ESCAPE;
const unsigned int keyF1 = VK_F1;
const unsigned int keyF2 = VK_F2;
const unsigned int keyF3 = VK_F3;
const unsigned int keyF4 = VK_F4;
const unsigned int keyF5 = VK_F5;
const unsigned int keyF6 = VK_F6;
const unsigned int keyF7 = VK_F7;
const unsigned int keyF8 = VK_F8;
const unsigned int keyF9 = VK_F9;
const unsigned int keyF10 = VK_F10;
const unsigned int keyF11 = VK_F11;
const unsigned int keyF12 = VK_F12;
const unsigned int keyPAUSE = VK_PAUSE;
const unsigned int key1 = '1';
const unsigned int key2 = '2';
const unsigned int key3 = '3';
const unsigned int key4 = '4';
const unsigned int key5 = '5';
const unsigned int key6 = '6';
const unsigned int key7 = '7';
const unsigned int key8 = '8';
const unsigned int key9 = '9';
const unsigned int key0 = '0';
const unsigned int keyBACKSPACE = VK_BACK;
const unsigned int keyINSERT = VK_INSERT;
const unsigned int keyHOME = VK_HOME;
const unsigned int keyPAGEUP = VK_PRIOR;
const unsigned int keyTAB = VK_TAB;
const unsigned int keyQ = 'Q';
const unsigned int keyW = 'W';
const unsigned int keyE = 'E';
const unsigned int keyR = 'R';
const unsigned int keyT = 'T';
const unsigned int keyY = 'Y';
const unsigned int keyU = 'U';
const unsigned int keyI = 'I';
const unsigned int keyO = 'O';
const unsigned int keyP = 'P';
const unsigned int keyDELETE = VK_DELETE;
const unsigned int keyEND = VK_END;
const unsigned int keyPAGEDOWN = VK_NEXT;
const unsigned int keyCAPSLOCK = VK_CAPITAL;
const unsigned int keyA = 'A';
const unsigned int keyS = 'S';
const unsigned int keyD = 'D';
const unsigned int keyF = 'F';
const unsigned int keyG = 'G';
const unsigned int keyH = 'H';
const unsigned int keyJ = 'J';
const unsigned int keyK = 'K';
const unsigned int keyL = 'L';
const unsigned int keyENTER = VK_RETURN;
const unsigned int keySHIFTLEFT = VK_SHIFT;
const unsigned int keyZ = 'Z';
const unsigned int keyX = 'X';
const unsigned int keyC = 'C';
const unsigned int keyV = 'V';
const unsigned int keyB = 'B';
const unsigned int keyN = 'N';
const unsigned int keyM = 'M';
const unsigned int keySHIFTRIGHT = VK_SHIFT;
const unsigned int keyARROWUP = VK_UP;
const unsigned int keyCTRLLEFT = VK_CONTROL;
const unsigned int keyAPPLEFT = VK_LWIN;
const unsigned int keyALT = VK_LMENU;
const unsigned int keySPACE = VK_SPACE;
const unsigned int keyALTGR = VK_CONTROL;
const unsigned int keyAPPRIGHT = VK_RWIN;
const unsigned int keyMENU = VK_APPS;
const unsigned int keyCTRLRIGHT = VK_CONTROL;
const unsigned int keyARROWLEFT = VK_LEFT;
const unsigned int keyARROWDOWN = VK_DOWN;
const unsigned int keyARROWRIGHT = VK_RIGHT;
const unsigned int keyPAD0 = 0x60;
const unsigned int keyPAD1 = 0x61;
const unsigned int keyPAD2 = 0x62;
const unsigned int keyPAD3 = 0x63;
const unsigned int keyPAD4 = 0x64;
const unsigned int keyPAD5 = 0x65;
const unsigned int keyPAD6 = 0x66;
const unsigned int keyPAD7 = 0x67;
const unsigned int keyPAD8 = 0x68;
const unsigned int keyPAD9 = 0x69;
const unsigned int keyPADADD = VK_ADD;
const unsigned int keyPADSUB = VK_SUBTRACT;
const unsigned int keyPADMUL = VK_MULTIPLY;
const unsigned int keyPADDIV = VK_DIVIDE;
#elif cimg_display==3
// Keycodes for MacOSX, when using the Carbon framework.
//
const unsigned int keyESC = kEscapeCharCode;
const unsigned int keyF1 = 2U;
const unsigned int keyF2 = 3U;
const unsigned int keyF3 = 4U;
const unsigned int keyF4 = 5U;
const unsigned int keyF5 = 6U;
const unsigned int keyF6 = 7U;
const unsigned int keyF7 = 8U;
const unsigned int keyF8 = 9U;
const unsigned int keyF9 = 10U;
const unsigned int keyF10 = 11U;
const unsigned int keyF11 = 12U;
const unsigned int keyF12 = 13U;
const unsigned int keyPAUSE = 14U;
const unsigned int key1 = '1';
const unsigned int key2 = '2';
const unsigned int key3 = '3';
const unsigned int key4 = '4';
const unsigned int key5 = '5';
const unsigned int key6 = '6';
const unsigned int key7 = '7';
const unsigned int key8 = '8';
const unsigned int key9 = '9';
const unsigned int key0 = '0';
const unsigned int keyBACKSPACE = kBackspaceCharCode;
const unsigned int keyINSERT = 26U;
const unsigned int keyHOME = kHomeCharCode;
const unsigned int keyPAGEUP = kPageUpCharCode;
const unsigned int keyTAB = kTabCharCode;
const unsigned int keyQ = 'q';
const unsigned int keyW = 'w';
const unsigned int keyE = 'e';
const unsigned int keyR = 'r';
const unsigned int keyT = 't';
const unsigned int keyY = 'y';
const unsigned int keyU = 'u';
const unsigned int keyI = 'i';
const unsigned int keyO = 'o';
const unsigned int keyP = 'p';
const unsigned int keyDELETE = kDeleteCharCode;
const unsigned int keyEND = kEndCharCode;
const unsigned int keyPAGEDOWN = kPageDownCharCode;
const unsigned int keyCAPSLOCK = 43U;
const unsigned int keyA = 'a';
const unsigned int keyS = 's';
const unsigned int keyD = 'd';
const unsigned int keyF = 'f';
const unsigned int keyG = 'g';
const unsigned int keyH = 'h';
const unsigned int keyJ = 'j';
const unsigned int keyK = 'k';
const unsigned int keyL = 'l';
const unsigned int keyENTER = kEnterCharCode;
const unsigned int keySHIFTLEFT = 54U; //Macintosh modifier key, emulated
const unsigned int keyZ = 'z';
const unsigned int keyX = 'x';
const unsigned int keyC = 'c';
const unsigned int keyV = 'v';
const unsigned int keyB = 'b';
const unsigned int keyN = 'n';
const unsigned int keyM = 'm';
const unsigned int keySHIFTRIGHT = 62U; //Macintosh modifier key, emulated
const unsigned int keyARROWUP = kUpArrowCharCode;
const unsigned int keyCTRLLEFT = 64U; //Macintosh modifier key, emulated
const unsigned int keyAPPLEFT = 65U; //Macintosh modifier key, emulated
const unsigned int keyALT = 66U;
const unsigned int keySPACE = kSpaceCharCode;
const unsigned int keyALTGR = 67U; //Macintosh modifier key, emulated
const unsigned int keyAPPRIGHT = 68U; //Aliased on keyAPPLEFT
const unsigned int keyMENU = 69U;
const unsigned int keyCTRLRIGHT = 70U; //Macintosh modifier key, emulated
const unsigned int keyARROWLEFT = kLeftArrowCharCode;
const unsigned int keyARROWDOWN = kDownArrowCharCode;
const unsigned int keyARROWRIGHT = kRightArrowCharCode;
const unsigned int keyPAD0 = 74U;
const unsigned int keyPAD1 = 75U;
const unsigned int keyPAD2 = 76U;
const unsigned int keyPAD3 = 77U;
const unsigned int keyPAD4 = 78U;
const unsigned int keyPAD5 = 79U;
const unsigned int keyPAD6 = 80U;
const unsigned int keyPAD7 = 81U;
const unsigned int keyPAD8 = 82U;
const unsigned int keyPAD9 = 83U;
const unsigned int keyPADADD = 84U;
const unsigned int keyPADSUB = 85U;
const unsigned int keyPADMUL = 86U;
const unsigned int keyPADDIV = 87U;
#else
// Define unknow keycodes when no display are available.
// (should rarely be used then !).
//
const unsigned int keyESC = 1U;
const unsigned int keyF1 = 2U;
const unsigned int keyF2 = 3U;
const unsigned int keyF3 = 4U;
const unsigned int keyF4 = 5U;
const unsigned int keyF5 = 6U;
const unsigned int keyF6 = 7U;
const unsigned int keyF7 = 8U;
const unsigned int keyF8 = 9U;
const unsigned int keyF9 = 10U;
const unsigned int keyF10 = 11U;
const unsigned int keyF11 = 12U;
const unsigned int keyF12 = 13U;
const unsigned int keyPAUSE = 14U;
const unsigned int key1 = 15U;
const unsigned int key2 = 16U;
const unsigned int key3 = 17U;
const unsigned int key4 = 18U;
const unsigned int key5 = 19U;
const unsigned int key6 = 20U;
const unsigned int key7 = 21U;
const unsigned int key8 = 22U;
const unsigned int key9 = 23U;
const unsigned int key0 = 24U;
const unsigned int keyBACKSPACE = 25U;
const unsigned int keyINSERT = 26U;
const unsigned int keyHOME = 27U;
const unsigned int keyPAGEUP = 28U;
const unsigned int keyTAB = 29U;
const unsigned int keyQ = 30U;
const unsigned int keyW = 31U;
const unsigned int keyE = 32U;
const unsigned int keyR = 33U;
const unsigned int keyT = 34U;
const unsigned int keyY = 35U;
const unsigned int keyU = 36U;
const unsigned int keyI = 37U;
const unsigned int keyO = 38U;
const unsigned int keyP = 39U;
const unsigned int keyDELETE = 40U;
const unsigned int keyEND = 41U;
const unsigned int keyPAGEDOWN = 42U;
const unsigned int keyCAPSLOCK = 43U;
const unsigned int keyA = 44U;
const unsigned int keyS = 45U;
const unsigned int keyD = 46U;
const unsigned int keyF = 47U;
const unsigned int keyG = 48U;
const unsigned int keyH = 49U;
const unsigned int keyJ = 50U;
const unsigned int keyK = 51U;
const unsigned int keyL = 52U;
const unsigned int keyENTER = 53U;
const unsigned int keySHIFTLEFT = 54U;
const unsigned int keyZ = 55U;
const unsigned int keyX = 56U;
const unsigned int keyC = 57U;
const unsigned int keyV = 58U;
const unsigned int keyB = 59U;
const unsigned int keyN = 60U;
const unsigned int keyM = 61U;
const unsigned int keySHIFTRIGHT = 62U;
const unsigned int keyARROWUP = 63U;
const unsigned int keyCTRLLEFT = 64U;
const unsigned int keyAPPLEFT = 65U;
const unsigned int keyALT = 66U;
const unsigned int keySPACE = 67U;
const unsigned int keyALTGR = 68U;
const unsigned int keyAPPRIGHT = 69U;
const unsigned int keyMENU = 70U;
const unsigned int keyCTRLRIGHT = 71U;
const unsigned int keyARROWLEFT = 72U;
const unsigned int keyARROWDOWN = 73U;
const unsigned int keyARROWRIGHT = 74U;
const unsigned int keyPAD0 = 75U;
const unsigned int keyPAD1 = 76U;
const unsigned int keyPAD2 = 77U;
const unsigned int keyPAD3 = 78U;
const unsigned int keyPAD4 = 79U;
const unsigned int keyPAD5 = 80U;
const unsigned int keyPAD6 = 81U;
const unsigned int keyPAD7 = 82U;
const unsigned int keyPAD8 = 83U;
const unsigned int keyPAD9 = 84U;
const unsigned int keyPADADD = 85U;
const unsigned int keyPADSUB = 86U;
const unsigned int keyPADMUL = 87U;
const unsigned int keyPADDIV = 88U;
#endif
const double valuePI = 3.14159265358979323846; //!< Definition of the mathematical constant PI
// Definition of a 7x11 font, used to return a default font for drawing text.
const unsigned int font7x11[7*11*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x90,0x0,0x7f0000,0x40000,0x0,0x0,0x4010c0a4,0x82000040,0x11848402,0x18480050,0x80430292,0x8023,0x9008000,
0x40218140,0x4000040,0x21800402,0x18000051,0x1060500,0x8083,0x10000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x24002,0x4031,0x80000000,0x10000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c0400,0x40020000,0x80070080,0x40440e00,0x0,0x0,0x1,0x88180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x200000,0x0,0x0,0x80000,0x0,0x0,0x20212140,0x5000020,0x22400204,0x240000a0,0x40848500,0x4044,0x80010038,0x20424285,0xa000020,
0x42428204,0x2428e0a0,0x82090a14,0x4104,0x85022014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10240a7,0x88484040,0x40800000,0x270c3,0x87811e0e,
0x7c70e000,0x78,0x3c23c1ef,0x1f3e1e89,0xf1c44819,0xa23cf0f3,0xc3cff120,0xc18307f4,0x4040400,0x20000,0x80080080,0x40200,0x0,
0x40000,0x2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8188,0x50603800,0xf3c00000,0x1c004003,0xc700003e,0x18180,0xc993880,0x10204081,
0x2071ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x7d1224,0x48906048,0x0,0x4000000,0x0,0x9000,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x10240aa,0x14944080,0x23610000,0x68940,0x40831010,0x8891306,0x802044,0x44522208,0x90202088,0x40448819,0xb242890a,0x24011111,
0x49448814,0x4040a00,0xe2c3c7,0x8e3f3cb9,0xc1c44216,0xee38b0f2,0xe78f9120,0xc18507e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x101c207,0x88a04001,0x9c00000,0x2200a041,0x8200113a,0x8240,0x50a3110,0x2850a142,0x850c2081,0x2040204,0x8104592,0x142850a1,
0x42cd1224,0x4888bc48,0x70e1c387,0xe3b3c70,0xe1c38e1c,0x38707171,0xc3870e1c,0x10791224,0x48906c41,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x10003ee,0x15140080,0x21810000,0x48840,0x40851020,0x8911306,0x31fd804,0x9c522408,0x90204088,0x4045081a,0xba42890a,0x24011111,
0x49285024,0x2041b00,0x132408,0x910844c8,0x4044821b,0x7244c913,0x24041111,0x49488822,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x28204,0x85006001,0x6a414000,0x3a004043,0xc700113a,0x8245,0x50a3a00,0x2850a142,0x850c4081,0x2040204,0x81045d2,0x142850a1,
0x24951224,0x48852250,0x8102040,0x81054089,0x12244204,0x8108992,0x24489122,0x991224,0x4888b222,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x1000143,0xa988080,0x2147c01f,0x88840,0x83091c2c,0x1070f000,0xc000608,0xa48bc408,0x9e3c46f8,0x40460816,0xaa42f10b,0xc3811111,
0x35102044,0x1041100,0xf22408,0x9f084488,0x40470212,0x62448912,0x6041111,0x55308846,0x8061c80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x1028704,0x8f805801,0x4be28fdf,0x220001f0,0x111a,0x60000182,0x82c5c710,0x44891224,0x489640f1,0xe3c78204,0x810e552,0x142850a1,
0x18a51224,0x48822250,0x78f1e3c7,0x8f1f40f9,0xf3e7c204,0x8108912,0x24489122,0x7ea91224,0x4888a222,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x10007e2,0x85648080,0x20010000,0x88841,0x8f8232,0x20881000,0xc1fc610,0xbefa2408,0x90204288,0x40450816,0xa642810a,0x4041110a,
0x36282084,0x1042080,0x1122408,0x90084488,0x40450212,0x62448912,0x184110a,0x55305082,0x8042700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x1028207,0x82004801,0x68050040,0x1c000040,0x110a,0x60000001,0x45484d10,0x7cf9f3e7,0xcf944081,0x2040204,0x8104532,0x142850a1,
0x18a51224,0x48822248,0x89122448,0x91244081,0x2040204,0x8108912,0x24489122,0xc91224,0x48852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x282,
0x89630080,0x20010c00,0x30108842,0x810222,0x20882306,0x3001800,0x408a2208,0x90202288,0x40448814,0xa642810a,0x2041110a,0x26442104,
0x840000,0x1122408,0x90084488,0x40448212,0x62448912,0x84130a,0x36485102,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x101c208,0x4f802801,
0x8028040,0x40,0x130a,0x2,0x85e897a0,0x44891224,0x489c2081,0x2040204,0x8104532,0x142850a1,0x24cd1224,0x48823c44,0x89122448,
0x91244081,0x2040204,0x8108912,0x24489122,0xc93264,0xc9852214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100028f,0x109f0080,0x20010c00,
0x303071f3,0xc7011c1c,0x4071c306,0x802010,0x3907c1ef,0x1f201e89,0xf3844f90,0xa23c80f2,0x17810e04,0x228223f4,0x840000,0xfbc3c7,
0x8f083c88,0x40444212,0x6238f0f2,0x7039d04,0x228423e2,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1008780,0x2201800,0xf0014000,0x1f0,
0x1d0a,0x5,0x851e140,0x83060c18,0x30671ef9,0xf3e7cf9f,0x3e7c7911,0xe3c78f1e,0x42f8e1c3,0x8702205c,0x7cf9f3e7,0xcf9b3c78,0xf1e3c204,
0x8107111,0xc3870e1c,0x10f1d3a7,0x4e823c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x40,0x40000400,0x200000,0x0,0x2,0x0,0x0,0x0,0x0,0x18,
0x0,0x4,0x44007f,0x0,0x400,0x400000,0x8010,0x0,0x6002,0x8040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000000,0x200800,0x0,0x0,0x100a,
0x400000,0x44,0x0,0x400,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x62018,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x31,0x80000800,
0x400000,0x0,0x4,0x0,0x0,0x0,0x0,0xc,0x0,0x7,0x3c0000,0x0,0x3800,0x3800000,0x8010,0x0,0x1c001,0x881c0000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x207000,0x0,0x0,0x100a,0xc00000,0x3c,0x0,0xc00,0x0,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x0,0x1c2070
};
// Definition of a 10x13 font (used in dialog boxes).
const unsigned int font10x13[256*10*13/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80100c0,
0x68000300,0x801,0xc00010,0x100c000,0x68100,0x100c0680,0x2,0x403000,0x1000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x0,0x0,0x0,0x0,0x0,0x4020120,
0x58120480,0x402,0x1205008,0x2012050,0x58080,0x20120581,0x40000001,0x804812,0x2000000,0x0,0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x140,0x80000,0x200402,0x800000,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x7010,0x7000000,0x8000200,0x20000,0xc0002000,0x8008,0x0,0x0,0x0,0x0,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x80000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x80100c0,0x68000480,0x1001,
0xc00010,0x1018000,0x68100,0x100c0680,0x4,0x403000,0x1020000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,0x28081883,0x200801,
0x2a00000,0x10,0x1c0201c0,0x70040f80,0xc0f81c07,0x0,0x70,0x3e0303c0,0x3c3c0f83,0xe03c2107,0xe08810,0x18c31070,0x3c0703c0,
0x783e0842,0x22222208,0x83e04010,0x1008000,0x4000200,0x20001,0x2002,0x408008,0x0,0x0,0x100000,0x0,0x1008,0x2000000,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20080,0x38000880,0x8078140f,0x81c00000,0x3e000,0xc020180,0x60080001,0xe0000002,0xc00042,0x108e2010,
0xc0300c0,0x300c0303,0xf83c3e0f,0x83e0f81c,0x701c070,0x3c0c41c0,0x701c0701,0xc0001d08,0x42108421,0x8820088,0x4020120,0x58140480,
0x802,0x1205008,0x3014050,0xc058080,0x20120581,0x40000002,0x804814,0x2020050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20140,
0x281e2484,0x80200801,0x1c02000,0x10,0x22060220,0x880c0801,0x82208,0x80000001,0x20008,0x41030220,0x40220802,0x402102,0x209010,
0x18c31088,0x22088220,0x80080842,0x22222208,0x80204010,0x1014000,0x200,0x20001,0x2000,0x8008,0x0,0x0,0x100000,0x0,0x1008,
0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x40000500,0x80800010,0x40200000,0x41000,0x12020040,0x10000003,0xa0000006,
0x12000c4,0x31014000,0xc0300c0,0x300c0302,0x80402008,0x2008008,0x2008020,0x220c4220,0x88220882,0x20002208,0x42108421,0x8820088,
0x0,0x300,0x0,0x0,0x0,0x14000000,0x0,0x200200,0x0,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xfc282504,0x80001000,
0x82a02000,0x20,0x22020020,0x8140802,0x102208,0x80801006,0x18008,0x9c848220,0x80210802,0x802102,0x20a010,0x15429104,0x22104220,
0x80080842,0x22221405,0x404008,0x1022000,0x703c0,0x381e0701,0xc0783c02,0xc09008,0x1d83c070,0x3c078140,0x381c0882,0x21242208,
0x81e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,0x40220500,0x80800027,0x20e02800,0x9c800,0x12020040,
0x20000883,0xa0200002,0x120a044,0x11064010,0x12048120,0x48120484,0x80802008,0x2008008,0x2008020,0x210a4411,0x4411044,0x10884508,
0x42108421,0x503c0b0,0x1c0701c0,0x701c0707,0x70381c07,0x1c07008,0x2008020,0x20f01c0,0x701c0701,0xc0201c08,0x82208822,0x883c088,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x50281903,0x20001000,0x80802000,0x20,0x22020040,0x30240f03,0xc0101c08,0x80801018,
0x1fc06010,0xa48483c0,0x80210f03,0xe0803f02,0x20c010,0x15429104,0x22104220,0x70080841,0x41540805,0x804008,0x1041000,0x8220,
0x40220881,0x882202,0x40a008,0x12422088,0x22088180,0x40100882,0x21241408,0x80201008,0x2031000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x20280,0x401c0200,0x700028,0x21205000,0x92800,0xc1fc080,0x10000883,0xa0200002,0x1205049,0x12c19010,0x12048120,0x48120484,
0xf0803c0f,0x3c0f008,0x2008020,0x790a4411,0x4411044,0x10504908,0x42108421,0x5022088,0x2008020,0x8020080,0x88402208,0x82208808,
0x2008020,0x1e088220,0x88220882,0x20002608,0x82208822,0x8822088,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x501c0264,
0xa0001000,0x8001fc00,0x7000020,0x22020080,0x83e0082,0x20202207,0x80000020,0x1020,0xa4848220,0x80210802,0x9c2102,0x20c010,
0x12425104,0x3c1043c0,0x8080841,0x41540802,0x804008,0x1000000,0x78220,0x40220f81,0x882202,0x40c008,0x12422088,0x22088100,
0x60100881,0x41540805,0x406008,0x1849000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0xf0140200,0x880028,0x20e0a03f,0x709c800,
0x201c0,0x60000881,0xa0000007,0xc0284b,0x122eb020,0x12048120,0x48120487,0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,
0x10204908,0x42108421,0x2022088,0x1e0781e0,0x781e0787,0xf8403e0f,0x83e0f808,0x2008020,0x22088220,0x88220882,0x21fc2a08,0x82208822,
0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xf80a0294,0x40001000,0x80002000,0x20,0x22020100,0x8040082,0x20202200,
0x80000018,0x1fc06020,0xa48fc220,0x80210802,0x842102,0x20a010,0x12425104,0x20104240,0x8080841,0x41541402,0x1004008,0x1000000,
0x88220,0x40220801,0x882202,0x40a008,0x12422088,0x22088100,0x18100881,0x41540805,0x801008,0x2046000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x20280,0x401c0f80,0x80880028,0x20005001,0x94800,0x20000,0x880,0xa0000000,0x5015,0x4215040,0x3f0fc3f0,0xfc3f0fc8,
0x80802008,0x2008008,0x2008020,0x21094411,0x4411044,0x10505108,0x42108421,0x203c088,0x22088220,0x88220888,0x80402008,0x2008008,
0x2008020,0x22088220,0x88220882,0x20002a08,0x82208822,0x5022050,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xa00a0494,0x60001000,
0x80002004,0x8020,0x22020200,0x88040882,0x20402201,0x801006,0x18000,0x9f084220,0x40220802,0x442102,0x209010,0x10423088,0x20088220,
0x8080840,0x80882202,0x2004008,0x1000000,0x88220,0x40220881,0x882202,0x409008,0x12422088,0x22088100,0x8100880,0x80881402,
0x1001008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20280,0x40220200,0x80700027,0x20002801,0x92800,0x1fc000,0x980,
0xa0000000,0xa017,0x84417840,0x21084210,0x84210848,0x80402008,0x2008008,0x2008020,0x2208c220,0x88220882,0x20882208,0x42108421,
0x2020088,0x22088220,0x88220888,0xc8402208,0x82208808,0x2008020,0x22088220,0x88220882,0x20203208,0x82208822,0x2022020,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0xa03c0463,0x90000801,0x2004,0x8040,0x1c0703e0,0x70040701,0xc0401c06,0x801001,0x20020,
0x400843c0,0x3c3c0f82,0x3c2107,0x1c0881e,0x10423070,0x20070210,0xf0080780,0x80882202,0x3e04004,0x1000000,0x783c0,0x381e0701,
0x782202,0x408808,0x12422070,0x3c078100,0x700c0780,0x80882202,0x1e01008,0x2000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x201e0,
0xf8000200,0x80080010,0x40000001,0x41000,0x0,0xe80,0xa0000000,0x21,0x8e21038,0x21084210,0x84210848,0xf83c3e0f,0x83e0f81c,
0x701c070,0x3c08c1c0,0x701c0701,0xc0005c07,0x81e0781e,0x20200b0,0x1e0781e0,0x781e0787,0x30381c07,0x1c07008,0x2008020,0x1c0881c0,
0x701c0701,0xc0201c07,0x81e0781e,0x203c020,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x801,0x4,0x40,0x0,0x0,0x0,0x1000,
0x0,0x3c000000,0x0,0x0,0x0,0x0,0x10000,0x0,0x0,0x4004,0x1000000,0x0,0x0,0x80000,0x400000,0x0,0x20008000,0x0,0x4,0x1008,0x2000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x8008000f,0x80000000,0x3e000,0x0,0x800,0xa0000400,0x0,0x0,0x0,0x0,0x80000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100000,0x0,0x0,0x0,0x0,0x2000,0x0,0x4020040,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,
0x402,0x8,0x40,0x0,0x0,0x0,0x2000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x7004,0x70000fc,0x0,0x0,0x700000,0x800000,0x0,0x20008000,
0x0,0x4,0x808,0x4000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80,0x0,0x80f00000,0x0,0x0,0x0,0x800,0xa0001800,0x0,0x0,0x0,0x0,
0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x4020040
};
// Definition of a 8x17 font.
const unsigned int font8x17[8*17*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x2400,0x2400,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20081834,0x1c0000,0x20081800,0x20081800,0x342008,
0x18340000,0x200818,0x80000,0x0,0x180000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4200000,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x380000,0x4000,0x2000c00,0x40100840,0x70000000,0x0,0x0,0x1c,0x10700000,0x7,0x0,
0x1800,0x1800,0x0,0x0,0x0,0x14,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1010242c,0x14140000,0x10102414,0x10102414,0x2c1010,0x242c1400,
0x101024,0x14100038,0x0,0x240000,0x0,0x0,0x30000000,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x12,0x0,0x8100000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x80000,0x10004000,0x2001000,0x40000040,0x10000000,0x0,0x0,0x10,0x10100000,0x4,
0x0,0x18000000,0x0,0x0,0x0,0x34002400,0x2400,0x0,0x0,0x0,0x3c,0x0,0x8000000,0x0,0x60607800,0x0,0x140000,0x0,0x0,0x0,0x0,0x0,
0x44,0x10081834,0x240000,0x10081800,0x10081800,0x1c341008,0x18340000,0x100818,0x84000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102812,
0x8601c10,0x8100800,0x2,0x1c383e3e,0x67e1e7f,0x3e3c0000,0x38,0x1e087e1e,0x7c7f7f1e,0x417c1c42,0x4063611c,0x7e1c7e3e,0xfe414181,
0x63827f10,0x40081000,0x8004000,0x2001000,0x40000040,0x10000000,0x0,0x10000000,0x10,0x10100000,0x3c000008,0x0,0x24003e00,
0x3f007f00,0x0,0x0,0x2ce91800,0x1882,0x10101c,0xc2103c,0x143c3c00,0x3c00,0x18003c3c,0x10001f00,0x181c00,0x20200810,0x8080808,
0x8083e1e,0x7f7f7f7f,0x7c7c7c7c,0x7c611c1c,0x1c1c1c00,0x1e414141,0x41824044,0x810242c,0x14180000,0x8102414,0x8102414,0x382c0810,
0x242c1400,0x81024,0x14104014,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102816,0x3e902010,0x10084910,0x4,0x22084343,0xa402102,0x41620000,
0x44,0x33144121,0x42404021,0x41100444,0x40636122,0x43224361,0x10416381,0x22440310,0x20082800,0x4000,0x2001000,0x40000040,
0x10000000,0x0,0x10000000,0x10,0x10100000,0x24000008,0x0,0x606100,0x68000300,0x8106c,0x34000000,0x4f0000,0x44,0x101020,0x441040,
0x420200,0x4200,0x24000404,0x7d00,0x82200,0x20203010,0x14141414,0x14082821,0x40404040,0x10101010,0x42612222,0x22222200,0x23414141,
0x41447e48,0x0,0x0,0x0,0x0,0x4000000,0x18,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10287f,0x49902010,0x10083e10,0x4,0x41080101,
0x1a404002,0x41411818,0x1004004,0x21144140,0x41404040,0x41100448,0x40555141,0x41414140,0x10412281,0x14280610,0x20084400,0x1c7c1c,
0x3e3c7c3a,0x5c703844,0x107f5c3c,0x7c3e3c3c,0x7e424281,0x66427e10,0x10100000,0x40100008,0x1010,0xa04000,0x48100610,0x100c3024,
0x24000000,0x4f3c00,0x2c107e28,0x3820,0x42281060,0x9d1e12,0xbd00,0x24100818,0x427d00,0x82248,0x20200800,0x14141414,0x14142840,
0x40404040,0x10101010,0x41514141,0x41414142,0x43414141,0x41284350,0x1c1c1c1c,0x1c1c6c1c,0x3c3c3c3c,0x70707070,0x3c5c3c3c,
0x3c3c3c18,0x3e424242,0x42427c42,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x102824,0x48623010,0x10081c10,0x8,0x41080103,0x127c5e04,
0x41411818,0xe7f3808,0x4f144140,0x41404040,0x41100450,0x40555141,0x41414160,0x1041225a,0x1c280410,0x1008c600,0x226622,0x66661066,
0x62100848,0x10496266,0x66663242,0x10426681,0x24220260,0x100c0000,0xf8280008,0x1010,0x606000,0x48280428,0x28042014,0x48000000,
0x494200,0x52280228,0x105420,0x3cee1058,0xa12236,0xa500,0x18101004,0x427d00,0x8226c,0x76767e10,0x14141414,0x14142840,0x40404040,
0x10101010,0x41514141,0x41414124,0x45414141,0x41284150,0x22222222,0x22221222,0x66666666,0x10101010,0x66626666,0x66666600,
0x66424242,0x42226622,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100024,0x381c4900,0x10086bfe,0x8,0x4908021c,0x22036304,0x3e630000,
0x70000710,0x51227e40,0x417f7f43,0x7f100470,0x40554941,0x43417e3e,0x1041225a,0x8100810,0x10080000,0x24240,0x42421042,0x42100850,
0x10494242,0x42422040,0x1042245a,0x18240410,0x10103900,0x407c003e,0x1818,0x1c3e10,0x4f7c087c,0x7c002010,0x48000000,0x4008,
0x527c0410,0x105078,0x2410104c,0xa13e6c,0x7f00b900,0xfe3c3c,0x421d18,0x1c1c36,0x38383810,0x22222222,0x22144e40,0x7f7f7f7f,
0x10101010,0xf1494141,0x41414118,0x49414141,0x4110435c,0x2020202,0x2021240,0x42424242,0x10101010,0x42424242,0x424242ff,0x4e424242,
0x42244224,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000fe,0xe664d00,0x10080810,0x380010,0x41080c03,0x42014108,0x633d0000,0x70000710,
0x51224140,0x41404041,0x41100448,0x40494541,0x7e414203,0x1041145a,0x14101010,0x10080000,0x3e4240,0x427e1042,0x42100870,0x10494242,
0x4242203c,0x1042245a,0x18241810,0x10104600,0xf8f60008,0x1010,0x600320,0x48f610f6,0xf6000000,0x187eff,0x3c04,0x5ef61810,0x105020,
0x24fe0064,0x9d006c,0x138ad00,0x100000,0x420518,0x36,0xc0c0c020,0x22222222,0x22224840,0x40404040,0x10101010,0x41454141,0x41414118,
0x51414141,0x41107e46,0x3e3e3e3e,0x3e3e7e40,0x7e7e7e7e,0x10101010,0x42424242,0x42424200,0x5a424242,0x42244224,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x28,0x9094500,0x10080010,0x10,0x41081801,0x7f014118,0x41010000,0xe7f3800,0x513e4140,0x41404041,0x41100444,
0x40414541,0x40414101,0x10411466,0x36103010,0x8080000,0x424240,0x42401042,0x42100848,0x10494242,0x42422002,0x10423c5a,0x18142010,
0x10100000,0x407c0010,0x1010,0x260140,0x487c307c,0x7c000000,0x180000,0x202,0x507c2010,0x105020,0x3c10003c,0x423e36,0x1004200,
0x100000,0x420500,0x3e6c,0x41e0440,0x3e3e3e3e,0x3e3e7840,0x40404040,0x10101010,0x41454141,0x41414124,0x61414141,0x41104042,
0x42424242,0x42425040,0x40404040,0x10101010,0x42424242,0x42424218,0x72424242,0x42144214,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,
0x49096200,0x8100010,0x18001810,0x22082043,0x2432310,0x61421818,0x1004010,0x4f634121,0x42404021,0x41104444,0x40414322,0x40234143,
0x10411466,0x22106010,0x8080000,0x466622,0x66621066,0x42100844,0x10494266,0x66662042,0x10461824,0x24184010,0x10100000,0x24381010,
0x34001018,0xda4320,0x68386038,0x38000000,0x0,0x4204,0x50384010,0x105420,0x4210100c,0x3c0012,0x3c00,0x0,0x460500,0x48,0xc020c44,
0x63636363,0x63228821,0x40404040,0x10101010,0x42432222,0x22222242,0x62414141,0x41104042,0x46464646,0x46465022,0x62626262,
0x10101010,0x66426666,0x66666618,0x66464646,0x46186618,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100048,0x3e063d00,0x8100000,0x18001820,
0x1c3e7f3e,0x23c1e20,0x3e3c1818,0x10,0x20417e1e,0x7c7f401e,0x417c3842,0x7f41431c,0x401e40be,0x103e0866,0x41107f10,0x4080000,
0x3a5c1c,0x3a3c103a,0x427c0842,0xe49423c,0x7c3e203c,0xe3a1824,0x66087e10,0x10100000,0x3c103010,0x245a1010,0x5a3e10,0x3f107f10,
0x10000000,0x0,0x3c08,0x2e107e10,0x1038fc,0x101004,0x0,0x0,0xfe0000,0x7f0500,0x0,0x14041438,0x41414141,0x41418e1e,0x7f7f7f7f,
0x7c7c7c7c,0x7c431c1c,0x1c1c1c00,0xbc3e3e3e,0x3e10405c,0x3a3a3a3a,0x3a3a6e1c,0x3c3c3c3c,0x7c7c7c7c,0x3c423c3c,0x3c3c3c00,
0x7c3a3a3a,0x3a087c08,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x4200000,0x10000020,0x0,0x0,0x10,0x0,0x30000000,0x0,
0x0,0x0,0x60000,0x0,0x1c,0x4380000,0x0,0x2,0x800,0x0,0x40020000,0x0,0x8000c,0x10600000,0x2010,0x48000000,0x240000,0x0,0x0,
0x0,0x0,0x0,0x1000,0x1078,0x0,0x0,0x0,0x400500,0x0,0x1e081e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x84008,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8000000,0x0,0x20000040,0x0,0x0,0x20,0x0,0x1e000000,0x0,0x0,0x0,0x20000,0x0,
0x0,0x2000000,0x0,0x26,0x800,0x0,0x40020000,0x0,0x100000,0x10000000,0x2030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x1000,0x0,
0x0,0x0,0x400000,0x8000000,0x41e0400,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x0,0x0,0x0,0x0,0x104010,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x1c,0x7000,0x0,0x40020000,0x0,0x300000,
0x0,0xe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1000,0x0,0x0,0x0,0x400000,0x38000000,0x0,0x0,0x1c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x1c,0x0,0x0,0x0,0x0,0x0,0x304030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
// Definition of a 10x19 font.
const unsigned int font10x19[10*19*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3600000,0x36000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x180181c0,0xe81b0300,0x1801,0x81c06c18,0x181c06c,0xe8180,0x181c0e81,0xb0000006,0x60701b,0x1800000,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x1c000,0x0,0x0,0x0,0x0,0x6c,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0xc030360,0xb81b0480,0xc03,0x3606c0c,0x303606c,0xb80c0,0x30360b81,0xb0000003,0xc0d81b,0x3000000,0x0,
0x300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x0,0x0,0x2200000,
0x22000,0x0,0x0,0x0,0x0,0x0,0x0,0x30000,0x0,0xe0,0x38078000,0x0,0x480,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000c080,0x480,0x3000,
0xc0800030,0xc08000,0x300,0xc080000,0xc,0x302000,0xc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x41c01,0xe020060c,
0x800000,0x4,0x1e0703e0,0xf8060fc1,0xe1fe1e07,0x80000000,0x78,0x307e0,0x3c7c1fe7,0xf83c408f,0x80f10440,0x18660878,0x7e0787e0,
0x78ff9024,0xa0140a0,0x27f83840,0x700e000,0x18000400,0x8000,0x70004002,0x410078,0x0,0x0,0x0,0x0,0x1808,0xc000000,0xf000000,
0xe000000,0x1400,0x1e0001f,0x8007f800,0x0,0x0,0x3a3b,0x61400000,0x14202,0x20000,0x38002020,0x3c1b00,0x3e00000,0xf8,0x1c0001c0,
0x78060001,0xf800000e,0x1e00020,0x8004020,0xc0300c0,0x300c0301,0xf83c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1821e0,0x781e0781,0xe0001f10,
0x24090240,0xa02400f8,0x18018140,0xe81b0480,0x1801,0x81406c18,0x181406c,0x190e8180,0x18140e81,0xb0000006,0x60501b,0x184006c,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x26042202,0x200c06,0x800000,0x8,0x210d0611,0x40e0803,0x10026188,0x40000000,
0x8c,0xf030418,0xc6431004,0xc64082,0x110840,0x18660884,0x41084410,0x8c081024,0xa012110,0x40082020,0x101b000,0xc000400,0x8000,
0x80004002,0x410008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x18800000,0x10000000,0x2200,0x2300024,0x800,0x0,0x0,0x2e13,0x60800000,
0x8104,0x20040,0x64001040,0x80401b07,0x80100000,0x1e000,0x22000020,0x40c0003,0xc8000002,0x3300020,0x8004020,0xc0300c0,0x300c0301,
0x40c64010,0x4010008,0x2008020,0x43182210,0x84210842,0x10002190,0x24090240,0x9044018c,0xc030220,0xb81b0300,0xc03,0x2206c0c,
0x302206c,0x1e0b80c0,0x30220b81,0xb0000003,0xc0881b,0x304006c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x241f2202,
0x200802,0x4900000,0x8,0x21010408,0x20a0802,0x44090,0x20000000,0x4,0x11878408,0x80411004,0x804082,0x111040,0x1ce50986,0x40986409,
0x81022,0x12012108,0x80102020,0x1031800,0x400,0x8000,0x80004000,0x10008,0x0,0x0,0x100000,0x0,0x2008,0x2000000,0x10000000,
0x10000000,0x18,0x4000044,0x1000,0x30180,0xd81b0000,0x13,0xe0000000,0x88,0x40,0x400018c0,0x80400018,0x61f00000,0x61800,0x22020020,
0x4000007,0xc8000002,0x2100020,0x8038000,0x1e0781e0,0x781e0301,0x40804010,0x4010008,0x2008020,0x41142619,0x86619866,0x18002190,
0x24090240,0x8887e104,0x0,0x0,0x0,0x0,0x0,0x2000000,0x0,0x0,0x0,0x40000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20120,0x2434a202,
0x200802,0x3e00000,0x10,0x40810008,0x21a0804,0x44090,0x20000000,0x80040004,0x20848409,0x409004,0x1004082,0x112040,0x14a50902,
0x40902409,0x81022,0x11321208,0x80202010,0x1060c00,0x7c5e0,0x781e8783,0xf07a5f0e,0x1c10808,0xfc5f078,0x5e07a170,0x7c7e1024,
0xa016190,0x27f82008,0x2000000,0x20000000,0x10000000,0x80200024,0x4000044,0x2000,0x18180,0xc8320000,0x12,0xa1f00037,0x7f888,
0x1e0,0x40410880,0x80600017,0xa2100000,0x5e800,0x22020040,0x38001027,0xc8000002,0x2100020,0x8004020,0x12048120,0x48120482,
0x41004010,0x4010008,0x2008020,0x40942409,0x2409024,0x9044390,0x24090240,0x88841918,0x1f07c1f0,0x7c1f07c3,0x70781e07,0x81e07838,
0xe0380e0,0x1f17c1e0,0x781e0781,0xe0001f90,0x24090240,0x9025e102,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0xff241c41,
0x1001,0x1c02000,0x10,0x40810008,0x6120f85,0xe0086190,0x20c03007,0x8007800c,0x27848419,0x409004,0x1004082,0x114040,0x14a48902,
0x40902409,0x81022,0x11321205,0x602010,0x1000000,0x86610,0x84218840,0x80866182,0x411008,0x9261884,0x61086189,0x82101022,0x12012108,
0x40082008,0x2000000,0x20030000,0x20000000,0x80200024,0x4000044,0x3006030,0xc018100,0x4c260000,0x12,0x26080048,0x83000850,
0x20250,0x403e0500,0x8078002c,0x12302200,0x92400,0x1c0200c0,0x4001027,0xc8000002,0x3308820,0x8004020,0x12048120,0x48120482,
0x41004010,0x4010008,0x2008020,0x40922409,0x2409024,0x8884690,0x24090240,0x85040920,0x21886218,0x86218860,0x88842108,0x42108408,
0x2008020,0x21186210,0x84210842,0x10302190,0x24090240,0x88461084,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x4c240182,
0x80001001,0x6b02000,0x20,0x4c810010,0x78220846,0x10081e10,0x20c0301c,0x1fe0e018,0x4d8487e1,0x409fe7,0xf9007f82,0x11a040,
0x13248902,0x41102418,0xe0081022,0x11320c05,0x402008,0x1000000,0x2409,0x409020,0x81024082,0x412008,0x9240902,0x40902101,0x101022,
0x11321208,0x40102008,0x2000000,0x7e0c8000,0xfc000003,0xf0fc0018,0x43802047,0x8c8040c8,0x32008300,0x44240000,0x0,0x4000048,
0x8c801050,0x20440,0x40221dc0,0x808c0028,0x11d0667f,0x8009c400,0x1fc180,0x4001023,0xc8300002,0x1e0ccfb,0x3ec7b020,0x12048120,
0x48120482,0x79007f9f,0xe7f9fe08,0x2008020,0xf0922409,0x2409024,0x8504490,0x24090240,0x85040920,0x802008,0x2008020,0x89004090,
0x24090208,0x2008020,0x40902409,0x2409024,0x8304390,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,
0x481c0606,0xc8001001,0x802000,0x20,0x4c810020,0x4220024,0x8102108,0x60000070,0x3820,0x48884419,0x409004,0x10e4082,0x112040,
0x13244902,0x7e1027e0,0x3c081021,0x21320c02,0x802008,0x1000000,0x7e409,0x409020,0x81024082,0x414008,0x9240902,0x40902101,
0x80101022,0x11320c08,0x40202008,0x2038800,0x200bc000,0x20000000,0x80200003,0x80f04044,0xbc080bc,0x2f000200,0x0,0x0,0x6001048,
0x8bc02020,0x20441,0xf8220200,0x80820028,0x1000cc00,0x80094400,0x201e0,0x78001021,0xc830000f,0x8000663c,0xf03c0c0,0x21084210,
0x84210846,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8204890,0x24090240,0x82040930,0x1f87e1f8,0x7e1f87e0,0x89004090,
0x24090208,0x2008020,0x40902409,0x2409024,0x8004690,0x24090240,0x88440884,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,
0x480719c4,0x48001001,0x81fc00,0x7800020,0x40810040,0x2420024,0x8104087,0xa0000070,0x3820,0x48884409,0x409004,0x1024082,0x111040,
0x13244902,0x40102410,0x2081021,0x214a1202,0x1802008,0x1000000,0x182409,0x409fe0,0x81024082,0x41a008,0x9240902,0x40902100,
0xf8101021,0x214a0c04,0x80c0c008,0x1847000,0x7c1ee000,0x20000000,0x8020000c,0x8c044,0x1ee181ee,0x7b800000,0x707,0xf3ff0000,
0x3e0084f,0x9ee0c020,0x20440,0x40221fc0,0xc2002c,0x13f11000,0x87892400,0x20000,0x1020,0x48000000,0x3f011c6,0x31cc6180,0x21084210,
0x84210844,0x41004010,0x4010008,0x2008020,0x40912409,0x2409024,0x8505090,0x24090240,0x8204191c,0x60982609,0x82609823,0xf9007f9f,
0xe7f9fe08,0x2008020,0x40902409,0x2409024,0x9fe4c90,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xfe048224,
0x28001001,0x2000,0x40,0x40810080,0x27f8024,0x8104080,0x2000001c,0x1fe0e020,0x488fc409,0x409004,0x1024082,0x110840,0x10242902,
0x40102408,0x2081021,0x214a1202,0x1002004,0x1000000,0x102409,0x409000,0x81024082,0x411008,0x9240902,0x40902100,0x6101021,
0x214a0c04,0x81002008,0x2000000,0x201dc000,0x20000000,0x80200000,0x98044,0x1dc101dc,0x77000000,0x700,0x0,0x180448,0x1dc10020,
0x20440,0x403e0200,0x620017,0xa000cc00,0x80052800,0x20000,0x1020,0x48000000,0x6606,0x206100,0x3f0fc3f0,0xfc3f0fc7,0xc1004010,
0x4010008,0x2008020,0x4090a409,0x2409024,0x8886090,0x24090240,0x8207e106,0x40902409,0x2409024,0x81004010,0x4010008,0x2008020,
0x40902409,0x2409024,0x8005890,0x24090240,0x84840848,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x98048224,0x30001001,0x2000,
0x40,0x21010100,0x2020024,0x8204080,0x40000007,0x80078000,0x48884408,0x80411004,0x824082,0x110840,0x10242986,0x40086409,0x2081021,
0xe14a2102,0x2002004,0x1000000,0x106409,0x409000,0x81024082,0x410808,0x9240902,0x40902100,0x2101021,0x214a1202,0x82002008,
0x2000000,0x300f8000,0x20000000,0x80fc001d,0xe4088044,0xf8200f8,0x3e000000,0x300,0x0,0x80c48,0xf820020,0x20640,0x40410200,
0x803c0018,0x60006600,0x61800,0x0,0x1020,0x48000000,0xcc0a,0x20a100,0x21084210,0x84210844,0x40804010,0x4010008,0x2008020,
0x4110a619,0x86619866,0x19046110,0x24090240,0x82040102,0x41906419,0x6419064,0x81004010,0x4010008,0x2008020,0x40902409,0x2409024,
0x8307090,0x24090240,0x82840828,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000,0x90248222,0x30000802,0x200c,0xc080,0x21010301,
0x4021042,0x10202108,0xc0c03000,0x80040020,0x4d902418,0xc6431004,0xc24082,0x6210440,0x10241884,0x40084409,0x86080840,0xc0842102,
0x4002002,0x1000000,0x18e610,0x84218820,0x80864082,0x410408,0x9240884,0x61086101,0x6101860,0xc0842103,0x4002008,0x2000000,
0x10850180,0x20330000,0x80200013,0x26184024,0x5040050,0x14000000,0x0,0x0,0x4180848,0x85040020,0x20350,0x40000200,0x800c0007,
0x80002200,0x1e000,0x0,0x1860,0x48000000,0x880a,0x40a188,0x40902409,0x2409028,0x40c64010,0x4010008,0x2008020,0x43106210,0x84210842,
0x10006108,0x42108421,0x2040102,0x6398e639,0x8e6398e4,0x88842088,0x22088208,0x2008020,0x21102210,0x84210842,0x10306118,0x66198661,
0x83061030,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20001,0x901f01c1,0xe8000802,0xc,0xc080,0x1e07c7f8,0xf8020f81,0xe0401e07,
0x80c03000,0x20,0x279027e0,0x3c7c1fe4,0x3c408f,0x83c1027f,0x90241878,0x4007c404,0xf8080780,0xc0844082,0x7f82002,0x1000000,
0xfa5e0,0x781e87c0,0x807a409f,0xc0410207,0x9240878,0x5e07a100,0xf80e0fa0,0xc0846183,0x7f82008,0x2000000,0xf020100,0x40321360,
0x80200014,0xa3e0201f,0x8207f820,0x8000000,0x0,0x0,0x3e01037,0x207f820,0x201e1,0xfc000200,0x80040000,0x0,0x0,0x1fc000,0x17b0,
0x48000000,0x12,0xc120f0,0x40902409,0x2409028,0x783c7f9f,0xe7f9fe3e,0xf83e0f8,0x7c1061e0,0x781e0781,0xe000be07,0x81e0781e,
0x204017c,0x3e8fa3e8,0xfa3e8fa3,0x70781f07,0xc1f07c7f,0x1fc7f1fc,0x1e1021e0,0x781e0781,0xe0007e0f,0xa3e8fa3e,0x8305e030,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0xc06,0xc,0x100,0x0,0x0,0x0,0x3000,0x0,0x20000000,0x0,0x0,0x0,0x0,0xc000,
0x0,0x0,0x2001,0x1000000,0x0,0x0,0x20000,0x400000,0x0,0x40002000,0x0,0x1,0x2008,0x2000000,0x100,0x40240000,0x80200008,0x40000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80040000,0x0,0x0,0x0,0x1000,0x48000000,0x1f,0x181f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1040010,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000,0x60c,0x18,0x0,
0x0,0x0,0x0,0x6000,0x0,0x10000000,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x3800,0x7000000,0x0,0x0,0x840000,0x400000,0x0,0x40002000,
0x0,0x2,0x2008,0x2000000,0x200,0x40440000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x80780000,0x0,0x0,0x0,0x1000,0x48000400,
0x2,0x1e02000,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000,0x0,0x0,0x0,0x0,0x0,0x0,0x2040020,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x4000,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x780000,0x3800000,0x0,0x40002000,0x0,0xe,0x1808,0xc000000,0x3,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x80000000,
0x0,0x0,0x0,0x1000,0x1c00,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x380000,0x0,0x0,0x0,0x0,0x0,0x0,0xe0400e0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
// Definition of a 12x24 font.
const unsigned int font12x24[12*24*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x19,0x80000000,0x198000,0x0,0x0,0x0,0x0,
0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc001806,0xc81980,0x60000000,0xc001806,0x1980c00,0x18060198,0xc80c,
0x180600,0xc8198000,0xc001,0x80601980,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x198,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x600300f,0x1301980,0x90000000,0x600300f,0x1980600,0x300f0198,0x13006,0x300f01,0x30198000,0x6003,
0xf01980,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x0,0x60000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7007,0x3c0000,0x3006019,
0x80000000,0x90000000,0x3006019,0x80000300,0x60198000,0x3,0x601980,0x0,0x3006,0x1980000,0x60000000,0x0,0x0,0xe0000000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000000,
0x0,0x0,0x0,0x0,0x0,0xc800019,0x80000000,0x198000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x1001,0x420000,0x0,0x0,0x90000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18000c06,0xc80001,0x10000000,0x18000c06,0x1800,0xc060000,0xc818,0xc0600,0xc8000000,
0x18000,0xc0600000,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80660207,0x800f8060,0x300c004,0x0,0x6,
0xe00703f,0x3f00383,0xf80f07fc,0x1f01f000,0x0,0xf8,0x607f,0x7c7e07,0xfe7fe0f8,0x6063fc1f,0x86066007,0xe7060f0,0x7f80f07f,
0x81f8fff6,0x6606c03,0x70ee077f,0xe0786000,0xf0070000,0xc000060,0xc0,0x3e000,0x60006003,0x600fc00,0x0,0x0,0x0,0x0,0x0,0x3c0603,
0xc0000000,0x7800000,0xf0000,0x0,0xf00001f,0x80001fe0,0x7fe000,0x0,0x0,0x0,0x168fe609,0x0,0x90e07,0x6000,0x3c000e,0x70000f8,
0x1980001f,0x0,0x1f8,0xf00000f,0xf00180,0xfe000,0xe00e,0x1001,0x20060,0x6006006,0x600600,0x600fe07c,0x7fe7fe7f,0xe7fe3fc3,
0xfc3fc3fc,0x7e07060f,0xf00f00,0xf00f0000,0xf360660,0x6606606e,0x76001e0,0xc00180f,0x1681981,0x10000000,0xc00180f,0x1980c00,
0x180f0198,0x3801680c,0x180f01,0x68198000,0xc001,0x80f01980,0x18600198,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,
0x8044020c,0xc01f8060,0x2004004,0x0,0xc,0x3f81f07f,0x87f80383,0xf81f87fc,0x3f83f800,0x0,0x1fc,0x780607f,0x81fe7f87,0xfe7fe1fc,
0x6063fc1f,0x860c6007,0xe7061f8,0x7fc1f87f,0xc3fcfff6,0x6606c03,0x30c6067f,0xe0783000,0xf00d8000,0x6000060,0xc0,0x7e000,0x60006003,
0x600fc00,0x0,0x0,0xc00,0x0,0x0,0x7c0603,0xe0000000,0xfc00000,0x1f0000,0x0,0x900003f,0xc0003fe0,0x7fe000,0x0,0x0,0x0,0x1302660f,
0x0,0xf0606,0x6004,0x7e0006,0x60601f8,0x19800001,0x80000000,0x1f8,0x19800010,0x81080300,0x3f2000,0x2011,0x1001,0x1c0060,0x6006006,
0x600600,0x601fe1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f87061f,0x81f81f81,0xf81f8000,0x3fa60660,0x66066066,0x66003f0,0x6003009,
0x1301981,0x10000000,0x6003009,0x1980600,0x30090198,0x1f013006,0x300901,0x30198000,0x6003,0x901980,0x30600198,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc0f8c,0xc0180060,0x6006044,0x40000000,0xc,0x3181b041,0xc41c0783,0x388018,
0x71c71800,0x0,0x106,0x18c0f061,0xc38261c6,0x600384,0x60606001,0x86186007,0xe78630c,0x60e30c60,0xe7040606,0x630cc03,0x39c30c00,
0xc0603000,0x3018c000,0x3000060,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,0x60000000,0x18400000,0x180000,
0x0,0x19800070,0x40003600,0xc000,0x0,0x0,0x0,0x25a06,0x0,0x6030c,0x4,0xe20007,0xe060180,0xf000,0x80000000,0xf0000,0x10800000,
0x80080600,0x7f2000,0x2020,0x80001001,0x20000,0xf00f00f,0xf00f00,0x601b0382,0x60060060,0x6000600,0x60060060,0x61c78630,0xc30c30c3,
0xc30c000,0x30e60660,0x66066063,0xc600738,0x3006019,0x80000000,0xe0000000,0x3006019,0x80000300,0x60198000,0x3e000003,0x601980,
0x0,0x3006,0x1980000,0x60600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x80cc1fcc,0xc0180060,0x6006035,0x80000000,
0x18,0x71c03000,0xc00c0583,0x300018,0x60c60c00,0x0,0x6,0x3060f060,0xc30060c6,0x600300,0x60606001,0x86306007,0x9e78670e,0x60670e60,
0x66000606,0x630c606,0x19830c01,0xc0601800,0x30306000,0x60,0xc0,0x60000,0x60000000,0x6000c00,0x0,0x0,0xc00,0x0,0x0,0x600600,
0x60000000,0x18000000,0x300000,0x0,0x78060,0x6600,0x1c000,0x300c,0x39819c0,0x0,0x25a00,0x0,0x30c,0x4,0xc00003,0xc060180,0x30c1f,
0x80000000,0x30c000,0x10800001,0x80700000,0x7f2000,0x2020,0x80001001,0x20060,0xf00f00f,0xf00f00,0xf01b0300,0x60060060,0x6000600,
0x60060060,0x60c78670,0xe70e70e7,0xe70e000,0x70c60660,0x66066063,0xc7f8618,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,
0x0,0x600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6019,0x87ff3a4c,0xc0180060,0x400600e,0x600000,0x18,0x60c03000,
0xc00c0d83,0x700018,0x60c60c00,0x20,0x400006,0x3060f060,0xc6006066,0x600600,0x60606001,0x86606006,0x966c6606,0x60660660,0x66000606,
0x630c666,0xf019801,0x80601800,0x30603000,0x1f06f,0xf01ec0,0xf03fe1ec,0x6703e01f,0x61c0c06,0xdc6701f0,0x6f01ec0c,0xe1f87fc6,
0xc60cc03,0x71c60c7f,0xc0600600,0x60000000,0x30000000,0x300000,0x40040,0x88060,0x6600,0x18000,0x300c,0x1981980,0x0,0x2421f,
0x80003ce0,0x7fc198,0x601f,0xc02021,0x980600c0,0x40230,0x80000000,0x402000,0x19806003,0x80006,0xc7f2000,0x2020,0x80001001,
0x420060,0xf00f00f,0xf00f00,0xf01b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x6606208,0x60e60660,0x66066061,
0x987fc670,0x1f01f01f,0x1f01f01,0xf039c0f0,0xf00f00f,0xf03e03,0xe03e03e0,0x1f06701f,0x1f01f01,0xf01f0060,0x1e660c60,0xc60c60c6,
0xc6f060c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x7ff3207,0x8c0c0000,0xc00300e,0x600000,0x30,0x60c03000,
0xc01c0983,0xf0600030,0x31860c06,0x6001e0,0x78000e,0x23e1f861,0xc6006066,0x600600,0x60606001,0x86c06006,0x966c6606,0x60660660,
0xe7000606,0x630c666,0xf01f803,0x600c00,0x30000000,0x3f87f,0x83f83fc3,0xf83fe3fc,0x7f83e01f,0x6380c07,0xfe7f83f8,0x7f83fc0d,
0xf3fc7fc6,0xc71cc03,0x3183187f,0xc0600600,0x60000000,0xff806000,0x300000,0x40040,0x88070,0x6600,0x60030060,0x6001818,0x1883180,
0x0,0x2423f,0xc0007ff0,0x607fc1f8,0x603f,0x80c01fc1,0xf80601e0,0x5f220,0x80420000,0x5f2000,0xf006006,0x80006,0xc7f2000,0x2020,
0x82107c07,0xc03c0060,0x1f81f81f,0x81f81f80,0xf03b0600,0x60060060,0x6000600,0x60060060,0x6066c660,0x66066066,0x660671c,0x61660660,
0x66066061,0xf860e6c0,0x3f83f83f,0x83f83f83,0xf87fe3f8,0x3f83f83f,0x83f83e03,0xe03e03e0,0x3f87f83f,0x83f83f83,0xf83f8060,
0x3fc60c60,0xc60c60c3,0x187f8318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x883200,0x300c0000,0xc003035,0x80600000,
0x30,0x66c03001,0xc0f81983,0xf86f0030,0x1f071c06,0x600787,0xfe1e001c,0x6261987f,0x86006067,0xfe7fc600,0x7fe06001,0x87c06006,
0xf6646606,0x60e6067f,0xc3e00606,0x61986f6,0x600f007,0x600c00,0x30000000,0x21c71,0x830831c3,0x1c06031c,0x71c06003,0x6700c06,
0x6671c318,0x71831c0f,0x16040c06,0xc318606,0x1b031803,0x80600600,0x60000000,0x30009000,0x300000,0x40040,0x7003e,0x67e0,0x90070090,
0x9001818,0x8c3100,0x0,0x60,0x4000e730,0x900380f0,0x6034,0x80c018c7,0xfe060338,0xb0121,0x80c60000,0x909000,0x6008,0x1080006,
0xc3f2000,0x2011,0x3180060,0x60060e0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0x60664660,0x66066066,
0x66063b8,0x62660660,0x66066060,0xf06066c0,0x21c21c21,0xc21c21c2,0x1c466308,0x31c31c31,0xc31c0600,0x60060060,0x31871c31,0x83183183,
0x18318000,0x71860c60,0xc60c60c3,0x18718318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1981a00,0xe03e0000,0xc003044,
0x40600000,0x60,0x66c03001,0x80f03182,0x1c7f8030,0x3f83fc06,0x601e07,0xfe078038,0x6661987f,0x86006067,0xfe7fc61e,0x7fe06001,
0x87e06006,0x66666606,0x7fc6067f,0x81f80606,0x61986f6,0x6006006,0x600600,0x30000000,0xc60,0xc60060c6,0xc06060c,0x60c06003,
0x6e00c06,0x6660c60c,0x60c60c0e,0x6000c06,0xc318666,0x1f031803,0x600600,0x603c2000,0x30016800,0x1fe0000,0x1f81f8,0x1c1f,0x804067e1,
0x68060168,0x16800810,0xc42300,0x0,0x60,0x20c331,0x68030060,0x6064,0x3fc1040,0xf006031c,0xa011e,0x818c7fe0,0x909000,0x7fe1f,
0x80f00006,0xc0f2060,0xf80e,0x18c0780,0x780781c0,0x19819819,0x81981981,0x9833c600,0x7fe7fe7f,0xe7fe0600,0x60060060,0xfc666660,
0x66066066,0x66061f0,0x66660660,0x66066060,0x606066e0,0xc00c00,0xc00c00c0,0xc066600,0x60c60c60,0xc60c0600,0x60060060,0x60c60c60,
0xc60c60c6,0xc60c000,0x61c60c60,0xc60c60c3,0x1860c318,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x1980f81,0x80373000,
0xc003004,0x7fe0001,0xf0000060,0x60c03003,0x183180,0xc71c060,0x3181ec00,0x7000,0xe070,0x66619860,0xc6006066,0x60061e,0x60606001,
0x87606006,0x66626606,0x7f860661,0xc01c0606,0x6198696,0xf00600e,0x600600,0x30000000,0x1fc60,0xc60060c7,0xfc06060c,0x60c06003,
0x7c00c06,0x6660c60c,0x60c60c0c,0x7f00c06,0xc3b8666,0xe01b007,0x3c00600,0x3c7fe000,0xff03ec00,0x1fe0000,0x40040,0xe001,0xc0806603,
0xec0e03ec,0x3ec00010,0x0,0x60000000,0x7f,0x10c3f3,0xec070060,0x6064,0x3fc1040,0x6000030c,0xa0100,0x3187fe1,0xf09f1000,0x7fe00,
0x6,0xc012060,0x0,0xc63c03,0xc03c0380,0x19819819,0x81981981,0x98330600,0x60060060,0x6000600,0x60060060,0xfc662660,0x66066066,
0x66060e0,0x6c660660,0x66066060,0x6060e630,0x1fc1fc1f,0xc1fc1fc1,0xfc3fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
0xc60c7fe,0x62c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe02c6,0x3c633000,0xc003004,
0x7fe0001,0xf00000c0,0x60c03006,0xc6180,0xc60c060,0x60c00c00,0x7000,0xe060,0x66639c60,0x66006066,0x600606,0x60606001,0x86306006,
0x66636606,0x60060660,0xc0060606,0x61f8696,0xf00600c,0x600300,0x30000000,0x3fc60,0xc60060c7,0xfc06060c,0x60c06003,0x7c00c06,
0x6660c60c,0x60c60c0c,0x1f80c06,0xc1b0666,0xe01b00e,0x3c00600,0x3c43c000,0x3007de00,0x600000,0x40040,0x30000,0x61006607,0xde0c07de,
0x7de00000,0x0,0xf07fefff,0x1f,0x8008c3f7,0xde0e0060,0x6064,0xc01047,0xfe00018c,0xb013f,0x86300061,0xf0911000,0x6000,0x6,
0xc012060,0x3f,0x8063c0cc,0x3cc0c700,0x39c39c39,0xc39c39c1,0x98630600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,
0x66061f0,0x78660660,0x66066060,0x607fc618,0x3fc3fc3f,0xc3fc3fc3,0xfc7fe600,0x7fc7fc7f,0xc7fc0600,0x60060060,0x60c60c60,0xc60c60c6,
0xc60c7fe,0x64c60c60,0xc60c60c1,0xb060c1b0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0xffe0260,0x6661b000,0xc003000,
0x600000,0xc0,0x60c0300c,0xc7fe0,0xc60c060,0x60c01c00,0x1e07,0xfe078060,0x6663fc60,0x66006066,0x600606,0x60606001,0x86386006,
0x6636606,0x60060660,0xe0060606,0x60f039c,0x1b806018,0x600300,0x30000000,0x70c60,0xc60060c6,0x6060c,0x60c06003,0x7600c06,
0x6660c60c,0x60c60c0c,0x1c0c06,0xc1b03fc,0xe01f01c,0xe00600,0x70000000,0x3007fc00,0x600000,0x40040,0x0,0x62006607,0xfc1807fc,
0x7fc00000,0x0,0xf0000000,0x1,0xc004c307,0xfc1c0060,0x6064,0xc018c0,0x600000d8,0x5f200,0x3180060,0x50a000,0x6000,0x6,0xc012000,
0x0,0xc601c0,0x4201c600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0600,0x60060060,0x6000600,0x60060060,0x60663660,0x66066066,0x66063b8,
0x70660660,0x66066060,0x607f860c,0x70c70c70,0xc70c70c7,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,
0x68c60c60,0xc60c60c1,0xf060c1f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3300260,0x6661e000,0xc003000,0x600000,
0x180,0x71c03018,0xc7fe0,0xc60c0c0,0x60c01800,0x787,0xfe1e0060,0x6663fc60,0x630060c6,0x600306,0x60606001,0x86186006,0x661e70e,
0x60070c60,0x60060606,0x60f039c,0x19806038,0x600180,0x30000000,0x60c60,0xc60060c6,0x6060c,0x60c06003,0x6700c06,0x6660c60c,
0x60c60c0c,0xc0c06,0xc1b039c,0x1f00e018,0x600600,0x60000000,0x1803f800,0x600000,0x40040,0x39e00,0x63006603,0xf83803f8,0x3f800000,
0x0,0x60000000,0x0,0xc00cc303,0xf8180060,0x6064,0xc01fc0,0x60060070,0x40200,0x18c0060,0x402000,0x6000,0x6,0xc012000,0x0,0x18c0140,
0x2014600,0x3fc3fc3f,0xc3fc3fc3,0xfc7f0300,0x60060060,0x6000600,0x60060060,0x60c61e70,0xe70e70e7,0xe70e71c,0x60e60660,0x66066060,
0x6060060c,0x60c60c60,0xc60c60c6,0xcc60600,0x60060060,0x6000600,0x60060060,0x60c60c60,0xc60c60c6,0xc60c000,0x70c60c60,0xc60c60c0,
0xe060c0e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x33022e0,0x6670c000,0xc003000,0x600600,0x60180,0x31803030,
0x41c0184,0x1831c0c0,0x71c23806,0x6001e0,0x780000,0x62630c60,0xe38261c6,0x600386,0x60606043,0x860c6006,0x661e30c,0x60030c60,
0x740e0607,0xe0f039c,0x31c06030,0x600180,0x30000000,0x61c71,0x830831c3,0x406031c,0x60c06003,0x6300c06,0x6660c318,0x71831c0c,
0x41c0c07,0x1c0e039c,0x1b00e030,0x600600,0x60000000,0x1c41b00e,0x601cc0,0x401f8,0x45240,0xe1803601,0xb03001b0,0x1b000000,
0x0,0x0,0x41,0xc008e711,0xb0300060,0x6034,0x80c02020,0x60060030,0x30c00,0xc60000,0x30c000,0x0,0x7,0x1c012000,0x0,0x3180240,
0x6024608,0x30c30c30,0xc30c30c3,0xc630382,0x60060060,0x6000600,0x60060060,0x61c61e30,0xc30c30c3,0xc30c208,0x70c70e70,0xe70e70e0,
0x6060068c,0x61c61c61,0xc61c61c6,0x1cc62308,0x30430430,0x43040600,0x60060060,0x31860c31,0x83183183,0x18318060,0x31c71c71,
0xc71c71c0,0xe07180e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2203fc0,0x663f6000,0x6006000,0x600600,0x60300,
0x3f81fe7f,0xc7f80187,0xf83f80c0,0x3f83f006,0x600020,0x400060,0x33e6067f,0xc1fe7f87,0xfe6001fe,0x6063fc7f,0x60e7fe6,0x660e3f8,
0x6001f860,0x37fc0603,0xfc06030c,0x30c0607f,0xe06000c0,0x30000000,0x7fc7f,0x83f83fc3,0xfc0603fc,0x60c7fe03,0x61807c6,0x6660c3f8,
0x7f83fc0c,0x7f80fc3,0xfc0e039c,0x3180607f,0xc0600600,0x60000000,0xfc0e00c,0x601986,0x66040040,0x4527f,0xc0803fe0,0xe07fe0e0,
0xe000000,0x0,0x0,0x7f,0x80107ff0,0xe07fc060,0x603f,0x83fe0000,0x60060018,0xf000,0x420000,0xf0000,0x7fe00,0x7,0xfe012000,
0x0,0x2100640,0xc0643f8,0x60660660,0x66066067,0xec3e1fe,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7f860e3f,0x83f83f83,0xf83f8000,
0x5fc3fc3f,0xc3fc3fc0,0x606006fc,0x7fc7fc7f,0xc7fc7fc7,0xfcffe3f8,0x3fc3fc3f,0xc3fc7fe7,0xfe7fe7fe,0x3f860c3f,0x83f83f83,
0xf83f8060,0x7f83fc3f,0xc3fc3fc0,0x607f8060,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x2201f80,0x3c1e7000,0x6006000,
0x600,0x60300,0xe01fe7f,0xc3f00183,0xe01f0180,0x1f01e006,0x600000,0x60,0x3006067f,0x807c7e07,0xfe6000f8,0x6063fc3e,0x6067fe6,
0x660e0f0,0x6000f060,0x3bf80601,0xf806030c,0x60e0607f,0xe06000c0,0x30000000,0x1ec6f,0xf01ec0,0xf80601ec,0x60c7fe03,0x61c03c6,
0x6660c1f0,0x6f01ec0c,0x3f007c1,0xcc0e030c,0x71c0c07f,0xc0600600,0x60000000,0x7804018,0xe01186,0x66040040,0x39e3f,0x80401fe0,
0x407fe040,0x4000000,0x0,0x0,0x3f,0x203ce0,0x407fc060,0x601f,0x3fe0000,0x60060018,0x0,0x0,0x0,0x7fe00,0x6,0xe6012000,0x0,
0x7e0,0x1807e1f0,0x60660660,0x66066066,0x6c3e07c,0x7fe7fe7f,0xe7fe3fc3,0xfc3fc3fc,0x7e060e0f,0xf00f00,0xf00f0000,0x8f01f81f,
0x81f81f80,0x60600670,0x1ec1ec1e,0xc1ec1ec1,0xec79c0f0,0xf80f80f,0x80f87fe7,0xfe7fe7fe,0x1f060c1f,0x1f01f01,0xf01f0000,0x4f01cc1c,
0xc1cc1cc0,0xc06f00c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x6006000,0x600,0x600,0x0,0x0,0x0,0x0,
0x600000,0x0,0x18000000,0x0,0x0,0x0,0x0,0x0,0x1800,0x0,0x0,0x0,0x600060,0x30000000,0x0,0x0,0xc,0x3,0x0,0x0,0x60000c00,0x0,
0x0,0xc000,0x600600,0x60000000,0x18,0xc03100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f8,0x0,0x0,0x0,0x0,0x6,
0x12000,0x2000000,0x40,0x20004000,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0xc06000c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x2004000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,
0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0xc00,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x21c,0x3,0x0,0x0,0x60000c00,0x0,0x0,0xc000,
0x7c0603,0xe0000000,0x10,0xc02300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x601f0,0x0,0x0,0x0,0x0,0x6,0x12000,0x1000000,
0x40,0x7e004000,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc06000c0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200,0x0,0x300c000,0xc00,0x0,0x0,0x0,0x0,0x0,0xc00000,0x0,0x7800000,0x0,
0x0,0x0,0x0,0x0,0x800,0x0,0x0,0x0,0x780000,0xf0000000,0x0,0x0,0x3f8,0x3e,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x3c0603,0xc0000000,
0x10,0xfc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4,0x0,0x60000,0x0,0x0,0x0,0x0,0x6,0x0,0x1000000,0x0,0x0,0x0,0x0,
0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,
0x0,0x1f0,0x3c,0x0,0x0,0x60000c00,0x0,0x0,0x38000,0x600,0x0,0x0,0xf000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x6,0x0,0xe000000,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x3,0x80600380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xffc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
// Definition of a 16x32 font.
const unsigned int font16x32[16*32*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x70000e0,0x3c00730,0xe7001c0,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0x730,0x70000e0,0x3c00730,
0xe700000,0x700,0xe003c0,0xe7000e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x18001c0,0x6600ff0,0xe7003e0,0x0,0x18001c0,0x6600e70,0x18001c0,0x6600e70,0xff0,0x18001c0,0x6600ff0,0xe700000,0x180,
0x1c00660,0xe7001c0,0x0,0x0,0x0,0x380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00380,
0xc300ce0,0xe700630,0x0,0x1c00380,0xc300e70,0x1c00380,0xc300e70,0xce0,0x1c00380,0xc300ce0,0xe700000,0x1c0,0x3800c30,0xe700380,
0x0,0x0,0x0,0x7c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0xc300000,0x0,0xc300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x700000,0x0,0x0,0x0,0x7c007c00,0x3e000000,
0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000070,0x1800000,0xc60,0x0,0xe000070,0x1800000,0xe000070,
0x1800000,0x0,0xe000070,0x1800000,0x0,0xe00,0x700180,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x800000,0x0,0x600600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x3f0,0xfc0,0x0,0x7000000,0x38000000,0x1c0000,0xfc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,
0x1801f00,0x0,0x0,0x1c,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7300000,0x6600000,0x0,0x6600000,0x0,0x0,0x0,0x0,0xe700000,
0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0x0,0xc000c00,0x43800000,0x0,0x0,0x630,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0xf80,0x70000e0,0x3c00730,0xe700c60,0x0,0x70000e0,0x3c00e70,0x70000e0,0x3c00e70,0xe000730,0x70000e0,0x3c00730,0xe700000,0x700,
0xe003c0,0xe7000e0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300000,0x803c00,0x7c00180,
0xc00300,0x1000000,0x0,0x1c,0x3c007c0,0xfc007e0,0xe01ff8,0x3f03ffc,0x7e007c0,0x0,0x0,0x7c0,0x1c0,0x7f8003f0,0x7f007ff8,0x7ff803f0,
0x70381ffc,0xff0700e,0x7000783c,0x783807c0,0x7fc007c0,0x7fc00fc0,0x7fff7038,0x700ee007,0x780f780f,0x7ffc03f0,0x70000fc0,0x3c00000,
0x3000000,0x38000000,0x1c0000,0x1fc0000,0x380001c0,0xe01c00,0x7f800000,0x0,0x0,0x0,0x0,0x0,0x0,0xfc,0x1801f80,0x0,0x1f80000,
0x7e,0x0,0x0,0x2400000,0xfc00000,0x7ff0000,0x7ffc0000,0x0,0x0,0x0,0x0,0xf30fb0c,0x2400000,0x0,0x240780f,0x1c0,0xfc,0x780f,
0x18003f0,0xe700000,0x7c00000,0x0,0xff0,0x3c00000,0x78007c0,0xc00000,0xff80000,0xf80,0x7c00000,0xc000c00,0x18001c0,0x1c001c0,
0x1c001c0,0x1c003e0,0x7fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007838,0x7c007c0,0x7c007c0,0x7c00000,0x7c67038,
0x70387038,0x7038780f,0x70001fe0,0x30000c0,0x2400f30,0xe700c60,0x0,0x30000c0,0x2400e70,0x30000c0,0x2400e70,0xf700f30,0x30000c0,
0x2400f30,0xe700000,0x300,0xc00240,0xe7000c0,0x38000e70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,
0x630018c,0x807e00,0xfe00180,0xc00300,0x1000000,0x0,0x38,0xff01fc0,0x3ff01ff0,0x1e01ff8,0x7f83ffc,0x1ff80ff0,0x0,0x0,0xff0,
0x1f003e0,0x7fe00ff8,0x7fc07ff8,0x7ff80ff8,0x70381ffc,0xff0701c,0x7000783c,0x78381ff0,0x7fe01ff0,0x7fe01ff0,0x7fff7038,0x781ee007,
0x3c1e380e,0x7ffc0380,0x380001c0,0x3c00000,0x1800000,0x38000000,0x1c0000,0x3c00000,0x380001c0,0xe01c00,0x3800000,0x0,0x0,
0x0,0x7000000,0x0,0x0,0x1e0,0x18003c0,0x0,0x3fc0000,0x70,0x0,0x0,0x6600000,0x1ff00000,0x1fff0000,0x7ffc0000,0x0,0x0,0x0,0x0,
0xcf0239c,0x3c00000,0x0,0x3c0380e,0x1c0,0x2001fe,0x380e,0x18007f8,0xe700000,0x8600000,0x0,0xff0,0x7e00000,0x8c00870,0x1800000,
0x1ff80000,0x180,0xc600000,0xc000c00,0x38001c0,0x3e003e0,0x3e003e0,0x3e001c0,0x7fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,
0x7fc07838,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x1fec7038,0x70387038,0x7038380e,0x70003ce0,0x1800180,0x6600cf0,0xe7007c0,0x0,
0x1800180,0x6600e70,0x1800180,0x6600e70,0x7c00cf0,0x1800180,0x6600cf0,0xe700000,0x180,0x1800660,0xe700180,0x38000e70,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630030c,0x3f0e700,0x1e200180,0x1800180,0x21100000,0x0,
0x38,0x1e7819c0,0x38781038,0x1e01c00,0xf080038,0x1c381c38,0x0,0x0,0x1878,0x7fc03e0,0x70e01e18,0x70e07000,0x70001e18,0x703801c0,
0x707038,0x70007c7c,0x7c381c70,0x70701c70,0x70703830,0x1c07038,0x381ce007,0x1c1c3c1e,0x3c0380,0x380001c0,0x7e00000,0xc00000,
0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,0x70c0000,0xe0,
0x0,0x0,0xc300000,0x38300000,0x3c700000,0x3c0000,0x0,0x0,0x0,0x0,0xce022f4,0x1800000,0x0,0x1803c1e,0x1c0,0x2003c2,0x3c1e,
0x1800e08,0x7e0,0x300000,0x0,0x7e00000,0xe700000,0x600030,0x3000000,0x3f980000,0x180,0x18200000,0xc000c00,0x1e0001c0,0x3e003e0,
0x3e003e0,0x3e003e0,0xfe01e18,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70e07c38,0x1c701c70,0x1c701c70,0x1c700000,0x3c787038,
0x70387038,0x70383c1e,0x70003870,0xc00300,0xc300ce0,0x380,0x0,0xc00300,0xc300000,0xc00300,0xc300000,0xfc00ce0,0xc00300,0xc300ce0,
0x0,0xc0,0x3000c30,0x300,0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630031c,0xff8c300,
0x1c000180,0x1800180,0x39380000,0x0,0x70,0x1c3801c0,0x203c001c,0x3e01c00,0x1c000038,0x381c3838,0x0,0x0,0x1038,0xe0e03e0,0x70703c08,
0x70707000,0x70003808,0x703801c0,0x707070,0x70007c7c,0x7c383838,0x70383838,0x70387010,0x1c07038,0x381c700e,0x1e3c1c1c,0x780380,
0x1c0001c0,0xe700000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,
0x0,0xe000000,0xe0,0x0,0x1000100,0x3800,0x70100000,0x38700000,0x780000,0x1c0,0x7801ce0,0xe380000,0x0,0x2264,0x0,0x0,0x1c1c,
0x0,0x200780,0x1c1c,0x1800c00,0x1818,0x7f00000,0x0,0x18180000,0xc300000,0x600070,0x0,0x7f980000,0x180,0x18300000,0xc000c00,
0x3000000,0x3e003e0,0x3e003e0,0x3e003e0,0xee03c08,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,
0x38380000,0x38387038,0x70387038,0x70381c1c,0x7fc03870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xbc00000,0x0,0x0,0x0,0x0,0x0,0x0,
0x38000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0xe88c300,0x1c000180,0x38001c0,
0xfe00180,0x0,0x70,0x1c3801c0,0x1c001c,0x6e01c00,0x1c000078,0x381c3818,0x0,0x40000,0x40000038,0x1c0607e0,0x70703800,0x70707000,
0x70003800,0x703801c0,0x7070e0,0x70007c7c,0x7c383838,0x70383838,0x70387000,0x1c07038,0x381c700e,0xf780e38,0x700380,0x1c0001c0,
0x1c380000,0x0,0x38000000,0x1c0000,0x3800000,0x38000000,0x1c00,0x3800000,0x0,0x0,0x0,0x7000000,0x0,0x0,0x1c0,0x18001c0,0x0,
0xe000000,0xe0,0x0,0x1000100,0x4400,0x70000000,0x38700000,0x700000,0xe0,0x7001c70,0xe380000,0x0,0x2264,0x0,0x0,0xe38,0x0,
0x200700,0xe38,0x1800c00,0x300c,0xc300000,0x0,0x300c0000,0xc300180,0x6003c0,0x0,0x7f980000,0x180,0x18300000,0xc000c00,0x1800000,
0x7e007e0,0x7e007e0,0x7e003e0,0xee03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70707c38,0x38383838,0x38383838,0x38380000,
0x38387038,0x70387038,0x70380e38,0x7ff039f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x40000,0x0,0x0,0x38000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6300318,0x1c80e700,0x1c000180,0x38001c0,0x3800180,
0x0,0xe0,0x381c01c0,0x1c001c,0x6e01c00,0x38000070,0x381c381c,0x0,0x3c0000,0x78000078,0x38030770,0x70707800,0x70387000,0x70007000,
0x703801c0,0x7071c0,0x7000745c,0x7638701c,0x7038701c,0x70387000,0x1c07038,0x1c38718e,0x7700f78,0xf00380,0xe0001c0,0x381c0000,
0x7e0,0x39e003e0,0x79c03f0,0x3ffc079c,0x39e01fc0,0xfe01c1e,0x3807778,0x39e007e0,0x39e0079c,0x73c07e0,0x7ff83838,0x701ce007,
0x783c701c,0x1ffc01c0,0x18001c0,0x0,0x1c000100,0xe0,0x0,0x1000100,0x4200,0x70000000,0x70700100,0xf00100,0x10000e0,0x7000c70,
0xc700000,0x0,0x2204,0x7e00000,0x1e380100,0x1ffc0f78,0x0,0xf80700,0xf78,0x1800e00,0x63e6,0x18300000,0x0,0x6fe60000,0xe700180,
0xc00060,0x3838,0x7f980000,0x180,0x18300000,0xc000c00,0x18001c0,0x7700770,0x7700770,0x77007f0,0xee07800,0x70007000,0x70007000,
0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1008,0x707c7038,0x70387038,0x70380f78,0x707039c0,0x7e007e0,0x7e007e0,
0x7e007e0,0x1f3c03e0,0x3f003f0,0x3f003f0,0x1fc01fc0,0x1fc01fc0,0x7f039e0,0x7e007e0,0x7e007e0,0x7e00380,0x7ce3838,0x38383838,
0x3838701c,0x39e0701c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x6307fff,0x1c807e0c,0xe000180,
0x30000c0,0x3800180,0x0,0xe0,0x381c01c0,0x1c001c,0xce01fe0,0x38000070,0x381c381c,0x3800380,0xfc0000,0x7e0000f0,0x30030770,
0x70707000,0x70387000,0x70007000,0x703801c0,0x707380,0x700076dc,0x7638701c,0x7038701c,0x70387800,0x1c07038,0x1c3873ce,0x7f00770,
0xe00380,0xe0001c0,0x700e0000,0x1ff8,0x3ff00ff0,0xffc0ff8,0x3ffc0ffc,0x3bf01fc0,0xfe01c3c,0x3807f78,0x3bf00ff0,0x3ff00ffc,
0x77e0ff0,0x7ff83838,0x3838e007,0x3c783838,0x1ffc01c0,0x18001c0,0x0,0x7ff00380,0x1e0,0x0,0x1000100,0x4200,0x78000000,0x70700380,
0xe00380,0x3800060,0xe000e30,0x1c600000,0x0,0x2204,0xff00000,0x7f7c0380,0x1ffc0770,0x1c0,0x3fc0700,0x18040770,0x1800780,0x4e12,
0x18300104,0x0,0x4c320000,0x7e00180,0x1c00030,0x3838,0x7f980000,0x180,0x18302080,0xc000c00,0x18001c0,0x7700770,0x7700770,
0x7700770,0x1ee07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c381c,0x705c7038,0x70387038,
0x70380770,0x70383b80,0x1ff81ff8,0x1ff81ff8,0x1ff81ff8,0x3fbe0ff0,0xff80ff8,0xff80ff8,0x1fc01fc0,0x1fc01fc0,0xff83bf0,0xff00ff0,
0xff00ff0,0xff00380,0xffc3838,0x38383838,0x38383838,0x3ff03838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x1c0,0x7fff,0x1c803c38,0xf000000,0x70000e0,0xfe00180,0x0,0x1c0,0x381c01c0,0x3c0078,0xce01ff0,0x39e000f0,0x1c38381c,0x3800380,
0x3e07ffc,0xf8001f0,0x307b0770,0x70e07000,0x70387000,0x70007000,0x703801c0,0x707700,0x700076dc,0x7638701c,0x7038701c,0x70387e00,
0x1c07038,0x1c3873ce,0x3e007f0,0x1e00380,0x70001c0,0x0,0x1038,0x3c381e18,0x1c7c1e3c,0x3801e3c,0x3c7801c0,0xe01c78,0x380739c,
0x3c781c38,0x3c381c3c,0x7c21e10,0x7003838,0x3838700e,0x1ef03838,0x3c01c0,0x18001c0,0x0,0x7fe007c0,0x1c0,0x0,0x1000100,0x6400,
0x7e000000,0x707007c0,0x1e007c0,0x7c00070,0xe000638,0x18600000,0x0,0x0,0x1e100000,0x73ce07c0,0x3c07f0,0x1c0,0x7240700,0x1ddc3ffe,
0x1800de0,0x8c01,0x1870030c,0x0,0x8c310000,0x3c00180,0x3800030,0x3838,0x7f980000,0x180,0x183030c0,0xc000c00,0x430001c0,0x7700770,
0x7700770,0x7700770,0x1ce07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x70387638,0x701c701c,0x701c701c,0x701c1c38,0x70dc7038,
0x70387038,0x703807f0,0x70383b80,0x10381038,0x10381038,0x10381038,0x21e71e18,0x1e3c1e3c,0x1e3c1e3c,0x1c001c0,0x1c001c0,0x1e383c78,
0x1c381c38,0x1c381c38,0x1c380380,0x1c383838,0x38383838,0x38383838,0x3c383838,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0x1e8000e0,0x1f000000,0x70000e0,0x39380180,0x0,0x1c0,0x3b9c01c0,0x3c07f0,0x18e01078,0x3bf800e0,
0x7e0383c,0x3800380,0x1f807ffc,0x3f001c0,0x61ff0e38,0x7fc07000,0x70387ff0,0x7ff07000,0x7ff801c0,0x707f00,0x7000729c,0x7338701c,
0x7070701c,0x70703fc0,0x1c07038,0x1e7873ce,0x1c003e0,0x3c00380,0x70001c0,0x0,0x1c,0x3c381c00,0x1c3c1c1c,0x3801c3c,0x383801c0,
0xe01cf0,0x380739c,0x38381c38,0x3c381c3c,0x7801c00,0x7003838,0x3838700e,0xfe03c78,0x7801c0,0x18001c0,0x0,0x1c000c20,0xff8,
0x0,0x1ff01ff0,0x3818,0x3fc00100,0x707e0c20,0x3c00c20,0xc200030,0xc000618,0x18c00000,0x0,0x0,0x1c000080,0xe1ce0c20,0x7803e0,
0x1c0,0xe200700,0xff83ffe,0x1801878,0x9801,0x1cf0071c,0x7ffc0000,0x8c310000,0x7ffe,0x7000030,0x3838,0x3f980380,0x180,0xc6038e0,
0x7f9c7f9c,0x3e1c01c0,0xe380e38,0xe380e38,0xe380f78,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,0x1c001c0,0xfe387338,0x701c701c,
0x701c701c,0x701c0e70,0x719c7038,0x70387038,0x703803e0,0x70383b80,0x1c001c,0x1c001c,0x1c001c,0xe71c00,0x1c1c1c1c,0x1c1c1c1c,
0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380000,0x3c383838,0x38383838,0x38383c78,0x3c383c78,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x630,0xf800380,0x3f830000,0x70000e0,0x31080180,0x0,0x380,0x3b9c01c0,
0x7807e0,0x38e00038,0x3c3800e0,0xff01c3c,0x3800380,0x7c000000,0x7c03c0,0x61870e38,0x7fc07000,0x70387ff0,0x7ff070fc,0x7ff801c0,
0x707f80,0x7000739c,0x7338701c,0x7ff0701c,0x7fe00ff0,0x1c07038,0xe7073ce,0x1c003e0,0x3800380,0x38001c0,0x0,0x1c,0x381c3800,
0x381c380e,0x380381c,0x383801c0,0xe01de0,0x380739c,0x3838381c,0x381c381c,0x7001e00,0x7003838,0x1c70718e,0x7e01c70,0xf00380,
0x18001e0,0x1e000000,0x1c001bb0,0xff8,0x0,0x1000100,0xe0,0xff00300,0x707e1bb0,0x3801bb0,0x1bb00010,0x8000308,0x30c00000,0x0,
0x0,0x1e0000c0,0xe1ce1bb0,0xf003e0,0x1c0,0x1c203ff8,0x63003e0,0x180181c,0x9801,0xfb00e38,0x7ffc0000,0x8fc10000,0x7ffe,0xe000860,
0x3838,0x1f980380,0x180,0x7c01c70,0x1f001f0,0x1f003c0,0xe380e38,0xe380e38,0xe380e38,0x1cfc7000,0x7ff07ff0,0x7ff07ff0,0x1c001c0,
0x1c001c0,0xfe387338,0x701c701c,0x701c701c,0x701c07e0,0x731c7038,0x70387038,0x703803e0,0x70383980,0x1c001c,0x1c001c,0x1c001c,
0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x387c3838,0x38383838,0x38381c70,
0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc30,0x7f00e00,0x33c30000,0x70000e0,0x1007ffe,
0x0,0x380,0x3b9c01c0,0xf00078,0x30e0001c,0x3c1c01c0,0x1c381fdc,0x0,0x70000000,0x1c0380,0x63030e38,0x70707000,0x70387000,0x700070fc,
0x703801c0,0x707b80,0x7000739c,0x7338701c,0x7fc0701c,0x7fc001f0,0x1c07038,0xe703e5c,0x3e001c0,0x7800380,0x38001c0,0x0,0x7fc,
0x381c3800,0x381c380e,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7001fc0,0x7003838,0x1c70718e,0x7c01c70,
0xe01f00,0x180007c,0x7f8c0000,0x7fc03fb8,0x1c0,0x0,0x1000100,0x700,0x1f00600,0x70703fb8,0x7803fb8,0x3fb80000,0x8000000,0x180,
0x0,0x0,0x1fc00060,0xe1ce3fb8,0xe001c0,0x1c0,0x1c203ff8,0xc1801c0,0x180c,0x9801,0x1c70,0xc0000,0x8cc10000,0x180,0xfe007c0,
0x3838,0x7980380,0xff0,0xe38,0x3e003e00,0x3e000380,0xe380e38,0xe380e38,0xe380e38,0x38e07000,0x70007000,0x70007000,0x1c001c0,
0x1c001c0,0x70387338,0x701c701c,0x701c701c,0x701c03c0,0x731c7038,0x70387038,0x703801c0,0x703838e0,0x7fc07fc,0x7fc07fc,0x7fc07fc,
0xe73800,0x380e380e,0x380e380e,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x38dc3838,0x38383838,0x38381c70,
0x381c1c70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xc60,0xf83878,0x71e30000,0x70000e0,0x1007ffe,
0x7f0,0x380,0x381c01c0,0x1e0003c,0x60e0001c,0x381c01c0,0x381c079c,0x0,0x7c000000,0x7c0380,0x63031c1c,0x70307000,0x70387000,
0x7000701c,0x703801c0,0x7071c0,0x7000739c,0x71b8701c,0x7000701c,0x71e00078,0x1c07038,0xe703e7c,0x7e001c0,0xf000380,0x38001c0,
0x0,0x1ffc,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fc0,0x380739c,0x3838381c,0x381c381c,0x7000ff0,0x7003838,0x1ef03bdc,
0x3800ee0,0x1e01f00,0x180007c,0x61fc0000,0x7fc07f3c,0x1c0,0x0,0x1000100,0x1800,0x780c00,0x70707f3c,0xf007f3c,0x7f3c0000,0x0,
0x3c0,0x3ffcffff,0x0,0xff00030,0xe1fe7f3c,0x1e001c0,0x1c0,0x1c200700,0xc183ffe,0xe0c,0x9801,0x1ff038e0,0xc07f0,0x8c610000,
0x180,0x0,0x3838,0x1980380,0x0,0x1ff0071c,0xe000e000,0xe0000f80,0x1c1c1c1c,0x1c1c1c1c,0x1c1c1e38,0x38e07000,0x70007000,0x70007000,
0x1c001c0,0x1c001c0,0x703871b8,0x701c701c,0x701c701c,0x701c03c0,0x761c7038,0x70387038,0x703801c0,0x70703870,0x1ffc1ffc,0x1ffc1ffc,
0x1ffc1ffc,0xfff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c7ffc,0x389c3838,0x38383838,
0x38380ee0,0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0xfffc,0xbc60fc,0x70e30000,0x70000e0,
0x180,0x7f0,0x700,0x381c01c0,0x3e0001c,0x7ffc001c,0x381c03c0,0x381c001c,0x0,0x1f807ffc,0x3f00380,0x63031ffc,0x70387000,0x70387000,
0x7000701c,0x703801c0,0x7071e0,0x7000701c,0x71b8701c,0x7000701c,0x70f00038,0x1c07038,0x7e03e7c,0x77001c0,0xe000380,0x1c001c0,
0x0,0x3c1c,0x381c3800,0x381c3ffe,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x70003f8,0x7003838,0xee03bdc,
0x3c00ee0,0x3c00380,0x18000e0,0xf00000,0x1c007e7c,0x3c0,0x0,0x1000100,0x0,0x381800,0x70707e7c,0xe007e7c,0x7e7c0000,0x0,0x7c0,
0x0,0x0,0x3f80018,0xe1fe7e7c,0x3c001c0,0x1c0,0x1c200700,0xc183ffe,0xf0c,0x8c01,0x38e0,0xc07f0,0x8c710000,0x180,0x0,0x3838,
0x1980000,0x0,0x71c,0x7000f0,0x700f00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x3fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
0x703871b8,0x701c701c,0x701c701c,0x701c07e0,0x7c1c7038,0x70387038,0x703801c0,0x7ff03838,0x3c1c3c1c,0x3c1c3c1c,0x3c1c3c1c,
0x3fff3800,0x3ffe3ffe,0x3ffe3ffe,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x391c3838,0x38383838,0x38380ee0,
0x381c0ee0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfffc,0x9c01ce,0x70f60000,0x70000e0,0x180,
0x0,0x700,0x381c01c0,0x780001c,0x7ffc001c,0x381c0380,0x381c003c,0x0,0x3e07ffc,0xf800380,0x63031ffc,0x70387000,0x70387000,
0x7000701c,0x703801c0,0x7070f0,0x7000701c,0x71b8701c,0x7000701c,0x70700038,0x1c07038,0x7e03e7c,0xf7801c0,0x1e000380,0x1c001c0,
0x0,0x381c,0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01fe0,0x380739c,0x3838381c,0x381c381c,0x7000078,0x7003838,0xee03a5c,
0x7c00fe0,0x78001c0,0x18001c0,0x0,0x1c003ef8,0x380,0x0,0x1000100,0x810,0x383000,0x70703ef8,0x1e003ef8,0x3ef80000,0x0,0x7c0,
0x0,0x0,0x78000c,0xe1c03ef8,0x78001c0,0x1c0,0x1c200700,0x63001c0,0x18003f8,0x4e12,0x1c70,0xc0000,0x4c320000,0x180,0x0,0x3838,
0x1980000,0x0,0xe38,0x700118,0x701e00,0x1ffc1ffc,0x1ffc1ffc,0x1ffc1ffc,0x7fe07000,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
0x703871b8,0x701c701c,0x701c701c,0x701c0e70,0x7c1c7038,0x70387038,0x703801c0,0x7fc0381c,0x381c381c,0x381c381c,0x381c381c,
0x78e03800,0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0000,0x3b1c3838,0x38383838,0x38380fe0,
0x381c0fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1860,0x9c0186,0x707e0000,0x30000c0,0x180,
0x0,0xe00,0x183801c0,0xf00001c,0xe0001c,0x181c0380,0x381c0038,0x0,0xfc0000,0x7e000000,0x61873c1e,0x70383800,0x70707000,0x7000381c,
0x703801c0,0x707070,0x7000701c,0x70f83838,0x70003838,0x70780038,0x1c07038,0x7e03c3c,0xe3801c0,0x1c000380,0xe001c0,0x0,0x381c,
0x381c3800,0x381c3800,0x380381c,0x383801c0,0xe01ef0,0x380739c,0x3838381c,0x381c381c,0x7000038,0x7003838,0xfe03e7c,0xfe007c0,
0x70001c0,0x18001c0,0x0,0xe001ff0,0x380,0x0,0x1000100,0x162c,0x381800,0x30701ff0,0x1c001ff0,0x1ff00000,0x0,0x3c0,0x0,0x0,
0x380018,0xe1c01ff0,0x70001c0,0x1c0,0x1c200700,0xff801c0,0x18000f0,0x63e6,0xe38,0x0,0x6c3e0000,0x0,0x0,0x3838,0x1980000,0x0,
0x1c70,0xf0000c,0xf01c00,0x3c1e3c1e,0x3c1e3c1e,0x3c1e3c1c,0x70e03800,0x70007000,0x70007000,0x1c001c0,0x1c001c0,0x707070f8,
0x38383838,0x38383838,0x38381c38,0x38387038,0x70387038,0x703801c0,0x7000381c,0x381c381c,0x381c381c,0x381c381c,0x70e03800,
0x38003800,0x38003800,0x1c001c0,0x1c001c0,0x381c3838,0x381c381c,0x381c381c,0x381c0380,0x3e1c3838,0x38383838,0x383807c0,0x381c07c0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x18c0,0x9c0186,0x783c0000,0x38001c0,0x180,0x3800000,
0x3800e00,0x1c3801c0,0x1e00003c,0xe00038,0x1c1c0780,0x381c0038,0x3800380,0x3c0000,0x78000000,0x61ff380e,0x70383808,0x70707000,
0x7000381c,0x703801c0,0x40707078,0x7000701c,0x70f83838,0x70003838,0x70384038,0x1c07038,0x7e03c3c,0x1e3c01c0,0x3c000380,0xe001c0,
0x0,0x383c,0x3c381c00,0x1c3c1c00,0x3801c3c,0x383801c0,0xe01c78,0x380739c,0x38381c38,0x3c381c3c,0x7000038,0x7003878,0x7c01e78,
0x1ef007c0,0xf0001c0,0x18001c0,0x0,0xe000ee0,0x7800380,0xe380000,0x1001ff0,0x2242,0x40380c00,0x38700ee0,0x3c000ee0,0xee00000,
0x0,0x0,0x0,0x0,0x380030,0xe1c00ee0,0xf0001c0,0x1c0,0xe200700,0xdd801c0,0x1800038,0x300c,0x71c,0x0,0x300c0000,0x0,0x0,0x3838,
0x1980000,0x0,0x38e0,0xb0000c,0xb01c08,0x380e380e,0x380e380e,0x380e380e,0x70e03808,0x70007000,0x70007000,0x1c001c0,0x1c001c0,
0x707070f8,0x38383838,0x38383838,0x3838381c,0x38387038,0x70387038,0x703801c0,0x7000381c,0x383c383c,0x383c383c,0x383c383c,
0x70e01c00,0x1c001c00,0x1c001c00,0x1c001c0,0x1c001c0,0x1c383838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383878,0x38783878,0x387807c0,
0x3c3807c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x18c0,0x10b801ce,0x3c3e0000,0x38001c0,0x180,
0x3800000,0x3801c00,0x1e7801c0,0x3c002078,0xe02078,0x1c380700,0x1c3810f0,0x3800380,0x40000,0x40000380,0x307b380e,0x70701e18,
0x70e07000,0x70001c1c,0x703801c0,0x60e0703c,0x7000701c,0x70f83c78,0x70003c70,0x703c70f0,0x1c03870,0x3c01c3c,0x3c1c01c0,0x78000380,
0x7001c0,0x0,0x3c7c,0x3c381e18,0x1c7c1e0c,0x3801c3c,0x383801c0,0xe01c38,0x3c0739c,0x38381c38,0x3c381c3c,0x7001078,0x7803c78,
0x7c01c38,0x1c780380,0x1e0001c0,0x18001c0,0x0,0x70c06c0,0x7000380,0xe300000,0x1000100,0x2142,0x70f00600,0x3c7006c0,0x780006c0,
0x6c00000,0x0,0x0,0x0,0x0,0x10780060,0x73e206c0,0x1e0001c0,0x1c0,0x7240700,0x180c01c0,0x1800018,0x1818,0x30c,0x0,0x18180000,
0x0,0x0,0x3c78,0x1980000,0x0,0x30c0,0x130000c,0x1301c18,0x380e380e,0x380e380e,0x380e380e,0x70e01e18,0x70007000,0x70007000,
0x1c001c0,0x1c001c0,0x70e070f8,0x3c783c78,0x3c783c78,0x3c781008,0x7c783870,0x38703870,0x387001c0,0x70003a3c,0x3c7c3c7c,0x3c7c3c7c,
0x3c7c3c7c,0x79f11e18,0x1e0c1e0c,0x1e0c1e0c,0x1c001c0,0x1c001c0,0x1c783838,0x1c381c38,0x1c381c38,0x1c380380,0x1c383c78,0x3c783c78,
0x3c780380,0x3c380380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x38c0,0x1ff800fc,0x1fee0000,
0x1800180,0x180,0x3800000,0x3801c00,0xff01ffc,0x3ffc3ff0,0xe03ff0,0xff00700,0x1ff81fe0,0x3800380,0x0,0x380,0x3000780f,0x7ff00ff8,
0x7fc07ff8,0x70000ffc,0x70381ffc,0x7fe0701c,0x7ff8701c,0x70781ff0,0x70001ff0,0x701c7ff0,0x1c01fe0,0x3c01c38,0x380e01c0,0x7ffc0380,
0x7001c0,0x0,0x1fdc,0x3ff00ff0,0xffc0ffc,0x3800fdc,0x38383ffe,0xe01c3c,0x1fc739c,0x38380ff0,0x3ff00ffc,0x7001ff0,0x3f81fb8,
0x7c01c38,0x3c3c0380,0x1ffc01c0,0x18001c0,0x0,0x3fc0380,0x7000380,0xc70718c,0x1000100,0x2244,0x7ff00200,0x1fff0380,0x7ffc0380,
0x3800000,0x0,0x0,0x0,0x0,0x1ff000c0,0x7f7e0380,0x1ffc01c0,0x1c0,0x3fc3ffe,0x1c0,0x1800018,0x7e0,0x104,0x0,0x7e00000,0x7ffe,
0x0,0x3fde,0x1980000,0x0,0x2080,0x3300018,0x3300ff0,0x780f780f,0x780f780f,0x780f780e,0xf0fe0ff8,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,
0x1ffc1ffc,0x7fc07078,0x1ff01ff0,0x1ff01ff0,0x1ff00000,0x7ff01fe0,0x1fe01fe0,0x1fe001c0,0x70003bf8,0x1fdc1fdc,0x1fdc1fdc,
0x1fdc1fdc,0x3fbf0ff0,0xffc0ffc,0xffc0ffc,0x3ffe3ffe,0x3ffe3ffe,0xff03838,0xff00ff0,0xff00ff0,0xff00000,0x3ff01fb8,0x1fb81fb8,
0x1fb80380,0x3ff00380,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0,0x31c0,0x7e00078,0x7cf0000,0x1800180,
0x0,0x3800000,0x3803800,0x3c01ffc,0x3ffc0fe0,0xe01fc0,0x3e00e00,0x7e00f80,0x3800380,0x0,0x380,0x18007007,0x7fc003f0,0x7f007ff8,
0x700003f0,0x70381ffc,0x3f80701e,0x7ff8701c,0x707807c0,0x700007c0,0x701e1fc0,0x1c00fc0,0x3c01818,0x780f01c0,0x7ffc0380,0x3801c0,
0x0,0xf9c,0x39e003e0,0x79c03f0,0x380079c,0x38383ffe,0xe01c1e,0x7c739c,0x383807e0,0x39e0079c,0x7000fc0,0x1f80f38,0x3801c38,
0x781e0380,0x1ffc01c0,0x18001c0,0x0,0x1f80100,0xe000700,0x1c60718c,0x1000100,0x1e3c,0x1fc00100,0x7ff0100,0x7ffc0100,0x1000000,
0x0,0x0,0x0,0x0,0xfc00080,0x3e3c0100,0x1ffc01c0,0x1c0,0xf83ffe,0x1c0,0x1800838,0x0,0x0,0x0,0x0,0x7ffe,0x0,0x3b9e,0x1980000,
0x0,0x0,0x2300038,0x23003e0,0x70077007,0x70077007,0x70077007,0xe0fe03f0,0x7ff87ff8,0x7ff87ff8,0x1ffc1ffc,0x1ffc1ffc,0x7f007078,
0x7c007c0,0x7c007c0,0x7c00000,0xc7c00fc0,0xfc00fc0,0xfc001c0,0x700039f0,0xf9c0f9c,0xf9c0f9c,0xf9c0f9c,0x1f1e03e0,0x3f003f0,
0x3f003f0,0x3ffe3ffe,0x3ffe3ffe,0x7e03838,0x7e007e0,0x7e007e0,0x7e00000,0x63e00f38,0xf380f38,0xf380380,0x39e00380,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x3000000,0x3800,0x0,0x0,0x0,0x0,
0x0,0x300,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0,0x0,0x0,0x0,0x0,0x380,0x3801c0,0x0,0x0,0x0,0x0,0x1c,0x0,0xe00000,
0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1c0,0x18001c0,0x0,0x0,0xe000700,0x18600000,0x1000100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800ff0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0x1800000,0x0,0x6300070,0x6300000,0x0,
0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40000000,
0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,0xc00300,0x0,0x7000000,
0x7000,0x0,0x0,0x0,0x0,0x0,0x700,0x0,0x0,0xf040000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x3f0,0x1c0fc0,0x0,0x0,
0x0,0x0,0x1c,0x0,0xe00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x700,0x1e0,0x18003c0,0x0,0x0,0xc000700,0x18c00000,0x1000000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x18007e0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
0x0,0x7f800e0,0x7f80000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x700,0x38000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,
0x0,0x600600,0x0,0x6000000,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,
0x3f0,0xfc0,0x0,0x0,0x0,0x0,0x838,0x0,0x1e00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0xf00,0xfc,0x1801f80,0x0,0x0,0x8008e00,0x30c00000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x1980000,0xc00000,
0x0,0x3001c0,0x300000,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0xf00,0x38000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x800000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0xff0,0x0,0x1fc00000,0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3e00,0x7c,0x1801f00,0x0,0x0,0x800fe00,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x200000,0x0,0x1800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7c00000,0x0,0x3001fc,0x300000,
0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x3e00,0x38003e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x7e0,0x0,0x1f000000,
0x0,0x0,0x3800001c,0x0,0x0,0x0,0x3c00,0x0,0x1800000,0x0,0x0,0x7800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00,0x38003c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
// Definition of a 19x38 font.
const unsigned int font19x38[19*38*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c380000,0x0,0x1c380,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800007,0x3c003,0x86000000,
0x1e00000,0x3,0x80000700,0x3c00000,0x380000,0x70003c00,0x0,0xe1800e,0x1c00,0xf000e18,0x0,0x0,0x700000e0,0x780000,0x7000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe700000,0x0,0xe700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c0000e,0x7e003,0xe60071c0,0x7f80000,0x1,0xc0000e00,0x7e0038e,0x1c0000,
0xe0007e00,0x38e00000,0xf98007,0x3800,0x1f800f98,0x1c70000,0x0,0x380001c0,0xfc0071,0xc000e000,0x0,0x0,0x0,0x0,0x3e00000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x7e00000,0x0,0x7e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe0001c,0xe7006,0x7c0071c0,0xe180000,0x0,0xe0001c00,0xe70038e,0xe0001,0xc000e700,0x38e00000,
0x19f0003,0x80007000,0x39c019f0,0x1c70000,0x0,0x1c000380,0x1ce0071,0xc001c000,0x0,0x0,0x0,0x0,0x7f00000,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,
0x0,0x3c00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x700038,0x1c3806,0x3c0071c0,0xc0c0000,0x0,0x70003800,0x1c38038e,0x70003,0x8001c380,0x38e00000,0x18f0001,0xc000e000,
0x70e018f0,0x1c70000,0x0,0xe000700,0x3870071,0xc0038000,0x0,0x0,0x0,0x0,0xe380000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x60000000,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c38,0x0,0x1,0xc3800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe000003,0x80018000,0x0,0xc180000,
0xe,0x380,0x1800000,0xe00000,0x38001800,0x0,0x38,0xe00,0x6000000,0x0,0x1,0xc0000070,0x300000,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78c00,0xc30,
0x0,0x0,0xc3000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800000,0x0,0x0,0x0,0xe0,0x1c000f,0xc0000000,0x0,0x0,
0x0,0xc0c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7000007,0x3c003,0xc6000000,0xc180000,0x7,0x700,
0x3c00000,0x700000,0x70003c00,0x0,0xf1801c,0x1c00,0xf000f18,0x0,0x0,0xe00000e0,0x780000,0x7000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x3800000,0x700000,0x38,
0x7,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf800e,0x3e0000,0x0,0x0,0x0,0x1e00000,0x0,0x1,
0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7cc00,0x660,0x0,0x0,0x66000000,0x0,0x0,0x0,0x0,0x7,0x1c000000,0x0,0x0,0x0,0x3fe00000,
0x0,0x0,0x7000000,0x0,0x0,0x0,0x3e0,0x7c001f,0xe0000000,0x0,0x0,0x0,0xe1c0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x1f80,0x380000e,0x7e007,0xe60071c0,0xc180000,0x3,0x80000e00,0x7e0038e,0x380000,0xe0007e00,0x38e00f00,0x1f9800e,
0x3800,0x1f801f98,0x1c70000,0x0,0x700001c0,0xfc0071,0xc000e007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61c00600,0x1e00007e,0x70000,0x18003000,0x1800000,0x0,0x0,0x1c01f0,0x7e003f,0xc003f800,
0x1e03ffc,0x7f01ff,0xfc03f000,0x7e000000,0x0,0x0,0xfc0,0x1e,0x7fe000,0x7e03fe00,0x3fff07ff,0xe007e038,0x383ffe0,0xff81c01,
0xe1c000f8,0xf8f00e0,0xfc01ffc,0x3f00ff,0xc000fe07,0xfffc7007,0x1c007700,0x73c01ef,0x78ffff,0xfe0380,0xfe000,0x38000000,0x1800000,
0x700000,0x38,0x1f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0xfc0000,
0x0,0x7f00000,0x0,0x1,0x98000000,0x7f00000,0x3ffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0xcf81f,0xee3807e0,0x0,0x0,0x7e03c01e,0x1c,
0x0,0x1f800000,0xf0078038,0xfc007,0x1c000000,0xfe00000,0x0,0x0,0x3fe000f0,0xf,0xc001f800,0x6000000,0xffc000,0x0,0x1c0007e0,
0x360,0x6c0010,0x70000700,0xf0001e,0x3c000,0x78000f00,0x7f800ff,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,
0x7807007,0xe000fc00,0x1f8003f0,0x7e0000,0x1f867,0x70e00e,0x1c01c380,0x38f00787,0x3fe0,0x180000c,0x66006,0x7c0071c0,0xe380000,
0x1,0x80000c00,0x660038e,0x180000,0xc0006600,0x38e0078e,0x19f0006,0x3000,0x198019f0,0x1c70000,0x0,0x30000180,0xcc0071,0xc000c007,
0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0x61800600,0x7f8001ff,0x70000,
0x38003800,0x1800000,0x0,0x0,0x3807fc,0x1fe00ff,0xf00ffe00,0x3e03ffc,0xff81ff,0xfc07fc01,0xff800000,0x0,0x0,0x3fe0,0xfe001e,
0x7ff801,0xff83ff80,0x3fff07ff,0xe01ff838,0x383ffe0,0xff81c03,0xc1c000f8,0xf8f80e0,0x3ff01fff,0xffc0ff,0xf003ff87,0xfffc7007,
0x1e00f700,0x71c03c7,0x70ffff,0xfe01c0,0xfe000,0x7c000000,0xc00000,0x700000,0x38,0x3f,0xe000001c,0x1c00,0x1c00700,0x7fc0000,
0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x3f800e,0x3f8000,0x0,0x3fe0000,0x0,0xff00000,0x0,0x3,0xc000000,0x1ffc0000,0xfffe00,
0xffff0,0x0,0x0,0x0,0x0,0x0,0xc781f,0xee3803c0,0x0,0x0,0x3c01c01c,0x1c,0xc000,0x7fc00000,0x70070038,0x3fe007,0x1c000000,0x1ff80000,
0x0,0x0,0x3fe003fc,0x1f,0xe003fc00,0xc000000,0x3ffc000,0x0,0x7c000ff0,0x60,0xc0000,0x30000700,0xf0001e,0x3c000,0x78000f00,
0x3f000ff,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x7c0701f,0xf803ff00,0x7fe00ffc,0x1ff8000,0x7fe67,
0x70e00e,0x1c01c380,0x38700707,0x7ff0,0xc00018,0xc3006,0x3c0071c0,0x7f00000,0x0,0xc0001800,0xc30038e,0xc0001,0x8000c300,0x38e003fc,
0x18f0003,0x6000,0x30c018f0,0x1c70000,0x0,0x18000300,0x1860071,0xc0018007,0x38e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe1801fc0,0x618001ff,0x70000,0x30001800,0x21840000,0x0,0x0,0x380ffe,0x1fe00ff,
0xfc0fff00,0x3e03ffc,0x1ff81ff,0xfc0ffe03,0xffc00000,0x0,0x0,0x7ff0,0x3ff803f,0x7ffc03,0xffc3ffc0,0x3fff07ff,0xe03ffc38,0x383ffe0,
0xff81c07,0x81c000f8,0xf8f80e0,0x7ff81fff,0x81ffe0ff,0xf80fff87,0xfffc7007,0xe00e700,0x70e0387,0x80f0ffff,0xe001c0,0xe000,
0xfe000000,0xe00000,0x700000,0x38,0x3c,0x1c,0x1c00,0x1c00700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x78000e,0x3c000,
0x0,0x7ff0000,0x0,0xf100000,0x0,0x7,0xe000000,0x7ffc0000,0x1fffe00,0xffff0,0x0,0x0,0x0,0x0,0x0,0x3,0xf780180,0x0,0x0,0x1801e03c,
0x1c,0xc000,0xffc00000,0x780f0038,0x786000,0x7f00,0x18380000,0x0,0xfe00,0x30c,0x10,0x70020e00,0x1c000000,0x7f8c000,0x0,0x6c001c38,
0x60,0xc0000,0x70000700,0x1f8003f,0x7e000,0xfc001f80,0x3f000ff,0xf03ffc1f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,
0x7c0703f,0xfc07ff80,0xfff01ffe,0x3ffc000,0xffec7,0x70e00e,0x1c01c380,0x38780f07,0xf070,0xe00038,0x1c3800,0x0,0x3e00000,0x0,
0xe0003800,0x1c380000,0xe0003,0x8001c380,0x3e0,0x3,0x8000e000,0x70e00000,0x0,0x0,0x1c000700,0x3870000,0x38007,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xe3807ff0,0xc0c003c1,0x70000,0x70001c00,
0x718e0000,0x0,0x0,0x700f1e,0x1ce00c0,0x3c0c0f80,0x7e03800,0x3e08000,0x381e0f03,0xc1e00000,0x0,0x0,0x7078,0x783c03f,0x701e07,
0xc1c383e0,0x38000700,0x7c1c38,0x3801c00,0x381c0f,0x1c000fc,0x1f8f80e0,0x78781c07,0x81e1e0e0,0x780f0180,0xe007007,0xe00e380,
0xe0f0783,0x80e0000e,0xe000e0,0xe001,0xef000000,0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,
0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xf830000,0x0,0x1e000000,0x0,0x0,0x10000,0x780c0000,0x3e38000,0xe0,0x0,0x0,0x0,0x0,0x0,0x3,
0xd580000,0x0,0x0,0xe038,0x1c,0xc000,0xf0400000,0x380e0038,0x702000,0x1ffc0,0xc0000,0x0,0x3ff80,0x606,0x0,0x30000600,0x0,
0x7f8c000,0x0,0xc001818,0x60,0xc0003,0xe0000700,0x1f8003f,0x7e000,0xfc001f80,0x73801ee,0x7c1c1c,0x38000,0x70000e00,0xe0001,
0xc0003800,0x700383e,0x7c0703c,0x3c078780,0xf0f01e1e,0x3c3c000,0xf0f87,0x70e00e,0x1c01c380,0x38380e07,0xe038,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0xff0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x1c,0x1c7000,0xc380fff0,0xc0c00380,0x70000,0x70001c00,0x3dbc0070,0x0,0x0,0x701e0f,0xe0000,0x1e000380,
0x6e03800,0x7800000,0x781c0707,0x80e00000,0x0,0x0,0x4038,0xe00c03f,0x700e07,0x4380f0,0x38000700,0x700438,0x3801c00,0x381c0e,
0x1c000ec,0x1b8fc0e0,0xf03c1c03,0xc3c0f0e0,0x3c1e0000,0xe007007,0xe00e380,0xe070703,0xc1e0001e,0xe000e0,0xe001,0xc7000000,
0x0,0x700000,0x38,0x38,0x1c,0x0,0x700,0x1c0000,0x0,0x0,0x0,0x0,0x1c000000,0x0,0x0,0x0,0x70000e,0x1c000,0x0,0xe010000,0x0,
0x1c000000,0x10,0x20000,0x6c000,0xf0000000,0x3838000,0x1e0,0x0,0xf000f,0xf1e00,0x78f00000,0x0,0x3,0xdd80000,0x0,0x0,0xf078,
0x0,0xc001,0xe0000000,0x1c1c0038,0x700000,0x3c1e0,0xc0000,0x0,0x783c0,0x606,0x0,0x30000e00,0x0,0xff8c000,0x0,0xc00300c,0x60,
0xc0003,0xe0000000,0x1f8003f,0x7e000,0xfc001f80,0x73801ce,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x7e07078,
0x1e0f03c1,0xe0783c0f,0x781e000,0x1c0787,0x70e00e,0x1c01c380,0x383c1e07,0xff00e038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x878,
0x0,0x0,0x0,0x7,0x80000080,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,
0x1c7000,0xc301e630,0xc0c00380,0x70000,0xe0000e00,0xff00070,0x0,0x0,0xe01c07,0xe0000,0xe000380,0xce03800,0x7000000,0x701c0707,
0x600000,0x0,0x4000010,0x38,0x1c00e07f,0x80700e0e,0x38070,0x38000700,0xe00038,0x3801c00,0x381c1c,0x1c000ec,0x1b8ec0e0,0xe01c1c01,
0xc38070e0,0x1c1c0000,0xe007007,0x701c380,0xe078e01,0xc1c0003c,0xe00070,0xe003,0x83800000,0x7f,0x71f000,0x3e003e38,0x3f007ff,
0xe01f1c1c,0x7801fc00,0x3fc00701,0xe01c0077,0x8f071e00,0xf801c7c,0x7c700e,0x3e01fc03,0xfff8380e,0xe007700,0x73c0787,0x387ffc,
0x70000e,0x1c000,0x0,0xe000000,0x0,0x1c000000,0x10,0x20000,0xc2000,0xe0000000,0x3838000,0x3c0,0x0,0xf000f,0x78e00,0x70e00000,
0x0,0x3,0xc980fe0,0x1f0,0xf8000007,0xffc07070,0x0,0x3f801,0xc0000000,0x1e3c0038,0x700000,0x70070,0x7fc0000,0x0,0xe00e0,0x606,
0x1c0000,0x70007c00,0x380e,0xff8c000,0x0,0xc00300c,0x60,0xc0000,0x70000000,0x3fc007f,0x800ff001,0xfe003fc0,0x73801ce,0xe0001c,
0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,0x7607070,0xe0e01c1,0xc0383807,0x700e000,0x1c0387,0x70e00e,0x1c01c380,0x381c1c07,
0xffc0e0f8,0x3f8007f,0xfe001,0xfc003f80,0x7f007e3,0xe003e001,0xf8003f00,0x7e000fc,0xfe001f,0xc003f800,0x7f00003c,0x38f0007,
0xc000f800,0x1f0003e0,0x7c0007,0x8003f0c3,0x80e0701c,0xe0381c0,0x70700387,0x1f01c00e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0xc0c00380,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03c07,
0x800e0000,0xe000380,0x1ce03800,0x7000000,0x701c0707,0x7003c0,0x780000,0x3c00001e,0x38,0x18006073,0x80700e0e,0x38070,0x38000700,
0xe00038,0x3801c00,0x381c38,0x1c000ee,0x3b8ee0e1,0xe01e1c01,0xc78078e0,0x1c1c0000,0xe007007,0x701c387,0xe03de00,0xe3800038,
0xe00070,0xe007,0x1c00000,0x1ff,0xc077f801,0xff807fb8,0xff807ff,0xe03fdc1d,0xfc01fc00,0x3fc00703,0xc01c007f,0xdf877f00,0x3fe01dfe,
0xff700e,0xff07ff03,0xfff8380e,0x700f700,0x71e0f03,0x80707ffc,0x70000e,0x1c000,0x0,0x1c000008,0x0,0x1c000000,0x10,0x20000,
0x82000,0xe0000000,0x7038000,0x80000380,0x2000040,0x7000e,0x38700,0xf1e00000,0x0,0x3,0xc183ff8,0x3fd,0xfc008007,0xffc038e0,
0x0,0xffc01,0xc0008008,0xe380038,0x380000,0xe3e38,0x1ffc0040,0x80000000,0x1cfc70,0x606,0x1c0000,0xe0007c00,0x380e,0xff8c000,
0x0,0xc00300c,0x8100060,0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0x73801ce,0xe0001c,0x38000,0x70000e00,0xe0001,
0xc0003800,0x7003807,0x77070f0,0xf1e01e3,0xc03c7807,0x8f00f080,0x83c0787,0x70e00e,0x1c01c380,0x380e3807,0xffe0e1c0,0xffe01ff,
0xc03ff807,0xff00ffe0,0x1ffc0ff7,0xf01ff807,0xfc00ff80,0x1ff003fe,0xfe001f,0xc003f800,0x7f0003fc,0x3bf801f,0xf003fe00,0x7fc00ff8,
0x1ff0007,0x8007fd83,0x80e0701c,0xe0381c0,0x70380707,0x7f80e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x1c,0x1c701f,0xfff1c600,0x618081c0,0x70000,0xe0000e00,0x3c00070,0x0,0x0,0xe03803,0x800e0000,0xe000380,0x18e03800,
0xf000000,0xf01c0707,0x7003c0,0x780000,0xfc00001f,0x80000078,0x301e6073,0x80700e1c,0x38038,0x38000700,0x1c00038,0x3801c00,
0x381c70,0x1c000e6,0x338ee0e1,0xc00e1c01,0xc70038e0,0x1c1c0000,0xe007007,0x701c387,0xe01dc00,0xf7800078,0xe00070,0xe00e,0xe00000,
0x3ff,0xe07ffc03,0xffc0fff8,0x1ffc07ff,0xe07ffc1d,0xfe01fc00,0x3fc00707,0x801c007f,0xdf877f80,0x7ff01fff,0x1fff00e,0xff07ff03,
0xfff8380e,0x700e380,0xe0e0e03,0x80707ffc,0x70000e,0x1c000,0x0,0x7ffc001c,0x0,0x1c000000,0x10,0x20000,0x82000,0xe0000000,
0x7038001,0xc0000780,0x70000e0,0x3800e,0x38700,0xe1c00000,0x0,0x3,0xc183ff8,0x7ff,0xfc01c007,0xffc03de0,0x0,0x1ffc01,0xc001c01c,
0xf780038,0x3c0000,0xcff18,0x380c00c1,0x80000000,0x18fe30,0x30c,0x1c0001,0xc0000e00,0x380e,0xff8c000,0x0,0xc00300c,0xc180060,
0xc0000,0x30000700,0x39c0073,0x800e7001,0xce0039c0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x877070e0,
0x71c00e3,0x801c7003,0x8e0071c0,0x1c380fc7,0x70e00e,0x1c01c380,0x380f7807,0x1e0e380,0x1fff03ff,0xe07ffc0f,0xff81fff0,0x3ffe0fff,
0xf03ffc0f,0xfe01ffc0,0x3ff807ff,0xfe001f,0xc003f800,0x7f0007fe,0x3bfc03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x800fff83,0x80e0701c,
0xe0381c0,0x70380707,0xffc0e01c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1c701f,
0xfff1c600,0x7f8381e0,0x70000,0xc0000600,0xff00070,0x0,0x0,0x1c03803,0x800e0000,0xe000f00,0x38e03fe0,0xe000000,0xe00e0e07,
0x7003c0,0x780007,0xf0ffff87,0xf00000f0,0x307fe0f3,0xc0703c1c,0x38038,0x38000700,0x1c00038,0x3801c00,0x381ce0,0x1c000e6,0x338e70e1,
0xc00e1c01,0xc70038e0,0x3c1e0000,0xe007007,0x783c38f,0x8e01fc00,0x770000f0,0xe00038,0xe01c,0x700000,0x381,0xe07c1e07,0xc0c1e0f8,
0x3c1e0038,0xf07c1f,0xe001c00,0x1c0070f,0x1c0079,0xf3c7c380,0xf0781f07,0x83c1f00f,0xc10f0300,0x1c00380e,0x700e380,0xe0f1e03,
0xc0f00078,0x70000e,0x1c000,0x0,0xfff8003e,0x0,0x3c000000,0x10,0x20000,0xc6000,0xf0000000,0x7038003,0xe0000f00,0xf8001f0,
0x3801c,0x18300,0xe1800000,0x0,0x3,0xc187818,0x70f,0x9e03e000,0x7801dc0,0x1c,0x3cc401,0xc000efb8,0x7f7f0038,0x3f0000,0x1ce11c,
0x300c01c3,0x80000000,0x38c638,0x3fc,0x1c0003,0x80000600,0x380e,0xff8c000,0x0,0xc00300c,0xe1c0060,0xc0010,0x70000700,0x79e00f3,
0xc01e7803,0xcf0079e0,0xe1c038e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,
0x8e0070e0,0x38381dc7,0x70e00e,0x1c01c380,0x38077007,0xf0e700,0x1c0f0381,0xe0703c0e,0x781c0f0,0x381e083e,0x787c0c1e,0xf03c1e0,
0x783c0f07,0x800e0001,0xc0003800,0x7000fff,0x3e1c078,0x3c0f0781,0xe0f03c1e,0x783c000,0x1e0f03,0x80e0701c,0xe0381c0,0x70380f07,
0xc1e0e03c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x1,0x8701c600,0x1e0f01e0,0x1,
0xc0000700,0x3dbc0070,0x0,0x0,0x1c03803,0x800e0000,0x1e01fe00,0x70e03ff8,0xe3e0001,0xe007fc07,0x80f003c0,0x78001f,0xc0ffff81,
0xfc0001e0,0x30e1e0e1,0xc07ff81c,0x38038,0x3ffe07ff,0xc1c0003f,0xff801c00,0x381de0,0x1c000e7,0x738e70e1,0xc00e1c03,0xc70038e0,
0x780f8000,0xe007007,0x383838d,0x8e00f800,0x7f0000e0,0xe00038,0xe000,0x0,0x200,0xf0780e07,0x8041c078,0x380e0038,0xe03c1e,
0xf001c00,0x1c0071e,0x1c0070,0xe1c783c0,0xe0381e03,0x8380f00f,0xe0000,0x1c00380e,0x381c380,0xe07bc01,0xc0e00078,0x70000e,
0x1c000,0x0,0x1c000061,0x0,0x38000000,0x10,0x20000,0x7c000,0x7c000000,0x703fc06,0x10000e00,0x18400308,0x1801c,0x1c381,0xc3800000,
0x0,0x0,0x7000,0xe0f,0xe061000,0x7801fc0,0x1c,0x38c001,0xc0007ff0,0x7fff0038,0x77c000,0x19c00c,0x301c0387,0x0,0x30c618,0xf0,
0x1c0007,0x600,0x380e,0x7f8c007,0x80000000,0xc001818,0x70e03fc,0x387f871f,0xe0e00700,0x70e00e1,0xc01c3803,0x870070e0,0xe1c038f,
0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,0xc0003800,0x7003803,0x873870e0,0x71c00e3,0x801c7003,0x8e007070,0x703839c7,0x70e00e,
0x1c01c380,0x3807f007,0x70e700,0x10078200,0xf0401e08,0x3c10078,0x200f001c,0x3878041c,0x70380e0,0x701c0e03,0x800e0001,0xc0003800,
0x7001e0f,0x3c1e070,0x1c0e0381,0xc070380e,0x701c000,0x1c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80e07038,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600e600,0x7803f0,0x1,0xc0000700,0x718e0070,0x0,0x0,0x38038c3,
0x800e0000,0x3c01f800,0x60e03ffc,0xeff8001,0xc001f003,0xc1f003c0,0x7800fe,0xffff80,0x3f8003c0,0x60c0e0e1,0xc07fe01c,0x38038,
0x3ffe07ff,0xc1c07e3f,0xff801c00,0x381fe0,0x1c000e3,0x638e30e1,0xc00e1c07,0x870038ff,0xf00ff800,0xe007007,0x38381cd,0x9c007000,
0x3e0001e0,0xe0001c,0xe000,0x0,0x0,0x70780f0f,0x3c078,0x70070038,0x1e03c1c,0x7001c00,0x1c0073c,0x1c0070,0xe1c701c1,0xe03c1e03,
0xc780f00f,0xe0000,0x1c00380e,0x381c387,0xe03f801,0xc0e000f0,0x70000e,0x1c007,0xe0100000,0x1c0000cd,0x80000003,0xffc00000,
0x3ff,0x807ff000,0xe0,0x7fc00060,0x703fc0c,0xd8001e00,0x3360066c,0x1c018,0xc181,0x83000000,0x0,0x0,0x7000,0x300e07,0xe0cd800,
0xf000f80,0x1c,0x78c00f,0xff0038e0,0x3e00038,0xe1e000,0x19800c,0x383c070e,0x7fffc00,0x30fc18,0x0,0xffff80e,0x20e00,0x380e,
0x7f8c007,0x80000000,0xc001c38,0x38703ff,0xf87fff0f,0xcfe00f00,0x70e00e1,0xc01c3803,0x870070e0,0x1e1e078f,0xe1c0001f,0xff03ffe0,
0x7ffc0fff,0x800e0001,0xc0003800,0x700ff83,0x871870e0,0x71c00e3,0x801c7003,0x8e007038,0xe03871c7,0x70e00e,0x1c01c380,0x3803e007,
0x70e700,0x38000,0x70000e00,0x1c00038,0x7001c,0x38f00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7001c07,0x380e0f0,0x1e1e03c3,
0xc078780f,0xf01e000,0x3c0f03,0x80e0701c,0xe0381c0,0x701c0e07,0x80f07038,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0x8600ff00,0x1e00778,0x38000001,0xc0000700,0x21843fff,0xe0000000,0x0,0x38039e3,0x800e0000,
0x7c01fe00,0xe0e0203e,0xeffc001,0xc00ffe03,0xff700000,0x7f0,0x0,0x7f00380,0x618060e1,0xc07ffc1c,0x38038,0x3ffe07ff,0xc1c07e3f,
0xff801c00,0x381ff0,0x1c000e3,0x638e38e1,0xc00e1fff,0x870038ff,0xc003fe00,0xe007007,0x38381cd,0x9c00f800,0x3e0003c0,0xe0001c,
0xe000,0x0,0x0,0x7070070e,0x38038,0x70070038,0x1c01c1c,0x7001c00,0x1c00778,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0xfc000,
0x1c00380e,0x381c3c7,0x1e01f001,0xe1e001e0,0xf0000e,0x1e01f,0xf8300000,0x1c00019c,0xc0000003,0xffc00000,0x10,0x20000,0x700,
0x1ff000c0,0x703fc19,0xcc003c00,0x67300ce6,0xc038,0xc181,0x83000000,0x0,0x0,0x7e00,0x180e07,0xe19cc00,0x1e000f80,0x1c,0x70c00f,
0xff007070,0x3e00038,0xe0f000,0x19800c,0x1fec0e1c,0x7fffc00,0x30f818,0x0,0xffff81f,0xf003fc00,0x380e,0x3f8c007,0x80000000,
0x7f800ff0,0x1c3803f,0xe007fc00,0xff800e00,0x70e00e1,0xc01c3803,0x870070e0,0x1c0e070f,0xe1c0001f,0xff03ffe0,0x7ffc0fff,0x800e0001,
0xc0003800,0x700ff83,0x871c70e0,0x71c00e3,0x801c7003,0x8e00701d,0xc038e1c7,0x70e00e,0x1c01c380,0x3803e007,0x70e3c0,0x38000,
0x70000e00,0x1c00038,0x7001c,0x38e00038,0x3870070,0xe00e1c01,0xc00e0001,0xc0003800,0x7003c07,0x8380e0e0,0xe1c01c3,0x80387007,
0xe00e1ff,0xfe381b83,0x80e0701c,0xe0381c0,0x701e1e07,0x707878,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x1c,0x3,0xe007fe0,0x7800e3c,0x38000001,0xc0000700,0x1803fff,0xe0000000,0x0,0x70039c3,0x800e0000,0xf8000f80,
0xc0e0000e,0xf83c003,0xc01e0f01,0xff700000,0x7c0,0x0,0x1f00780,0x618061c0,0xe0701e1c,0x38038,0x38000700,0x1c07e38,0x3801c00,
0x381e78,0x1c000e3,0xe38e18e1,0xc00e1fff,0x70038ff,0xe0007f80,0xe007007,0x1c701dd,0x9c00f800,0x1c000780,0xe0000e,0xe000,0x0,
0x7f,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x7fc00,0x1c00380e,
0x1c381c7,0x1c01f000,0xe1c001c0,0xfe0000e,0xfe1f,0xfff00000,0x7ff003fc,0xe0000003,0xffc00000,0x10,0x20000,0x3800,0x3fc0180,
0x703803f,0xce007800,0xff381fe7,0x30,0x0,0xc0,0x0,0x0,0x3fe0,0xc0e07,0xfe3fce00,0x1c000700,0x1c,0x70c00f,0xff006030,0x1c00000,
0xe07800,0x19800c,0xfcc1c38,0x7fffc00,0x30d818,0x0,0xffff81f,0xf001f800,0x380e,0xf8c007,0x80000000,0x7f8007e0,0xe1c3fe,0x7fc00f,
0xf8001e00,0xe0701c0,0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700ff83,0x870c70e0,
0x71c00e3,0x801c7003,0x8e00700f,0x8038c1c7,0x70e00e,0x1c01c380,0x3801c007,0xf0e3e0,0x3ff807f,0xf00ffe01,0xffc03ff8,0x7ff03ff,
0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe383383,0x80e0701c,
0xe0381c0,0x700e1c07,0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x3,0xc000ff0,
0x3c1e1c1c,0x38000001,0xc0000700,0x1803fff,0xe0000007,0xf8000000,0x7003803,0x800e0001,0xf0000381,0xc0e00007,0xf01e003,0x801c0700,
0x7c700000,0x7c0,0x0,0x1f00700,0x618061c0,0xe0700e1c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381e38,0x1c000e1,0xc38e1ce1,
0xc00e1ffc,0x70038e0,0xf0000780,0xe007007,0x1c701dd,0xdc01fc00,0x1c000780,0xe0000e,0xe000,0x0,0x1ff,0xf070070e,0x38038,0x7fff0038,
0x1c01c1c,0x7001c00,0x1c007f8,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3ff00,0x1c00380e,0x1c381cd,0x9c00e000,0xe1c003c0,
0xf80000e,0x3e18,0x3ff00000,0xffe007fd,0xf0000000,0x38000000,0x10,0x20000,0x1c000,0x3c0300,0x703807f,0xdf007801,0xff7c3fef,
0x80000000,0x0,0x3e0,0x7ffe7ff,0xff000000,0x1ff8,0x60e07,0xfe7fdf00,0x3c000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0xf03800,
0x19800c,0x1c38,0x1c07,0xf830cc18,0x0,0x1c0000,0x0,0x380e,0x18c007,0x80000000,0x0,0xe1cfe0,0x1fc003f,0x80003c00,0xe0701c0,
0xe0381c07,0x380e070,0x1c0e070e,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,
0x8e007007,0x3981c7,0x70e00e,0x1c01c380,0x3801c007,0x1e0e0f8,0xfff81ff,0xf03ffe07,0xffc0fff8,0x1fff07ff,0xf8e0003f,0xff87fff0,
0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e1ff,0xfe386383,0x80e0701c,0xe0381c0,0x700e1c07,
0x703870,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0x7f,0xffc00678,0x707f9c1e,0x38000001,
0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0003,0xe00001c3,0x80e00007,0xe00e007,0x80380380,0x700000,0x7f0,0x0,0x7f00700,
0x618061ff,0xe070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,0x381c3c,0x1c000e1,0xc38e1ce1,0xc00e1c00,0x70038e0,0x700003c0,
0xe007007,0x1c701d8,0xdc03dc00,0x1c000f00,0xe00007,0xe000,0x0,0x3ff,0xf070070e,0x38038,0x7fff0038,0x1c01c1c,0x7001c00,0x1c007fc,
0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x3f00,0x1c00380e,0x1c381cd,0x9c01f000,0x73800780,0xfe0000e,0xfe10,0x7c00000,0x1c000ffb,
0xf8000000,0x38000000,0x10,0x20000,0x20000,0x1e0700,0x70380ff,0xbf80f003,0xfefe7fdf,0xc0000000,0x0,0x3f0,0x7ffe7ff,0xff000000,
0x1f8,0x30e07,0xfeffbf80,0x78000700,0x1c,0x70c001,0xc0006030,0x7fff0000,0x783800,0x1ce11c,0xe1c,0x1c07,0xf838ce38,0x0,0x1c0000,
0x0,0x380e,0x18c000,0x0,0x0,0x1c38c00,0x1800030,0x7800,0xfff01ff,0xe03ffc07,0xff80fff0,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,
0xe0001,0xc0003800,0x7003803,0x870e70e0,0x71c00e3,0x801c7003,0x8e00700f,0x803b81c7,0x70e00e,0x1c01c380,0x3801c007,0xffe0e03c,
0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0fff,0xf8e0003f,0xff87fff0,0xfffe1fff,0xc00e0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,
0x80387007,0xe00e000,0x38c383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0063c,0x40619c0f,0x30000001,0xc0000700,0x70,0x7,0xf8000000,0xe003803,0x800e0007,0xc00001c3,
0xfffc0007,0xe00e007,0x380380,0xf00000,0xfe,0xffff80,0x3f800700,0x618063ff,0xf070071c,0x38038,0x38000700,0x1c00e38,0x3801c00,
0x381c1e,0x1c000e0,0x38e0ee1,0xc00e1c00,0x70038e0,0x380001c0,0xe007007,0x1ef01d8,0xdc038e00,0x1c001e00,0xe00007,0xe000,0x0,
0x7c0,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0079e,0x1c0070,0xe1c701c1,0xc01c1c01,0xc700700e,0x780,0x1c00380e,
0xe701cd,0x9c01f000,0x73800f00,0xe0000e,0xe000,0x0,0x1c0007f7,0xf0000000,0x70000000,0x10,0x20000,0x0,0xe0e00,0x703807f,0x7f01e001,
0xfdfc3fbf,0x80000000,0x0,0x7f0,0x0,0x0,0x3c,0x18e07,0x7f7f00,0xf0000700,0x1c,0x70c001,0xc0007070,0x1c00000,0x3e7000,0xcff18,
0x3ffc070e,0x1c07,0xf818c630,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x3870000,0xe000fc00,0x380f000,0x1fff83ff,0xf07ffe0f,
0xffc1fff8,0x3fff0ffe,0x1c0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003803,0x870770e0,0x71c00e3,0x801c7003,0x8e00701d,
0xc03f01c7,0x70e00e,0x1c01c380,0x3801c007,0xffc0e01c,0x3e0387c0,0x70f80e1f,0x1c3e038,0x7c071e1c,0xe00038,0x70000,0xe0001c00,
0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x398383,0x80e0701c,0xe0381c0,0x70073807,0x701ce0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f,0xffc0061c,0xc0dc07,0xf0000001,0xc0000700,
0x70,0x0,0x0,0x1c003c07,0x800e000f,0x1c3,0xfffc0007,0xe00e007,0x380380,0xe00000,0x1f,0xc0ffff81,0xfc000700,0x618063ff,0xf070070e,
0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0e,0x1c000e0,0x38e0ee1,0xe01e1c00,0x78078e0,0x380001c0,0xe007007,0xee01f8,0xfc078f00,
0x1c001c00,0xe00003,0x8000e000,0x0,0x700,0x7070070e,0x38038,0x70000038,0x1c01c1c,0x7001c00,0x1c0070e,0x1c0070,0xe1c701c1,
0xc01c1c01,0xc700700e,0x380,0x1c00380e,0xe700ed,0xb803f800,0x77800f00,0x70000e,0x1c000,0x0,0xe0003f7,0xe0000000,0x70000000,
0x10,0x20000,0x1c0e0,0xe1c00,0x703803f,0x7e01c000,0xfdf81fbf,0x0,0x0,0x3f0,0x0,0x0,0x1c,0x1ce07,0x3f7e00,0xf0000700,0x1c,
0x70c001,0xc00038e0,0x1c00038,0xf7000,0xe3e38,0x3ffc0387,0x1c00,0x1cc770,0x0,0x1c0000,0x0,0x380e,0x18c000,0x0,0x3ffc,0x70e0001,
0xe001fe00,0x780e000,0x1fff83ff,0xf07ffe0f,0xffc1fff8,0x3fff0ffe,0xe0001c,0x38000,0x70000e00,0xe0001,0xc0003800,0x7003807,
0x70770f0,0xf1e01e3,0xc03c7807,0x8f00f038,0xe03e03c7,0x70e00e,0x1c01c380,0x3801c007,0xff00e00e,0x38038700,0x70e00e1c,0x1c38038,
0x70071c1c,0xe00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003803,0x8380e0e0,0xe1c01c3,0x80387007,0xe00e000,0x3b0383,0x80e0701c,
0xe0381c0,0x70077807,0x701de0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6,0x1c00061c,
0xc0de03,0xe0000001,0xc0000700,0x70,0x0,0x0,0x1c001c07,0xe001e,0x1c3,0xfffc0007,0x600e00e,0x380380,0xe00000,0x7,0xf0ffff87,
0xf0000000,0x60c0e380,0x7070070e,0x38070,0x38000700,0xe00e38,0x3801c00,0x381c0f,0x1c000e0,0x38e06e0,0xe01c1c00,0x38070e0,
0x1c0001c0,0xe007007,0xee00f8,0xf80f0700,0x1c003c00,0xe00003,0x8000e000,0x0,0x700,0x70780f0f,0x3c078,0x70000038,0x1e03c1c,
0x7001c00,0x1c0070f,0x1c0070,0xe1c701c1,0xe03c1e03,0xc780f00e,0x380,0x1c00380e,0xe700f8,0xf807bc00,0x3f001e00,0x70000e,0x1c000,
0x0,0xe0001ff,0xc0000000,0x70000000,0x10,0x20000,0x33110,0xe0e00,0x383801f,0xfc03c000,0x7ff00ffe,0x0,0x0,0x3e0,0x0,0x0,0x1c,
0x38e07,0x1ffc01,0xe0000700,0x1c,0x78c001,0xc0007ff0,0x1c00038,0x7c000,0x70070,0x1c3,0x80001c00,0xe00e0,0x0,0x1c0000,0x0,
0x380e,0x18c000,0x0,0x0,0xe1c0001,0xe0010700,0x780e000,0x1c038380,0x70700e0e,0x1c1c038,0x78070e0e,0xe0001c,0x38000,0x70000e00,
0xe0001,0xc0003800,0x7003807,0x7037070,0xe0e01c1,0xc0383807,0x700e070,0x701c0387,0x70e00e,0x1c01c380,0x3801c007,0xe00e,0x38038700,
0x70e00e1c,0x1c38038,0x70071c1c,0xf00038,0x70000,0xe0001c00,0xe0001,0xc0003800,0x7003c07,0x8380e0f0,0x1e1e03c3,0xc078780f,
0xf01e007,0x803e0783,0x80e0701c,0xe0381c0,0x7003f007,0x80f00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x6,0x1800061c,0xc0de01,0xc0000000,0xc0000e00,0x70,0xf0000,0x3c00,0x38001c0f,0xe003c,0x3c0,0xe0000e,0x701e00e,
0x3c0780,0x1e003c0,0x780000,0xfc00001f,0x80000000,0x60e1e780,0x78700f07,0x4380f0,0x38000700,0xf00e38,0x3801c00,0xc0781c07,
0x81c000e0,0x38e07e0,0xe03c1c00,0x380f0e0,0x1e0003c0,0xe00780f,0xee00f0,0x780e0780,0x1c007800,0xe00001,0xc000e000,0x0,0x700,
0xf0780e07,0x8041c078,0x38020038,0xe03c1c,0x7001c00,0x1c00707,0x801c0070,0xe1c701c0,0xe0381e03,0x8380f00e,0x80380,0x1c003c1e,
0x7e00f8,0xf80f1e00,0x3f003c00,0x70000e,0x1c000,0x0,0xf0100f7,0x80078000,0x700078f0,0x10,0x7ff000,0x61208,0x1e0700,0x383800f,
0x78078000,0x3de007bc,0x0,0x0,0x0,0x0,0x0,0x401c,0x70e0f,0xf7803,0xc0000700,0x1c,0x38c001,0xc000efb8,0x1c00038,0x1e000,0x3c1e0,
0xc1,0x80000000,0x783c0,0x0,0x0,0x0,0x3c1e,0x18c000,0x0,0x0,0xc180003,0x60000300,0xd80e010,0x3c03c780,0x78f00f1e,0x1e3c03c,
0x70039c0e,0x70041c,0x38000,0x70000e00,0xe0001,0xc0003800,0x700380f,0x703f070,0x1e0e03c1,0xc078380f,0x701e0e0,0x381c0787,
0x80f0f01e,0x1e03c3c0,0x7801c007,0xe00e,0x38078700,0xf0e01e1c,0x3c38078,0x700f1c1c,0x78041c,0x1038020,0x70040e00,0x800e0001,
0xc0003800,0x7001c07,0x380e070,0x1c0e0381,0xc070380e,0x701c007,0x801e0703,0xc1e0783c,0xf0781e0,0xf003f007,0x80e00fc0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xe,0x1801867c,0xc0cf83,0xe0000000,0xe0000e00,
0x70,0xf0000,0x3c00,0x38000f1e,0xe0070,0x180780,0xe0603e,0x783c01e,0x1e0f01,0x7c003c0,0x780000,0x3c00001e,0x700,0x307fe700,
0x38701e07,0xc1c383e0,0x38000700,0x7c1e38,0x3801c00,0xe0f01c03,0x81c000e0,0x38e03e0,0x78781c00,0x1e1e0e0,0xe180780,0xe003c1e,
0x7c00f0,0x781e03c0,0x1c007000,0xe00001,0xc000e000,0x0,0x783,0xf07c1e07,0xc0c1e0f8,0x3e0e0038,0xf07c1c,0x7001c00,0x1c00703,
0xc01e0070,0xe1c701c0,0xf0781f07,0x83c1f00e,0xe0f80,0x1e003c3e,0x7e00f8,0xf80e0e00,0x3f003800,0x70000e,0x1c000,0x0,0x7830077,
0xf0000,0x700078f0,0x10,0x20000,0x41208,0xc03c0380,0x3c38007,0x70070000,0x1dc003b8,0x0,0x0,0x0,0x0,0x0,0x707c,0x6070f,0x86077003,
0x80000700,0x1c,0x3ec401,0xc001c01c,0x1c00038,0xf000,0x1ffc0,0x40,0x80000000,0x3ff80,0x0,0x0,0x0,0x3e3e,0x18c000,0x0,0x0,
0x8100006,0x60000300,0x1980f070,0x3801c700,0x38e0071c,0xe3801c,0x70039c0e,0x7c1c1c,0x38000,0x70000e00,0xe0001,0xc0003800,
0x700383e,0x701f03c,0x3c078780,0xf0f01e1e,0x3c3c1c0,0x1c3f0f03,0xc1e0783c,0xf0781e0,0xf001c007,0xe81e,0x3c1f8783,0xf0f07e1e,
0xfc3c1f8,0x783f1e3e,0x187c0c1f,0x703e0e0,0x7c1c0f83,0x800e0001,0xc0003800,0x7001e0f,0x380e078,0x3c0f0781,0xe0f03c1e,0x783c007,
0x801e0f03,0xc3e0787c,0xf0f81e1,0xf003f007,0xc1e00fc0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x1c,0xe,0x3801fff8,0x6187ff,0xe0000000,0xe0000e00,0x70,0xf0000,0x3c00,0x38000ffe,0x1fff0ff,0xfe1fff80,0xe07ffc,0x3ffc01c,
0x1fff01,0xff8003c0,0x780000,0x4000010,0x700,0x301e6700,0x387ffe03,0xffc3ffc0,0x3fff0700,0x3ffe38,0x383ffe0,0xfff01c03,0xc1fff8e0,
0x38e03e0,0x7ff81c00,0x1ffe0e0,0xf1fff80,0xe003ffe,0x7c00f0,0x781c01c0,0x1c00ffff,0xe00001,0xc000e000,0x0,0x3ff,0x707ffc03,
0xffc0fff8,0x1ffe0038,0x7ffc1c,0x707fff0,0x1c00701,0xc00ff070,0xe1c701c0,0x7ff01fff,0x1fff00e,0xfff00,0xff81fee,0x7e00f0,
0x781e0f00,0x1e007ffc,0x70000e,0x1c000,0x0,0x3ff003e,0xf0000,0xe00070e0,0x60830010,0x20000,0x41208,0xfffc01c0,0x1fffe03,0xe00ffff0,
0xf8001f0,0x0,0x0,0x0,0x0,0x0,0x7ff8,0xc07fd,0xfe03e007,0xffc00700,0x1c,0x1ffc1f,0xffc08008,0x1c00038,0x7000,0x7f00,0x0,0x0,
0xfe00,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0x6,0x60000700,0x19807ff0,0x3801c700,0x38e0071c,0xe3801c,0x70039c0f,0xf03ffc1f,
0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ffc,0x701f03f,0xfc07ff80,0xfff01ffe,0x3ffc080,0x83fff03,0xffe07ffc,0xfff81ff,
0xf001c007,0xeffc,0x1ffb83ff,0x707fee0f,0xfdc1ffb8,0x3ff70ff7,0xf83ffc0f,0xff01ffe0,0x3ffc07ff,0x83fff87f,0xff0fffe1,0xfffc0ffe,
0x380e03f,0xf807ff00,0xffe01ffc,0x3ff8007,0x803ffe01,0xfee03fdc,0x7fb80ff,0x7001e007,0xffc00780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x3801fff0,0x7f83fe,0x70000000,0xe0000e00,0x0,0xf0000,0x3c00,0x700007fc,
0x1fff0ff,0xfe1ffe00,0xe07ff8,0x1ff801c,0xffe01,0xff0003c0,0x780000,0x0,0x700,0x38000f00,0x3c7ffc01,0xff83ff80,0x3fff0700,
0x1ffc38,0x383ffe0,0x7fe01c01,0xe1fff8e0,0x38e03e0,0x3ff01c00,0xffc0e0,0x71fff00,0xe001ffc,0x7c00f0,0x783c01e0,0x1c00ffff,
0xe00000,0xe000e000,0x0,0x1ff,0x7077f801,0xff807fb8,0xffc0038,0x3fdc1c,0x707fff0,0x1c00701,0xe007f070,0xe1c701c0,0x3fe01dfe,
0xff700e,0x7fe00,0xff80fee,0x3c0070,0x703c0780,0x1e007ffc,0x70000e,0x1c000,0x0,0x1fe001c,0xe0000,0xe000e1c0,0x71c78010,0x20000,
0x21318,0xfff800c0,0xfffe01,0xc00ffff0,0x70000e0,0x0,0x0,0x0,0x0,0x0,0x3ff0,0x1803fd,0xfe01c007,0xffc00700,0x1c,0xffc1f,0xffc00000,
0x1c00038,0x7000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x3ff7,0x8018c000,0x0,0x0,0xc,0x60000e00,0x31803fe0,0x7801ef00,0x3de007bc,
0xf7801e,0xf003fc0f,0xf01ff81f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83ff8,0x701f01f,0xf803ff00,0x7fe00ffc,0x1ff8000,
0x67fe01,0xffc03ff8,0x7ff00ff,0xe001c007,0xeff8,0xffb81ff,0x703fee07,0xfdc0ffb8,0x1ff70ff7,0xf81ff807,0xfe00ffc0,0x1ff803ff,
0x3fff87f,0xff0fffe1,0xfffc07fc,0x380e01f,0xf003fe00,0x7fc00ff8,0x1ff0000,0x37fc00,0xfee01fdc,0x3fb807f,0x7001e007,0x7f800780,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1c,0xc,0x30007fc0,0x1e00f8,0x78000000,0x70001c00,
0x0,0xe0000,0x3c00,0x700001f0,0x1fff0ff,0xfe07f800,0xe01fe0,0x7e0038,0x3f800,0xfc0003c0,0x700000,0x0,0x700,0x18000e00,0x1c7ff000,
0x7e03fe00,0x3fff0700,0x7f038,0x383ffe0,0x1f801c00,0xf1fff8e0,0x38e01e0,0xfc01c00,0x3f80e0,0x787fc00,0xe0007f0,0x7c00f0,0x387800f0,
0x1c00ffff,0xe00000,0xe000e000,0x0,0xfc,0x7071f000,0x3f003e38,0x3f00038,0x1f1c1c,0x707fff0,0x1c00700,0xf003f070,0xe1c701c0,
0x1f801c7c,0x7c700e,0x1f800,0x3f8078e,0x3c0070,0x707803c0,0x1c007ffc,0x70000e,0x1c000,0x0,0x7c0008,0x1e0000,0xe000e1c0,0x71c30010,
0x20000,0x1e1f0,0x3fe00020,0x3ffe00,0x800ffff0,0x2000040,0x0,0x0,0x0,0x0,0x0,0xfc0,0x3001f0,0x78008007,0xffc00700,0x1c,0x3f81f,
0xffc00000,0x1c00038,0x407000,0x0,0x0,0x0,0x0,0x0,0xffff800,0x0,0x39c7,0x18c000,0x0,0x0,0x18,0x60001c00,0x61801f80,0x7000ee00,
0x1dc003b8,0x77000e,0xe001f80f,0xf007e01f,0xff83fff0,0x7ffe0fff,0xc1fff03f,0xfe07ffc0,0xfff83fc0,0x700f007,0xe000fc00,0x1f8003f0,
0x7e0000,0xe1f800,0x7f000fe0,0x1fc003f,0x8001c007,0xe7f0,0x7e380fc,0x701f8e03,0xf1c07e38,0xfc703c1,0xe003f001,0xf8003f00,
0x7e000fc,0x3fff87f,0xff0fffe1,0xfffc03f8,0x380e00f,0xc001f800,0x3f0007e0,0xfc0000,0x61f800,0x78e00f1c,0x1e3803c,0x7001c007,
0x1f000700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x70001c00,0x0,
0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xc000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000,
0x70000e,0x1c000,0x0,0x0,0x1c0000,0xe000c180,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
0x0,0x38,0x70e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x2000,0x0,0x1f,0xf8003800,0x7fe00000,0x0,0x0,0x0,0x0,0x4000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x400000,
0x0,0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x30001800,
0x0,0x1c0000,0x0,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xe00000,0x0,0x0,0xe000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e000,
0x0,0x0,0x0,0x0,0x0,0xe00000,0x7000e000,0x0,0x0,0x0,0x0,0x0,0x1c00,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x1c000000,
0x70000e,0x1c000,0x0,0x0,0x1c0001,0xe001c380,0x10,0x20000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
0x0,0x38,0x7fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x3000,0x0,0x1f,0xf8007000,0x7fe00000,0x0,0x0,0x0,0x0,0x6000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x6000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x1c007,0x700,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x38003800,
0x0,0x380000,0x1,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x1c00000,0x0,0x0,0x3c18000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf000,
0x0,0x0,0x0,0x0,0x0,0xfe0000,0x380fe000,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x38000000,
0x78000e,0x3c000,0x0,0x0,0x180001,0xc0018300,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,
0x38,0x1f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x18c000,0x1800,0x0,0x0,0x6000e000,0x1800000,0x0,0x0,0x0,0x0,0x3000,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x38007,0xe00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x18003000,
0x0,0x300000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1800000,0x0,0x0,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,
0x0,0x0,0x0,0xfe0000,0xfe000,0x0,0x0,0x0,0x0,0x0,0x607800,0x0,0x3c00000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x0,0x78000000,
0x3f800e,0x3f8000,0x0,0x0,0x300043,0xc0018200,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,
0x0,0x38,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x11800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x23000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78007,
0x1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x600,0x0,0x0,0x1c007000,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe0000,
0xfe000,0x0,0x0,0x0,0x0,0x0,0x7ff000,0x0,0x7f800000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf8000000,0x3f800e,0x3f8000,0x0,
0x0,0x10007f,0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x38,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x3800,0x0,0x1f800,0x0,0x0,0x6001ff00,0x1800000,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f8007,0xfe00,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x7fe000,0x0,
0x7f000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xf0000000,0xf800e,0x3e0000,0x0,0x0,0x7f,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3800,0x0,0x1f000,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x3f0007,0xfc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x1fc000,0x0,0x7e000000,0x0,0x0,0x1c00,0x7000,0x0,0x0,0x0,0x3,0xc0000000,0xe,0x0,
0x0,0x0,0x3e,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x3800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c0007,0xf000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
// Definition of a 29x57 font.
const unsigned int font29x57[29*57*256/32] = {
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x781e00,0x0,0x0,0x7,0x81e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0000,0xf8000,0x7e00000,0x0,0x7,
0xc0000000,0x0,0x7c00,0xf80,0x7e000,0x0,0x7c00000,0xf80000,0x7e000000,0x0,0x0,0x1f00,0x3e0,0x1f800,0x0,0x0,0x0,0x3,0xe0000000,
0x7c00003f,0x0,0xf8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x3c3c00,0x0,0x0,0x3,0xc3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e0000,
0x1f0000,0x7e00000,0xf838001f,0xf80001f,0xf0000000,0x0,0x3e00,0x1f00,0x7e000,0x3e1f000,0x3e00000,0x1f00000,0x7e00003e,0x1f000000,
0x3e0,0xe0000f80,0x7c0,0x1f800,0x3e0e00,0x7c3e000,0x0,0x1,0xf0000000,0xf800003f,0x1f0f,0x800001f0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e7800,0x0,0x0,
0x1,0xe7800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x1e0000,0xff00001,0xfe38001f,0xf80003f,
0xf8000000,0x0,0x1e00,0x1e00,0xff000,0x3e1f000,0x1e00000,0x1e00000,0xff00003e,0x1f000000,0x7f8,0xe0000780,0x780,0x3fc00,0x7f8e00,
0x7c3e000,0x0,0x0,0xf0000000,0xf000007f,0x80001f0f,0x800001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xef000,0x0,0x0,0x0,0xef000000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000,0x3c0000,0x1e780003,0xfff8001f,0xf80003c,0x78000000,0x0,0xf00,0x3c00,0x1e7800,
0x3e1f000,0xf00000,0x3c00001,0xe780003e,0x1f000000,0xfff,0xe00003c0,0xf00,0x79e00,0xfffe00,0x7c3e000,0x0,0x0,0x78000001,0xe00000f3,
0xc0001f0f,0x800003c0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x78000,0x780000,0x3c3c0003,0x8ff0001f,0xf800078,0x3c000000,0x0,0x780,0x7800,0x3c3c00,0x3e1f000,0x780000,0x7800003,0xc3c0003e,
0x1f000000,0xe3f,0xc00001e0,0x1e00,0xf0f00,0xe3fc00,0x7c3e000,0x0,0x0,0x3c000003,0xc00001e1,0xe0001f0f,0x80000780,0x0,0x0,
0x0,0x0,0x0,0x0,0x1f,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x7e000,0x0,0x0,0x0,0x7e000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc00,0x7e000,0xfe000,0x0,0x3c000,0xf00000,0x781e0003,
0x83e0001f,0xf800070,0x1c000000,0x0,0x3c0,0xf000,0x781e00,0x3e1f000,0x3c0000,0xf000007,0x81e0003e,0x1f000000,0xe0f,0x800000f0,
0x3c00,0x1e0780,0xe0f800,0x7c3e000,0x0,0x0,0x1e000007,0x800003c0,0xf0001f0f,0x80000f00,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf8000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ff800,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x78,0xf000000,0x0,0x0,0x780f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c0,
0x0,0x0,0x0,0x0,0x0,0x0,0x3fc00,0x1fe000,0x3ffc00,0x0,0x0,0x0,0x0,0x0,0x70,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00000,0x3e000,0x3e00000,0x0,0x78,0x3c000000,0x0,0x1f000,0x3e0,
0x3e000,0x0,0x1f000000,0x3e0000,0x3e000000,0x0,0x0,0x7c00,0xf8,0xf800,0x0,0x0,0x0,0xf,0x80000000,0x1f00001f,0x0,0x3e,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x30000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x781c0000,0x38,0xe000000,0x0,0x0,0x380e0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x0,0x0,0x0,0x0,0x0,0x0,0x39c00,0x1ce000,0x303e00,
0x0,0x0,0x0,0x0,0x0,0x78,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4000,0x0,0x0,0x0,0x0,
0x0,0x0,0xf80000,0x7c000,0x3e00000,0xf0380000,0x70,0x1c000000,0x0,0xf800,0x7c0,0x3e000,0x0,0xf800000,0x7c0000,0x3e000000,
0x0,0x3c0,0xe0003e00,0x1f0,0xf800,0x3c0e00,0x0,0x0,0x7,0xc0000000,0x3e00001f,0x0,0x7c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0xff,0x0,
0xf8,0xf8000,0x1c000,0x0,0x0,0x0,0x0,0x1f,0xc0000000,0x1ff8,0xff00,0x0,0x0,0x3fe000,0x0,0x1fc00001,0xfe000000,0x0,0x0,0x0,
0x0,0x7f800,0x0,0x0,0x0,0xff00000,0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf8000000,0xfe,0x0,0x7f80,0x0,0x0,0x0,0x0,0x0,
0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x780000,0x1,0xe0000000,0x0,0x780000,0x3,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x3fc00,0x0,0x0,0x1fc000,0x0,0x0,0x0,0x1fc0,
0x0,0xff000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xfe1c0000,0x1c,0x1c000000,0x0,0x0,0x1c1c0,0x0,0x0,0x0,0x0,0x1fe0000,
0x0,0x0,0x1ff,0x1f0f8,0x0,0xff000,0x0,0x0,0x0,0x3f,0xff00000f,0x80000000,0xfe0,0x3f80,0xf00,0x0,0x0,0x0,0x1,0xf8000003,0xe0000000,
0x1c00,0xe000,0xe00,0x0,0x0,0x0,0x0,0x0,0x3c,0x78000000,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,
0x7f0000,0x0,0x1fc07000,0x0,0x0,0x0,0x0,0x0,0x3f800,0x780000,0x78000,0x7f00001,0xfc38001f,0xf800070,0x1c000000,0x0,0x7800,
0x780,0x7f000,0x3e1f000,0x7800000,0x780000,0x7f00003e,0x1f0003f0,0x7f0,0xe0001e00,0x1e0,0x1fc00,0x7f0e00,0x7c3e000,0x0,0x3,
0xc0000000,0x3c00003f,0x80001f0f,0x80000078,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x1e078000,0x30000000,0x3ff,0xc00001e0,0xf0,
0x78000,0x1c000,0x0,0x0,0x0,0x0,0x1e0007f,0xf000007e,0x1ffff,0x7ffe0,0x1f80,0x3ffff80,0xfff803,0xfffff800,0xfff80007,0xff800000,
0x0,0x0,0x0,0x0,0x1ffe00,0x0,0xfe0003,0xfff80000,0x3ffe01ff,0xe00003ff,0xffe01fff,0xff0003ff,0xe01e0007,0x803ffff0,0xfff80,
0x3c000fc0,0x7800001f,0x8003f07e,0x1e000f,0xfe0007ff,0xf00003ff,0x8007ffe0,0x1fff8,0x7fffffe,0xf0003c1,0xe000079e,0xf1f,0x1f3e0,
0x1f01ff,0xfff8003f,0xf003c000,0x7fe0,0x3f00,0x0,0x3c0000,0x1,0xe0000000,0x0,0x780000,0xf,0xfe000000,0x78000,0x3c00,0xf000,
0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xfc0000f0,0x3fe00,0x0,0x0,0xfff00,0x0,0x0,0x3fe000,
0x0,0x0,0x0,0x1dc0,0x0,0x3fff00,0x0,0x3ffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff1c07ff,0x3c0f001e,0x3c000000,
0x0,0x0,0x1e3c0,0xf80007c,0x0,0x780000,0x0,0xfff8000,0x3e00,0x1f00000,0x7ff,0xc001f0f8,0x0,0x3ffc00,0x0,0x0,0x0,0x3f,0xff00003f,
0xe0000000,0x3ff8,0xffe0,0x1e00,0x0,0xfffc00,0x0,0x7,0xf800000f,0xf8000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,
0x3f800001,0xfc00003f,0xf80000ff,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,
0xfc00,0x3c001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x0,0x7ff8f0f0,0x3c0780,0x1e03c00,0xf01e000,0x783e0001,0xf01e0000,0xffe00,
0x3c0000,0xf0000,0x7700001,0xfe38001f,0xf800070,0x1c000000,0x0,0x3c00,0xf00,0x77000,0x3e1f000,0x3c00000,0xf00000,0x7700003e,
0x1f0000f8,0xc0007f8,0xe0000f00,0x3c0,0x1dc00,0x7f8e00,0x7c3e000,0x0,0x1,0xe0000000,0x7800003b,0x80001f0f,0x800000f0,0x1e0000,
0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x780000,0x3c1e0000,0x1e070000,0x300001f0,0x7ff,0xc00001e0,0x1e0,0x7c000,0x1c000,0x0,0x0,0x0,0x0,0x3c000ff,0xf80007fe,
0x3ffff,0x801ffff8,0x1f80,0x3ffff80,0x3fff803,0xfffff801,0xfffc000f,0xffc00000,0x0,0x0,0x0,0x0,0x7fff80,0x0,0xfe0003,0xffff0000,
0xffff01ff,0xfc0003ff,0xffe01fff,0xff000fff,0xf01e0007,0x803ffff0,0xfff80,0x3c001f80,0x7800001f,0xc007f07e,0x1e001f,0xff0007ff,
0xfc0007ff,0xc007fffc,0x3fffc,0x7fffffe,0xf0003c1,0xf0000f9e,0xf0f,0x8003e1e0,0x1e01ff,0xfff8003f,0xf001e000,0x7fe0,0x3f00,
0x0,0x1e0000,0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x1fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x3de0,0x0,0x7fff80,0x0,0xfffff80,
0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe7bc07ff,0x3e1f000f,0x78000000,0x0,0x0,0xf780,0x7800078,0x0,0x780000,0x180000,
0x1fff8000,0x1e00,0x1e0003c,0xfff,0xc001f0f8,0x0,0x7ffe00,0x0,0x0,0x0,0x3f,0xff00007f,0xf0000000,0x3ffc,0xfff0,0x3c00,0x0,
0x7fffc00,0x0,0x7,0xf800003f,0xfe000000,0x1c00,0xe000,0xe00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xe00001ff,
0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000fc00,0x3c003ffe,0x1fff0,
0xfff80,0x7ffc00,0x3ffe000,0x0,0xfffce0f0,0x3c0780,0x1e03c00,0xf01e000,0x781e0001,0xe01e0000,0x3fff00,0x1e0000,0x1e0000,0xf780003,
0xcf78001f,0xf800078,0x3c000000,0x0,0x1e00,0x1e00,0xf7800,0x3e1f000,0x1e00000,0x1e00000,0xf780003e,0x1f0000fc,0x7c000f3d,
0xe0000780,0x780,0x3de00,0xf3de00,0x7c3e000,0x0,0x0,0xf0000000,0xf000007b,0xc0001f0f,0x800001e0,0x1e0000,0x3e1f00,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
0x3c1e0000,0x1e0f0000,0x300007fc,0xfff,0xc00001e0,0x1e0,0x3c000,0x1c000,0x0,0x0,0x0,0x0,0x3c001ff,0xfc001ffe,0x3ffff,0xc01ffffc,
0x3f80,0x3ffff80,0x7fff803,0xfffff803,0xfffe001f,0xffe00000,0x0,0x0,0x0,0x0,0xffff80,0x7f800,0xfe0003,0xffff8001,0xffff01ff,
0xff0003ff,0xffe01fff,0xff001fff,0xf01e0007,0x803ffff0,0xfff80,0x3c003f00,0x7800001f,0xc007f07f,0x1e003f,0xff8007ff,0xff000fff,
0xe007ffff,0x7fffc,0x7fffffe,0xf0003c0,0xf0000f1e,0xf07,0x8003c1f0,0x3e01ff,0xfff8003f,0xf001e000,0x7fe0,0x7f80,0x0,0xe0000,
0x1,0xe0000000,0x0,0x780000,0x1f,0xfe000000,0x78000,0x3c00,0xf000,0x7800003,0xffe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,
0x0,0x0,0x0,0x0,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x3fff80,0x0,0x0,0xffe000,0x0,0x0,0x0,0x78f0,0x0,0xffff80,0x0,0x3fffff80,0x1f,
0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc7f80070,0x3e1f0007,0x70000000,0x0,0x0,0x7700,0x7c000f8,0x0,0x780000,0x180000,
0x3fff8000,0x1f00,0x3e0003c,0x1f03,0xc001f0f8,0x0,0x703f00,0x0,0x0,0x0,0x3f,0xff0000f0,0xf8000000,0x303e,0xc0f8,0x7800,0x0,
0xffffc00,0x0,0x7,0x3800003e,0x3e000000,0x1c00,0xe000,0x3c00,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00000f,0xe00001ff,
0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000fe00,0x3c007fff,0x3fff8,
0x1fffc0,0xfffe00,0x7fff000,0x1,0xffffc0f0,0x3c0780,0x1e03c00,0xf01e000,0x781f0003,0xe01e0000,0x3fff80,0xe0000,0x3c0000,0x1e3c0003,
0x8ff0001f,0xf80003c,0x78000000,0x0,0xe00,0x3c00,0x1e3c00,0x3e1f000,0xe00000,0x3c00001,0xe3c0003e,0x1f00007f,0xf8000e3f,0xc0000380,
0xf00,0x78f00,0xe3fc00,0x7c3e000,0x0,0x0,0x70000001,0xe00000f1,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0000,
0x30000ffe,0xf80,0xc00001e0,0x3c0,0x1e000,0x101c040,0x0,0x0,0x0,0x0,0x78003f0,0x7e001ffe,0x3f807,0xe01f00fe,0x3f80,0x3ffff80,
0x7e01803,0xfffff007,0xe03f003f,0x3f00000,0x0,0x0,0x0,0x0,0xfc0fc0,0x3ffe00,0xfe0003,0xffffc003,0xf81f01ff,0xff8003ff,0xffe01fff,
0xff003f01,0xf01e0007,0x803ffff0,0xfff80,0x3c007e00,0x7800001f,0xc007f07f,0x1e007e,0xfc007ff,0xff801f83,0xf007ffff,0x800fc07c,
0x7fffffe,0xf0003c0,0xf0000f0f,0x1e07,0xc007c0f8,0x7c01ff,0xfff8003c,0xf000,0x1e0,0xffc0,0x0,0xf0000,0x1,0xe0000000,0x0,0x780000,
0x3e,0x0,0x78000,0x3c00,0xf000,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0x800000f0,0x1f80,
0x0,0x0,0x7e0780,0x0,0x0,0x1f82000,0x0,0x0,0x0,0x7070,0x0,0x1f80f80,0x0,0x7fffff80,0x1f,0xffff8000,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x1,0xc3f80070,0x3f3f0007,0xf0000000,0x0,0x0,0x7f00,0x3e001f0,0x0,0x780000,0x180000,0x7f018000,0xf80,0x7c0003c,0x3e00,
0x4001f0f8,0xfe00,0x400f00,0x0,0x0,0x0,0x7f000000,0xe0,0x38000000,0x1e,0x38,0x7800,0x0,0x1ffe1c00,0x0,0x0,0x38000078,0xf000000,
0x1c00,0xe000,0x7f800,0xf000,0x1fc000,0xfe0000,0x7f00000,0x3f800001,0xfc00001f,0xf00001ff,0xffc03f81,0xf007ffff,0xc03ffffe,
0x1fffff0,0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf800fe00,0x3c00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,
0x3,0xf07fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x780f8007,0xc01e0000,0x7e0fc0,0xf0000,0x3c0000,0x1c1c0003,0x87f0001f,0xf80003f,
0xf8000000,0x0,0xf00,0x3c00,0x1c1c00,0x3e1f000,0xf00000,0x3c00001,0xc1c0003e,0x1f00003f,0xc0000e1f,0xc00003c0,0xf00,0x70700,
0xe1fc00,0x7c3e000,0x0,0x0,0x78000001,0xe00000e0,0xe0001f0f,0x800003c0,0x1e0000,0x3e1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c0f0001,0xff801e0f,
0x1f00,0x1e0,0x3c0,0x1e000,0x3c1c1e0,0x0,0x0,0x0,0x0,0x78007c0,0x1f001f9e,0x3c001,0xf010003e,0x7780,0x3c00000,0xf800000,0xf007,
0xc01f007c,0x1f80000,0x0,0x0,0x0,0x0,0xe003e0,0x7fff00,0x1ef0003,0xc007e007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x301e0007,
0x80007800,0x780,0x3c00fc00,0x7800001f,0xe00ff07f,0x1e00f8,0x3e00780,0x1fc03e00,0xf807801f,0xc01f001c,0xf000,0xf0003c0,0xf0000f0f,
0x1e03,0xc00f8078,0x780000,0xf0003c,0xf000,0x1e0,0x1f3e0,0x0,0x78000,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,
0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1f,0xf0,0xf80,0x0,0x0,0xf80180,0x0,0x0,0x1e00000,
0x0,0x0,0x0,0xe038,0x0,0x3e00380,0x0,0xfe0f0000,0x0,0xf0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xc0f00070,0x3b370003,0xe0000000,
0x0,0x0,0x3e00,0x1e001e0,0x0,0x780000,0x180000,0x7c000000,0x780,0x780003c,0x3c00,0x0,0x7ffc0,0x780,0x0,0x0,0x3,0xffe00000,
0x1c0,0x3c000000,0xe,0x38,0xf000,0x0,0x3ffe1c00,0x0,0x0,0x38000078,0xf000000,0x1c00,0xe000,0x7f000,0xf000,0x3de000,0x1ef0000,
0xf780000,0x7bc00003,0xde00001e,0xf00003e7,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
0xe0001e03,0xfc00fe00,0x3c01f007,0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x7,0xc01f80f0,0x3c0780,0x1e03c00,0xf01e000,0x78078007,
0x801e0000,0x7803c0,0x78000,0x780000,0x380e0003,0x81e00000,0x1f,0xf0000000,0x0,0x780,0x7800,0x380e00,0x0,0x780000,0x7800003,
0x80e00000,0x1ff,0x80000e07,0x800001e0,0x1e00,0xe0380,0xe07800,0x0,0x0,0x0,0x3c000003,0xc00001c0,0x70000000,0x780,0x1e0000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x780000,0x3c1e0000,0x3c0e0007,0xfff01c07,0x1e00,0x1e0,0x780,0xf000,0x3e1c3e0,0x0,0x0,0x0,0x0,0xf0007c0,0x1f00181e,0x20000,
0xf000001f,0xf780,0x3c00000,0x1f000000,0x1f00f,0x800f8078,0xf80000,0x0,0x0,0x0,0x0,0x8003e0,0x1fc0f80,0x1ef0003,0xc001e007,
0x800101e0,0x7e003c0,0x1e00,0x7800,0x101e0007,0x80007800,0x780,0x3c00f800,0x7800001e,0xe00ef07f,0x801e00f0,0x1e00780,0x7c03c00,
0x78078007,0xc01e0004,0xf000,0xf0003c0,0x78001e0f,0x1e03,0xe00f807c,0xf80000,0x1f0003c,0x7800,0x1e0,0x3e1f0,0x0,0x3c000,0x1,
0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,
0x1e,0xf0,0x780,0x0,0x0,0x1f00080,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x1e03c,0x0,0x3c00080,0x0,0xf80f0000,0x0,0x1f0000,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x70,0x3bf70003,0xe0000000,0x0,0x0,0x3e00,0x1f003e0,0x0,0x780000,0x180000,0x78000000,0x7c0,0xf80003c,
0x3c00,0x0,0x1f01f0,0x780,0x0,0x0,0xf,0x80f80000,0x1c0,0x1c000000,0xe,0x38,0x1e000,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,0x7800000,
0x1c00,0xe000,0x7fc00,0xf000,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x80007800,0x10078000,0x3c0000,
0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00ff00,0x3c01e003,0xc00f001e,0x7800f0,0x3c00780,0x1e003c00,
0x7,0x800f00f0,0x3c0780,0x1e03c00,0xf01e000,0x7807c00f,0x801e0000,0xf803c0,0x3c000,0xf00000,0x780f0000,0x0,0x7,0xc0000000,
0x0,0x3c0,0xf000,0x780f00,0x0,0x3c0000,0xf000007,0x80f00000,0x7ff,0xc0000000,0xf0,0x3c00,0x1e03c0,0x0,0x0,0x0,0x0,0x1e000007,
0x800003c0,0x78000000,0xf00,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x3c1e001f,0xfff03803,0x80001e00,0x1e0,0x780,0xf000,0xf9cf80,
0x0,0x0,0x0,0x0,0xf000780,0xf00001e,0x0,0xf800000f,0xe780,0x3c00000,0x1e000000,0x1e00f,0x78078,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,
0x3f003c0,0x1ef0003,0xc000f00f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,0x3c01f000,0x7800001e,0xe00ef07f,
0x801e01f0,0x1e00780,0x3c07c00,0x78078003,0xc03e0000,0xf000,0xf0003c0,0x78001e0f,0x1e01,0xf01f003c,0xf00000,0x3e0003c,0x7800,
0x1e0,0x7c0f8,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,
0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x8,0x40,0x0,0x7e0000,0x7c00000,0x1,0xf00f0000,
0x0,0x3e0000,0x0,0x3f,0xfc0,0xfc3f0,0xfc3f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,0xf003c0,0x0,0x0,0x180000,0xf8000000,
0x3c0,0xf00003c,0x3c00,0x0,0x3c0078,0x7ff80,0x0,0x0,0x1e,0x3c0000,0x1c0,0x1c000000,0xe,0xf0,0x0,0x0,0x7ffe1c00,0x0,0x0,0x380000f0,
0x7800000,0x1c00,0xe000,0x3c00,0x0,0x3de000,0x1ef0000,0xf780000,0x7bc00003,0xde00001e,0xf00003c7,0x8000f800,0x78000,0x3c0000,
0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00ff00,0x3c03e003,0xc01f001e,0xf800f0,0x7c00780,0x3e003c00,
0xf,0x800f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803c00f,0x1fffc0,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x307,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1e0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,0x781e003f,0xfff03803,
0x80001e00,0x1e0,0xf80,0xf000,0x3dde00,0x0,0x0,0x0,0x0,0xf000f00,0x780001e,0x0,0x7800000f,0x1e780,0x3c00000,0x3e000000,0x3e00f,
0x780f0,0x7c0000,0x0,0x0,0x0,0x0,0x1e0,0x7c001e0,0x3ef8003,0xc000f00f,0x1e0,0xf003c0,0x1e00,0xf000,0x1e0007,0x80007800,0x780,
0x3c03e000,0x7800001e,0xf01ef07b,0xc01e01e0,0xf00780,0x3e07800,0x3c078003,0xe03c0000,0xf000,0xf0003c0,0x78001e0f,0x1e00,0xf01e003e,
0x1f00000,0x3c0003c,0x7800,0x1e0,0x78078,0x0,0x0,0x1,0xe0000000,0x0,0x780000,0x3c,0x0,0x78000,0x0,0x0,0x7800000,0x1e00000,
0x0,0x0,0x0,0x0,0x0,0x0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,
0xe70000,0x7800000,0x1,0xe00f0000,0x0,0x3c0000,0x0,0x3f,0xfc0,0xfc1f0,0x1f83f0,0x0,0x0,0x0,0x70,0x39e70000,0x0,0x0,0x0,0x0,
0xf807c0,0x0,0x0,0x180000,0xf0000000,0x3e0,0x1f00003c,0x3e00,0x0,0x70001c,0x3fff80,0x0,0x0,0x38,0xe0000,0x1c0,0x1c000078,
0x1c,0x1fe0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x7df000,0x3ef8000,0x1f7c0000,0xfbe00007,
0xdf00003c,0x780003c7,0x8000f000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f780,
0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0xf80f0,0x3c0780,0x1e03c00,0xf01e000,0x7803e01f,0x1ffff8,0xf001e0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0xc000,0x0,0x0,0x0,0x0,0x1e0000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x780000,0x3c1e0000,0x781e003e,0x30703803,0x80001e00,0x1e0,0xf00,0x7800,0xff800,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,
0x0,0x7800000f,0x3c780,0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x2000000,0x800000,0x1e0,0x78000e0,0x3c78003,
0xc000f01e,0x1e0,0xf803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x701cf07b,0xc01e01e0,0xf00780,0x1e07800,
0x3c078001,0xe03c0000,0xf000,0xf0003c0,0x7c003e0f,0x1e00,0xf83e001e,0x1e00000,0x7c0003c,0x3c00,0x1e0,0xf807c,0x0,0x0,0x1fe0001,
0xe1fc0000,0x7f00003,0xf8780007,0xf000003c,0x7f0,0x783f0,0x0,0x0,0x7800000,0x1e00000,0x3e0f8000,0xfc00007,0xf8000007,0xf00001fc,
0xf,0xc0003fc0,0x3c000,0x0,0x0,0x0,0x0,0x0,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,0x0,0x0,0x3c00000,0x0,0x18,0xc0,0x0,0x1818000,
0x7800000,0x1,0xe00f0000,0x0,0x7c0000,0x0,0x1f,0x80001f80,0x7c1f8,0x1f83e0,0x0,0x0,0x0,0x70,0x38c70007,0xf8000000,0x7f03,
0xf0000000,0x0,0x780780,0x0,0x0,0xfe0000,0xf0000000,0x1e0,0x1e00003c,0x3f00,0x0,0xe07f0e,0x7fff80,0x0,0x0,0x70,0x70000,0x1c0,
0x1c000078,0x3c,0x1fc0,0x0,0x0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800000,0x1c00,0xe000,0xe00,0x0,0x78f000,0x3c78000,0x1e3c0000,
0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
0xf80f780,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0xf,0x1f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801e01e,0x1ffffc,
0xf007e0,0x3fc000,0x1fe0000,0xff00000,0x7f800003,0xfc00001f,0xe0000fc0,0xfc00007f,0xfe0,0x7f00,0x3f800,0x1fc000,0x0,0x0,0x0,
0x1,0xf000001f,0x80000ff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x1f80000,0x1fc1e000,0x0,0x0,0x0,0x0,0x1e1fc0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e0000,
0x781c007c,0x30003803,0x80001f00,0x1e0,0xf00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x1e000f00,0x780001e,0x0,0x7800000f,0x3c780,
0x3c00000,0x3c000000,0x3c00f,0x780f0,0x3c0000,0x0,0x0,0x1e000000,0xf00000,0x3e0,0xf0000e0,0x3c78003,0xc000f01e,0x1e0,0x7803c0,
0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c0f8000,0x7800001e,0x701cf079,0xe01e01e0,0xf00780,0x1e07800,0x3c078001,0xe03c0000,
0xf000,0xf0003c0,0x3c003c0f,0x3e00,0x787c001f,0x3e00000,0xf80003c,0x3c00,0x1e0,0x1f003e,0x0,0x0,0x1fffc001,0xe7ff0000,0x3ffe000f,
0xfe78003f,0xfc001fff,0xfe001ffc,0xf0078ffc,0x1ffc00,0x7ff000,0x7800f80,0x1e0000f,0x7f1fc01e,0x3ff0001f,0xfe00079f,0xfc0007ff,
0x3c003c7f,0xf001fff8,0x1fffff0,0x3c003c0,0xf0000f1e,0xf1f,0x7c1f0,0x1f00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3c00000,0x100000,
0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7800000,0x1,0xe00f0000,0x1000000,0xf80000,0x40000002,0xf,0x80001f00,0x7e0f8,0x1f07c0,
0x0,0x0,0x0,0x70,0x38c7003f,0xff000000,0xff8f,0xf8000100,0xffffe,0x7c0f80,0x0,0x0,0x3ffc000,0xf0000020,0x1001f0,0x3c00003c,
0x1f80,0x0,0x1c3ffc7,0x7c0780,0x0,0x0,0xe3,0xff038000,0xe0,0x38000078,0x78,0x1ff0,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,
0x7800000,0x1c00,0xe000,0xe00,0xf000,0x78f000,0x3c78000,0x1e3c0000,0xf1e00007,0x8f00003c,0x78000787,0x8001e000,0x78000,0x3c0000,
0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,
0x4000200f,0x3f80f0,0x3c0780,0x1e03c00,0xf01e000,0x7801f03e,0x1ffffe,0xf01fe0,0x3fff800,0x1fffc000,0xfffe0007,0xfff0003f,
0xff8001ff,0xfc003ff3,0xfe0003ff,0xe0007ff8,0x3ffc0,0x1ffe00,0xfff000,0x3ff80001,0xffc0000f,0xfe00007f,0xf000003f,0xf8003c7f,
0xe0003ffc,0x1ffe0,0xfff00,0x7ff800,0x3ffc000,0x1f80000,0xfff1c03c,0x3c01e0,0x1e00f00,0xf007800,0x781f0001,0xf01e7ff0,0x7c0007c,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
0x3c1e003f,0xfffff078,0x30003803,0x80000f00,0x1e0,0x1f00,0x7800,0x7f000,0x1e0000,0x0,0x0,0x0,0x3c000f00,0x780001e,0x0,0x7800000f,
0x78780,0x3c00000,0x3c000000,0x7c00f,0x780f0,0x3c0007,0xe000003f,0x0,0xfe000000,0xfe0000,0x3c0,0x1f000070,0x7c7c003,0xc000f01e,
0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c1f0000,0x7800001e,0x783cf079,0xe01e03c0,0xf00780,0x1e0f000,0x3c078001,
0xe03c0000,0xf000,0xf0003c0,0x3c003c07,0x81f03c00,0x7c7c000f,0x87c00000,0xf00003c,0x1e00,0x1e0,0x3e001f,0x0,0x0,0x3fffe001,
0xefff8000,0x7fff001f,0xff78007f,0xfe001fff,0xfe003ffe,0xf0079ffe,0x1ffc00,0x7ff000,0x7801f00,0x1e0000f,0xffbfe01e,0x7ff8003f,
0xff0007bf,0xfe000fff,0xbc003cff,0xf803fffc,0x1fffff0,0x3c003c0,0x78001e1e,0xf0f,0x800f80f0,0x1e00ff,0xffe0001e,0xf0,0x780,
0x0,0x0,0x3c00000,0x380000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1008000,0x7800000,0x3,0xe00f0000,0x3800000,0xf00000,0xe0000007,
0xf,0x80001f00,0x3e0f8,0x1e07c0,0x0,0x0,0x0,0x70,0x3807007f,0xff800000,0x1ffdf,0xfc000380,0xffffe,0x3e1f00,0x0,0x0,0xfffe000,
0xf0000030,0x3800f8,0x7c00003c,0xfc0,0x0,0x18780c3,0xf00780,0x80100,0x0,0xc3,0xffc18000,0xf0,0x78000078,0xf0,0xf0,0x0,0x3c003c0,
0xfffe1c00,0x0,0x0,0x380000f0,0x7800801,0x1c00,0xe000,0x1e00,0xf000,0xf8f800,0x7c7c000,0x3e3e0001,0xf1f0000f,0x8f80007c,0x7c000787,
0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078001,0xe03c000f,
0x1e00078,0xf0003c0,0x78001e00,0xe000701f,0x3fc0f0,0x3c0780,0x1e03c00,0xf01e000,0x7800f87c,0x1e007f,0xf07e00,0x7fffc00,0x3fffe001,
0xffff000f,0xfff8007f,0xffc003ff,0xfe007ff7,0xff0007ff,0xf000fffc,0x7ffe0,0x3fff00,0x1fff800,0x3ff80001,0xffc0000f,0xfe00007f,
0xf00000ff,0xf8003cff,0xf0007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x1f80001,0xfffb803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,
0xe01efff8,0x3c00078,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x780000,0x3c1e003f,0xfffff078,0x30001c07,0xf80,0x1e0,0x1e00,0x3c00,0xff800,0x1e0000,0x0,0x0,0x0,0x3c001e00,
0x3c0001e,0x0,0x7800001e,0x70780,0x3c00000,0x78000000,0x78007,0x800f00f0,0x3e0007,0xe000003f,0x3,0xfe000000,0xff8000,0x7c0,
0x1e000070,0x783c003,0xc001f01e,0x1e0,0x7803c0,0x1e00,0x1e000,0x1e0007,0x80007800,0x780,0x3c3e0000,0x7800001e,0x3838f079,
0xe01e03c0,0x780780,0x1e0f000,0x1e078001,0xe03c0000,0xf000,0xf0003c0,0x3c007c07,0x81f03c00,0x3ef80007,0x87800000,0x1f00003c,
0x1e00,0x1e0,0x7c000f,0x80000000,0x0,0x3ffff001,0xffffc000,0xffff003f,0xff7800ff,0xff001fff,0xfe007ffe,0xf007bffe,0x1ffc00,
0x7ff000,0x7803e00,0x1e0000f,0xffffe01e,0xfff8007f,0xff8007ff,0xff001fff,0xbc003dff,0xf807fffc,0x1fffff0,0x3c003c0,0x78001e0f,
0x1e07,0xc01f00f0,0x1e00ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7c00000,0x7c0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1018000,0x7800000,
0x3,0xc00f0000,0x7c00000,0x1f00001,0xf000000f,0x80000007,0xc0003e00,0x1e07c,0x3e0780,0x0,0x0,0x0,0x70,0x380700ff,0xff800000,
0x3ffff,0xfe0007c0,0xffffe,0x1e1e00,0x0,0x780000,0x1fffe000,0xf0000078,0x7c0078,0x7800003c,0xff0,0x0,0x38e0003,0x80f00780,
0x180300,0x0,0x1c3,0x81e1c000,0x7f,0xf0000078,0x1e0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x380000f0,0x7800c01,0x80001c00,
0xe000,0x603e00,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x7800078,0x3c000f87,0x8001e000,0x78000,0x3c0000,0x1e00000,
0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f3c0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f01,0xf000f81e,
0x7bc0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007878,0x1e001f,0xf0f800,0x7fffe00,0x3ffff001,0xffff800f,0xfffc007f,0xffe003ff,
0xff007fff,0xff800fff,0xf001fffe,0xffff0,0x7fff80,0x3fffc00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00001ff,0xfc003dff,0xf000ffff,
0x7fff8,0x3fffc0,0x1fffe00,0xffff000,0x1f80003,0xffff803c,0x3c01e0,0x1e00f00,0xf007800,0x780f0001,0xe01ffffc,0x3c00078,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,
0x3c1e003f,0xfffff078,0x30001e0f,0x300780,0x1e0,0x1e00,0x3c00,0x3dde00,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf800003e,
0xf0780,0x3dfc000,0x783f8000,0xf8007,0xc01f00f0,0x3e0007,0xe000003f,0x1f,0xfc000000,0x7ff000,0xf80,0x3e007c70,0x783c003,0xc001e03c,
0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,0x80007800,0x780,0x3c7c0000,0x7800001e,0x3878f078,0xf01e03c0,0x780780,0x1e0f000,0x1e078001,
0xe03e0000,0xf000,0xf0003c0,0x1e007807,0x83f03c00,0x3ef00007,0xcf800000,0x3e00003c,0xf00,0x1e0,0xf80007,0xc0000000,0x0,0x3e01f801,
0xfe07e001,0xf80f007e,0x7f801f8,0x1f801fff,0xfe00fc0f,0xf007f83f,0x1ffc00,0x7ff000,0x7807c00,0x1e0000f,0x87e1e01f,0xe0fc00fc,
0xfc007f8,0x1f803f03,0xfc003df0,0x3807e03c,0x1fffff0,0x3c003c0,0x78003e0f,0x1e03,0xe03e00f8,0x3e00ff,0xffe0001e,0xf0,0x780,
0x0,0x0,0x7800000,0xfe0000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0x1818000,0x7c00000,0x3,0xc00f0000,0xfe00000,0x3e00003,0xf800001f,
0xc0000007,0xc0003e00,0x1e03c,0x3c0f80,0x0,0x0,0x0,0x70,0x380700fc,0x7800000,0x7c1fe,0x3e000fe0,0xffffe,0x1f3e00,0x0,0x780000,
0x3f98e000,0xf000003c,0xfcf8007c,0xf800003c,0x3ffc,0x0,0x31c0001,0x80f00f80,0x380700,0x0,0x183,0x80e0c000,0x3f,0xe0000078,
0x3c0,0x38,0x0,0x3c003c0,0xfffe1c00,0x0,0x0,0x38000078,0xf000e01,0xc003ffe0,0x1fff00,0x7ffc00,0xf000,0xf07800,0x783c000,0x3c1e0001,
0xe0f0000f,0x7800078,0x3c000f07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,
0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf801f01e,0xf3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78007cf8,
0x1e000f,0x80f0f000,0x7c03f00,0x3e01f801,0xf00fc00f,0x807e007c,0x3f003e0,0x1f80707f,0x8f801f80,0xf003f03f,0x1f81f8,0xfc0fc0,
0x7e07e00,0x3ff80001,0xffc0000f,0xfe00007f,0xf00003ff,0xfc003fc1,0xf801f81f,0x800fc0fc,0x7e07e0,0x3f03f00,0x1f81f800,0x1f80007,
0xe07f003c,0x3c01e0,0x1e00f00,0xf007800,0x780f8003,0xe01fe07e,0x3e000f8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3f,0xfffff078,0x30000ffe,0x1f007c0,0x0,0x1e00,
0x3c00,0xf9cf80,0x1e0000,0x0,0x0,0x0,0x78001e00,0x3c0001e,0x0,0xf00000fc,0x1e0780,0x3fff800,0x78ffe000,0xf0003,0xe03e00f0,
0x3e0007,0xe000003f,0x7f,0xe01fffff,0xf00ffc00,0x1f80,0x3c01ff70,0x783c003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x1e0007,
0x80007800,0x780,0x3cfc0000,0x7800001e,0x3c78f078,0xf01e03c0,0x780780,0x3e0f000,0x1e078003,0xc01f0000,0xf000,0xf0003c0,0x1e007807,
0x83f83c00,0x1ff00003,0xcf000000,0x3e00003c,0xf00,0x1e0,0x0,0x0,0x0,0x20007801,0xfc03e003,0xe003007c,0x3f803e0,0x7c0003c,
0xf807,0xf007e00f,0x3c00,0xf000,0x780f800,0x1e0000f,0x87e1f01f,0x803c00f8,0x7c007f0,0xf803e01,0xfc003f80,0x80f8004,0x3c000,
0x3c003c0,0x3c003c0f,0x1e03,0xe03e0078,0x3c0000,0x7c0001e,0xf0,0x780,0x0,0x0,0x3ffff800,0x1ff0000,0x0,0x7800000,0x0,0x18,
0xc0,0x0,0x1818000,0x3e00000,0x3,0xc00f0000,0x1ff00000,0x3e00007,0xfc00003f,0xe0000003,0xc0003c00,0xf03c,0x3c0f00,0x0,0x0,
0x0,0x70,0x380701f0,0x800000,0x780fc,0x1e001ff0,0x7c,0xf3c00,0x0,0x780000,0x7e182000,0xf000001f,0xfff00ffc,0xffc0003c,0x3cfe,
0x0,0x31c0001,0x80f01f80,0x780f00,0x0,0x183,0x80e0c000,0xf,0x80000078,0x780,0x38,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x38000078,
0xf000f01,0xe003ffe0,0x1fff00,0x7ff800,0xf000,0xf07800,0x783c000,0x3c1e0001,0xe0f0000f,0x78000f8,0x3e000f07,0x8003c000,0x78000,
0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f1e0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
0x78000f00,0x7c03e01e,0x1e3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78003cf0,0x1e0007,0x80f1e000,0x4000f00,0x20007801,0x3c008,
0x1e0040,0xf00200,0x780403f,0x7803e00,0x3007c00f,0x803e007c,0x1f003e0,0xf801f00,0x780000,0x3c00000,0x1e000000,0xf00007f0,
0x3e003f00,0x7801f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e003c,0x3c01e0,0x1e00f00,0xf007800,0x78078003,
0xc01fc03e,0x1e000f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xf078007c,0x300007fc,0x7e00fe0,0x0,0x1e00,0x3c00,0x3e1c3e0,0x1e0000,0x0,0x0,0x0,0xf0001e00,
0x3c0001e,0x1,0xf000fff8,0x1e0780,0x3fffe00,0x79fff000,0x1f0001,0xfffc00f0,0x7e0007,0xe000003f,0x3ff,0x801fffff,0xf003ff80,
0x3f00,0x3c03fff0,0xf01e003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3df80000,0x7800001e,
0x1c70f078,0x781e03c0,0x780780,0x3c0f000,0x1e078007,0xc01f8000,0xf000,0xf0003c0,0x1e007807,0x83f83c00,0xfe00003,0xff000000,
0x7c00003c,0x780,0x1e0,0x0,0x0,0x0,0x7c01,0xf801f007,0xc00100f8,0x1f803c0,0x3c0003c,0x1f003,0xf007c00f,0x80003c00,0xf000,
0x783f000,0x1e0000f,0x3c0f01f,0x3e01f0,0x3e007e0,0x7c07c00,0xfc003f00,0xf0000,0x3c000,0x3c003c0,0x3c003c0f,0x1e01,0xf07c007c,
0x7c0000,0xfc0001e,0xf0,0x780,0x0,0x0,0x3ffff000,0x3838000,0x0,0x7800000,0x0,0x18,0xc0,0x0,0xff0000,0x3f00000,0x3,0xc00fff00,
0x38380000,0x7c0000e,0xe000070,0x70000001,0xe0003c00,0xf01e,0x780e00,0x0,0x0,0x0,0x0,0x1e0,0x0,0x780f8,0xf003838,0xfc,0xffc00,
0x0,0x780000,0x7c180000,0xf000000f,0xffe00fff,0xffc0003c,0x783f,0x80000000,0x6380000,0xc0f83f80,0xf81f00,0x0,0x303,0x80e06000,
0x0,0x78,0xf00,0x78,0x0,0x3c003c0,0x7ffe1c00,0x0,0x0,0x3800003c,0x3e000f81,0xf003ffe0,0x1fff00,0x1fc000,0xf000,0x1e03c00,
0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e000f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,
0x3c000001,0xe0001e00,0x3c0f0f0,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3e07c01e,0x1e3c0f0,0x3c0780,0x1e03c00,
0xf01e000,0x78003ff0,0x1e0007,0x80f1e000,0xf80,0x7c00,0x3e000,0x1f0000,0xf80000,0x7c0001e,0x3c07c00,0x10078007,0x803c003c,
0x1e001e0,0xf000f00,0x780000,0x3c00000,0x1e000000,0xf00007c0,0x1e003e00,0x7c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,
0xf,0x801f003c,0x3c01e0,0x1e00f00,0xf007800,0x7807c007,0xc01f801f,0x1f001f0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x0,0xe078003c,0x300001f0,0x3f801ff0,0x0,
0x3c00,0x1e00,0x3c1c1e0,0x1e0000,0x0,0x0,0x0,0xf0001e0f,0x3c0001e,0x3,0xe000fff0,0x3c0780,0x3ffff00,0x7bfff800,0x1e0000,0x7ff00078,
0x7e0007,0xe000003f,0x1ffc,0x1fffff,0xf0007ff0,0x7e00,0x3c07c3f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,
0x1fffff,0x80007800,0x780,0x3ffc0000,0x7800001e,0x1ef0f078,0x781e03c0,0x780780,0x7c0f000,0x1e07801f,0x800ff000,0xf000,0xf0003c0,
0xf00f807,0x83b83c00,0xfc00001,0xfe000000,0xf800003c,0x780,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0xc00000f0,0xf80780,0x3c0003c,
0x1e001,0xf007c007,0x80003c00,0xf000,0x787e000,0x1e0000f,0x3c0f01f,0x1e01e0,0x1e007c0,0x3c07800,0x7c003f00,0xf0000,0x3c000,
0x3c003c0,0x3e007c07,0x80003c00,0xf8f8003c,0x780000,0xf80001e,0xf0,0x780,0x0,0x0,0x7ffff000,0x601c000,0x3,0xffff0000,0x0,
0xfff,0xf8007fff,0xc0000000,0x7e003c,0x1fe0000,0xc0003,0xc00fff00,0x601c0000,0xf800018,0x70000c0,0x38000001,0xe0007800,0x701e,
0x701e00,0x0,0x0,0x0,0x0,0x1e0,0x6,0x700f8,0xf00601c,0xf8,0x7f800,0x0,0x780000,0xf8180000,0xf000000f,0x87c00fff,0xffc0003c,
0xf01f,0xc0000000,0x6380000,0xc07ff780,0x1f03e03,0xfffffe00,0x303,0x81c06000,0x0,0x1ffff,0xfe001e00,0x180f8,0x0,0x3c003c0,
0x3ffe1c00,0x3f00000,0x0,0x3800003f,0xfe0007c0,0xf8000000,0x18000000,0xc0000006,0x1f000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,
0x3c000f0,0x1e001f07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f0f0,
0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f0f801e,0x3c3c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,
0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07c00,0xf0007,0x8078003c,0x3c001e0,0x1e000f00,0x780000,0x3c00000,
0x1e000000,0xf0000f80,0x1f003e00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0xf,0x3f003c,0x3c01e0,0x1e00f00,0xf007800,
0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe078003f,0xb0000000,0xfc003cf0,0x0,0x3c00,0x1e00,0x101c040,0x1e0000,0x0,0x0,0x1,
0xe0001e1f,0x83c0001e,0x7,0xe000fff0,0x3c0780,0x3c03f80,0x7fc0fc00,0x1e0000,0xfff80078,0xfe0007,0xe000003f,0x7fe0,0x1fffff,
0xf0000ffc,0xfc00,0x780f81f0,0xf01e003,0xffff003c,0x1e0,0x3c03ff,0xffc01fff,0xfe03c000,0x1fffff,0x80007800,0x780,0x3ffc0000,
0x7800001e,0x1ef0f078,0x3c1e03c0,0x780780,0x1fc0f000,0x1e07ffff,0x7ff00,0xf000,0xf0003c0,0xf00f007,0xc3b87c00,0x7c00001,0xfe000000,
0xf800003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xf000f007,0x800000f0,0xf80780,0x1e0003c,0x1e001,0xf0078007,0x80003c00,0xf000,0x78fc000,
0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,0x3c07800,0x7c003e00,0xf0000,0x3c000,0x3c003c0,0x1e007807,0x80003c00,0x7df0003c,0x780000,
0x1f00001e,0xf0,0x780,0x0,0x0,0x7800000,0xe7ce000,0x3,0xffff0000,0x0,0xfff,0xf8007fff,0xc0000000,0x1f0,0xffe000,0x1c0003,
0xc00fff00,0xe7ce0000,0xf800039,0xf38001cf,0x9c000000,0xe0007800,0x780e,0x701c00,0x0,0x0,0x0,0x0,0x1e0,0x7,0xf0078,0xf00e7ce,
0x1f0,0x7f800,0x0,0x780000,0xf0180000,0xf000000e,0x1c0001f,0xe000003c,0xf007,0xe0000000,0x6380000,0xc03fe780,0x3e07c03,0xfffffe00,
0x303,0xffc06000,0x0,0x1ffff,0xfe003ffe,0x1fff0,0x0,0x3c003c0,0x1ffe1c00,0x3f00000,0x7,0xffc0001f,0xfc0003e0,0x7c000001,0xfc00000f,
0xe000007f,0x1e000,0x1e03c00,0xf01e000,0x780f0003,0xc078001e,0x3c000f0,0x1e001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,
0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,
0x783c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78001fe0,0x1e0007,0x80f1e000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c07800,
0xf0003,0xc078001e,0x3c000f0,0x1e000780,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,
0x7800780,0x3c003c00,0xf,0x7f003c,0x3c01e0,0x1e00f00,0xf007800,0x7803c007,0x801f000f,0xf001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe070001f,0xf8000007,
0xf0007cf8,0x7800000,0x3c00,0x1e00,0x1c000,0x1e0000,0x0,0x0,0x1,0xe0001e1f,0x83c0001e,0xf,0xc000fff8,0x780780,0x2000f80,0x7f803e00,
0x3e0003,0xfffe007c,0x1fe0000,0x0,0x3ff00,0x0,0x1ff,0x8001f000,0x780f00f0,0x1f00f003,0xffffc03c,0x1e0,0x3c03ff,0xffc01fff,
0xfe03c00f,0xf81fffff,0x80007800,0x780,0x3ffe0000,0x7800001e,0xee0f078,0x3c1e03c0,0x7807ff,0xff80f000,0x1e07fffe,0x3ffe0,
0xf000,0xf0003c0,0xf00f003,0xc7bc7800,0xfc00000,0xfc000001,0xf000003c,0x3c0,0x1e0,0x0,0x0,0x0,0x3c01,0xe000f80f,0x800001e0,
0xf80f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x79f8000,0x1e0000f,0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003e00,
0xf0000,0x3c000,0x3c003c0,0x1e007807,0x81e03c00,0x7df0003e,0xf80000,0x3e00003e,0xf0,0x7c0,0xfc000,0x80000000,0x7800000,0x1e7cf000,
0x3,0xffff0000,0x0,0x18,0xc0,0x0,0xf80,0x7ffc00,0x380003,0xc00fff01,0xe7cf0000,0x1f000079,0xf3c003cf,0x9e000000,0xe0007000,
0x380e,0xe01c00,0x0,0x0,0x0,0x0,0x1e0,0x3,0x800f0078,0xf01e7cf,0x3e0,0x3f000,0x0,0x780000,0xf018001f,0xfff8001e,0x1e0000f,
0xc000003c,0xf003,0xe0000000,0x6380000,0xc00fc780,0x7c0f803,0xfffffe00,0x303,0xfe006000,0x0,0x1ffff,0xfe003ffe,0x1ffe0,0x0,
0x3c003c0,0xffe1c00,0x3f00000,0x7,0xffc00007,0xf00001f0,0x3e00001f,0xfc0000ff,0xe00007ff,0x3e000,0x3e01e00,0x1f00f000,0xf8078007,
0xc03c003e,0x1e001e0,0xf001e07,0xff83c000,0x7ffff,0x803ffffc,0x1ffffe0,0xfffff00,0xf00000,0x7800000,0x3c000001,0xe000fff8,
0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000fc0,
0x1e0007,0x80f1f000,0x780,0x3c00,0x1e000,0xf0000,0x780000,0x3c0001e,0x3c0f800,0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,
0x3c00000,0x1e000000,0xf0000f00,0xf003c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1e,0xf7803c,0x3c01e0,0x1e00f00,
0xf007800,0x7803e00f,0x801e000f,0x80f803e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1,0xe0f0000f,0xff00001f,0x8000f87c,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,
0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x1f,0x800000fe,0xf00780,0x7c0,0x7f001e00,0x3c0007,0xe03f003f,0x3fe0000,0x0,0x3fc00,0x0,
0x7f,0x8001e000,0x781f00f0,0x1e00f003,0xc007e03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3f9f0000,0x7800001e,
0xfe0f078,0x3c1e03c0,0x7807ff,0xff00f000,0x1e07fff8,0xfff8,0xf000,0xf0003c0,0xf81f003,0xc7bc7800,0xfe00000,0x78000003,0xe000003c,
0x1e0,0x1e0,0x0,0x0,0x0,0x1fffc01,0xe000780f,0x1e0,0x780f00,0x1e0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7bf0000,0x1e0000f,
0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xf8000,0x3c000,0x3c003c0,0x1f00f807,0x81f03c00,0x3fe0001e,0xf00000,0x7c00007c,
0xf0,0x3e0,0x3ff801,0x80000000,0x7800000,0x3cfcf800,0x3,0xffff0000,0x0,0x18,0xc0,0x0,0x7c00,0x1fff00,0x700003,0xc00f0003,
0xcfcf8000,0x3e0000f3,0xf3e0079f,0x9f000000,0xf000,0x1000,0x0,0x0,0x0,0x0,0x0,0x1f0,0x1,0xc00f0078,0xf03cfcf,0x800007c0,0x1e000,
0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x8000003c,0xf001,0xf0000000,0x6380000,0xc0000000,0xf81f003,0xfffffe00,0x303,
0x87006000,0x0,0x1ffff,0xfe003ffe,0x7f00,0x0,0x3c003c0,0x3fe1c00,0x3f00000,0x7,0xffc00000,0xf8,0x1f0001ff,0xf0000fff,0x80007ffc,
0xfc000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf001e07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,
0x7800000,0x3c000001,0xe000fff8,0x3c0f078,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x3fc001e,0x1e03c0f0,0x3c0780,
0x1e03c00,0xf01e000,0x78000780,0x1e0007,0x80f0fc00,0x3fff80,0x1fffc00,0xfffe000,0x7fff0003,0xfff8001f,0xffc0001e,0x3c0f000,
0x1e0003,0xc0f0001e,0x78000f0,0x3c000780,0x780000,0x3c00000,0x1e000000,0xf0001e00,0xf803c00,0x3c078001,0xe03c000f,0x1e00078,
0xf0003c0,0x78001e07,0xfffffe1e,0x1e7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801e00f,0x1e0007,0x807803c0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00007,
0xffc0007e,0xf03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x0,0x3,0xc0001e1f,0x83c0001e,0x3f,0x3e,0xf00780,0x3c0,0x7e001e00,
0x7c000f,0x800f001f,0xffde0000,0x0,0x3e000,0x0,0xf,0x8003e000,0x781e0070,0x1e00f003,0xc001f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,
0xf81e0007,0x80007800,0x780,0x3f1f0000,0x7800001e,0x7c0f078,0x1e1e03c0,0x7807ff,0xfc00f000,0x1e07fffe,0xffc,0xf000,0xf0003c0,
0x781e003,0xc71c7800,0x1ff00000,0x78000003,0xe000003c,0x1e0,0x1e0,0x0,0x0,0x0,0xffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,
0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7f000,0x3c000,
0x3c003c0,0xf00f007,0xc1f07c00,0x1fc0001f,0x1f00000,0xfc000ff8,0xf0,0x1ff,0xfffe07,0x80000000,0x7800000,0x7ffcfc00,0x0,0xf000000,
0x0,0x18,0xc0,0x0,0x3e000,0x1ff80,0xe00003,0xc00f0007,0xffcfc000,0x3e0001ff,0xf3f00fff,0x9f800000,0x6000,0x0,0x0,0x7c000,
0x0,0x0,0x0,0xfe,0x0,0xe00f007f,0xff07ffcf,0xc0000fc0,0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00007,0x80000000,0xf800,
0xf0000000,0x6380000,0xc0000000,0x1f03c000,0x1e00,0x303,0x83806000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xfe1c00,0x3f00000,0x0,
0x0,0x3c,0xf801fff,0xfff8,0x7ffc0,0x1f8000,0x3c01e00,0x1e00f000,0xf0078007,0x803c003c,0x1e001e0,0xf003c07,0x8003c000,0x78000,
0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,
0x78000f00,0x1f8001e,0x1e03c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e000f,0x80f0ff00,0x1ffff80,0xffffc00,0x7fffe003,
0xffff001f,0xfff800ff,0xffc007ff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,
0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x3c7803c,0x3c01e0,0x1e00f00,0xf007800,0x7801f01f,
0x1e0007,0x807c07c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x780000,0x3,0xc0f00000,0xfff003f0,0x1f00f03e,0x7800000,0x3c00,0x1e00,0x1c000,0x7fffff80,0x0,0x7ff80000,0x3,
0xc0001e0f,0x3c0001e,0x7e,0x1f,0x1e00780,0x3e0,0x7e000f00,0x78000f,0x7800f,0xff9e0000,0x0,0x3fc00,0x0,0x7f,0x8003c000,0x781e0070,
0x3e00f803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c00f,0xf81e0007,0x80007800,0x780,0x3e0f8000,0x7800001e,0x7c0f078,0x1e1e03c0,
0x7807ff,0xf000f000,0x1e07807f,0xfe,0xf000,0xf0003c0,0x781e003,0xc71c7800,0x3ef00000,0x78000007,0xc000003c,0x1e0,0x1e0,0x0,
0x0,0x0,0x1ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7ff0000,0x1e0000f,0x3c0f01e,
0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x7ff80,0x3c000,0x3c003c0,0xf00f003,0xc1f07800,0x1fc0000f,0x1e00000,0xf8000ff0,0xf0,
0xff,0xffffff,0x80000000,0x3fffc000,0xfff9fe00,0x0,0xf000000,0x0,0x18,0xc0,0x0,0x1f0000,0x1fc0,0x1c00003,0xc00f000f,0xff9fe000,
0x7c0003ff,0xe7f81fff,0x3fc00000,0x0,0x0,0x0,0xfe000,0x1ffffc0f,0xfffffc00,0x0,0xff,0xf0000000,0x700f007f,0xff0fff9f,0xe0000f80,
0x1e000,0x0,0x780001,0xe018001f,0xfff8001c,0xe00fff,0xffc00000,0xf800,0xf0000000,0x6380000,0xc0ffff80,0x3e078000,0x1e00,0x7ff80303,
0x83c06000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,0x0,0x7f,0xff00001e,0x7c1fff0,0xfff80,0x7ffc00,0x3f0000,0x7c01f00,
0x3e00f801,0xf007c00f,0x803e007c,0x1f003e0,0xf803c07,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
0xe0001e00,0x3c0f03c,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x1f8001e,0x3c03c0f0,0x3c0780,0x1e03c00,0xf01e000,
0x78000780,0x1e001f,0xf07f80,0x3ffff80,0x1ffffc00,0xffffe007,0xffff003f,0xfff801ff,0xffc03fff,0xffc0f000,0x1fffff,0xc0fffffe,
0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,
0xfffffe1e,0x787803c,0x3c01e0,0x1e00f00,0xf007800,0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x3ff80fc0,0x7fc1e01f,
0x7800000,0x3c00,0x1e00,0x0,0x7fffff80,0x0,0x7ff80000,0x7,0x80001e00,0x3c0001e,0xfc,0xf,0x1e00780,0x1e0,0x7c000f00,0x78000f,
0x78007,0xff1e0000,0x0,0x3ff00,0x0,0x1ff,0x8003c000,0x781e0070,0x3c007803,0xc000f03c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,
0x80007800,0x780,0x3c07c000,0x7800001e,0x7c0f078,0xf1e03c0,0x780780,0xf000,0x1e07801f,0x3e,0xf000,0xf0003c0,0x781e003,0xcf1c7800,
0x3cf80000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,0x0,0x0,0x3ffffc01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,
0x80003c00,0xf000,0x7ff8000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3fff0,0x3c000,0x3c003c0,0xf81f003,
0xc3b87800,0xf80000f,0x1e00001,0xf0000ff0,0xf0,0xff,0xf03fff,0x80000000,0x3fff8001,0xfff1ff00,0x0,0xf000000,0x0,0x18,0xc0,
0x0,0x380000,0x7c0,0x3c00003,0xc00f001f,0xff1ff000,0xf80007ff,0xc7fc3ffe,0x3fe00000,0x0,0x0,0x0,0x1ff000,0x7ffffe1f,0xffffff00,
0x0,0x7f,0xfe000000,0x780f007f,0xff1fff1f,0xf0001f00,0x1e000,0x0,0x780001,0xe0180000,0xf000001c,0xe00fff,0xffc00000,0x7c00,
0xf0000000,0x31c0001,0x80ffff80,0x3e078000,0x1e00,0x7ff80183,0x81c0c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x3f00000,
0x0,0x7f,0xff00001e,0x7c7ff03,0xc03ff8fe,0x1ffc0f0,0x7e0000,0x7800f00,0x3c007801,0xe003c00f,0x1e0078,0xf003c0,0x7803c07,0x8003c000,
0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,
0xf0001e0,0x78000f00,0x3fc001e,0x7803c0f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e007f,0xf03fe0,0x7ffff80,0x3ffffc01,
0xffffe00f,0xffff007f,0xfff803ff,0xffc07fff,0xffc0f000,0x1fffff,0xc0fffffe,0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,
0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e07,0xfffffe1e,0x707803c,0x3c01e0,0x1e00f00,0xf007800,
0x7800f01e,0x1e0007,0x803c0780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x30f81f00,0xffe1e00f,0x87800000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,
0x7,0x80001e00,0x3c0001e,0x1f8,0x7,0x83c00780,0x1e0,0x7c000f00,0xf8001e,0x3c001,0xfc1e0000,0x0,0x7fe0,0x0,0xffc,0x3c000,0x781e0070,
0x3ffff803,0xc000783c,0x1e0,0x3c03c0,0x1e00,0x3c000,0x781e0007,0x80007800,0x780,0x3c07c000,0x7800001e,0x380f078,0xf1e03c0,
0x780780,0xf000,0x1e07800f,0x8000001e,0xf000,0xf0003c0,0x3c3c003,0xcf1e7800,0x7c780000,0x7800000f,0x8000003c,0xf0,0x1e0,0x0,
0x0,0x0,0x7f003c01,0xe000780f,0x1e0,0x780fff,0xffe0003c,0x3c000,0xf0078007,0x80003c00,0xf000,0x7f7c000,0x1e0000f,0x3c0f01e,
0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfff8,0x3c000,0x3c003c0,0x781e003,0xc3b87800,0x1fc00007,0x83e00003,0xe0000ff8,0xf0,
0x1ff,0xc007fe,0x0,0x7fff8001,0xffe3ff00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x3c0,0x7800003,0xc00f001f,0xfe3ff000,0xf80007ff,
0x8ffc3ffc,0x7fe00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x1f,0xff000000,0x3c0f007f,0xff1ffe3f,0xf0003e00,0x1e000,0x0,0x780001,
0xe0180000,0xf000001e,0x1e00fff,0xffc00000,0x3f00,0xf0000000,0x31c0001,0x80ffff80,0x1f03c000,0x1e00,0x7ff80183,0x81c0c000,
0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x7f,0xff00003c,0xf87f007,0xc03f83ff,0x81fc01f0,0x7c0000,0x7ffff00,0x3ffff801,
0xffffc00f,0xfffe007f,0xfff003ff,0xff807fff,0x8003c000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
0xe0001e00,0x3c0f01e,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0x7fe001e,0xf003c0f0,0x3c0780,0x1e03c00,0xf01e000,
0x78000780,0x1ffffe,0xf00ff0,0xfe00780,0x7f003c03,0xf801e01f,0xc00f00fe,0x7807f0,0x3c0ffff,0xffc0f000,0x1fffff,0xc0fffffe,
0x7fffff0,0x3fffff80,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
0x1e,0xf07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783e,0x1e0007,0x801e0f80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1ff,0xffff8000,0x307c0801,0xe1f1e00f,0x87000000,
0x3c00,0x1e00,0x0,0x1e0000,0x0,0x7ff80000,0xf,0x1e00,0x3c0001e,0x3f0,0x7,0x83fffffc,0x1e0,0x7c000f00,0xf0001e,0x3c000,0x3e0000,
0x0,0x1ffc,0x1fffff,0xf0007ff0,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x3c000,0x781e0007,0x80007800,
0x780,0x3c03e000,0x7800001e,0xf078,0x79e03c0,0x780780,0xf000,0x1e078007,0x8000000f,0xf000,0xf0003c0,0x3c3c001,0xee0ef000,
0xf87c0000,0x7800001f,0x3c,0x78,0x1e0,0x0,0x0,0x0,0x7c003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
0xf000,0x7e3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x1ffc,0x3c000,0x3c003c0,0x781e003,0xe3b8f800,
0x1fc00007,0x83c00007,0xc00000fc,0xf0,0x3e0,0x8001f8,0x0,0x7800000,0xffc7fe00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,
0xf000003,0xc00f000f,0xfc7fe001,0xf00003ff,0x1ff81ff8,0xffc00000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x3,0xff800000,0x1e0f0078,
0xffc7f,0xe0007c00,0x1e000,0x0,0x780001,0xe0180000,0xf000000e,0x1c00007,0x80000000,0x1f81,0xe0000000,0x38e0003,0x80000000,
0xf81f000,0x1e00,0x7ff801c3,0x80e1c000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf8,0x1f070007,0xc03803ff,0xc1c001f0,
0xf80000,0xfffff00,0x7ffff803,0xffffc01f,0xfffe00ff,0xfff007ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,
0xf00000,0x7800000,0x3c000001,0xe0001e00,0x780f00f,0x3c078000,0xf03c0007,0x81e0003c,0xf0001e0,0x78000f00,0xf9f001e,0xf003c0f0,
0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1ffffc,0xf003f8,0xf800780,0x7c003c03,0xe001e01f,0xf00f8,0x7807c0,0x3c0fc1e,0xf000,
0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,
0xf0003c0,0x78001e00,0x1e,0x1e07803c,0x3c01e0,0x1e00f00,0xf007800,0x7800783c,0x1e0007,0x801e0f00,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xffff8000,0x303c0001,
0xc071e007,0xcf000000,0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0xf,0xf00,0x780001e,0x7e0,0x7,0x83fffffc,0x1e0,0x7c000f00,0x1f0001e,
0x3c000,0x3c0000,0x0,0x3ff,0x801fffff,0xf003ff80,0x3c000,0x781e0070,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,
0x80007800,0x780,0x3c01f000,0x7800001e,0xf078,0x79e03c0,0xf00780,0xf000,0x3e078007,0xc000000f,0xf000,0xf0003c0,0x3c3c001,
0xee0ef000,0xf03e0000,0x7800003e,0x3c,0x78,0x1e0,0x0,0x0,0x0,0xf8003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,
0x80003c00,0xf000,0x7c3e000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0xfc,0x3c000,0x3c003c0,0x3c3e001,0xe7b8f000,
0x3fe00007,0xc7c0000f,0xc000003e,0xf0,0x7c0,0x0,0x0,0x7c00000,0x7fcffc00,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x0,0x1e0,0x1e000003,
0xc00f0007,0xfcffc003,0xe00001ff,0x3ff00ff9,0xff800000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x1f800000,0xf0f0078,0x7fcff,
0xc000fc00,0x1e000,0x0,0x780001,0xe0180000,0xf000000f,0x87c00007,0x80000000,0xfe3,0xe0000000,0x18780c3,0x0,0x7c0f800,0x1e00,
0xc3,0x80e18000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x1f0,0x3e00000f,0xc0000303,0xe00003f0,0xf00000,0xfffff80,
0x7ffffc03,0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,
0x3c000001,0xe0001e00,0x780f00f,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1f0f801f,0xe00780f0,0x3c0780,0x1e03c00,
0xf01e000,0x78000780,0x1ffff8,0xf000f8,0x1f000780,0xf8003c07,0xc001e03e,0xf01f0,0x780f80,0x3c1f01e,0xf000,0x1e0000,0xf00000,
0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,
0x1e,0x3c07803c,0x3c01e0,0x1e00f00,0xf007800,0x78007c7c,0x1e0007,0x801f1f00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x81c00000,0x303c0003,0x8039e003,0xef000000,
0x3c00,0x1e00,0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0xfc0,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,
0x0,0x7f,0xe01fffff,0xf00ffc00,0x3c000,0x781f00f0,0x7ffffc03,0xc000781e,0x1e0,0x7803c0,0x1e00,0x1e000,0x781e0007,0x80007800,
0x780,0x3c01f000,0x7800001e,0xf078,0x7de01e0,0xf00780,0x7800,0x3c078003,0xc000000f,0xf000,0xf0003c0,0x3e7c001,0xee0ef001,
0xf01e0000,0x7800003e,0x3c,0x3c,0x1e0,0x0,0x0,0x0,0xf0003c01,0xe000780f,0x1e0,0x780f00,0x3c,0x3c000,0xf0078007,0x80003c00,
0xf000,0x781f000,0x1e0000f,0x3c0f01e,0x1e03c0,0xf00780,0x1e0f000,0x3c003c00,0x3e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0x7df00003,
0xc780000f,0x8000003e,0xf0,0x780,0x0,0x0,0x3c00000,0x3fcff800,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x1f00fc,0x1e0,0x1e000001,
0xe00f0003,0xfcff8003,0xe00000ff,0x3fe007f9,0xff000000,0x0,0x0,0x0,0x1ff000,0x0,0x0,0x0,0x0,0x7c00000,0xf0f0078,0x3fcff,0x8000f800,
0x1e000,0x0,0x780001,0xe0180000,0xf000001f,0xffe00007,0x8000003c,0x7ff,0xc0000000,0x1c3ffc7,0x0,0x3e07c00,0x1e00,0xe3,0x80738000,
0x0,0x78,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0x3e0,0x7c00001d,0xc0000001,0xe0000770,0x1f00000,0xfffff80,0x7ffffc03,
0xffffe01f,0xffff00ff,0xfff807ff,0xffc07fff,0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,
0xe0001e00,0x780f00f,0x3c03c001,0xe01e000f,0xf00078,0x78003c0,0x3c001e00,0x3e07c01f,0xc00780f0,0x3c0780,0x1e03c00,0xf01e000,
0x78000780,0x1fffc0,0xf0007c,0x1e000780,0xf0003c07,0x8001e03c,0xf01e0,0x780f00,0x3c1e01e,0xf000,0x1e0000,0xf00000,0x7800000,
0x3c000000,0x780000,0x3c00000,0x1e000000,0xf0001e00,0x7803c00,0x3c078001,0xe03c000f,0x1e00078,0xf0003c0,0x78001e00,0x1e,0x7807803c,
0x3c01e0,0x1e00f00,0xf007800,0x78003c78,0x1e0007,0x800f1e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x83c00000,0x303c0003,0x8039e001,0xee000000,0x1e00,0x3c00,
0x0,0x1e0000,0x0,0x0,0x1e,0xf00,0x780001e,0x1f80,0x7,0x83fffffc,0x1e0,0x3c000f00,0x1e0001e,0x3c000,0x3c0000,0x0,0x1f,0xfc1fffff,
0xf07ff000,0x0,0x780f00f0,0x78003c03,0xc000781e,0x1e0,0xf803c0,0x1e00,0x1e000,0x781e0007,0x80007800,0x780,0x3c00f800,0x7800001e,
0xf078,0x3de01e0,0xf00780,0x7800,0x3c078003,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfe0ff003,0xe01f0000,0x7800007c,0x3c,0x3c,
0x1e0,0x0,0x0,0x0,0xf0007c01,0xe000f80f,0x800001e0,0xf80f00,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x780f800,0x1e0000f,
0x3c0f01e,0x1e03c0,0x1f00780,0x3e0f000,0x7c003c00,0x1e,0x3c000,0x3c003c0,0x3c3c001,0xe71cf000,0xf8f80003,0xe780001f,0x1e,
0xf0,0x780,0x0,0x0,0x3c00000,0x1ffff000,0x0,0x1e000000,0x0,0x18,0xc0,0x0,0x3bc1de,0x1e0,0xf000001,0xe00f0001,0xffff0007,0xc000007f,
0xffc003ff,0xfe000000,0x0,0x0,0x0,0xfe000,0x0,0x0,0x0,0x0,0x3c00000,0x1e0f0078,0x1ffff,0x1f000,0x1e000,0x0,0x780000,0xf0180000,
0xf000001f,0xfff00007,0x8000003c,0x1ff,0x80000000,0xe0ff0e,0x0,0x1f03e00,0x1e00,0x70,0x70000,0x0,0x78,0x0,0x0,0x0,0x3c003c0,
0xe1c00,0x0,0x0,0x0,0x7c0,0xf8000019,0xc0000000,0xe0000670,0x1e00000,0xf000780,0x78003c03,0xc001e01e,0xf00f0,0x780780,0x3c0f807,
0x8001e000,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf80f007,0xbc03c001,0xe01e000f,
0xf00078,0x78003c0,0x3c001e00,0x7c03e00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,
0xf0007c07,0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0xf800,0x1e0000,0xf00000,0x7800000,0x3c000000,0x780000,0x3c00000,0x1e000000,
0xf0001e00,0x7803c00,0x3c078003,0xe03c001f,0x1e000f8,0xf0007c0,0x78003e00,0x1f8001f,0xf00f803c,0x3c01e0,0x1e00f00,0xf007800,
0x78003e78,0x1e000f,0x800f9e00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf,0x3c00000,0x303c0003,0x8039f001,0xfe000000,0x1e00,0x3c00,0x0,0x1e0000,0x0,0x0,0x3c,0xf00,
0x780001e,0x3f00,0x7,0x80000780,0x3e0,0x3e000f00,0x3c0001e,0x3c000,0x7c0000,0x0,0x3,0xfe000000,0xff8000,0x0,0x3c0f81f0,0xf0001e03,
0xc000780f,0x1e0,0xf003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x780,0x3c007c00,0x7800001e,0xf078,0x3de01e0,0xf00780,0x7800,
0x3c078001,0xe000000f,0xf000,0xf0003c0,0x1e78001,0xfc07f003,0xe00f0000,0x78000078,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
0xf000f007,0x800000f0,0xf80780,0x3c,0x1e001,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78001,0xe71df000,0xf8f80001,0xef80003e,0x1e,0xf0,0x780,0x0,0x0,0x3c00000,
0xfffe000,0x0,0x3e000000,0x0,0x18,0x7fff,0xc0000000,0x60c306,0x1e0,0x7800001,0xe00f0000,0xfffe0007,0x8000003f,0xff8001ff,
0xfc000000,0x0,0x0,0x0,0x7c000,0x0,0x0,0x0,0x0,0x3c00000,0x3c0f0078,0xfffe,0x3e000,0x1e000,0x0,0x780000,0xf0180000,0xf000003c,
0xfcf80007,0x8000003c,0x7f,0x0,0x70001c,0x0,0xf81f00,0x0,0x38,0xe0000,0x0,0x0,0x0,0x0,0x0,0x3c003c0,0xe1c00,0x0,0x0,0x0,0xf81,
0xf0000039,0xc0000000,0xe0000e70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,0x8000f000,0x78000,
0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0xf00f007,0xbc03c001,0xe01e000f,0xf00078,0x78003c0,
0x3c001e00,0xf801f00f,0x800780f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,0x8003e03c,
0x1f01e0,0xf80f00,0x7c1e01e,0x7800,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,0xf003c00,
0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xe00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef8,0x1f000f,
0x7be00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0xf,0x3c00000,0x307c0003,0x8038f000,0xfc000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e00003c,0x780,0xf00001e,
0x7e00,0xf,0x80000780,0x3c0,0x3e001e00,0x3c0001f,0x7c000,0x780007,0xe000003f,0x0,0xfe000000,0xfe0000,0x0,0x3c07c3f0,0xf0001e03,
0xc000f80f,0x800001e0,0x1f003c0,0x1e00,0xf000,0x781e0007,0x80007800,0x4000f80,0x3c003c00,0x7800001e,0xf078,0x1fe01f0,0x1f00780,
0x7c00,0x7c078001,0xf000001f,0xf000,0xf0003c0,0x1e78001,0xfc07f007,0xc00f8000,0x780000f8,0x3c,0x1e,0x1e0,0x0,0x0,0x0,0xf0007c01,
0xf000f007,0xc00000f0,0xf80780,0x3c,0x1f003,0xf0078007,0x80003c00,0xf000,0x7807c00,0x1e0000f,0x3c0f01e,0x1e01e0,0x1e007c0,
0x3c07800,0x7c003c00,0x1e,0x3c000,0x3c007c0,0x1e78000,0xfe0fe001,0xf07c0001,0xef00007c,0x1e,0xf0,0x780,0x0,0x0,0x1e00000,
0x7cfc000,0xfc00000,0x3c00000f,0xc3f00000,0x18,0x7fff,0xc0000000,0x406303,0x3e0,0x3c00001,0xf00f0000,0x7cfc000f,0x8000001f,
0x3f0000f9,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x780700f8,0x7cfc,0x7c000,0x1e000,0x0,0x780000,0xf8180000,
0xf0000070,0x3c0007,0x8000003c,0x3f,0x80000000,0x3c0078,0x0,0x780f00,0x0,0x1e,0x3c0000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,0xe1c00,
0x0,0x0,0x0,0xf01,0xe0000071,0xc0000000,0xe0001c70,0x1e00000,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
0x8000f800,0x78000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x1f00f003,0xfc03e003,0xe01f001f,
0xf800f8,0x7c007c0,0x3e003e01,0xf000f80f,0xf00f0,0x3c0780,0x1e03c00,0xf01e000,0x78000780,0x1e0000,0xf0003c,0x1e000f80,0xf0007c07,
0x8003e03c,0x1f01e0,0xf80f00,0x7c1e01e,0x7c00,0xf0000,0x780000,0x3c00000,0x1e000000,0x780000,0x3c00000,0x1e000000,0xf0000f00,
0xf003c00,0x3c03c003,0xc01e001e,0xf000f0,0x7800780,0x3c003c00,0x1f8000f,0xc00f003c,0x7c01e0,0x3e00f00,0x1f007800,0xf8001ef0,
0x1f000f,0x7bc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x780000,0xf,0x3800040,0x30780003,0x8038f800,0x78000000,0x1e00,0x3c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
0x780,0x1f00001e,0xfc00,0x20001f,0x780,0x80007c0,0x1f001e00,0x7c0000f,0x78000,0xf80007,0xe000003f,0x0,0x1e000000,0xf00000,
0x3c000,0x3c03fff0,0xf0001e03,0xc001f007,0x800101e0,0x7e003c0,0x1e00,0x7800,0x781e0007,0x80007800,0x6000f00,0x3c003e00,0x7800001e,
0xf078,0x1fe00f0,0x1e00780,0x3c00,0x78078000,0xf020001e,0xf000,0x7800780,0xff0001,0xfc07f00f,0x8007c000,0x780001f0,0x3c,0xf,
0x1e0,0x0,0x0,0x0,0xf800fc01,0xf801f007,0xc00100f8,0x1f807c0,0x40003c,0xf807,0xf0078007,0x80003c00,0xf000,0x7803e00,0x1f0000f,
0x3c0f01e,0x1e01f0,0x3e007e0,0x7c07c00,0xfc003c00,0x1e,0x3e000,0x3e007c0,0x1ff8000,0xfe0fe003,0xe03e0001,0xff0000fc,0x1e,
0xf0,0x780,0x0,0x0,0x1f00080,0x3cf8000,0xfc00000,0x3c00001f,0x83f00000,0x18,0xc0,0x0,0xc06203,0x40003c0,0x1c00000,0xf80f0000,
0x3cf8001f,0xf,0x3e000079,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x700780fc,0x3cf8,0xfc000,0x1e000,0x0,0x780000,
0x7c180000,0xf0000020,0x100007,0x8000003c,0xf,0x80000000,0x1f01f0,0x0,0x380700,0x0,0xf,0x80f80000,0x0,0x0,0x0,0x0,0x0,0x3e007c0,
0xe1c00,0x0,0x0,0x0,0xe01,0xc0000071,0xc0000001,0xc0001c70,0x1e00040,0x1e0003c0,0xf0001e07,0x8000f03c,0x781e0,0x3c0f00,0x1e0f007,
0x80007800,0x10078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e00,0x7e00f003,0xfc01e003,0xc00f001e,
0x7800f0,0x3c00780,0x1e003c00,0xe000700f,0x800f0078,0x7803c0,0x3c01e00,0x1e00f000,0xf0000780,0x1e0000,0xf0003c,0x1f001f80,
0xf800fc07,0xc007e03e,0x3f01f0,0x1f80f80,0xfc1e01f,0x7c00,0x100f8000,0x807c0004,0x3e00020,0x1f000100,0x780000,0x3c00000,0x1e000000,
0xf0000f80,0x1f003c00,0x3c03e007,0xc01f003e,0xf801f0,0x7c00f80,0x3e007c00,0x1f8000f,0x801f003e,0x7c01f0,0x3e00f80,0x1f007c00,
0xf8001ff0,0x1f801f,0x7fc00,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0xf,0x7800078,0x31f80001,0xc070fc00,0xfc000000,0x1e00,0x7c00,0x0,0x1e0000,0xfc0000,0x0,0x7e000078,
0x7c0,0x1f00001e,0x1f000,0x38003f,0x780,0xe000f80,0x1f803e00,0x780000f,0x800f8000,0x1f00007,0xe000003f,0x0,0x2000000,0x800000,
0x3c000,0x3e01ff71,0xf0001f03,0xc007f007,0xc00301e0,0x1fc003c0,0x1e00,0x7c00,0x781e0007,0x80007800,0x7801f00,0x3c001f00,0x7800001e,
0xf078,0xfe00f8,0x3e00780,0x3e00,0xf8078000,0xf838003e,0xf000,0x7c00f80,0xff0000,0xfc07e00f,0x8003c000,0x780001e0,0x3c,0xf,
0x1e0,0x0,0x0,0x0,0xf801fc01,0xfc03e003,0xe003007c,0x3f803e0,0x1c0003c,0xfc0f,0xf0078007,0x80003c00,0xf000,0x7801f00,0xf8000f,
0x3c0f01e,0x1e00f8,0x7c007f0,0xf803e01,0xfc003c00,0x8003e,0x1f000,0x1e00fc0,0xff0000,0xfe0fe007,0xc01f0000,0xfe0000f8,0x1e,
0xf0,0x780,0x0,0x0,0xf80180,0x1cf0000,0x1f800000,0x3c00001f,0x83e00000,0x18,0xc0,0x0,0xc06203,0x70007c0,0xe00000,0x7e0f0000,
0x1cf0001e,0x7,0x3c000039,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x100,0x7c00000,0xe00780fc,0x2001cf0,0xf8000,0x1e000,0x0,
0x780000,0x7e182000,0xf0000000,0x7,0x8000003c,0x7,0xc0000000,0x7ffc0,0x0,0x180300,0x0,0x3,0xffe00000,0x0,0x0,0x0,0x0,0x0,
0x3f00fc0,0xe1c00,0x0,0x0,0x0,0xc01,0x800000e1,0xc0000003,0xc0003870,0x1f001c0,0x3e0003e1,0xf0001f0f,0x8000f87c,0x7c3e0,0x3e1f00,
0x1f1e007,0x80007c00,0x30078000,0x3c0000,0x1e00000,0xf000000,0xf00000,0x7800000,0x3c000001,0xe0001e03,0xfc00f001,0xfc01f007,
0xc00f803e,0x7c01f0,0x3e00f80,0x1f007c00,0x4000201f,0xc01f007c,0xf803e0,0x7c01f00,0x3e00f801,0xf0000780,0x1e0000,0xf0007c,
0x1f003f80,0xf801fc07,0xc00fe03e,0x7f01f0,0x3f80f80,0x1fc1f03f,0x803e00,0x3007c003,0x803e001c,0x1f000e0,0xf800700,0x780000,
0x3c00000,0x1e000000,0xf00007c0,0x3e003c00,0x3c01f00f,0x800f807c,0x7c03e0,0x3e01f00,0x1f00f800,0x1f80007,0xc03e001e,0xfc00f0,
0x7e00780,0x3f003c01,0xf8000fe0,0x1fc03e,0x3f800,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,0xfff00001,0xe0f07f03,0xfe000000,0xf00,0x7800,0x0,
0x1e0000,0xfc0000,0x0,0x7e0000f0,0x3f0,0x7e000fff,0xfc03ffff,0xf83f00fe,0x780,0xfc03f80,0xfc0fc00,0xf800007,0xe03f0018,0x7e00007,
0xe000003f,0x0,0x0,0x0,0x3c000,0x1e007c71,0xe0000f03,0xffffe003,0xf01f01ff,0xff8003ff,0xffe01e00,0x3f01,0xf81e0007,0x803ffff0,
0x7e03f00,0x3c000f00,0x7ffffe1e,0xf078,0xfe007e,0xfc00780,0x1f83,0xf0078000,0x783f00fe,0xf000,0x3f03f00,0xff0000,0xfc07e01f,
0x3e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7e07fc01,0xfe07e001,0xf80f007e,0x7f801f8,0xfc0003c,0x7ffe,0xf0078007,
0x807ffffe,0xf000,0x7801f00,0xfff00f,0x3c0f01e,0x1e00fc,0xfc007f8,0x1f803f03,0xfc003c00,0xf80fc,0x1fff0,0x1f83fc0,0xff0000,
0xfc07e007,0xc01f0000,0xfe0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfe0780,0xfe0000,0x1f000000,0x3c00001f,0x7c00e03,0x81c00018,
0xc0,0x0,0x406203,0x7e01fc0,0x700000,0x7fffff80,0xfe0003f,0xffffc003,0xf800001f,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f0,
0x1f800001,0xc007c1fe,0x6000fe0,0x1ffffe,0x1e000,0x0,0x780000,0x3f98e03f,0xffff8000,0x7,0x8000003c,0x7,0xc0000000,0xfe00,
0x0,0x80100,0x0,0x0,0x7f000000,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3f83fe8,0xe1c00,0x0,0x0,0x0,0x801,0xc1,0xc0000007,0x80003070,
0xfc0fc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc03f01,0xf007ffff,0xc03ffffe,0x1fffff0,0xfffff80,0x7fffe003,
0xffff001f,0xfff800ff,0xffc01fff,0xf800f001,0xfc00fc1f,0x8007e0fc,0x3f07e0,0x1f83f00,0xfc1f800,0x1f,0xf07e003f,0x3f001f8,
0x1f800fc0,0xfc007e07,0xe0000780,0x1e0000,0xf301f8,0xfc0ff80,0x7e07fc03,0xf03fe01f,0x81ff00fc,0xff807e0,0x7fc0f87f,0x81801f80,
0xf003f01f,0x801f80fc,0xfc07e0,0x7e03f00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff807e0,0x7e003c00,0x3c01f81f,0x800fc0fc,0x7e07e0,
0x3f03f00,0x1f81f800,0x1f8000f,0xe07e001f,0x83fc00fc,0x1fe007e0,0xff003f07,0xf8000fe0,0x1fe07e,0x3f800,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x780007f,
0xffe00000,0xffe03fff,0xdf000000,0xf00,0x7800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0x1ff,0xfc000fff,0xfc03ffff,0xf83ffffc,0x780,
0xfffff00,0x7fff800,0xf000007,0xffff001f,0xffe00007,0xe000003f,0x0,0x0,0x0,0x3c000,0x1e000001,0xe0000f03,0xffffc001,0xffff01ff,
0xff0003ff,0xffe01e00,0x1fff,0xf81e0007,0x803ffff0,0x7fffe00,0x3c000f80,0x7ffffe1e,0xf078,0xfe003f,0xff800780,0xfff,0xf0078000,
0x7c3ffffc,0xf000,0x3ffff00,0xff0000,0xf803e01e,0x1e000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x7fffbc01,0xffffc000,
0xffff003f,0xfff800ff,0xffc0003c,0x3ffe,0xf0078007,0x807ffffe,0xf000,0x7800f80,0x7ff00f,0x3c0f01e,0x1e007f,0xff8007ff,0xff001fff,
0xbc003c00,0xffffc,0x1fff0,0x1fffbc0,0xff0000,0x7c07c00f,0x800f8000,0x7e0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x7fff80,0x7c0000,
0x1f000000,0x3c00001e,0x7c00f07,0xc1e00018,0xc0,0x0,0x60e303,0x7ffff80,0x380000,0x3fffff80,0x7c0003f,0xffffc001,0xf000000f,
0x80000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff800003,0x8003ffff,0xfe0007c0,0x1ffffe,0x1e000,0x0,0x780000,0x1fffe03f,0xffff8000,
0x7,0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3fffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x1c1,
0xc000000f,0x7070,0x7fffc0,0x3c0001e1,0xe0000f0f,0x7878,0x3c3c0,0x1e1e00,0xf1e007,0xffc01fff,0xf007ffff,0xc03ffffe,0x1fffff0,
0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xf000f001,0xfc007fff,0x3fff8,0x1fffc0,0xfffe00,0x7fff000,0x3b,0xfffc003f,
0xfff001ff,0xff800fff,0xfc007fff,0xe0000780,0x1e0000,0xf3fff8,0xffff780,0x7fffbc03,0xfffde01f,0xffef00ff,0xff7807ff,0xfbc0ffff,
0xff800fff,0xf001ffff,0x800ffffc,0x7fffe0,0x3ffff00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff803ff,0xfc003c00,0x3c00ffff,0x7fff8,
0x3fffc0,0x1fffe00,0xffff000,0x1f,0xfffc001f,0xffbc00ff,0xfde007ff,0xef003fff,0x780007e0,0x1ffffc,0x1f800,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0x700003f,
0xffc00000,0x7fc01fff,0x9f800000,0xf80,0xf800,0x0,0x0,0xfc0000,0x0,0x7e0000f0,0xff,0xf8000fff,0xfc03ffff,0xf83ffff8,0x780,
0xffffe00,0x7fff000,0xf000003,0xfffe001f,0xffc00007,0xe000003f,0x0,0x0,0x0,0x3c000,0xf000003,0xe0000f83,0xffff0000,0xffff01ff,
0xfc0003ff,0xffe01e00,0xfff,0xf01e0007,0x803ffff0,0x7fffc00,0x3c0007c0,0x7ffffe1e,0xf078,0x7e003f,0xff000780,0x7ff,0xe0078000,
0x3c3ffff8,0xf000,0x1fffe00,0x7e0000,0xf803e03e,0x1f000,0x780003ff,0xfffc003c,0x7,0x800001e0,0x0,0x0,0x0,0x3fff3c01,0xefff8000,
0x7ffe001f,0xff78007f,0xff80003c,0x1ffc,0xf0078007,0x807ffffe,0xf000,0x78007c0,0x3ff00f,0x3c0f01e,0x1e003f,0xff0007bf,0xfe000fff,
0xbc003c00,0xffff8,0xfff0,0xfff3c0,0x7e0000,0x7c07c01f,0x7c000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0x3fff80,0x380000,
0x3e000000,0x7c00003e,0x7801f07,0xc1e00018,0xc0,0x0,0x39c1ce,0x7ffff00,0x1c0000,0xfffff80,0x380003f,0xffffc000,0xe0000007,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0xff000007,0x1ffcf,0xfe000380,0x1ffffe,0x1e000,0x0,0x780000,0xfffe03f,0xffff8000,0x7,
0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dffdf8,0xe1c00,0x0,0x0,0x0,0x0,0x381,
0xc000001e,0xe070,0x7fff80,0x7c0001f3,0xe0000f9f,0x7cf8,0x3e7c0,0x1f3e00,0xfbe007,0xffc00fff,0xf007ffff,0xc03ffffe,0x1fffff0,
0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01fff,0xc000f000,0xfc007ffe,0x3fff0,0x1fff80,0xfffc00,0x7ffe000,0x79,0xfff8001f,
0xffe000ff,0xff0007ff,0xf8003fff,0xc0000780,0x1e0000,0xf3fff0,0x7ffe780,0x3fff3c01,0xfff9e00f,0xffcf007f,0xfe7803ff,0xf3c07ff3,
0xff8007ff,0xe000ffff,0x7fff8,0x3fffc0,0x1fffe00,0xfffffc07,0xffffe03f,0xffff01ff,0xfff801ff,0xf8003c00,0x3c007ffe,0x3fff0,
0x1fff80,0xfffc00,0x7ffe000,0x1d,0xfff8000f,0xff3c007f,0xf9e003ff,0xcf001ffe,0x780007c0,0x1efff8,0x1f000,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780000,0x1e,0xf000003,
0xfe000000,0x1f000fff,0xfc00000,0x780,0xf000,0x0,0x0,0xf80000,0x0,0x7e0001e0,0x7f,0xf0000fff,0xfc03ffff,0xf81ffff0,0x780,
0x7fff800,0x1ffe000,0x1f000000,0xfff8001f,0xff000007,0xe000003e,0x0,0x0,0x0,0x3c000,0xf800003,0xc0000783,0xfff80000,0x3ffe01ff,
0xe00003ff,0xffe01e00,0x7ff,0xc01e0007,0x803ffff0,0x3fff800,0x3c0003c0,0x7ffffe1e,0xf078,0x7e000f,0xfe000780,0x3ff,0xc0078000,
0x3e1fffe0,0xf000,0x7ff800,0x7e0000,0xf803e07c,0xf800,0x780003ff,0xfffc003c,0x3,0xc00001e0,0x0,0x0,0x0,0xffe3c01,0xe7ff0000,
0x3ffc000f,0xfe78003f,0xfe00003c,0x7f0,0xf0078007,0x807ffffe,0xf000,0x78003e0,0xff00f,0x3c0f01e,0x1e001f,0xfe00079f,0xfc0007ff,
0x3c003c00,0x7ffe0,0x1ff0,0x7fe3c0,0x7e0000,0x7c07c03e,0x3e000,0x7c0001ff,0xffe0001e,0xf0,0x780,0x0,0x0,0xfff00,0x100000,
0x3e000000,0x7800003c,0xf800f07,0xc1e00018,0xc0,0x0,0x1f80fc,0x3fffc00,0xc0000,0x3ffff80,0x100003f,0xffffc000,0x40000002,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xff,0xfc000006,0xff87,0xfc000100,0x1ffffe,0x1e000,0x0,0x780000,0x3ffc03f,0xffff8000,0x7,
0x8000003c,0x3,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ffff,0xfe000000,0x0,0x0,0x3dff9f8,0xe1c00,0x0,0x0,0x0,0x0,0x3ff,
0xf800003c,0xfffe,0x1ffe00,0x780000f3,0xc000079e,0x3cf0,0x1e780,0xf3c00,0x7bc007,0xffc003ff,0xe007ffff,0xc03ffffe,0x1fffff0,
0xfffff80,0x7fffe003,0xffff001f,0xfff800ff,0xffc01ffc,0xf000,0xfc001ffc,0xffe0,0x7ff00,0x3ff800,0x1ffc000,0x70,0xfff00007,
0xff80003f,0xfc0001ff,0xe0000fff,0x780,0x1e0000,0xf3ffe0,0x1ffc780,0xffe3c00,0x7ff1e003,0xff8f001f,0xfc7800ff,0xe3c03fe1,
0xff0003ff,0xc0007ffc,0x3ffe0,0x1fff00,0xfff800,0xfffffc07,0xffffe03f,0xffff01ff,0xfff800ff,0xf0003c00,0x3c003ffc,0x1ffe0,
0xfff00,0x7ff800,0x3ffc000,0x38,0xfff00007,0xfe3c003f,0xf1e001ff,0x8f000ffc,0x780007c0,0x1e7ff0,0x1f000,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
0x1fc,0x0,0x780,0xf000,0x0,0x0,0x1f80000,0x0,0x1e0,0x1f,0xc0000000,0x0,0x1ff80,0x0,0xffc000,0x7f8000,0x0,0x3fe00007,0xfc000000,
0x7e,0x0,0x0,0x0,0x0,0x7c00000,0x0,0x0,0xff00000,0x0,0x0,0xfe,0x0,0x0,0x3fc000,0x0,0x0,0x0,0x3,0xf8000000,0xff,0xc0000000,
0x1ff00,0x0,0x1fe000,0x0,0x0,0x0,0x0,0x3c,0x3,0xc00001e0,0x0,0x0,0x0,0x3f80000,0x1fc0000,0x7f00003,0xf8000007,0xf0000000,
0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x7,0xf8000787,0xf00001fc,0x3c000000,0x7f80,0x0,0x1f8000,0x0,0x0,0x0,0x7c000000,0x1e,
0xf0,0x780,0x0,0x0,0x3fc00,0x0,0x3c000000,0x7800003c,0xf000601,0xc00018,0xc0,0x0,0x0,0x3fe000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0xf,0xf0000000,0x7e03,0xf0000000,0x0,0x0,0x0,0x0,0xfe0000,0x0,0x0,0x3c,0x2007,0x80000000,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c7e0f0,0xe1c00,0x0,0x3800000,0x0,0x0,0x3ff,0xf8000078,0xfffe,0x7f800,0x0,0x0,0x0,0x0,
0x0,0x0,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f0,0x3f80,0x1fc00,0xfe000,0x7f0000,0x70,0x3fc00001,0xfe00000f,0xf000007f,
0x800003fc,0x0,0x0,0xff00,0x7f0000,0x3f80000,0x1fc00000,0xfe000007,0xf000003f,0x80001f80,0xfc00007f,0xfe0,0x7f00,0x3f800,
0x1fc000,0x0,0x0,0x0,0x3f,0xc0000000,0xff0,0x7f80,0x3fc00,0x1fe000,0xff0000,0x78,0x3fc00001,0xf800000f,0xc000007e,0x3f0,0x7c0,
0x1e1fc0,0x1f000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0xe0000000,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,0x1e,0xf0,0x780,0x0,0x0,0x0,0x0,0x3c000000,0x78000078,0xf000000,0x18,0xc0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3c0f,0x80000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0x1800000,0x0,0x0,0x3ff,0xf80000f0,0xfffe,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0xc,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30,0x0,0x0,0x0,0x0,0x780,0x1e0000,0x1e000,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,
0x0,0x0,0x3c0,0x1e000,0x0,0x0,0x1f00000,0x0,0x3c0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0x1f80000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3,0xf0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x1,0xe00001e0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf8000000,
0x1f,0xf0,0xf80,0x0,0x0,0x0,0x0,0x78000000,0xf8000078,0x1e000000,0x8,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3fff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x3c00000,0xe1c00,0x0,0x1c00000,0x0,0x0,0x1,0xc00001e0,0x70,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf80,0x1e0000,0x3e000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x3c000,0x0,0x0,0x1f00000,
0x0,0x780,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7c,0x0,0x0,0x0,0x0,0xfe0100,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0xf8000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0xf0007fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0xe0000000,
0x0,0xf000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x0,0xf0000000,0x1f,0x800000f0,0x1f80,0x0,0x0,0x0,0x0,
0x78000000,0xf0000070,0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x3ffe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,
0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0xf00,0x1e0000,0x3c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0x1e0,0x7c000,0x0,0x0,0x1e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x78,0x0,0x0,0x0,0x0,0x7fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x78000000,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x4003,0xe0000000,0x0,0x1f000,0x0,0x0,
0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x1,0xf0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0x70000001,0xf00000e0,
0x1c000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,
0x0,0x0,0x3c,0xff8,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0xe1c00,0x0,0xe00000,0x0,0x0,0x1,0xc00003ff,
0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1f00,0x1e0000,
0x7c000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,0xf0,0x78000,0x0,0x0,0x3e00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf8,0x0,
0x0,0x0,0x0,0x1fff80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x20000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,
0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x780f,0xc0000000,0x0,0x3e000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
0x0,0x0,0x0,0x0,0x3,0xe0000000,0xf,0xfc0000f0,0x3ff00,0x0,0x0,0x0,0x0,0xf0000103,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x21e00000,0x0,0x0,0x1,0xc00003ff,0xe0000070,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10f,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3e00,0x1e0000,0xf8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x30000000,0x0,0x0,
0xf8,0xf8000,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x1fe00,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3f,0xf0000000,0x7fe0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x7fff,0xc0000000,0x0,0x3ffe000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0xe0000000,0x7,0xfc0000f0,
0x3fe00,0x0,0x0,0x0,0x0,0x600001ff,0xe0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x180000,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,
0x3fe00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x7fe00,0x1e0000,0x1ff8000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fff,0x80000000,0x0,0x3ffc000,0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,
0x0,0x0,0x0,0x0,0x7f,0xc0000000,0x0,0xfc0000f0,0x3f000,0x0,0x0,0x0,0x0,0x1ff,0xc0000000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3fc00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fe,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7fc00,0x1e0000,0x1ff0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x3ffe,0x0,0x0,0x3ff8000,0x0,0x0,0x0,
0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7f,0x80000000,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x1ff,0x80000000,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x3f800000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fc,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f800,0x1e0000,0x1fe0000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x7f8,0x0,0x0,0x3fe0000,
0x0,0x0,0x0,0x0,0x780,0x0,0x3c000000,0x0,0x0,0x0,0x0,0x0,0x7e,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0xfe,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x3c00000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x7e000,0x1e0000,0x1f80000,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1fffffe0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xf0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0 };
// Definition of a 40x38 'danger' color logo.
const unsigned char logo40x38[4576] = {
177,200,200,200,3,123,123,0,36,200,200,200,1,123,123,0,2,255,255,0,1,189,189,189,1,0,0,0,34,200,200,200,
1,123,123,0,4,255,255,0,1,189,189,189,1,0,0,0,1,123,123,123,32,200,200,200,1,123,123,0,5,255,255,0,1,0,0,
0,2,123,123,123,30,200,200,200,1,123,123,0,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,29,200,200,200,
1,123,123,0,7,255,255,0,1,0,0,0,2,123,123,123,28,200,200,200,1,123,123,0,8,255,255,0,1,189,189,189,1,0,0,0,
2,123,123,123,27,200,200,200,1,123,123,0,9,255,255,0,1,0,0,0,2,123,123,123,26,200,200,200,1,123,123,0,10,255,
255,0,1,189,189,189,1,0,0,0,2,123,123,123,25,200,200,200,1,123,123,0,3,255,255,0,1,189,189,189,3,0,0,0,1,189,
189,189,3,255,255,0,1,0,0,0,2,123,123,123,24,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,3,255,255,0,1,189,
189,189,1,0,0,0,2,123,123,123,23,200,200,200,1,123,123,0,4,255,255,0,5,0,0,0,4,255,255,0,1,0,0,0,2,123,123,123,
22,200,200,200,1,123,123,0,5,255,255,0,5,0,0,0,4,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,21,200,200,200,
1,123,123,0,5,255,255,0,5,0,0,0,5,255,255,0,1,0,0,0,2,123,123,123,20,200,200,200,1,123,123,0,6,255,255,0,5,0,0,
0,5,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,19,200,200,200,1,123,123,0,6,255,255,0,1,123,123,0,3,0,0,0,1,
123,123,0,6,255,255,0,1,0,0,0,2,123,123,123,18,200,200,200,1,123,123,0,7,255,255,0,1,189,189,189,3,0,0,0,1,189,
189,189,6,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,17,200,200,200,1,123,123,0,8,255,255,0,3,0,0,0,8,255,255,
0,1,0,0,0,2,123,123,123,16,200,200,200,1,123,123,0,9,255,255,0,1,123,123,0,1,0,0,0,1,123,123,0,8,255,255,0,1,189,
189,189,1,0,0,0,2,123,123,123,15,200,200,200,1,123,123,0,9,255,255,0,1,189,189,189,1,0,0,0,1,189,189,189,9,255,255,
0,1,0,0,0,2,123,123,123,14,200,200,200,1,123,123,0,11,255,255,0,1,0,0,0,10,255,255,0,1,189,189,189,1,0,0,0,2,123,
123,123,13,200,200,200,1,123,123,0,23,255,255,0,1,0,0,0,2,123,123,123,12,200,200,200,1,123,123,0,11,255,255,0,1,189,
189,189,2,0,0,0,1,189,189,189,9,255,255,0,1,189,189,189,1,0,0,0,2,123,123,123,11,200,200,200,1,123,123,0,11,255,255,
0,4,0,0,0,10,255,255,0,1,0,0,0,2,123,123,123,10,200,200,200,1,123,123,0,12,255,255,0,4,0,0,0,10,255,255,0,1,189,189,
189,1,0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,12,255,255,0,1,189,189,189,2,0,0,0,1,189,189,189,11,255,255,0,1,
0,0,0,2,123,123,123,9,200,200,200,1,123,123,0,27,255,255,0,1,0,0,0,3,123,123,123,8,200,200,200,1,123,123,0,26,255,
255,0,1,189,189,189,1,0,0,0,3,123,123,123,9,200,200,200,1,123,123,0,24,255,255,0,1,189,189,189,1,0,0,0,4,123,123,
123,10,200,200,200,1,123,123,0,24,0,0,0,5,123,123,123,12,200,200,200,27,123,123,123,14,200,200,200,25,123,123,123,86,
200,200,200,91,49,124,118,124,71,32,124,95,49,56,114,52,82,121,0};
//! Display a warning message.
/**
\param format is a C-string describing the format of the message, as in <tt>std::printf()</tt>.
**/
inline void warn(const char *format, ...) {
if (cimg::exception_mode()>=1) {
char message[8192];
cimg_std::va_list ap;
va_start(ap,format);
cimg_std::vsprintf(message,format,ap);
va_end(ap);
#ifdef cimg_strict_warnings
throw CImgWarningException(message);
#else
cimg_std::fprintf(cimg_stdout,"\n%s# CImg Warning%s :\n%s\n",cimg::t_red,cimg::t_normal,message);
#endif
}
}
// Execute an external system command.
/**
\note This function is similar to <tt>std::system()</tt>
and is here because using the <tt>std::</tt> version on
Windows may open undesired consoles.
**/
inline int system(const char *const command, const char *const module_name=0) {
#if cimg_OS==2
PROCESS_INFORMATION pi;
STARTUPINFO si;
cimg_std::memset(&pi,0,sizeof(PROCESS_INFORMATION));
cimg_std::memset(&si,0,sizeof(STARTUPINFO));
GetStartupInfo(&si);
si.cb = sizeof(si);
si.wShowWindow = SW_HIDE;
si.dwFlags |= SW_HIDE;
const BOOL res = CreateProcess((LPCTSTR)module_name,(LPTSTR)command,0,0,FALSE,0,0,0,&si,&pi);
if (res) {
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
} else
#endif
return cimg_std::system(command);
return module_name?0:1;
}
//! Return a reference to a temporary variable of type T.
template<typename T>
inline T& temporary(const T&) {
static T temp;
return temp;
}
//! Exchange values of variables \p a and \p b.
template<typename T>
inline void swap(T& a, T& b) { T t = a; a = b; b = t; }
//! Exchange values of variables (\p a1,\p a2) and (\p b1,\p b2).
template<typename T1, typename T2>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2) {
cimg::swap(a1,b1); cimg::swap(a2,b2);
}
//! Exchange values of variables (\p a1,\p a2,\p a3) and (\p b1,\p b2,\p b3).
template<typename T1, typename T2, typename T3>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3) {
cimg::swap(a1,b1,a2,b2); cimg::swap(a3,b3);
}
//! Exchange values of variables (\p a1,\p a2,...,\p a4) and (\p b1,\p b2,...,\p b4).
template<typename T1, typename T2, typename T3, typename T4>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4) {
cimg::swap(a1,b1,a2,b2,a3,b3); cimg::swap(a4,b4);
}
//! Exchange values of variables (\p a1,\p a2,...,\p a5) and (\p b1,\p b2,...,\p b5).
template<typename T1, typename T2, typename T3, typename T4, typename T5>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5) {
cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4); cimg::swap(a5,b5);
}
//! Exchange values of variables (\p a1,\p a2,...,\p a6) and (\p b1,\p b2,...,\p b6).
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6) {
cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5); cimg::swap(a6,b6);
}
//! Exchange values of variables (\p a1,\p a2,...,\p a7) and (\p b1,\p b2,...,\p b7).
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
T7& a7, T7& b7) {
cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6); cimg::swap(a7,b7);
}
//! Exchange values of variables (\p a1,\p a2,...,\p a8) and (\p b1,\p b2,...,\p b8).
template<typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8>
inline void swap(T1& a1, T1& b1, T2& a2, T2& b2, T3& a3, T3& b3, T4& a4, T4& b4, T5& a5, T5& b5, T6& a6, T6& b6,
T7& a7, T7& b7, T8& a8, T8& b8) {
cimg::swap(a1,b1,a2,b2,a3,b3,a4,b4,a5,b5,a6,b6,a7,b7); cimg::swap(a8,b8);
}
//! Return the current endianness of the CPU.
/**
\return \c false for "Little Endian", \c true for "Big Endian".
**/
inline bool endianness() {
const int x = 1;
return ((unsigned char*)&x)[0]?false:true;
}
//! Invert endianness of a memory buffer.
template<typename T>
inline void invert_endianness(T* const buffer, const unsigned int size) {
if (size) switch (sizeof(T)) {
case 1 : break;
case 2 : { for (unsigned short *ptr = (unsigned short*)buffer+size; ptr>(unsigned short*)buffer; ) {
const unsigned short val = *(--ptr);
*ptr = (unsigned short)((val>>8)|((val<<8)));
}} break;
case 4 : { for (unsigned int *ptr = (unsigned int*)buffer+size; ptr>(unsigned int*)buffer; ) {
const unsigned int val = *(--ptr);
*ptr = (val>>24)|((val>>8)&0xff00)|((val<<8)&0xff0000)|(val<<24);
}} break;
default : { for (T* ptr = buffer+size; ptr>buffer; ) {
unsigned char *pb = (unsigned char*)(--ptr), *pe = pb + sizeof(T);
for (int i=0; i<(int)sizeof(T)/2; ++i) swap(*(pb++),*(--pe));
}}
}
}
//! Invert endianness of a single variable.
template<typename T>
inline T& invert_endianness(T& a) {
invert_endianness(&a,1);
return a;
}
//! Get the value of a system timer with a millisecond precision.
inline unsigned long time() {
#if cimg_OS==1
struct timeval st_time;
gettimeofday(&st_time,0);
return (unsigned long)(st_time.tv_usec/1000 + st_time.tv_sec*1000);
#elif cimg_OS==2
static SYSTEMTIME st_time;
GetSystemTime(&st_time);
return (unsigned long)(st_time.wMilliseconds + 1000*(st_time.wSecond + 60*(st_time.wMinute + 60*st_time.wHour)));
#else
return 0;
#endif
}
//! Sleep for a certain numbers of milliseconds.
/**
This function frees the CPU ressources during the sleeping time.
It may be used to temporize your program properly, without wasting CPU time.
**/
inline void sleep(const unsigned int milliseconds) {
#if cimg_OS==1
struct timespec tv;
tv.tv_sec = milliseconds/1000;
tv.tv_nsec = (milliseconds%1000)*1000000;
nanosleep(&tv,0);
#elif cimg_OS==2
Sleep(milliseconds);
#endif
}
inline unsigned int _sleep(const unsigned int milliseconds, unsigned long& timer) {
if (!timer) timer = cimg::time();
const unsigned long current_time = cimg::time();
if (current_time>=timer+milliseconds) { timer = current_time; return 0; }
const unsigned long time_diff = timer + milliseconds - current_time;
timer = current_time + time_diff;
cimg::sleep(time_diff);
return (unsigned int)time_diff;
}
//! Wait for a certain number of milliseconds since the last call.
/**
This function is equivalent to sleep() but the waiting time is computed with regard to the last call
of wait(). It may be used to temporize your program properly.
**/
inline unsigned int wait(const unsigned int milliseconds) {
static unsigned long timer = 0;
if (!timer) timer = cimg::time();
return _sleep(milliseconds,timer);
}
// Use a specific srand initialization to avoid multi-threads to have to the
// same series of random numbers (executed only once for a single program).
inline void srand() {
static bool first_time = true;
if (first_time) {
cimg_std::srand(cimg::time());
unsigned char *const rand_ptr = new unsigned char[1+cimg_std::rand()%2048];
cimg_std::srand((unsigned int)cimg_std::rand() + *(unsigned int*)(void*)rand_ptr);
delete[] rand_ptr;
first_time = false;
}
}
//! Return a left bitwise-rotated number.
template<typename T>
inline const T rol(const T a, const unsigned int n=1) {
return n?(T)((a<<n)|(a>>((sizeof(T)<<3)-n))):a;
}
//! Return a right bitwise-rotated number.
template<typename T>
inline const T ror(const T a, const unsigned int n=1) {
return n?(T)((a>>n)|(a<<((sizeof(T)<<3)-n))):a;
}
//! Return the absolute value of a number.
/**
\note This function is different from <tt>std::abs()</tt> or <tt>std::fabs()</tt>
because it is able to consider a variable of any type, without cast needed.
**/
template<typename T>
inline T abs(const T a) {
return a>=0?a:-a;
}
inline bool abs(const bool a) {
return a;
}
inline unsigned char abs(const unsigned char a) {
return a;
}
inline unsigned short abs(const unsigned short a) {
return a;
}
inline unsigned int abs(const unsigned int a) {
return a;
}
inline unsigned long abs(const unsigned long a) {
return a;
}
inline double abs(const double a) {
return cimg_std::fabs(a);
}
inline float abs(const float a) {
return (float)cimg_std::fabs((double)a);
}
inline int abs(const int a) {
return cimg_std::abs(a);
}
//! Return the square of a number.
template<typename T>
inline T sqr(const T val) {
return val*val;
}
//! Return 1 + log_10(x).
inline int xln(const int x) {
return x>0?(int)(1+cimg_std::log10((double)x)):1;
}
//! Return the minimum value between two numbers.
template<typename t1, typename t2>
inline typename cimg::superset<t1,t2>::type min(const t1& a, const t2& b) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return (t1t2)(a<=b?a:b);
}
//! Return the minimum value between three numbers.
template<typename t1, typename t2, typename t3>
inline typename cimg::superset2<t1,t2,t3>::type min(const t1& a, const t2& b, const t3& c) {
typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
return (t1t2t3)cimg::min(cimg::min(a,b),c);
}
//! Return the minimum value between four numbers.
template<typename t1, typename t2, typename t3, typename t4>
inline typename cimg::superset3<t1,t2,t3,t4>::type min(const t1& a, const t2& b, const t3& c, const t4& d) {
typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
return (t1t2t3t4)cimg::min(cimg::min(a,b,c),d);
}
//! Return the maximum value between two numbers.
template<typename t1, typename t2>
inline typename cimg::superset<t1,t2>::type max(const t1& a, const t2& b) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return (t1t2)(a>=b?a:b);
}
//! Return the maximum value between three numbers.
template<typename t1, typename t2, typename t3>
inline typename cimg::superset2<t1,t2,t3>::type max(const t1& a, const t2& b, const t3& c) {
typedef typename cimg::superset2<t1,t2,t3>::type t1t2t3;
return (t1t2t3)cimg::max(cimg::max(a,b),c);
}
//! Return the maximum value between four numbers.
template<typename t1, typename t2, typename t3, typename t4>
inline typename cimg::superset3<t1,t2,t3,t4>::type max(const t1& a, const t2& b, const t3& c, const t4& d) {
typedef typename cimg::superset3<t1,t2,t3,t4>::type t1t2t3t4;
return (t1t2t3t4)cimg::max(cimg::max(a,b,c),d);
}
//! Return the sign of a number.
template<typename T>
inline T sign(const T x) {
return (x<0)?(T)(-1):(x==0?(T)0:(T)1);
}
//! Return the nearest power of 2 higher than a given number.
template<typename T>
inline unsigned long nearest_pow2(const T x) {
unsigned long i = 1;
while (x>i) i<<=1;
return i;
}
//! Return the modulo of a number.
/**
\note This modulo function accepts negative and floating-points modulo numbers, as well as
variable of any type.
**/
template<typename T>
inline T mod(const T& x, const T& m) {
const double dx = (double)x, dm = (double)m;
if (x<0) { return (T)(dm+dx+dm*cimg_std::floor(-dx/dm)); }
return (T)(dx-dm*cimg_std::floor(dx/dm));
}
inline int mod(const bool x, const bool m) {
return m?(x?1:0):0;
}
inline int mod(const char x, const char m) {
return x>=0?x%m:(x%m?m+x%m:0);
}
inline int mod(const short x, const short m) {
return x>=0?x%m:(x%m?m+x%m:0);
}
inline int mod(const int x, const int m) {
return x>=0?x%m:(x%m?m+x%m:0);
}
inline int mod(const long x, const long m) {
return x>=0?x%m:(x%m?m+x%m:0);
}
inline int mod(const unsigned char x, const unsigned char m) {
return x%m;
}
inline int mod(const unsigned short x, const unsigned short m) {
return x%m;
}
inline int mod(const unsigned int x, const unsigned int m) {
return x%m;
}
inline int mod(const unsigned long x, const unsigned long m) {
return x%m;
}
//! Return the minmod of two numbers.
/**
<i>minmod(\p a,\p b)</i> is defined to be :
- <i>minmod(\p a,\p b) = min(\p a,\p b)</i>, if \p a and \p b have the same sign.
- <i>minmod(\p a,\p b) = 0</i>, if \p a and \p b have different signs.
**/
template<typename T>
inline T minmod(const T a, const T b) {
return a*b<=0?0:(a>0?(a<b?a:b):(a<b?b:a));
}
//! Return a random variable between [0,1] with respect to an uniform distribution.
inline double rand() {
static bool first_time = true;
if (first_time) { cimg::srand(); first_time = false; }
return (double)cimg_std::rand()/RAND_MAX;
}
//! Return a random variable between [-1,1] with respect to an uniform distribution.
inline double crand() {
return 1-2*cimg::rand();
}
//! Return a random variable following a gaussian distribution and a standard deviation of 1.
inline double grand() {
double x1, w;
do {
const double x2 = 2*cimg::rand() - 1.0;
x1 = 2*cimg::rand()-1.0;
w = x1*x1 + x2*x2;
} while (w<=0 || w>=1.0);
return x1*cimg_std::sqrt((-2*cimg_std::log(w))/w);
}
//! Return a random variable following a Poisson distribution of parameter z.
inline unsigned int prand(const double z) {
if (z<=1.0e-10) return 0;
if (z>100.0) return (unsigned int)((std::sqrt(z) * cimg::grand()) + z);
unsigned int k = 0;
const double y = std::exp(-z);
for (double s = 1.0; s>=y; ++k) s*=cimg::rand();
return k-1;
}
//! Return a rounded number.
/**
\param x is the number to be rounded.
\param y is the rounding precision.
\param rounding_type defines the type of rounding (0=nearest, -1=backward, 1=forward).
**/
inline double round(const double x, const double y, const int rounding_type=0) {
if (y<=0) return x;
const double delta = cimg::mod(x,y);
if (delta==0.0) return x;
const double
backward = x - delta,
forward = backward + y;
return rounding_type<0?backward:(rounding_type>0?forward:(2*delta<y?backward:forward));
}
inline double _pythagore(double a, double b) {
const double absa = cimg::abs(a), absb = cimg::abs(b);
if (absa>absb) { const double tmp = absb/absa; return absa*cimg_std::sqrt(1.0+tmp*tmp); }
else { const double tmp = absa/absb; return (absb==0?0:absb*cimg_std::sqrt(1.0+tmp*tmp)); }
}
//! Remove the 'case' of an ASCII character.
inline char uncase(const char x) {
return (char)((x<'A'||x>'Z')?x:x-'A'+'a');
}
//! Remove the 'case' of a C string.
/**
Acts in-place.
**/
inline void uncase(char *const string) {
if (string) for (char *ptr = string; *ptr; ++ptr) *ptr = uncase(*ptr);
}
//! Read a float number from a C-string.
/**
\note This function is quite similar to <tt>std::atof()</tt>,
but that it allows the retrieval of fractions as in "1/2".
**/
inline float atof(const char *const str) {
float x = 0,y = 1;
if (!str) return 0; else { cimg_std::sscanf(str,"%g/%g",&x,&y); return x/y; }
}
//! Compute the length of a C-string.
/**
\note This function is similar to <tt>std::strlen()</tt>
and is here because some old compilers do not
define the <tt>std::</tt> version.
**/
inline int strlen(const char *const s) {
if (!s) return -1;
int k = 0;
for (const char *ns = s; *ns; ++ns) ++k;
return k;
}
//! Compare the first \p n characters of two C-strings.
/**
\note This function is similar to <tt>std::strncmp()</tt>
and is here because some old compilers do not
define the <tt>std::</tt> version.
**/
inline int strncmp(const char *const s1, const char *const s2, const int l) {
if (!s1) return s2?-1:0;
const char *ns1 = s1, *ns2 = s2;
int k, diff = 0; for (k = 0; k<l && !(diff = *ns1-*ns2); ++k) { ++ns1; ++ns2; }
return k!=l?diff:0;
}
//! Compare the first \p n characters of two C-strings, ignoring the case.
/**
\note This function is similar to <tt>std::strncasecmp()</tt>
and is here because some old compilers do not
define the <tt>std::</tt> version.
**/
inline int strncasecmp(const char *const s1, const char *const s2, const int l) {
if (!s1) return s2?-1:0;
const char *ns1 = s1, *ns2 = s2;
int k, diff = 0; for (k = 0; k<l && !(diff = uncase(*ns1)-uncase(*ns2)); ++k) { ++ns1; ++ns2; }
return k!=l?diff:0;
}
//! Compare two C-strings.
/**
\note This function is similar to <tt>std::strcmp()</tt>
and is here because some old compilers do not
define the <tt>std::</tt> version.
**/
inline int strcmp(const char *const s1, const char *const s2) {
const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
return cimg::strncmp(s1,s2,1+(l1<l2?l1:l2));
}
//! Compare two C-strings, ignoring the case.
/**
\note This function is similar to <tt>std::strcasecmp()</tt>
and is here because some old compilers do not
define the <tt>std::</tt> version.
**/
inline int strcasecmp(const char *const s1, const char *const s2) {
const int l1 = cimg::strlen(s1), l2 = cimg::strlen(s2);
return cimg::strncasecmp(s1,s2,1+(l1<l2?l1:l2));
}
//! Find a character in a C-string.
inline int strfind(const char *const s, const char c) {
if (!s) return -1;
int l; for (l = cimg::strlen(s); l>=0 && s[l]!=c; --l) {}
return l;
}
//! Remove useless delimiters on the borders of a C-string
inline bool strpare(char *const s, const char delimiter=' ', const bool symmetric=false) {
if (!s) return false;
const int l = cimg::strlen(s);
int p, q;
if (symmetric) for (p = 0, q = l-1; p<q && s[p]==delimiter && s[q]==delimiter; ++p) --q;
else {
for (p = 0; p<l && s[p]==delimiter; ) ++p;
for (q = l-1; q>p && s[q]==delimiter; ) --q;
}
const int n = q - p + 1;
if (n!=l) { cimg_std::memmove(s,s+p,n); s[n] = '\0'; return true; }
return false;
}
//! Remove useless spaces and symmetric delimiters ', " and ` from a C-string.
inline void strclean(char *const s) {
if (!s) return;
strpare(s,' ',false);
for (bool need_iter = true; need_iter; ) {
need_iter = false;
need_iter |= strpare(s,'\'',true);
need_iter |= strpare(s,'\"',true);
need_iter |= strpare(s,'`',true);
}
}
//! Replace explicit escape sequences '\x' in C-strings (where x in [ntvbrfa?'"0]).
inline void strescape(char *const s) {
#define cimg_strescape(ci,co) case ci: *nd = co; break;
char *ns, *nd;
for (ns = nd = s; *ns; ++ns, ++nd)
if (*ns=='\\') switch (*(++ns)) {
cimg_strescape('n','\n');
cimg_strescape('t','\t');
cimg_strescape('v','\v');
cimg_strescape('b','\b');
cimg_strescape('r','\r');
cimg_strescape('f','\f');
cimg_strescape('a','\a');
cimg_strescape('\\','\\');
cimg_strescape('\?','\?');
cimg_strescape('\'','\'');
cimg_strescape('\"','\"');
cimg_strescape('\0','\0');
}
else *nd = *ns;
*nd = 0;
}
//! Compute the basename of a filename.
inline const char* basename(const char *const s) {
return (cimg_OS!=2)?(s?s+1+cimg::strfind(s,'/'):0):(s?s+1+cimg::strfind(s,'\\'):0);
}
// Generate a random filename.
inline const char* filenamerand() {
static char id[9] = { 0,0,0,0,0,0,0,0,0 };
cimg::srand();
for (unsigned int k=0; k<8; ++k) {
const int v = (int)cimg_std::rand()%3;
id[k] = (char)(v==0?('0'+(cimg_std::rand()%10)):(v==1?('a'+(cimg_std::rand()%26)):('A'+(cimg_std::rand()%26))));
}
return id;
}
// Convert filename into a Windows-style filename.
inline void winformat_string(char *const s) {
if (s && s[0]) {
#if cimg_OS==2
char *const ns = new char[MAX_PATH];
if (GetShortPathNameA(s,ns,MAX_PATH)) cimg_std::strcpy(s,ns);
#endif
}
}
//! Return or set path to store temporary files.
inline const char* temporary_path(const char *const user_path=0, const bool reinit_path=false) {
#define _cimg_test_temporary_path(p) \
if (!path_found) { \
cimg_std::sprintf(st_path,"%s",p); \
cimg_std::sprintf(tmp,"%s%s%s",st_path,cimg_OS==2?"\\":"/",filetmp); \
if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; } \
}
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
char tmp[1024], filetmp[512];
cimg_std::FILE *file = 0;
cimg_std::sprintf(filetmp,"%s.tmp",cimg::filenamerand());
char *tmpPath = getenv("TMP");
if (!tmpPath) { tmpPath = getenv("TEMP"); winformat_string(tmpPath); }
if (tmpPath) _cimg_test_temporary_path(tmpPath);
#if cimg_OS==2
_cimg_test_temporary_path("C:\\WINNT\\Temp");
_cimg_test_temporary_path("C:\\WINDOWS\\Temp");
_cimg_test_temporary_path("C:\\Temp");
_cimg_test_temporary_path("C:");
_cimg_test_temporary_path("D:\\WINNT\\Temp");
_cimg_test_temporary_path("D:\\WINDOWS\\Temp");
_cimg_test_temporary_path("D:\\Temp");
_cimg_test_temporary_path("D:");
#else
_cimg_test_temporary_path("/tmp");
_cimg_test_temporary_path("/var/tmp");
#endif
if (!path_found) {
st_path[0]='\0';
cimg_std::strcpy(tmp,filetmp);
if ((file=cimg_std::fopen(tmp,"wb"))!=0) { cimg_std::fclose(file); cimg_std::remove(tmp); path_found = true; }
}
if (!path_found)
throw CImgIOException("cimg::temporary_path() : Unable to find a temporary path accessible for writing\n"
"you have to set the macro 'cimg_temporary_path' to a valid path where you have writing access :\n"
"#define cimg_temporary_path \"path\" (before including 'CImg.h')");
}
return st_path;
}
// Return or set path to the "Program files/" directory (windows only).
#if cimg_OS==2
inline const char* programfiles_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[MAX_PATH];
cimg_std::memset(st_path,0,MAX_PATH);
// Note : in the following line, 0x26 = CSIDL_PROGRAM_FILES (not defined on every compiler).
#if !defined(__INTEL_COMPILER)
if (!SHGetSpecialFolderPathA(0,st_path,0x0026,false)) {
const char *pfPath = getenv("PROGRAMFILES");
if (pfPath) cimg_std::strncpy(st_path,pfPath,MAX_PATH-1);
else cimg_std::strcpy(st_path,"C:\\PROGRA~1");
}
#else
cimg_std::strcpy(st_path,"C:\\PROGRA~1");
#endif
}
return st_path;
}
#endif
//! Return or set path to the ImageMagick's \c convert tool.
inline const char* imagemagick_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
const char *pf_path = programfiles_path();
if (!path_found) {
cimg_std::sprintf(st_path,".\\convert.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\convert.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\convert.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\convert.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%.2d-\\VISUA~1\\BIN\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d-Q\\VISUA~1\\BIN\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\IMAGEM~1.%d\\VISUA~1\\BIN\\convert.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
if (!path_found) cimg_std::strcpy(st_path,"convert.exe");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./convert");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"convert");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Return path of the GraphicsMagick's \c gm tool.
inline const char* graphicsmagick_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
const char* pf_path = programfiles_path();
if (!path_found) {
cimg_std::sprintf(st_path,".\\gm.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\gm.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\gm.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\gm.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"%s\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",pf_path,k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"C:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=10 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%.2d-\\VISUA~1\\BIN\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=9; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d-Q\\VISUA~1\\BIN\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
{ for (int k=32; k>=0 && !path_found; --k) {
cimg_std::sprintf(st_path,"D:\\GRAPHI~1.%d\\VISUA~1\\BIN\\gm.exe",k);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}}
if (!path_found) cimg_std::strcpy(st_path,"gm.exe");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./gm");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"gm");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Return or set path of the \c XMedcon tool.
inline const char* medcon_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
const char* pf_path = programfiles_path();
if (!path_found) {
cimg_std::sprintf(st_path,".\\medcon.bat");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) {
cimg_std::sprintf(st_path,".\\medcon.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) {
cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.bat",pf_path);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) {
cimg_std::sprintf(st_path,"%s\\XMedCon\\bin\\medcon.exe",pf_path);
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"medcon.bat");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./medcon");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"medcon");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Return or set path to the 'ffmpeg' command.
inline const char *ffmpeg_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
if (!path_found) {
cimg_std::sprintf(st_path,".\\ffmpeg.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"ffmpeg.exe");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./ffmpeg");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"ffmpeg");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Return or set path to the 'gzip' command.
inline const char *gzip_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
if (!path_found) {
cimg_std::sprintf(st_path,".\\gzip.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"gzip.exe");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./gzip");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"gzip");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Return or set path to the 'gunzip' command.
inline const char *gunzip_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
if (!path_found) {
cimg_std::sprintf(st_path,".\\gunzip.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"gunzip.exe");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./gunzip");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"gunzip");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Return or set path to the 'dcraw' command.
inline const char *dcraw_path(const char *const user_path=0, const bool reinit_path=false) {
static char *st_path = 0;
if (reinit_path && st_path) { delete[] st_path; st_path = 0; }
if (user_path) {
if (!st_path) st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
cimg_std::strncpy(st_path,user_path,1023);
} else if (!st_path) {
st_path = new char[1024];
cimg_std::memset(st_path,0,1024);
bool path_found = false;
cimg_std::FILE *file = 0;
#if cimg_OS==2
if (!path_found) {
cimg_std::sprintf(st_path,".\\dcraw.exe");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"dcraw.exe");
#else
if (!path_found) {
cimg_std::sprintf(st_path,"./dcraw");
if ((file=cimg_std::fopen(st_path,"r"))!=0) { cimg_std::fclose(file); path_found = true; }
}
if (!path_found) cimg_std::strcpy(st_path,"dcraw");
#endif
winformat_string(st_path);
}
return st_path;
}
//! Split a filename into two strings 'body' and 'extension'.
inline const char *split_filename(const char *const filename, char *const body=0) {
if (!filename) { if (body) body[0]='\0'; return 0; }
int l = cimg::strfind(filename,'.');
if (l>=0) { if (body) { cimg_std::strncpy(body,filename,l); body[l]='\0'; }}
else { if (body) cimg_std::strcpy(body,filename); l = (int)cimg::strlen(filename)-1; }
return filename+l+1;
}
//! Create a numbered version of a filename.
inline char* number_filename(const char *const filename, const int number, const unsigned int n, char *const string) {
if (!filename) { if (string) string[0]='\0'; return 0; }
char format[1024],body[1024];
const char *ext = cimg::split_filename(filename,body);
if (n>0) cimg_std::sprintf(format,"%s_%%.%ud.%s",body,n,ext);
else cimg_std::sprintf(format,"%s_%%d.%s",body,ext);
cimg_std::sprintf(string,format,number);
return string;
}
//! Open a file, and check for possible errors.
inline cimg_std::FILE *fopen(const char *const path, const char *const mode) {
if(!path || !mode)
throw CImgArgumentException("cimg::fopen() : File '%s', cannot open with mode '%s'.",
path?path:"(null)",mode?mode:"(null)");
if (path[0]=='-') return (mode[0]=='r')?stdin:stdout;
cimg_std::FILE *dest = cimg_std::fopen(path,mode);
if (!dest)
throw CImgIOException("cimg::fopen() : File '%s', cannot open file %s",
path,mode[0]=='r'?"for reading.":(mode[0]=='w'?"for writing.":"."),path);
return dest;
}
//! Close a file, and check for possible errors.
inline int fclose(cimg_std::FILE *file) {
if (!file) warn("cimg::fclose() : Can't close (null) file");
if (!file || file==stdin || file==stdout) return 0;
const int errn = cimg_std::fclose(file);
if (errn!=0) warn("cimg::fclose() : Error %d during file closing",errn);
return errn;
}
//! Try to guess the image format of a filename, using its magick numbers.
inline const char *file_type(cimg_std::FILE *const file, const char *const filename) {
static const char
*const _pnm = "pnm",
*const _bmp = "bmp",
*const _gif = "gif",
*const _jpeg = "jpeg",
*const _off = "off",
*const _pan = "pan",
*const _png = "png",
*const _tiff = "tiff";
if (!filename && !file) throw CImgArgumentException("cimg::file_type() : Cannot load (null) filename.");
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
const char *ftype = 0, *head;
char header[2048], item[1024];
const unsigned char *const uheader = (unsigned char*)header;
int err;
const unsigned int siz = (unsigned int)cimg_std::fread(header,2048,1,nfile); // Read first 2048 bytes.
if (!file) cimg::fclose(nfile);
if (!ftype) { // Check for BMP format.
if (header[0]=='B' && header[1]=='M') ftype = _bmp;
}
if (!ftype) { // Check for GIF format.
if (header[0]=='G' && header[1]=='I' && header[2]=='F' && header[3]=='8' && header[5]=='a' &&
(header[4]=='7' || header[4]=='9')) ftype = _gif;
}
if (!ftype) { // Check for JPEG format.
if (uheader[0]==0xFF && uheader[1]==0xD8 && uheader[2]==0xFF) ftype = _jpeg;
}
if (!ftype) { // Check for OFF format.
if (header[0]=='O' && header[1]=='F' && header[2]=='F' && header[3]=='\n') ftype = _off;
}
if (!ftype) { // Check for PAN format.
if (header[0]=='P' && header[1]=='A' && header[2]=='N' && header[3]=='D' && header[4]=='O' &&
header[5]=='R' && header[6]=='E') ftype = _pan;
}
if (!ftype) { // Check for PNG format.
if (uheader[0]==0x89 && uheader[1]==0x50 && uheader[2]==0x4E && uheader[3]==0x47 &&
uheader[4]==0x0D && uheader[5]==0x0A && uheader[6]==0x1A && uheader[7]==0x0A) ftype = _png;
}
if (!ftype) { // Check for PNM format.
head = header;
while (head<header+siz && (err=cimg_std::sscanf(head,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err))
head+=1+(err?cimg::strlen(item):0);
if (cimg_std::sscanf(item," P%d",&err)==1) ftype = _pnm;
}
if (!ftype) { // Check for TIFF format.
if ((uheader[0]==0x49 && uheader[1]==0x49) || (uheader[0]==0x4D && uheader[1]==0x4D)) ftype = _tiff;
}
return ftype;
}
//! Read file data, and check for possible errors.
template<typename T>
inline int fread(T *const ptr, const unsigned int nmemb, cimg_std::FILE *stream) {
if (!ptr || nmemb<=0 || !stream)
throw CImgArgumentException("cimg::fread() : Can't read %u x %u bytes of file pointer '%p' in buffer '%p'",
nmemb,sizeof(T),stream,ptr);
const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
unsigned int toread = nmemb, alread = 0, ltoread = 0, lalread = 0;
do {
ltoread = (toread*sizeof(T))<wlimitT?toread:wlimit;
lalread = (unsigned int)cimg_std::fread((void*)(ptr+alread),sizeof(T),ltoread,stream);
alread+=lalread;
toread-=lalread;
} while (ltoread==lalread && toread>0);
if (toread>0) warn("cimg::fread() : File reading problems, only %u/%u elements read",alread,nmemb);
return alread;
}
//! Write data to a file, and check for possible errors.
template<typename T>
inline int fwrite(const T *ptr, const unsigned int nmemb, cimg_std::FILE *stream) {
if (!ptr || !stream)
throw CImgArgumentException("cimg::fwrite() : Can't write %u x %u bytes of file pointer '%p' from buffer '%p'",
nmemb,sizeof(T),stream,ptr);
if (nmemb<=0) return 0;
const unsigned long wlimitT = 63*1024*1024, wlimit = wlimitT/sizeof(T);
unsigned int towrite = nmemb, alwrite = 0, ltowrite = 0, lalwrite = 0;
do {
ltowrite = (towrite*sizeof(T))<wlimitT?towrite:wlimit;
lalwrite = (unsigned int)cimg_std::fwrite((void*)(ptr+alwrite),sizeof(T),ltowrite,stream);
alwrite+=lalwrite;
towrite-=lalwrite;
} while (ltowrite==lalwrite && towrite>0);
if (towrite>0) warn("cimg::fwrite() : File writing problems, only %u/%u elements written",alwrite,nmemb);
return alwrite;
}
inline const char* option(const char *const name, const int argc, const char *const *const argv,
const char *defaut, const char *const usage=0) {
static bool first = true, visu = false;
const char *res = 0;
if (first) {
first=false;
visu = (cimg::option("-h",argc,argv,(char*)0)!=0);
visu |= (cimg::option("-help",argc,argv,(char*)0)!=0);
visu |= (cimg::option("--help",argc,argv,(char*)0)!=0);
}
if (!name && visu) {
if (usage) {
cimg_std::fprintf(cimg_stdout,"\n %s%s%s",cimg::t_red,cimg::basename(argv[0]),cimg::t_normal);
cimg_std::fprintf(cimg_stdout," : %s",usage);
cimg_std::fprintf(cimg_stdout," (%s, %s)\n\n",__DATE__,__TIME__);
}
if (defaut) cimg_std::fprintf(cimg_stdout,"%s\n",defaut);
}
if (name) {
if (argc>0) {
int k = 0;
while (k<argc && cimg::strcmp(argv[k],name)) ++k;
res = (k++==argc?defaut:(k==argc?argv[--k]:argv[k]));
} else res = defaut;
if (visu && usage) cimg_std::fprintf(cimg_stdout," %s%-16s%s %-24s %s%s%s\n",
cimg::t_bold,name,cimg::t_normal,res?res:"0",cimg::t_green,usage,cimg::t_normal);
}
return res;
}
inline bool option(const char *const name, const int argc, const char *const *const argv,
const bool defaut, const char *const usage=0) {
const char *s = cimg::option(name,argc,argv,(char*)0);
const bool res = s?(cimg::strcasecmp(s,"false") && cimg::strcasecmp(s,"off") && cimg::strcasecmp(s,"0")):defaut;
cimg::option(name,0,0,res?"true":"false",usage);
return res;
}
inline int option(const char *const name, const int argc, const char *const *const argv,
const int defaut, const char *const usage=0) {
const char *s = cimg::option(name,argc,argv,(char*)0);
const int res = s?cimg_std::atoi(s):defaut;
char tmp[256];
cimg_std::sprintf(tmp,"%d",res);
cimg::option(name,0,0,tmp,usage);
return res;
}
inline char option(const char *const name, const int argc, const char *const *const argv,
const char defaut, const char *const usage=0) {
const char *s = cimg::option(name,argc,argv,(char*)0);
const char res = s?s[0]:defaut;
char tmp[8];
tmp[0] = res; tmp[1] ='\0';
cimg::option(name,0,0,tmp,usage);
return res;
}
inline float option(const char *const name, const int argc, const char *const *const argv,
const float defaut, const char *const usage=0) {
const char *s = cimg::option(name,argc,argv,(char*)0);
const float res = s?cimg::atof(s):defaut;
char tmp[256];
cimg_std::sprintf(tmp,"%g",res);
cimg::option(name,0,0,tmp,usage);
return res;
}
inline double option(const char *const name, const int argc, const char *const *const argv,
const double defaut, const char *const usage=0) {
const char *s = cimg::option(name,argc,argv,(char*)0);
const double res = s?cimg::atof(s):defaut;
char tmp[256];
cimg_std::sprintf(tmp,"%g",res);
cimg::option(name,0,0,tmp,usage);
return res;
}
inline const char* argument(const unsigned int nb, const int argc, const char *const *const argv, const unsigned int nb_singles=0, ...) {
for (int k = 1, pos = 0; k<argc;) {
const char *const item = argv[k];
bool option = (*item=='-'), single_option = false;
if (option) {
va_list ap;
va_start(ap,nb_singles);
for (unsigned int i=0; i<nb_singles; ++i) if (!cimg::strcasecmp(item,va_arg(ap,char*))) { single_option = true; break; }
va_end(ap);
}
if (option) { ++k; if (!single_option) ++k; }
else { if (pos++==(int)nb) return item; else ++k; }
}
return 0;
}
- //! Print informations about %CImg environement variables.
+ //! Print information about %CImg environment variables.
/**
Printing is done on the standard error output.
**/
inline void info() {
char tmp[1024] = { 0 };
cimg_std::fprintf(cimg_stdout,"\n %sCImg Library %u.%u.%u%s, compiled %s ( %s ) with the following flags :\n\n",
cimg::t_red,cimg_version/100,(cimg_version/10)%10,cimg_version%10,
cimg::t_normal,__DATE__,__TIME__);
cimg_std::fprintf(cimg_stdout," > Operating System : %s%-13s%s %s('cimg_OS'=%d)%s\n",
cimg::t_bold,
cimg_OS==1?"Unix":(cimg_OS==2?"Windows":"Unknow"),
cimg::t_normal,cimg::t_green,
cimg_OS,
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > CPU endianness : %s%s Endian%s\n",
cimg::t_bold,
cimg::endianness()?"Big":"Little",
cimg::t_normal);
#ifdef cimg_use_visualcpp6
cimg_std::fprintf(cimg_stdout," > Using Visual C++ 6.0 : %s%-13s%s %s('cimg_use_visualcpp6' defined)%s\n",
cimg::t_bold,"Yes",cimg::t_normal,cimg::t_green,cimg::t_normal);
#endif
cimg_std::fprintf(cimg_stdout," > Debug messages : %s%-13s%s %s('cimg_debug'=%d)%s\n",
cimg::t_bold,
cimg_debug==0?"Quiet":(cimg_debug==1?"Console":(cimg_debug==2?"Dialog":(cimg_debug==3?"Console+Warnings":"Dialog+Warnings"))),
cimg::t_normal,cimg::t_green,
cimg_debug,
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Stricts warnings : %s%-13s%s %s('cimg_strict_warnings' %s)%s\n",
cimg::t_bold,
#ifdef cimg_strict_warnings
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using VT100 messages : %s%-13s%s %s('cimg_use_vt100' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_vt100
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Display type : %s%-13s%s %s('cimg_display'=%d)%s\n",
cimg::t_bold,
cimg_display==0?"No display":
(cimg_display==1?"X11":
(cimg_display==2?"Windows GDI":
(cimg_display==3?"Carbon":"Unknow"))),
cimg::t_normal,cimg::t_green,
cimg_display,
cimg::t_normal);
#if cimg_display==1
cimg_std::fprintf(cimg_stdout," > Using XShm for X11 : %s%-13s%s %s('cimg_use_xshm' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_xshm
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using XRand for X11 : %s%-13s%s %s('cimg_use_xrandr' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_xrandr
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
#endif
cimg_std::fprintf(cimg_stdout," > Using OpenMP : %s%-13s%s %s('cimg_use_openmp' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_openmp
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using PNG library : %s%-13s%s %s('cimg_use_png' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_png
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using JPEG library : %s%-13s%s %s('cimg_use_jpeg' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_jpeg
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using TIFF library : %s%-13s%s %s('cimg_use_tiff' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_tiff
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using Magick++ library : %s%-13s%s %s('cimg_use_magick' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_magick
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using FFTW3 library : %s%-13s%s %s('cimg_use_fftw3' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_fftw3
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::fprintf(cimg_stdout," > Using LAPACK library : %s%-13s%s %s('cimg_use_lapack' %s)%s\n",
cimg::t_bold,
#ifdef cimg_use_lapack
"Yes",cimg::t_normal,cimg::t_green,"defined",
#else
"No",cimg::t_normal,cimg::t_green,"undefined",
#endif
cimg::t_normal);
cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::imagemagick_path());
cimg_std::fprintf(cimg_stdout," > Path of ImageMagick : %s%-13s%s\n",
cimg::t_bold,
tmp,
cimg::t_normal);
cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::graphicsmagick_path());
cimg_std::fprintf(cimg_stdout," > Path of GraphicsMagick : %s%-13s%s\n",
cimg::t_bold,
tmp,
cimg::t_normal);
cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::medcon_path());
cimg_std::fprintf(cimg_stdout," > Path of 'medcon' : %s%-13s%s\n",
cimg::t_bold,
tmp,
cimg::t_normal);
cimg_std::sprintf(tmp,"\"%.1020s\"",cimg::temporary_path());
cimg_std::fprintf(cimg_stdout," > Temporary path : %s%-13s%s\n",
cimg::t_bold,
tmp,
cimg::t_normal);
cimg_std::fprintf(cimg_stdout,"\n");
}
// Declare LAPACK function signatures if necessary.
//
#ifdef cimg_use_lapack
template<typename T>
inline void getrf(int &N, T *lapA, int *IPIV, int &INFO) {
dgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
}
inline void getrf(int &N, float *lapA, int *IPIV, int &INFO) {
sgetrf_(&N,&N,lapA,&N,IPIV,&INFO);
}
template<typename T>
inline void getri(int &N, T *lapA, int *IPIV, T* WORK, int &LWORK, int &INFO) {
dgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
}
inline void getri(int &N, float *lapA, int *IPIV, float* WORK, int &LWORK, int &INFO) {
sgetri_(&N,lapA,&N,IPIV,WORK,&LWORK,&INFO);
}
template<typename T>
inline void gesvd(char &JOB, int &M, int &N, T *lapA, int &MN,
T *lapS, T *lapU, T *lapV, T *WORK, int &LWORK, int &INFO) {
dgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
}
inline void gesvd(char &JOB, int &M, int &N, float *lapA, int &MN,
float *lapS, float *lapU, float *lapV, float *WORK, int &LWORK, int &INFO) {
sgesvd_(&JOB,&JOB,&M,&N,lapA,&MN,lapS,lapU,&M,lapV,&N,WORK,&LWORK,&INFO);
}
template<typename T>
inline void getrs(char &TRANS, int &N, T *lapA, int *IPIV, T *lapB, int &INFO) {
int one = 1;
dgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
}
inline void getrs(char &TRANS, int &N, float *lapA, int *IPIV, float *lapB, int &INFO) {
int one = 1;
sgetrs_(&TRANS,&N,&one,lapA,&N,IPIV,lapB,&N,&INFO);
}
template<typename T>
inline void syev(char &JOB, char &UPLO, int &N, T *lapA, T *lapW, T *WORK, int &LWORK, int &INFO) {
dsyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
}
inline void syev(char &JOB, char &UPLO, int &N, float *lapA, float *lapW, float *WORK, int &LWORK, int &INFO) {
ssyev_(&JOB,&UPLO,&N,lapA,&N,lapW,WORK,&LWORK,&INFO);
}
#endif
// End of the 'cimg' namespace
}
/*------------------------------------------------
#
#
# Definition of mathematical operators and
# external functions.
#
#
-------------------------------------------------*/
//
// These functions are extern to any classes and can be used for a "functional-style" programming,
// such as writing :
// cos(img);
// instead of img.get_cos();
//
// Note that only the arithmetic operators and functions are implemented here.
//
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator+(const CImg<t>& img, const t val) {
return CImg<t>(img,false)+=val;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img,false)+=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator+(const t val, const CImg<t>& img) {
return img + val;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator+(const t1 val, const CImg<t2>& img) {
return img + val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator+(const CImgList<t>& list, const t val) {
return CImgList<t>(list)+=val;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)+=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator+(const t val, const CImgList<t>& list) {
return list + val;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const t1 val, const CImgList<t2>& list) {
return list + val;
}
#endif
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img1, const CImg<t2>& img2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img1,false)+=img2;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImg<t1>& img, const CImgList<t2>& list) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)+=img;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list, const CImg<t2>& img) {
return img + list;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator+(const CImgList<t1>& list1, const CImgList<t2>& list2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list1)+=list2;
}
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator-(const CImg<t>& img, const t val) {
return CImg<t>(img,false)-=val;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img,false)-=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator-(const t val, const CImg<t>& img) {
return CImg<t>(img.width,img.height,img.depth,img.dim,val)-=img;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator-(const t1 val, const CImg<t2>& img) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img.width,img.height,img.depth,img.dim,(t1t2)val)-=img;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator-(const CImgList<t>& list, const t val) {
return CImgList<t>(list)-=val;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)-=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<double> operator-(const t val, const CImgList<t>& list) {
CImgList<t> res(list.size);
cimglist_for(res,l) res[l] = val - list[l];
return res;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const t1 val, const CImgList<t2>& list) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(list.size);
cimglist_for(res,l) res[l] = val - list[l];
return res;
}
#endif
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img1, const CImg<t2>& img2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img1,false)-=img2;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImg<t1>& img, const CImgList<t2>& list) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(list.size);
cimglist_for(res,l) res[l] = img - list[l];
return res;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list, const CImg<t2>& img) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)-=img;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator-(const CImgList<t1>& list1, const CImgList<t2>& list2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list1)-=list2;
}
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator*(const CImg<t>& img, const double val) {
return CImg<t>(img,false)*=val;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img,false)*=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator*(const double val, const CImg<t>& img) {
return img*val;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator*(const t1 val, const CImg<t2>& img) {
return img*val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator*(const CImgList<t>& list, const double val) {
return CImgList<t>(list)*=val;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)*=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator*(const double val, const CImgList<t>& list) {
return list*val;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const t1 val, const CImgList<t2>& list) {
return list*val;
}
#endif
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img1, const CImg<t2>& img2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
if (img1.width!=img2.height)
throw CImgArgumentException("operator*() : can't multiply a matrix (%ux%u) by a matrix (%ux%u)",
img1.width,img1.height,img2.width,img2.height);
CImg<t1t2> res(img2.width,img1.height);
t1t2 val;
#ifdef cimg_use_openmp
#pragma omp parallel for if (img1.size()>=1000 && img2.size()>=1000) private(val)
#endif
cimg_forXY(res,i,j) { val = 0; cimg_forX(img1,k) val+=img1(k,j)*img2(i,k); res(i,j) = val; }
return res;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImg<t1>& img, const CImgList<t2>& list) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(list.size);
cimglist_for(res,l) res[l] = img*list[l];
return res;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list, const CImg<t2>& img) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(list.size);
cimglist_for(res,l) res[l] = list[l]*img;
return res;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator*(const CImgList<t1>& list1, const CImgList<t2>& list2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(cimg::min(list1.size,list2.size));
cimglist_for(res,l) res[l] = list1[l]*list2[l];
return res;
}
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator/(const CImg<t>& img, const double val) {
return CImg<t>(img,false)/=val;
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img,false)/=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImg<t> operator/(const double val, CImg<t>& img) {
return val*img.get_invert();
}
#else
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator/(const t1 val, CImg<t2>& img) {
return val*img.get_invert();
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator/(const CImgList<t>& list, const double val) {
return CImgList<t>(list)/=val;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list, const t2 val) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)/=val;
}
#endif
#ifdef cimg_use_visualcpp6
template<typename t>
inline CImgList<t> operator/(const double val, const CImgList<t>& list) {
CImgList<t> res(list.size);
cimglist_for(res,l) res[l] = val/list[l];
return res;
}
#else
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const t1 val, const CImgList<t2>& list) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(list.size);
cimglist_for(res,l) res[l] = val/list[l];
return res;
}
#endif
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img1, const CImg<t2>& img2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImg<t1t2>(img1,false)*=img2.get_invert();
}
template<typename t1, typename t2>
inline CImg<typename cimg::superset<t1,t2>::type> operator/(const CImg<t1>& img, const CImgList<t2>& list) {
typedef typename cimg::superset<t1,t2>::type t1t2;
CImgList<t1t2> res(list.size);
cimglist_for(res,l) res[l] = img/list[l];
return res;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list, const CImg<t2>& img) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list)/=img;
}
template<typename t1, typename t2>
inline CImgList<typename cimg::superset<t1,t2>::type> operator/(const CImgList<t1>& list1, const CImgList<t2>& list2) {
typedef typename cimg::superset<t1,t2>::type t1t2;
return CImgList<t1t2>(list1)/=list2;
}
template<typename T>
inline CImg<_cimg_Tfloat> sqr(const CImg<T>& instance) {
return instance.get_sqr();
}
template<typename T>
inline CImg<_cimg_Tfloat> sqrt(const CImg<T>& instance) {
return instance.get_sqrt();
}
template<typename T>
inline CImg<_cimg_Tfloat> exp(const CImg<T>& instance) {
return instance.get_exp();
}
template<typename T>
inline CImg<_cimg_Tfloat> log(const CImg<T>& instance) {
return instance.get_log();
}
template<typename T>
inline CImg<_cimg_Tfloat> log10(const CImg<T>& instance) {
return instance.get_log10();
}
template<typename T>
inline CImg<_cimg_Tfloat> abs(const CImg<T>& instance) {
return instance.get_abs();
}
template<typename T>
inline CImg<_cimg_Tfloat> cos(const CImg<T>& instance) {
return instance.get_cos();
}
template<typename T>
inline CImg<_cimg_Tfloat> sin(const CImg<T>& instance) {
return instance.get_sin();
}
template<typename T>
inline CImg<_cimg_Tfloat> tan(const CImg<T>& instance) {
return instance.get_tan();
}
template<typename T>
inline CImg<_cimg_Tfloat> acos(const CImg<T>& instance) {
return instance.get_acos();
}
template<typename T>
inline CImg<_cimg_Tfloat> asin(const CImg<T>& instance) {
return instance.get_asin();
}
template<typename T>
inline CImg<_cimg_Tfloat> atan(const CImg<T>& instance) {
return instance.get_atan();
}
template<typename T>
inline CImg<T> transpose(const CImg<T>& instance) {
return instance.get_transpose();
}
template<typename T>
inline CImg<_cimg_Tfloat> invert(const CImg<T>& instance) {
return instance.get_invert();
}
template<typename T>
inline CImg<_cimg_Tfloat> pseudoinvert(const CImg<T>& instance) {
return instance.get_pseudoinvert();
}
/*-------------------------------------------
#
#
#
# Definition of the CImgDisplay structure
#
#
#
--------------------------------------------*/
//! This class represents a window which can display \ref CImg images and handles mouse and keyboard events.
/**
Creating a \c CImgDisplay instance opens a window that can be used to display a \c CImg<T> image
of a \c CImgList<T> image list inside. When a display is created, associated window events
(such as mouse motion, keyboard and window size changes) are handled and can be easily
detected by testing specific \c CImgDisplay data fields.
See \ref cimg_displays for a complete tutorial on using the \c CImgDisplay class.
**/
struct CImgDisplay {
//! Width of the display
unsigned int width;
//! Height of the display
unsigned int height;
//! Normalization type used for the display
unsigned int normalization;
//! Display title
char* title;
//! X-pos of the display on the screen
volatile int window_x;
//! Y-pos of the display on the screen
volatile int window_y;
//! Width of the underlying window
volatile unsigned int window_width;
//! Height of the underlying window
volatile unsigned int window_height;
//! X-coordinate of the mouse pointer on the display
volatile int mouse_x;
//! Y-coordinate of the mouse pointer on the display
volatile int mouse_y;
//! Button state of the mouse
volatile unsigned int buttons[512];
volatile unsigned int& button;
//! Wheel state of the mouse
volatile int wheel;
//! Key value if pressed
volatile unsigned int& key;
volatile unsigned int keys[512];
//! Key value if released
volatile unsigned int& released_key;
volatile unsigned int released_keys[512];
//! Closed state of the window
volatile bool is_closed;
//! Resized state of the window
volatile bool is_resized;
//! Moved state of the window
volatile bool is_moved;
//! Event state of the window
volatile bool is_event;
//! Current state of the corresponding key (exists for all referenced keys).
volatile bool is_keyESC;
volatile bool is_keyF1;
volatile bool is_keyF2;
volatile bool is_keyF3;
volatile bool is_keyF4;
volatile bool is_keyF5;
volatile bool is_keyF6;
volatile bool is_keyF7;
volatile bool is_keyF8;
volatile bool is_keyF9;
volatile bool is_keyF10;
volatile bool is_keyF11;
volatile bool is_keyF12;
volatile bool is_keyPAUSE;
volatile bool is_key1;
volatile bool is_key2;
volatile bool is_key3;
volatile bool is_key4;
volatile bool is_key5;
volatile bool is_key6;
volatile bool is_key7;
volatile bool is_key8;
volatile bool is_key9;
volatile bool is_key0;
volatile bool is_keyBACKSPACE;
volatile bool is_keyINSERT;
volatile bool is_keyHOME;
volatile bool is_keyPAGEUP;
volatile bool is_keyTAB;
volatile bool is_keyQ;
volatile bool is_keyW;
volatile bool is_keyE;
volatile bool is_keyR;
volatile bool is_keyT;
volatile bool is_keyY;
volatile bool is_keyU;
volatile bool is_keyI;
volatile bool is_keyO;
volatile bool is_keyP;
volatile bool is_keyDELETE;
volatile bool is_keyEND;
volatile bool is_keyPAGEDOWN;
volatile bool is_keyCAPSLOCK;
volatile bool is_keyA;
volatile bool is_keyS;
volatile bool is_keyD;
volatile bool is_keyF;
volatile bool is_keyG;
volatile bool is_keyH;
volatile bool is_keyJ;
volatile bool is_keyK;
volatile bool is_keyL;
volatile bool is_keyENTER;
volatile bool is_keySHIFTLEFT;
volatile bool is_keyZ;
volatile bool is_keyX;
volatile bool is_keyC;
volatile bool is_keyV;
volatile bool is_keyB;
volatile bool is_keyN;
volatile bool is_keyM;
volatile bool is_keySHIFTRIGHT;
volatile bool is_keyARROWUP;
volatile bool is_keyCTRLLEFT;
volatile bool is_keyAPPLEFT;
volatile bool is_keyALT;
volatile bool is_keySPACE;
volatile bool is_keyALTGR;
volatile bool is_keyAPPRIGHT;
volatile bool is_keyMENU;
volatile bool is_keyCTRLRIGHT;
volatile bool is_keyARROWLEFT;
volatile bool is_keyARROWDOWN;
volatile bool is_keyARROWRIGHT;
volatile bool is_keyPAD0;
volatile bool is_keyPAD1;
volatile bool is_keyPAD2;
volatile bool is_keyPAD3;
volatile bool is_keyPAD4;
volatile bool is_keyPAD5;
volatile bool is_keyPAD6;
volatile bool is_keyPAD7;
volatile bool is_keyPAD8;
volatile bool is_keyPAD9;
volatile bool is_keyPADADD;
volatile bool is_keyPADSUB;
volatile bool is_keyPADMUL;
volatile bool is_keyPADDIV;
//! Fullscreen state of the display
bool is_fullscreen;
float fps_fps, min, max;
unsigned long timer, fps_frames, fps_timer;
#ifdef cimgdisplay_plugin
#include cimgdisplay_plugin
#endif
#ifdef cimgdisplay_plugin1
#include cimgdisplay_plugin1
#endif
#ifdef cimgdisplay_plugin2
#include cimgdisplay_plugin2
#endif
#ifdef cimgdisplay_plugin3
#include cimgdisplay_plugin3
#endif
#ifdef cimgdisplay_plugin4
#include cimgdisplay_plugin4
#endif
#ifdef cimgdisplay_plugin5
#include cimgdisplay_plugin5
#endif
#ifdef cimgdisplay_plugin6
#include cimgdisplay_plugin6
#endif
#ifdef cimgdisplay_plugin7
#include cimgdisplay_plugin7
#endif
#ifdef cimgdisplay_plugin8
#include cimgdisplay_plugin8
#endif
//! Create an empty display window.
CImgDisplay():
width(0),height(0),normalization(0),title(0),
window_x(0),window_y(0),window_width(0),window_height(0),
mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),
min(0),max(0) {}
//! Create a display window with a specified size \p pwidth x \p height.
/** \param dimw Width of the display window.
\param dimh Height of the display window.
\param title Title of the display window.
\param normalization_type Normalization type of the display window (0=none, 1=always, 2=once).
\param fullscreen_flag : Fullscreen mode.
\param closed_flag : Initially visible mode.
A black image will be initially displayed in the display window.
**/
CImgDisplay(const unsigned int dimw, const unsigned int dimh, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false):
width(0),height(0),normalization(0),title(0),
window_x(0),window_y(0),window_width(0),window_height(0),
mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),
min(0),max(0) {
assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
}
//! Create a display window from an image.
/** \param img : Image that will be used to create the display window.
\param title : Title of the display window
\param normalization_type : Normalization type of the display window.
\param fullscreen_flag : Fullscreen mode.
\param closed_flag : Initially visible mode.
**/
template<typename T>
CImgDisplay(const CImg<T>& img, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false):
width(0),height(0),normalization(0),title(0),
window_x(0),window_y(0),window_width(0),window_height(0),
mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
assign(img,title,normalization_type,fullscreen_flag,closed_flag);
}
//! Create a display window from an image list.
/** \param list : The list of images to display.
\param title : Title of the display window
\param normalization_type : Normalization type of the display window.
\param fullscreen_flag : Fullscreen mode.
\param closed_flag : Initially visible mode.
**/
template<typename T>
CImgDisplay(const CImgList<T>& list, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false):
width(0),height(0),normalization(0),title(0),
window_x(0),window_y(0),window_width(0),window_height(0),
mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
assign(list,title,normalization_type,fullscreen_flag,closed_flag);
}
//! Create a display window by copying another one.
/**
\param disp : Display window to copy.
**/
CImgDisplay(const CImgDisplay& disp):
width(0),height(0),normalization(0),title(0),
window_x(0),window_y(0),window_width(0),window_height(0),
mouse_x(0),mouse_y(0),button(*buttons),wheel(0),key(*keys),released_key(*released_keys),
is_closed(true),is_resized(false),is_moved(false),is_event(false),is_fullscreen(false),min(0),max(0) {
assign(disp);
}
//! Destructor.
~CImgDisplay() {
assign();
}
//! Assignment operator.
CImgDisplay& operator=(const CImgDisplay& disp) {
return assign(disp);
}
//! Return true is display is empty.
bool is_empty() const {
return (!width || !height);
}
//! Return true if display is not empty.
operator bool() const {
return !is_empty();
}
//! Return display width.
int dimx() const {
return (int)width;
}
//! Return display height.
int dimy() const {
return (int)height;
}
//! Return display window width.
int window_dimx() const {
return (int)window_width;
}
//! Return display window height.
int window_dimy() const {
return (int)window_height;
}
//! Return X-coordinate of the window.
int window_posx() const {
return window_x;
}
//! Return Y-coordinate of the window.
int window_posy() const {
return window_y;
}
//! Synchronized waiting function. Same as cimg::wait().
CImgDisplay& wait(const unsigned int milliseconds) {
cimg::_sleep(milliseconds,timer);
return *this;
}
- //! Wait for an event occuring on the current display.
+ //! Wait for an event occurring on the current display.
CImgDisplay& wait() {
if (!is_empty()) wait(*this);
return *this;
}
- //! Wait for any event occuring on the display \c disp1.
+ //! Wait for any event occurring on the display \c disp1.
static void wait(CImgDisplay& disp1) {
disp1.is_event = 0;
while (!disp1.is_event) wait_all();
}
- //! Wait for any event occuring either on the display \c disp1 or \c disp2.
+ //! Wait for any event occurring either on the display \c disp1 or \c disp2.
static void wait(CImgDisplay& disp1, CImgDisplay& disp2) {
disp1.is_event = disp2.is_event = 0;
while (!disp1.is_event && !disp2.is_event) wait_all();
}
- //! Wait for any event occuring either on the display \c disp1, \c disp2 or \c disp3.
+ //! Wait for any event occurring either on the display \c disp1, \c disp2 or \c disp3.
static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3) {
disp1.is_event = disp2.is_event = disp3.is_event = 0;
while (!disp1.is_event && !disp2.is_event && !disp3.is_event) wait_all();
}
- //! Wait for any event occuring either on the display \c disp1, \c disp2, \c disp3 or \c disp4.
+ //! Wait for any event occurring either on the display \c disp1, \c disp2, \c disp3 or \c disp4.
static void wait(CImgDisplay& disp1, CImgDisplay& disp2, CImgDisplay& disp3, CImgDisplay& disp4) {
disp1.is_event = disp2.is_event = disp3.is_event = disp4.is_event = 0;
while (!disp1.is_event && !disp2.is_event && !disp3.is_event && !disp4.is_event) wait_all();
}
//! Return the frame per second rate.
float frames_per_second() {
if (!fps_timer) fps_timer = cimg::time();
const float delta = (cimg::time()-fps_timer)/1000.0f;
++fps_frames;
if (delta>=1) {
fps_fps = fps_frames/delta;
fps_frames = 0;
fps_timer = cimg::time();
}
return fps_fps;
}
//! Display an image list CImgList<T> into a display window.
/** First, all images of the list are appended into a single image used for visualization,
then this image is displayed in the current display window.
\param list : The list of images to display.
\param axis : The axis used to append the image for visualization. Can be 'x' (default),'y','z' or 'v'.
\param align : Defines the relative alignment of images when displaying images of different sizes.
- Can be '\p c' (centered, which is the default), '\p p' (top alignment) and '\p n' (bottom aligment).
+ Can be '\p c' (centered, which is the default), '\p p' (top alignment) and '\p n' (bottom alignment).
**/
template<typename T>
CImgDisplay& display(const CImgList<T>& list, const char axis='x', const char align='p') {
return display(list.get_append(axis,align));
}
//! Display an image CImg<T> into a display window.
template<typename T>
CImgDisplay& operator<<(const CImg<T>& img) {
return display(img);
}
//! Display an image CImg<T> into a display window.
template<typename T>
CImgDisplay& operator<<(const CImgList<T>& list) {
return display(list);
}
//! Resize a display window with the size of an image.
/** \param img : Input image. \p image.width and \p image.height give the new dimensions of the display window.
\param redraw : If \p true (default), the current displayed image in the display window will
be bloc-interpolated to fit the new dimensions. If \p false, a black image will be drawn in the resized window.
**/
template<typename T>
CImgDisplay& resize(const CImg<T>& img, const bool redraw=true) {
return resize(img.width,img.height,redraw);
}
//! Resize a display window using the size of the given display \p disp.
CImgDisplay& resize(const CImgDisplay& disp, const bool redraw=true) {
return resize(disp.width,disp.height,redraw);
}
//! Resize a display window in its current size.
CImgDisplay& resize(const bool redraw=true) {
resize(window_width,window_height,redraw);
return *this;
}
//! Set fullscreen mode.
CImgDisplay& fullscreen(const bool redraw=true) {
if (is_empty() || is_fullscreen) return *this;
return toggle_fullscreen(redraw);
}
//! Set normal screen mode.
CImgDisplay& normalscreen(const bool redraw=true) {
if (is_empty() || !is_fullscreen) return *this;
return toggle_fullscreen(redraw);
}
// Inner routine used for fast resizing of buffer to display size.
template<typename t, typename T>
static void _render_resize(const T *ptrs, const unsigned int ws, const unsigned int hs,
t *ptrd, const unsigned int wd, const unsigned int hd) {
unsigned int *const offx = new unsigned int[wd], *const offy = new unsigned int[hd+1], *poffx, *poffy;
float s, curr, old;
s = (float)ws/wd;
poffx = offx; curr = 0; for (unsigned int x=0; x<wd; ++x) { old=curr; curr+=s; *(poffx++) = (unsigned int)curr-(unsigned int)old; }
s = (float)hs/hd;
poffy = offy; curr = 0; for (unsigned int y=0; y<hd; ++y) { old=curr; curr+=s; *(poffy++) = ws*((unsigned int)curr-(unsigned int)old); }
*poffy = 0;
poffy = offy;
{for (unsigned int y=0; y<hd; ) {
const T *ptr = ptrs;
poffx = offx;
for (unsigned int x=0; x<wd; ++x) { *(ptrd++) = *ptr; ptr+=*(poffx++); }
++y;
unsigned int dy=*(poffy++);
for (;!dy && y<hd; cimg_std::memcpy(ptrd, ptrd-wd, sizeof(t)*wd), ++y, ptrd+=wd, dy=*(poffy++)) {}
ptrs+=dy;
}}
delete[] offx; delete[] offy;
}
//! Clear all events of the current display.
CImgDisplay& flush() {
cimg_std::memset((void*)buttons,0,512*sizeof(unsigned int));
cimg_std::memset((void*)keys,0,512*sizeof(unsigned int));
cimg_std::memset((void*)released_keys,0,512*sizeof(unsigned int));
is_keyESC = is_keyF1 = is_keyF2 = is_keyF3 = is_keyF4 = is_keyF5 = is_keyF6 = is_keyF7 = is_keyF8 = is_keyF9 =
is_keyF10 = is_keyF11 = is_keyF12 = is_keyPAUSE = is_key1 = is_key2 = is_key3 = is_key4 = is_key5 = is_key6 =
is_key7 = is_key8 = is_key9 = is_key0 = is_keyBACKSPACE = is_keyINSERT = is_keyHOME = is_keyPAGEUP = is_keyTAB =
is_keyQ = is_keyW = is_keyE = is_keyR = is_keyT = is_keyY = is_keyU = is_keyI = is_keyO = is_keyP = is_keyDELETE =
is_keyEND = is_keyPAGEDOWN = is_keyCAPSLOCK = is_keyA = is_keyS = is_keyD = is_keyF = is_keyG = is_keyH = is_keyJ =
is_keyK = is_keyL = is_keyENTER = is_keySHIFTLEFT = is_keyZ = is_keyX = is_keyC = is_keyV = is_keyB = is_keyN =
is_keyM = is_keySHIFTRIGHT = is_keyARROWUP = is_keyCTRLLEFT = is_keyAPPLEFT = is_keyALT = is_keySPACE = is_keyALTGR = is_keyAPPRIGHT =
is_keyMENU = is_keyCTRLRIGHT = is_keyARROWLEFT = is_keyARROWDOWN = is_keyARROWRIGHT = is_keyPAD0 = is_keyPAD1 = is_keyPAD2 =
is_keyPAD3 = is_keyPAD4 = is_keyPAD5 = is_keyPAD6 = is_keyPAD7 = is_keyPAD8 = is_keyPAD9 = is_keyPADADD = is_keyPADSUB =
is_keyPADMUL = is_keyPADDIV = false;
is_resized = is_moved = is_event = false;
fps_timer = fps_frames = timer = wheel = 0;
mouse_x = mouse_y = -1;
fps_fps = 0;
return *this;
}
// Update 'is_key' fields.
void update_iskey(const unsigned int key, const bool pressed=true) {
#define _cimg_iskey_case(k) if (key==cimg::key##k) is_key##k = pressed;
_cimg_iskey_case(ESC); _cimg_iskey_case(F1); _cimg_iskey_case(F2); _cimg_iskey_case(F3);
_cimg_iskey_case(F4); _cimg_iskey_case(F5); _cimg_iskey_case(F6); _cimg_iskey_case(F7);
_cimg_iskey_case(F8); _cimg_iskey_case(F9); _cimg_iskey_case(F10); _cimg_iskey_case(F11);
_cimg_iskey_case(F12); _cimg_iskey_case(PAUSE); _cimg_iskey_case(1); _cimg_iskey_case(2);
_cimg_iskey_case(3); _cimg_iskey_case(4); _cimg_iskey_case(5); _cimg_iskey_case(6);
_cimg_iskey_case(7); _cimg_iskey_case(8); _cimg_iskey_case(9); _cimg_iskey_case(0);
_cimg_iskey_case(BACKSPACE); _cimg_iskey_case(INSERT); _cimg_iskey_case(HOME);
_cimg_iskey_case(PAGEUP); _cimg_iskey_case(TAB); _cimg_iskey_case(Q); _cimg_iskey_case(W);
_cimg_iskey_case(E); _cimg_iskey_case(R); _cimg_iskey_case(T); _cimg_iskey_case(Y);
_cimg_iskey_case(U); _cimg_iskey_case(I); _cimg_iskey_case(O); _cimg_iskey_case(P);
_cimg_iskey_case(DELETE); _cimg_iskey_case(END); _cimg_iskey_case(PAGEDOWN);
_cimg_iskey_case(CAPSLOCK); _cimg_iskey_case(A); _cimg_iskey_case(S); _cimg_iskey_case(D);
_cimg_iskey_case(F); _cimg_iskey_case(G); _cimg_iskey_case(H); _cimg_iskey_case(J);
_cimg_iskey_case(K); _cimg_iskey_case(L); _cimg_iskey_case(ENTER);
_cimg_iskey_case(SHIFTLEFT); _cimg_iskey_case(Z); _cimg_iskey_case(X); _cimg_iskey_case(C);
_cimg_iskey_case(V); _cimg_iskey_case(B); _cimg_iskey_case(N); _cimg_iskey_case(M);
_cimg_iskey_case(SHIFTRIGHT); _cimg_iskey_case(ARROWUP); _cimg_iskey_case(CTRLLEFT);
_cimg_iskey_case(APPLEFT); _cimg_iskey_case(ALT); _cimg_iskey_case(SPACE); _cimg_iskey_case(ALTGR);
_cimg_iskey_case(APPRIGHT); _cimg_iskey_case(MENU); _cimg_iskey_case(CTRLRIGHT);
_cimg_iskey_case(ARROWLEFT); _cimg_iskey_case(ARROWDOWN); _cimg_iskey_case(ARROWRIGHT);
_cimg_iskey_case(PAD0); _cimg_iskey_case(PAD1); _cimg_iskey_case(PAD2);
_cimg_iskey_case(PAD3); _cimg_iskey_case(PAD4); _cimg_iskey_case(PAD5);
_cimg_iskey_case(PAD6); _cimg_iskey_case(PAD7); _cimg_iskey_case(PAD8);
_cimg_iskey_case(PAD9); _cimg_iskey_case(PADADD); _cimg_iskey_case(PADSUB);
_cimg_iskey_case(PADMUL); _cimg_iskey_case(PADDIV);
}
//! Test if any key has been pressed.
bool is_key(const bool remove=false) {
for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs) { if (remove) *ptrs = 0; return true; }
return false;
}
//! Test if a key has been pressed.
bool is_key(const unsigned int key1, const bool remove) {
for (unsigned int *ptrs=(unsigned int*)keys+512-1; ptrs>=keys; --ptrs) if (*ptrs==key1) { if (remove) *ptrs = 0; return true; }
return false;
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const bool remove) {
const unsigned int seq[] = { key1, key2 };
return is_key(seq,2,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3, const bool remove) {
const unsigned int seq[] = { key1, key2, key3 };
return is_key(seq,3,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
const unsigned int key4, const bool remove) {
const unsigned int seq[] = { key1, key2, key3, key4 };
return is_key(seq,4,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
const unsigned int key4, const unsigned int key5, const bool remove) {
const unsigned int seq[] = { key1, key2, key3, key4, key5 };
return is_key(seq,5,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
const unsigned int key4, const unsigned int key5, const unsigned int key6, const bool remove) {
const unsigned int seq[] = { key1, key2, key3, key4, key5, key6 };
return is_key(seq,6,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
const unsigned int key4, const unsigned int key5, const unsigned int key6,
const unsigned int key7, const bool remove) {
const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7 };
return is_key(seq,7,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
const unsigned int key4, const unsigned int key5, const unsigned int key6,
const unsigned int key7, const unsigned int key8, const bool remove) {
const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8 };
return is_key(seq,8,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int key1, const unsigned int key2, const unsigned int key3,
const unsigned int key4, const unsigned int key5, const unsigned int key6,
const unsigned int key7, const unsigned int key8, const unsigned int key9, const bool remove) {
const unsigned int seq[] = { key1, key2, key3, key4, key5, key6, key7, key8, key9 };
return is_key(seq,9,remove);
}
//! Test if a key sequence has been typed.
bool is_key(const unsigned int *const keyseq, const unsigned int N, const bool remove=true) {
if (keyseq && N) {
const unsigned int *const ps_end = keyseq+N-1, k = *ps_end, *const pk_end = (unsigned int*)keys+1+512-N;
for (unsigned int *pk = (unsigned int*)keys; pk<pk_end; ) {
if (*(pk++)==k) {
bool res = true;
const unsigned int *ps = ps_end, *pk2 = pk;
for (unsigned int i=1; i<N; ++i) res = (*(--ps)==*(pk2++));
if (res) {
if (remove) cimg_std::memset((void*)(pk-1),0,sizeof(unsigned int)*N);
return true;
}
}
}
}
return false;
}
// Find the good width and height of a window to display an image (internal routine).
#define cimg_fitscreen(dx,dy,dz) CImgDisplay::_fitscreen(dx,dy,dz,128,-85,false),CImgDisplay::_fitscreen(dx,dy,dz,128,-85,true)
static unsigned int _fitscreen(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1,
const int dmin=128, const int dmax=-85,const bool return_last=false) {
unsigned int nw = dx + (dz>1?dz:0), nh = dy + (dz>1?dz:0);
const unsigned int
sw = CImgDisplay::screen_dimx(), sh = CImgDisplay::screen_dimy(),
mw = dmin<0?(unsigned int)(sw*-dmin/100):(unsigned int)dmin,
mh = dmin<0?(unsigned int)(sh*-dmin/100):(unsigned int)dmin,
Mw = dmax<0?(unsigned int)(sw*-dmax/100):(unsigned int)dmax,
Mh = dmax<0?(unsigned int)(sh*-dmax/100):(unsigned int)dmax;
if (nw<mw) { nh = nh*mw/nw; nh+=(nh==0); nw = mw; }
if (nh<mh) { nw = nw*mh/nh; nw+=(nw==0); nh = mh; }
if (nw>Mw) { nh = nh*Mw/nw; nh+=(nh==0); nw = Mw; }
if (nh>Mh) { nw = nw*Mh/nh; nw+=(nw==0); nh = Mh; }
if (nw<mw) nw = mw;
if (nh<mh) nh = mh;
if (return_last) return nh;
return nw;
}
// When no display available
//---------------------------
#if cimg_display==0
//! Return the width of the screen resolution.
static int screen_dimx() {
return 0;
}
//! Return the height of the screen resolution.
static int screen_dimy() {
return 0;
}
//! Wait for a window event in any CImg window.
static void wait_all() {}
//! In-place version of the destructor.
CImgDisplay& assign() {
return *this;
}
//! In-place version of the previous constructor.
CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
throw CImgDisplayException("CImgDisplay() : Display has been required but is not available (cimg_display=0)");
const char* avoid_warning = title + dimw + dimh + normalization_type + (int)fullscreen_flag + (int)closed_flag;
(void)avoid_warning;
return *this;
}
//! In-place version of the previous constructor.
template<typename T>
CImgDisplay& assign(const CImg<T>& img, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)");
const char* avoid_warning = title + img.width + normalization_type + (int)fullscreen_flag + (int)closed_flag;
avoid_warning = 0;
return assign(0,0);
}
//! In-place version of the previous constructor.
template<typename T>
CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
throw CImgDisplayException("CImgDisplay()::assign() : Display has been required but is not available (cimg_display=0)");
const char* avoid_warning = title + list.size + normalization_type + (int)fullscreen_flag + (int)closed_flag;
(void)avoid_warning;
return assign(0,0);
}
//! In-place version of the previous constructor.
CImgDisplay& assign(const CImgDisplay &disp) {
return assign(disp.width,disp.height);
}
//! Resize window.
CImgDisplay& resize(const int width, const int height, const bool redraw=true) {
int avoid_warning = width | height | (int)redraw;
(void)avoid_warning;
return *this;
}
//! Toggle fullscreen mode.
CImgDisplay& toggle_fullscreen(const bool redraw=true) {
bool avoid_warning = redraw;
(void)avoid_warning;
return *this;
}
//! Show a closed display.
CImgDisplay& show() {
return *this;
}
//! Close a visible display.
CImgDisplay& close() {
return *this;
}
//! Move window.
CImgDisplay& move(const int posx, const int posy) {
int avoid_warning = posx | posy;
(void)avoid_warning;
return *this;
}
//! Show mouse pointer.
CImgDisplay& show_mouse() {
return *this;
}
//! Hide mouse pointer.
CImgDisplay& hide_mouse() {
return *this;
}
//! Move mouse pointer to a specific location.
CImgDisplay& set_mouse(const int posx, const int posy) {
int avoid_warning = posx | posy;
(void)avoid_warning;
return *this;
}
//! Set the window title.
CImgDisplay& set_title(const char *format, ...) {
const char *avoid_warning = format;
(void)avoid_warning;
return *this;
}
//! Display an image in a window.
template<typename T>
CImgDisplay& display(const CImg<T>& img) {
unsigned int avoid_warning = img.width;
(void)avoid_warning;
return *this;
}
//! Re-paint image content in window.
CImgDisplay& paint() {
return *this;
}
//! Render image buffer into GDI native image format.
template<typename T>
CImgDisplay& render(const CImg<T>& img) {
unsigned int avoid_warning = img.width;
(void)avoid_warning;
return *this;
}
//! Take a snapshot of the display in the specified image.
template<typename T>
const CImgDisplay& snapshot(CImg<T>& img) const {
img.assign(width,height,1,3,0);
return *this;
}
// X11-based display
//-------------------
#elif cimg_display==1
Atom wm_delete_window, wm_delete_protocol;
Window window, background_window;
Colormap colormap;
XImage *image;
void *data;
#ifdef cimg_use_xshm
XShmSegmentInfo *shminfo;
#endif
static int screen_dimx() {
int res = 0;
if (!cimg::X11attr().display) {
Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0"));
if (!disp)
throw CImgDisplayException("CImgDisplay::screen_dimx() : Can't open X11 display.");
res = DisplayWidth(disp,DefaultScreen(disp));
XCloseDisplay(disp);
} else {
#ifdef cimg_use_xrandr
if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution)
res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width;
else
#endif
res = DisplayWidth(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
}
return res;
}
static int screen_dimy() {
int res = 0;
if (!cimg::X11attr().display) {
Display *disp = XOpenDisplay((cimg_std::getenv("DISPLAY") ? cimg_std::getenv("DISPLAY") : ":0.0"));
if (!disp)
throw CImgDisplayException("CImgDisplay::screen_dimy() : Can't open X11 display.");
res = DisplayHeight(disp,DefaultScreen(disp));
XCloseDisplay(disp);
} else {
#ifdef cimg_use_xrandr
if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution)
res = cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height;
else
#endif
res = DisplayHeight(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
}
return res;
}
static void wait_all() {
if (cimg::X11attr().display) {
XLockDisplay(cimg::X11attr().display);
bool flag = true;
XEvent event;
while (flag) {
XNextEvent(cimg::X11attr().display, &event);
for (unsigned int i = 0; i<cimg::X11attr().nb_wins; ++i)
if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window) {
cimg::X11attr().wins[i]->_handle_events(&event);
if (cimg::X11attr().wins[i]->is_event) flag = false;
}
}
XUnlockDisplay(cimg::X11attr().display);
}
}
void _handle_events(const XEvent *const pevent) {
XEvent event = *pevent;
switch (event.type) {
case ClientMessage : {
if ((int)event.xclient.message_type==(int)wm_delete_protocol &&
(int)event.xclient.data.l[0]==(int)wm_delete_window) {
XUnmapWindow(cimg::X11attr().display,window);
mouse_x = mouse_y = -1;
if (button) { cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button = 0; }
if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; }
if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; }
is_closed = is_event = true;
}
} break;
case ConfigureNotify : {
while (XCheckWindowEvent(cimg::X11attr().display,window,StructureNotifyMask,&event)) {}
const unsigned int
nw = event.xconfigure.width,
nh = event.xconfigure.height;
const int
nx = event.xconfigure.x,
ny = event.xconfigure.y;
if (nw && nh && (nw!=window_width || nh!=window_height)) {
window_width = nw;
window_height = nh;
mouse_x = mouse_y = -1;
XResizeWindow(cimg::X11attr().display,window,window_width,window_height);
is_resized = is_event = true;
}
if (nx!=window_x || ny!=window_y) {
window_x = nx;
window_y = ny;
is_moved = is_event = true;
}
} break;
case Expose : {
while (XCheckWindowEvent(cimg::X11attr().display,window,ExposureMask,&event)) {}
_paint(false);
if (is_fullscreen) {
XWindowAttributes attr;
XGetWindowAttributes(cimg::X11attr().display, window, &attr);
while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False);
XSetInputFocus(cimg::X11attr().display, window, RevertToParent, CurrentTime);
}
} break;
case ButtonPress : {
do {
mouse_x = event.xmotion.x;
mouse_y = event.xmotion.y;
if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
switch (event.xbutton.button) {
case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=1; is_event = true; break;
case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=4; is_event = true; break;
case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button|=2; is_event = true; break;
}
} while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonPressMask,&event));
} break;
case ButtonRelease : {
do {
mouse_x = event.xmotion.x;
mouse_y = event.xmotion.y;
if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
switch (event.xbutton.button) {
case 1 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~1U; is_event = true; break;
case 2 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~4U; is_event = true; break;
case 3 : cimg_std::memmove((void*)(buttons+1),(void*)buttons,512-1); button&=~2U; is_event = true; break;
case 4 : ++wheel; is_event = true; break;
case 5 : --wheel; is_event = true; break;
}
} while (XCheckWindowEvent(cimg::X11attr().display,window,ButtonReleaseMask,&event));
} break;
case KeyPress : {
char tmp;
KeySym ksym;
XLookupString(&event.xkey,&tmp,1,&ksym,0);
update_iskey((unsigned int)ksym,true);
if (key) cimg_std::memmove((void*)(keys+1),(void*)keys,512-1);
key = (unsigned int)ksym;
if (released_key) { cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1); released_key = 0; }
is_event = true;
} break;
case KeyRelease : {
char tmp;
KeySym ksym;
XLookupString(&event.xkey,&tmp,1,&ksym,0);
update_iskey((unsigned int)ksym,false);
if (key) { cimg_std::memmove((void*)(keys+1),(void*)keys,512-1); key = 0; }
if (released_key) cimg_std::memmove((void*)(released_keys+1),(void*)released_keys,512-1);
released_key = (unsigned int)ksym;
is_event = true;
} break;
case EnterNotify: {
while (XCheckWindowEvent(cimg::X11attr().display,window,EnterWindowMask,&event)) {}
mouse_x = event.xmotion.x;
mouse_y = event.xmotion.y;
if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
} break;
case LeaveNotify : {
while (XCheckWindowEvent(cimg::X11attr().display,window,LeaveWindowMask,&event)) {}
mouse_x = mouse_y =-1;
is_event = true;
} break;
case MotionNotify : {
while (XCheckWindowEvent(cimg::X11attr().display,window,PointerMotionMask,&event)) {}
mouse_x = event.xmotion.x;
mouse_y = event.xmotion.y;
if (mouse_x<0 || mouse_y<0 || mouse_x>=dimx() || mouse_y>=dimy()) mouse_x = mouse_y = -1;
is_event = true;
} break;
}
}
static void* _events_thread(void *arg) {
arg = 0;
XEvent event;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
for (;;) {
XLockDisplay(cimg::X11attr().display);
bool event_flag = XCheckTypedEvent(cimg::X11attr().display, ClientMessage, &event);
if (!event_flag) event_flag = XCheckMaskEvent(cimg::X11attr().display,
ExposureMask|StructureNotifyMask|ButtonPressMask|
KeyPressMask|PointerMotionMask|EnterWindowMask|LeaveWindowMask|
ButtonReleaseMask|KeyReleaseMask,&event);
if (event_flag) {
for (unsigned int i=0; i<cimg::X11attr().nb_wins; ++i)
if (!cimg::X11attr().wins[i]->is_closed && event.xany.window==cimg::X11attr().wins[i]->window)
cimg::X11attr().wins[i]->_handle_events(&event);
}
XUnlockDisplay(cimg::X11attr().display);
pthread_testcancel();
cimg::sleep(7);
}
return 0;
}
void _set_colormap(Colormap& colormap, const unsigned int dim) {
XColor palette[256];
switch (dim) {
case 1 : { // palette for greyscale images
for (unsigned int index=0; index<256; ++index) {
palette[index].pixel = index;
palette[index].red = palette[index].green = palette[index].blue = (unsigned short)(index<<8);
palette[index].flags = DoRed | DoGreen | DoBlue;
}
} break;
case 2 : { // palette for RG images
for (unsigned int index=0, r=8; r<256; r+=16)
for (unsigned int g=8; g<256; g+=16) {
palette[index].pixel = index;
palette[index].red = palette[index].blue = (unsigned short)(r<<8);
palette[index].green = (unsigned short)(g<<8);
palette[index++].flags = DoRed | DoGreen | DoBlue;
}
} break;
default : { // palette for RGB images
for (unsigned int index=0, r=16; r<256; r+=32)
for (unsigned int g=16; g<256; g+=32)
for (unsigned int b=32; b<256; b+=64) {
palette[index].pixel = index;
palette[index].red = (unsigned short)(r<<8);
palette[index].green = (unsigned short)(g<<8);
palette[index].blue = (unsigned short)(b<<8);
palette[index++].flags = DoRed | DoGreen | DoBlue;
}
}
}
XStoreColors(cimg::X11attr().display,colormap,palette,256);
}
void _map_window() {
XWindowAttributes attr;
XEvent event;
bool exposed = false, mapped = false;
XMapRaised(cimg::X11attr().display,window);
XSync(cimg::X11attr().display,False);
do {
XWindowEvent(cimg::X11attr().display,window,StructureNotifyMask | ExposureMask,&event);
switch (event.type) {
case MapNotify : mapped = true; break;
case Expose : exposed = true; break;
default : XSync(cimg::X11attr().display, False); cimg::sleep(10);
}
} while (!(exposed && mapped));
do {
XGetWindowAttributes(cimg::X11attr().display, window, &attr);
if (attr.map_state!=IsViewable) { XSync(cimg::X11attr().display,False); cimg::sleep(10); }
} while (attr.map_state != IsViewable);
window_x = attr.x;
window_y = attr.y;
}
void _paint(const bool wait_expose=true) {
if (!is_closed) {
if (wait_expose) {
static XEvent event;
event.xexpose.type = Expose;
event.xexpose.serial = 0;
event.xexpose.send_event = True;
event.xexpose.display = cimg::X11attr().display;
event.xexpose.window = window;
event.xexpose.x = 0;
event.xexpose.y = 0;
event.xexpose.width = dimx();
event.xexpose.height = dimy();
event.xexpose.count = 0;
XSendEvent(cimg::X11attr().display, window, False, 0, &event);
} else {
#ifdef cimg_use_xshm
if (shminfo) XShmPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height,False);
else
#endif
XPutImage(cimg::X11attr().display,window,*cimg::X11attr().gc,image,0,0,0,0,width,height);
XSync(cimg::X11attr().display, False);
}
}
}
template<typename T>
void _resize(T foo, const unsigned int ndimx, const unsigned int ndimy, const bool redraw) {
foo = 0;
#ifdef cimg_use_xshm
if (shminfo) {
XShmSegmentInfo *nshminfo = new XShmSegmentInfo;
XImage *nimage = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
cimg::X11attr().nb_bits,ZPixmap,0,nshminfo,ndimx,ndimy);
if (!nimage) {
delete nshminfo;
return;
} else {
nshminfo->shmid = shmget(IPC_PRIVATE, ndimx*ndimy*sizeof(T), IPC_CREAT | 0777);
if (nshminfo->shmid==-1) {
XDestroyImage(nimage);
delete nshminfo;
return;
} else {
nshminfo->shmaddr = nimage->data = (char*)shmat(nshminfo->shmid,0,0);
if (nshminfo->shmaddr==(char*)-1) {
shmctl(nshminfo->shmid,IPC_RMID,0);
XDestroyImage(nimage);
delete nshminfo;
return;
} else {
nshminfo->readOnly = False;
cimg::X11attr().shm_enabled = true;
XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
XShmAttach(cimg::X11attr().display, nshminfo);
XSync(cimg::X11attr().display, False);
XSetErrorHandler(oldXErrorHandler);
if (!cimg::X11attr().shm_enabled) {
shmdt(nshminfo->shmaddr);
shmctl(nshminfo->shmid,IPC_RMID,0);
XDestroyImage(nimage);
delete nshminfo;
return;
} else {
T *const ndata = (T*)nimage->data;
if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy);
else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
XShmDetach(cimg::X11attr().display, shminfo);
XDestroyImage(image);
shmdt(shminfo->shmaddr);
shmctl(shminfo->shmid,IPC_RMID,0);
delete shminfo;
shminfo = nshminfo;
image = nimage;
data = (void*)ndata;
}
}
}
}
} else
#endif
{
T *ndata = (T*)cimg_std::malloc(ndimx*ndimy*sizeof(T));
if (redraw) _render_resize((T*)data,width,height,ndata,ndimx,ndimy);
else cimg_std::memset(ndata,0,sizeof(T)*ndimx*ndimy);
data = (void*)ndata;
XDestroyImage(image);
image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,ndimx,ndimy,8,0);
}
}
void _init_fullscreen() {
background_window = 0;
if (is_fullscreen && !is_closed) {
#ifdef cimg_use_xrandr
int foo;
if (XRRQueryExtension(cimg::X11attr().display,&foo,&foo)) {
XRRRotations(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display), &cimg::X11attr().curr_rotation);
if (!cimg::X11attr().resolutions) {
cimg::X11attr().resolutions = XRRSizes(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display),&foo);
cimg::X11attr().nb_resolutions = (unsigned int)foo;
}
if (cimg::X11attr().resolutions) {
cimg::X11attr().curr_resolution = 0;
for (unsigned int i=0; i<cimg::X11attr().nb_resolutions; ++i) {
const unsigned int
nw = (unsigned int)(cimg::X11attr().resolutions[i].width),
nh = (unsigned int)(cimg::X11attr().resolutions[i].height);
if (nw>=width && nh>=height &&
nw<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].width) &&
nh<=(unsigned int)(cimg::X11attr().resolutions[cimg::X11attr().curr_resolution].height))
cimg::X11attr().curr_resolution = i;
}
if (cimg::X11attr().curr_resolution>0) {
XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display));
XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display),
cimg::X11attr().curr_resolution, cimg::X11attr().curr_rotation, CurrentTime);
XRRFreeScreenConfigInfo(config);
XSync(cimg::X11attr().display, False);
}
}
}
if (!cimg::X11attr().resolutions)
cimg::warn("CImgDisplay::_create_window() : Xrandr extension is not supported by the X server.");
#endif
const unsigned int sx = screen_dimx(), sy = screen_dimy();
XSetWindowAttributes winattr;
winattr.override_redirect = True;
if (sx!=width || sy!=height) {
background_window = XCreateWindow(cimg::X11attr().display,
RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),0,0,
sx,sy,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
const unsigned int bufsize = sx*sy*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
void *background_data = cimg_std::malloc(bufsize);
cimg_std::memset(background_data,0,bufsize);
XImage *background_image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
cimg::X11attr().nb_bits,ZPixmap,0,(char*)background_data,sx,sy,8,0);
XEvent event;
XSelectInput(cimg::X11attr().display,background_window,StructureNotifyMask);
XMapRaised(cimg::X11attr().display,background_window);
do XWindowEvent(cimg::X11attr().display,background_window,StructureNotifyMask,&event);
while (event.type!=MapNotify);
#ifdef cimg_use_xshm
if (shminfo) XShmPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy,False);
else
#endif
XPutImage(cimg::X11attr().display,background_window,*cimg::X11attr().gc,background_image,0,0,0,0,sx,sy);
XWindowAttributes attr;
XGetWindowAttributes(cimg::X11attr().display, background_window, &attr);
while (attr.map_state != IsViewable) XSync(cimg::X11attr().display, False);
XDestroyImage(background_image);
}
}
}
void _desinit_fullscreen() {
if (is_fullscreen) {
XUngrabKeyboard(cimg::X11attr().display,CurrentTime);
#ifdef cimg_use_xrandr
if (cimg::X11attr().resolutions && cimg::X11attr().curr_resolution) {
XRRScreenConfiguration *config = XRRGetScreenInfo(cimg::X11attr().display, DefaultRootWindow(cimg::X11attr().display));
XRRSetScreenConfig(cimg::X11attr().display, config, DefaultRootWindow(cimg::X11attr().display),
0, cimg::X11attr().curr_rotation, CurrentTime);
XRRFreeScreenConfigInfo(config);
XSync(cimg::X11attr().display, False);
cimg::X11attr().curr_resolution = 0;
}
#endif
if (background_window) XDestroyWindow(cimg::X11attr().display,background_window);
background_window = 0;
is_fullscreen = false;
}
}
static int _assign_xshm(Display *dpy, XErrorEvent *error) {
dpy = 0; error = 0;
cimg::X11attr().shm_enabled = false;
return 0;
}
void _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
// Allocate space for window title
const int s = cimg::strlen(ptitle)+1;
char *tmp_title = s?new char[s]:0;
if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));
// Destroy previous display window if existing
if (!is_empty()) assign();
// Open X11 display if necessary.
if (!cimg::X11attr().display) {
static bool xinit_threads = false;
if (!xinit_threads) { XInitThreads(); xinit_threads = true; }
cimg::X11attr().nb_wins = 0;
cimg::X11attr().display = XOpenDisplay((cimg_std::getenv("DISPLAY")?cimg_std::getenv("DISPLAY"):":0.0"));
if (!cimg::X11attr().display)
throw CImgDisplayException("CImgDisplay::_create_window() : Can't open X11 display");
cimg::X11attr().nb_bits = DefaultDepth(cimg::X11attr().display, DefaultScreen(cimg::X11attr().display));
if (cimg::X11attr().nb_bits!=8 && cimg::X11attr().nb_bits!=16 && cimg::X11attr().nb_bits!=24 && cimg::X11attr().nb_bits!=32)
throw CImgDisplayException("CImgDisplay::_create_window() : %u bits mode is not supported "
"(only 8, 16, 24 and 32 bits modes are supported)",cimg::X11attr().nb_bits);
cimg::X11attr().gc = new GC;
*cimg::X11attr().gc = DefaultGC(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
Visual *visual = DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display));
XVisualInfo vtemplate;
vtemplate.visualid = XVisualIDFromVisual(visual);
int nb_visuals;
XVisualInfo *vinfo = XGetVisualInfo(cimg::X11attr().display,VisualIDMask,&vtemplate,&nb_visuals);
if (vinfo && vinfo->red_mask<vinfo->blue_mask) cimg::X11attr().blue_first = true;
cimg::X11attr().byte_order = ImageByteOrder(cimg::X11attr().display);
XFree(vinfo);
XLockDisplay(cimg::X11attr().display);
cimg::X11attr().event_thread = new pthread_t;
pthread_create(cimg::X11attr().event_thread,0,_events_thread,0);
} else XLockDisplay(cimg::X11attr().display);
// Set display variables
width = cimg::min(dimw,(unsigned int)screen_dimx());
height = cimg::min(dimh,(unsigned int)screen_dimy());
normalization = normalization_type<4?normalization_type:3;
is_fullscreen = fullscreen_flag;
window_x = window_y = 0;
is_closed = closed_flag;
title = tmp_title;
flush();
// Create X11 window and palette (if 8bits display)
if (is_fullscreen) {
if (!is_closed) _init_fullscreen();
const unsigned int sx = screen_dimx(), sy = screen_dimy();
XSetWindowAttributes winattr;
winattr.override_redirect = True;
window = XCreateWindow(cimg::X11attr().display,
RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
(sx-width)/2,(sy-height)/2,
width,height,0,0,InputOutput,CopyFromParent,CWOverrideRedirect,&winattr);
} else
window = XCreateSimpleWindow(cimg::X11attr().display,
RootWindow(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
0,0,width,height,2,0,0x0L);
XStoreName(cimg::X11attr().display,window,title?title:" ");
if (cimg::X11attr().nb_bits==8) {
colormap = XCreateColormap(cimg::X11attr().display,window,DefaultVisual(cimg::X11attr().display,
DefaultScreen(cimg::X11attr().display)),AllocAll);
_set_colormap(colormap,3);
XSetWindowColormap(cimg::X11attr().display,window,colormap);
}
window_width = width;
window_height = height;
// Create XImage
const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
#ifdef cimg_use_xshm
shminfo = 0;
if (XShmQueryExtension(cimg::X11attr().display)) {
shminfo = new XShmSegmentInfo;
image = XShmCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
cimg::X11attr().nb_bits,ZPixmap,0,shminfo,width,height);
if (!image) {
delete shminfo;
shminfo = 0;
} else {
shminfo->shmid = shmget(IPC_PRIVATE, bufsize, IPC_CREAT | 0777);
if (shminfo->shmid==-1) {
XDestroyImage(image);
delete shminfo;
shminfo = 0;
} else {
shminfo->shmaddr = image->data = (char*)(data = shmat(shminfo->shmid,0,0));
if (shminfo->shmaddr==(char*)-1) {
shmctl(shminfo->shmid,IPC_RMID,0);
XDestroyImage(image);
delete shminfo;
shminfo = 0;
} else {
shminfo->readOnly = False;
cimg::X11attr().shm_enabled = true;
XErrorHandler oldXErrorHandler = XSetErrorHandler(_assign_xshm);
XShmAttach(cimg::X11attr().display, shminfo);
XSync(cimg::X11attr().display, False);
XSetErrorHandler(oldXErrorHandler);
if (!cimg::X11attr().shm_enabled) {
shmdt(shminfo->shmaddr);
shmctl(shminfo->shmid,IPC_RMID,0);
XDestroyImage(image);
delete shminfo;
shminfo = 0;
}
}
}
}
}
if (!shminfo)
#endif
{
data = cimg_std::malloc(bufsize);
image = XCreateImage(cimg::X11attr().display,DefaultVisual(cimg::X11attr().display,DefaultScreen(cimg::X11attr().display)),
cimg::X11attr().nb_bits,ZPixmap,0,(char*)data,width,height,8,0);
}
wm_delete_window = XInternAtom(cimg::X11attr().display, "WM_DELETE_WINDOW", False);
wm_delete_protocol = XInternAtom(cimg::X11attr().display, "WM_PROTOCOLS", False);
XSetWMProtocols(cimg::X11attr().display, window, &wm_delete_window, 1);
XSelectInput(cimg::X11attr().display,window,
ExposureMask | StructureNotifyMask | ButtonPressMask | KeyPressMask | PointerMotionMask |
EnterWindowMask | LeaveWindowMask | ButtonReleaseMask | KeyReleaseMask);
if (is_fullscreen) XGrabKeyboard(cimg::X11attr().display, window, True, GrabModeAsync, GrabModeAsync, CurrentTime);
cimg::X11attr().wins[cimg::X11attr().nb_wins++]=this;
if (!is_closed) _map_window(); else { window_x = window_y = cimg::type<int>::min(); }
XUnlockDisplay(cimg::X11attr().display);
}
CImgDisplay& assign() {
if (is_empty()) return *this;
XLockDisplay(cimg::X11attr().display);
// Remove display window from event thread list.
unsigned int i;
for (i = 0; i<cimg::X11attr().nb_wins && cimg::X11attr().wins[i]!=this; ++i) {}
for (; i<cimg::X11attr().nb_wins-1; ++i) cimg::X11attr().wins[i] = cimg::X11attr().wins[i+1];
--cimg::X11attr().nb_wins;
// Destroy window, image, colormap and title.
if (is_fullscreen && !is_closed) _desinit_fullscreen();
XDestroyWindow(cimg::X11attr().display,window);
window = 0;
#ifdef cimg_use_xshm
if (shminfo) {
XShmDetach(cimg::X11attr().display, shminfo);
XDestroyImage(image);
shmdt(shminfo->shmaddr);
shmctl(shminfo->shmid,IPC_RMID,0);
delete shminfo;
shminfo = 0;
} else
#endif
XDestroyImage(image);
data = 0; image = 0;
if (cimg::X11attr().nb_bits==8) XFreeColormap(cimg::X11attr().display,colormap);
colormap = 0;
XSync(cimg::X11attr().display, False);
// Reset display variables
if (title) delete[] title;
width = height = normalization = window_width = window_height = 0;
window_x = window_y = 0;
is_fullscreen = false;
is_closed = true;
min = max = 0;
title = 0;
flush();
// End event thread and close display if necessary
XUnlockDisplay(cimg::X11attr().display);
/* The code below was used to close the X11 display when not used anymore,
- unfortunately, since the latest Xorg versions, it randomely hangs, so
+ unfortunately, since the latest Xorg versions, it randomly hangs, so
I prefer to remove it. A fix would be needed anyway.
if (!cimg::X11attr().nb_wins) {
// Kill event thread
pthread_cancel(*cimg::X11attr().event_thread);
XUnlockDisplay(cimg::X11attr().display);
pthread_join(*cimg::X11attr().event_thread,0);
delete cimg::X11attr().event_thread;
cimg::X11attr().event_thread = 0;
XCloseDisplay(cimg::X11attr().display);
cimg::X11attr().display = 0;
delete cimg::X11attr().gc;
cimg::X11attr().gc = 0;
} else XUnlockDisplay(cimg::X11attr().display);
*/
return *this;
}
CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!dimw || !dimh) return assign();
_assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
min = max = 0;
cimg_std::memset(data,0,(cimg::X11attr().nb_bits==8?sizeof(unsigned char):
(cimg::X11attr().nb_bits==16?sizeof(unsigned short):sizeof(unsigned int)))*width*height);
return paint();
}
template<typename T>
CImgDisplay& assign(const CImg<T>& img, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!img) return assign();
CImg<T> tmp;
const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
_assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
if (normalization==2) min = (float)nimg.minmax(max);
return render(nimg).paint();
}
template<typename T>
CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!list) return assign();
CImg<T> tmp;
const CImg<T> img = list.get_append('x','p'),
&nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
_assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
if (normalization==2) min = (float)nimg.minmax(max);
return render(nimg).paint();
}
CImgDisplay& assign(const CImgDisplay& win) {
if (!win) return assign();
_assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
cimg_std::memcpy(data,win.data,(cimg::X11attr().nb_bits==8?sizeof(unsigned char):
cimg::X11attr().nb_bits==16?sizeof(unsigned short):
sizeof(unsigned int))*width*height);
return paint();
}
CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
if (is_empty()) return assign(nwidth,nheight);
const unsigned int
tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
dimx = tmpdimx?tmpdimx:1,
dimy = tmpdimy?tmpdimy:1;
XLockDisplay(cimg::X11attr().display);
if (window_width!=dimx || window_height!=dimy) XResizeWindow(cimg::X11attr().display,window,dimx,dimy);
if (width!=dimx || height!=dimy) switch (cimg::X11attr().nb_bits) {
case 8 : { unsigned char foo = 0; _resize(foo,dimx,dimy,redraw); } break;
case 16 : { unsigned short foo = 0; _resize(foo,dimx,dimy,redraw); } break;
default : { unsigned int foo = 0; _resize(foo,dimx,dimy,redraw); }
}
window_width = width = dimx; window_height = height = dimy;
is_resized = false;
XUnlockDisplay(cimg::X11attr().display);
if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
if (redraw) return paint();
return *this;
}
CImgDisplay& toggle_fullscreen(const bool redraw=true) {
if (is_empty()) return *this;
if (redraw) {
const unsigned int bufsize = width*height*(cimg::X11attr().nb_bits==8?1:(cimg::X11attr().nb_bits==16?2:4));
void *odata = cimg_std::malloc(bufsize);
cimg_std::memcpy(odata,data,bufsize);
assign(width,height,title,normalization,!is_fullscreen,false);
cimg_std::memcpy(data,odata,bufsize);
cimg_std::free(odata);
return paint(false);
}
return assign(width,height,title,normalization,!is_fullscreen,false);
}
CImgDisplay& show() {
if (!is_empty() && is_closed) {
XLockDisplay(cimg::X11attr().display);
if (is_fullscreen) _init_fullscreen();
_map_window();
is_closed = false;
XUnlockDisplay(cimg::X11attr().display);
return paint();
}
return *this;
}
CImgDisplay& close() {
if (!is_empty() && !is_closed) {
XLockDisplay(cimg::X11attr().display);
if (is_fullscreen) _desinit_fullscreen();
XUnmapWindow(cimg::X11attr().display,window);
window_x = window_y = -1;
is_closed = true;
XUnlockDisplay(cimg::X11attr().display);
}
return *this;
}
CImgDisplay& move(const int posx, const int posy) {
if (is_empty()) return *this;
show();
XLockDisplay(cimg::X11attr().display);
XMoveWindow(cimg::X11attr().display,window,posx,posy);
window_x = posx; window_y = posy;
is_moved = false;
XUnlockDisplay(cimg::X11attr().display);
return paint();
}
CImgDisplay& show_mouse() {
if (is_empty()) return *this;
XLockDisplay(cimg::X11attr().display);
XDefineCursor(cimg::X11attr().display,window,None);
XUnlockDisplay(cimg::X11attr().display);
return *this;
}
CImgDisplay& hide_mouse() {
if (is_empty()) return *this;
XLockDisplay(cimg::X11attr().display);
const char pix_data[8] = { 0 };
XColor col;
col.red = col.green = col.blue = 0;
Pixmap pix = XCreateBitmapFromData(cimg::X11attr().display,window,pix_data,8,8);
Cursor cur = XCreatePixmapCursor(cimg::X11attr().display,pix,pix,&col,&col,0,0);
XFreePixmap(cimg::X11attr().display,pix);
XDefineCursor(cimg::X11attr().display,window,cur);
XUnlockDisplay(cimg::X11attr().display);
return *this;
}
CImgDisplay& set_mouse(const int posx, const int posy) {
if (is_empty() || is_closed) return *this;
XLockDisplay(cimg::X11attr().display);
XWarpPointer(cimg::X11attr().display,None,window,0,0,0,0,posx,posy);
mouse_x = posx; mouse_y = posy;
is_moved = false;
XSync(cimg::X11attr().display, False);
XUnlockDisplay(cimg::X11attr().display);
return *this;
}
CImgDisplay& set_title(const char *format, ...) {
if (is_empty()) return *this;
char tmp[1024] = {0};
va_list ap;
va_start(ap, format);
cimg_std::vsprintf(tmp,format,ap);
va_end(ap);
if (title) delete[] title;
const int s = cimg::strlen(tmp)+1;
title = new char[s];
cimg_std::memcpy(title,tmp,s*sizeof(char));
XLockDisplay(cimg::X11attr().display);
XStoreName(cimg::X11attr().display,window,tmp);
XUnlockDisplay(cimg::X11attr().display);
return *this;
}
template<typename T>
CImgDisplay& display(const CImg<T>& img) {
if (img.is_empty())
throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image.");
if (is_empty()) assign(img.width,img.height);
return render(img).paint(false);
}
CImgDisplay& paint(const bool wait_expose=true) {
if (is_empty()) return *this;
XLockDisplay(cimg::X11attr().display);
_paint(wait_expose);
XUnlockDisplay(cimg::X11attr().display);
return *this;
}
template<typename T>
CImgDisplay& render(const CImg<T>& img, const bool flag8=false) {
if (is_empty()) return *this;
if (!img)
throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
img.width,img.height,img.depth,img.dim,img.data);
if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
if (cimg::X11attr().nb_bits==8 && (img.width!=width || img.height!=height)) return render(img.get_resize(width,height,1,-100,1));
if (cimg::X11attr().nb_bits==8 && !flag8 && img.dim==3) return render(img.get_RGBtoLUT(true),true);
const T
*data1 = img.data,
*data2 = (img.dim>1)?img.ptr(0,0,0,1):data1,
*data3 = (img.dim>2)?img.ptr(0,0,0,2):data1;
if (cimg::X11attr().blue_first) cimg::swap(data1,data3);
XLockDisplay(cimg::X11attr().display);
if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
min = max = 0;
switch (cimg::X11attr().nb_bits) {
case 8 : { // 256 color palette, no normalization
_set_colormap(colormap,img.dim);
unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height];
unsigned char *ptrd = (unsigned char*)ndata;
switch (img.dim) {
case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) (*ptrd++) = (unsigned char)*(data1++);
break;
case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++);
(*ptrd++) = (R&0xf0) | (G>>4);
} break;
default : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char R = (unsigned char)*(data1++), G = (unsigned char)*(data2++), B = (unsigned char)*(data3++);
(*ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; }
} break;
case 16 : { // 16 bits colors, no normalization
unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height];
unsigned char *ptrd = (unsigned char*)ndata;
const unsigned int M = 248;
switch (img.dim) {
case 1 :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)*(data1++), G = val>>2;
*(ptrd++) = (val&M) | (G>>3);
*(ptrd++) = (G<<5) | (G>>1);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)*(data1++), G = val>>2;
*(ptrd++) = (G<<5) | (G>>1);
*(ptrd++) = (val&M) | (G>>3);
}
break;
case 2 :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)*(data2++)>>2;
*(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
*(ptrd++) = (G<<5);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)*(data2++)>>2;
*(ptrd++) = (G<<5);
*(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
}
break;
default :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)*(data2++)>>2;
*(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
*(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)*(data2++)>>2;
*(ptrd++) = (G<<5) | ((unsigned char)*(data3++)>>3);
*(ptrd++) = ((unsigned char)*(data1++)&M) | (G>>3);
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; }
} break;
default : { // 24 bits colors, no normalization
unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height];
if (sizeof(int)==4) { // 32 bits int uses optimized version
unsigned int *ptrd = ndata;
switch (img.dim) {
case 1 :
if (cimg::X11attr().byte_order==cimg::endianness())
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)*(data1++);
*(ptrd++) = (val<<16) | (val<<8) | val;
}
else
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)*(data1++)<<8;
*(ptrd++) = (val<<16) | (val<<8) | val;
}
break;
case 2 :
if (cimg::X11attr().byte_order==cimg::endianness())
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
else
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
break;
default :
if (cimg::X11attr().byte_order==cimg::endianness())
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
else
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data3++)<<24) | ((unsigned char)*(data2++)<<16) | ((unsigned char)*(data1++)<<8);
}
} else {
unsigned char *ptrd = (unsigned char*)ndata;
switch (img.dim) {
case 1 :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
*(ptrd++) = 0;
*(ptrd++) = (unsigned char)*(data1++);
*(ptrd++) = 0;
*(ptrd++) = 0;
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
*(ptrd++) = 0;
*(ptrd++) = 0;
*(ptrd++) = (unsigned char)*(data1++);
*(ptrd++) = 0;
}
break;
case 2 :
if (cimg::X11attr().byte_order) cimg::swap(data1,data2);
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
*(ptrd++) = 0;
*(ptrd++) = (unsigned char)*(data2++);
*(ptrd++) = (unsigned char)*(data1++);
*(ptrd++) = 0;
}
break;
default :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
*(ptrd++) = 0;
*(ptrd++) = (unsigned char)*(data1++);
*(ptrd++) = (unsigned char)*(data2++);
*(ptrd++) = (unsigned char)*(data3++);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
*(ptrd++) = (unsigned char)*(data3++);
*(ptrd++) = (unsigned char)*(data2++);
*(ptrd++) = (unsigned char)*(data1++);
*(ptrd++) = 0;
}
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; }
}
};
} else {
if (normalization==3) {
if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
else { min = (float)cimg::type<T>::min(); max = (float)cimg::type<T>::max(); }
} else if ((min>max) || normalization==1) min = (float)img.minmax(max);
const float delta = max-min, mm = delta?delta:1.0f;
switch (cimg::X11attr().nb_bits) {
case 8 : { // 256 color palette, with normalization
_set_colormap(colormap,img.dim);
unsigned char *const ndata = (img.width==width && img.height==height)?(unsigned char*)data:new unsigned char[img.width*img.height];
unsigned char *ptrd = (unsigned char*)ndata;
switch (img.dim) {
case 1 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char R = (unsigned char)(255*(*(data1++)-min)/mm);
*(ptrd++) = R;
} break;
case 2 : for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char
R = (unsigned char)(255*(*(data1++)-min)/mm),
G = (unsigned char)(255*(*(data2++)-min)/mm);
(*ptrd++) = (R&0xf0) | (G>>4);
} break;
default :
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char
R = (unsigned char)(255*(*(data1++)-min)/mm),
G = (unsigned char)(255*(*(data2++)-min)/mm),
B = (unsigned char)(255*(*(data3++)-min)/mm);
*(ptrd++) = (R&0xe0) | ((G>>5)<<2) | (B>>6);
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned char*)data,width,height); delete[] ndata; }
} break;
case 16 : { // 16 bits colors, with normalization
unsigned short *const ndata = (img.width==width && img.height==height)?(unsigned short*)data:new unsigned short[img.width*img.height];
unsigned char *ptrd = (unsigned char*)ndata;
const unsigned int M = 248;
switch (img.dim) {
case 1 :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2;
*(ptrd++) = (val&M) | (G>>3);
*(ptrd++) = (G<<5) | (val>>3);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm), G = val>>2;
*(ptrd++) = (G<<5) | (val>>3);
*(ptrd++) = (val&M) | (G>>3);
}
break;
case 2 :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
*(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
*(ptrd++) = (G<<5);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
*(ptrd++) = (G<<5);
*(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
}
break;
default :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
*(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
*(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char G = (unsigned char)(255*(*(data2++)-min)/mm)>>2;
*(ptrd++) = (G<<5) | ((unsigned char)(255*(*(data3++)-min)/mm)>>3);
*(ptrd++) = ((unsigned char)(255*(*(data1++)-min)/mm)&M) | (G>>3);
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned short*)data,width,height); delete[] ndata; }
} break;
default : { // 24 bits colors, with normalization
unsigned int *const ndata = (img.width==width && img.height==height)?(unsigned int*)data:new unsigned int[img.width*img.height];
if (sizeof(int)==4) { // 32 bits int uses optimized version
unsigned int *ptrd = ndata;
switch (img.dim) {
case 1 :
if (cimg::X11attr().byte_order==cimg::endianness())
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
*(ptrd++) = (val<<16) | (val<<8) | val;
}
else
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
*(ptrd++) = (val<<24) | (val<<16) | (val<<8);
}
break;
case 2 :
if (cimg::X11attr().byte_order==cimg::endianness())
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) =
((unsigned char)(255*(*(data1++)-min)/mm)<<16) |
((unsigned char)(255*(*(data2++)-min)/mm)<<8);
else
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) =
((unsigned char)(255*(*(data2++)-min)/mm)<<16) |
((unsigned char)(255*(*(data1++)-min)/mm)<<8);
break;
default :
if (cimg::X11attr().byte_order==cimg::endianness())
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) =
((unsigned char)(255*(*(data1++)-min)/mm)<<16) |
((unsigned char)(255*(*(data2++)-min)/mm)<<8) |
(unsigned char)(255*(*(data3++)-min)/mm);
else
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) =
((unsigned char)(255*(*(data3++)-min)/mm)<<24) |
((unsigned char)(255*(*(data2++)-min)/mm)<<16) |
((unsigned char)(255*(*(data1++)-min)/mm)<<8);
}
} else {
unsigned char *ptrd = (unsigned char*)ndata;
switch (img.dim) {
case 1 :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
(*ptrd++) = 0;
(*ptrd++) = val;
(*ptrd++) = val;
(*ptrd++) = val;
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
(*ptrd++) = val;
(*ptrd++) = val;
(*ptrd++) = val;
(*ptrd++) = 0;
}
break;
case 2 :
if (cimg::X11attr().byte_order) cimg::swap(data1,data2);
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
(*ptrd++) = 0;
(*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
(*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
(*ptrd++) = 0;
}
break;
default :
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
(*ptrd++) = 0;
(*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
(*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
(*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
(*ptrd++) = (unsigned char)(255*(*(data3++)-min)/mm);
(*ptrd++) = (unsigned char)(255*(*(data2++)-min)/mm);
(*ptrd++) = (unsigned char)(255*(*(data1++)-min)/mm);
(*ptrd++) = 0;
}
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,(unsigned int*)data,width,height); delete[] ndata; }
}
}
}
XUnlockDisplay(cimg::X11attr().display);
return *this;
}
template<typename T>
const CImgDisplay& snapshot(CImg<T>& img) const {
if (is_empty()) img.assign();
else {
img.assign(width,height,1,3);
T
*data1 = img.ptr(0,0,0,0),
*data2 = img.ptr(0,0,0,1),
*data3 = img.ptr(0,0,0,2);
if (cimg::X11attr().blue_first) cimg::swap(data1,data3);
switch (cimg::X11attr().nb_bits) {
case 8 : {
unsigned char *ptrs = (unsigned char*)data;
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = *(ptrs++);
*(data1++) = val&0xe0;
*(data2++) = (val&0x1c)<<3;
*(data3++) = val<<6;
}
} break;
case 16 : {
unsigned char *ptrs = (unsigned char*)data;
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val0 = *(ptrs++), val1 = *(ptrs++);
*(data1++) = val0&0xf8;
*(data2++) = (val0<<5) | ((val1&0xe0)>>5);
*(data3++) = val1<<3;
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned short val0 = *(ptrs++), val1 = *(ptrs++);
*(data1++) = val1&0xf8;
*(data2++) = (val1<<5) | ((val0&0xe0)>>5);
*(data3++) = val0<<3;
}
} break;
default : {
unsigned char *ptrs = (unsigned char*)data;
if (cimg::X11attr().byte_order) for (unsigned int xy = img.width*img.height; xy>0; --xy) {
++ptrs;
*(data1++) = *(ptrs++);
*(data2++) = *(ptrs++);
*(data3++) = *(ptrs++);
} else for (unsigned int xy = img.width*img.height; xy>0; --xy) {
*(data3++) = *(ptrs++);
*(data2++) = *(ptrs++);
*(data1++) = *(ptrs++);
++ptrs;
}
}
}
}
return *this;
}
// Windows-based display
//-----------------------
#elif cimg_display==2
CLIENTCREATESTRUCT ccs;
BITMAPINFO bmi;
unsigned int *data;
DEVMODE curr_mode;
HWND window;
HWND background_window;
HDC hdc;
HANDLE thread;
HANDLE created;
HANDLE mutex;
bool mouse_tracking;
bool visible_cursor;
static int screen_dimx() {
DEVMODE mode;
mode.dmSize = sizeof(DEVMODE);
mode.dmDriverExtra = 0;
EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
return mode.dmPelsWidth;
}
static int screen_dimy() {
DEVMODE mode;
mode.dmSize = sizeof(DEVMODE);
mode.dmDriverExtra = 0;
EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&mode);
return mode.dmPelsHeight;
}
static void wait_all() {
WaitForSingleObject(cimg::Win32attr().wait_event,INFINITE);
}
static LRESULT APIENTRY _handle_events(HWND window,UINT msg,WPARAM wParam,LPARAM lParam) {
#ifdef _WIN64
CImgDisplay* disp = (CImgDisplay*)GetWindowLongPtr(window,GWLP_USERDATA);
#else
CImgDisplay* disp = (CImgDisplay*)GetWindowLong(window,GWL_USERDATA);
#endif
MSG st_msg;
switch (msg) {
case WM_CLOSE :
disp->mouse_x = disp->mouse_y = -1;
disp->window_x = disp->window_y = 0;
if (disp->button) {
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button = 0;
}
if (disp->key) {
cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
disp->key = 0;
}
if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
disp->is_closed = true;
ReleaseMutex(disp->mutex);
ShowWindow(disp->window,SW_HIDE);
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
return 0;
case WM_SIZE : {
while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
WaitForSingleObject(disp->mutex,INFINITE);
const unsigned int nw = LOWORD(lParam),nh = HIWORD(lParam);
if (nw && nh && (nw!=disp->width || nh!=disp->height)) {
disp->window_width = nw;
disp->window_height = nh;
disp->mouse_x = disp->mouse_y = -1;
disp->is_resized = disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
}
ReleaseMutex(disp->mutex);
} break;
case WM_MOVE : {
while (PeekMessage(&st_msg,window,WM_SIZE,WM_SIZE,PM_REMOVE)) {}
WaitForSingleObject(disp->mutex,INFINITE);
const int nx = (int)(short)(LOWORD(lParam)), ny = (int)(short)(HIWORD(lParam));
if (nx!=disp->window_x || ny!=disp->window_y) {
disp->window_x = nx;
disp->window_y = ny;
disp->is_moved = disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
}
ReleaseMutex(disp->mutex);
} break;
case WM_PAINT :
disp->paint();
break;
case WM_KEYDOWN :
disp->update_iskey((unsigned int)wParam,true);
if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
disp->key = (unsigned int)wParam;
if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_MOUSEMOVE : {
while (PeekMessage(&st_msg,window,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE)) {}
disp->mouse_x = LOWORD(lParam);
disp->mouse_y = HIWORD(lParam);
#if (_WIN32_WINNT>=0x0400) && !defined(NOTRACKMOUSEEVENT)
if (!disp->mouse_tracking) {
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = disp->window;
if (TrackMouseEvent(&tme)) disp->mouse_tracking = true;
}
#endif
if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy())
disp->mouse_x = disp->mouse_y = -1;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
} break;
case WM_MOUSELEAVE : {
disp->mouse_x = disp->mouse_y = -1;
disp->mouse_tracking = false;
} break;
case WM_LBUTTONDOWN :
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button|=1U;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_RBUTTONDOWN :
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button|=2U;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_MBUTTONDOWN :
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button|=4U;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case 0x020A : // WM_MOUSEWHEEL:
disp->wheel+=(int)((short)HIWORD(wParam))/120;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
case WM_KEYUP :
disp->update_iskey((unsigned int)wParam,false);
if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; }
if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
disp->released_key = (unsigned int)wParam;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_LBUTTONUP :
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button&=~1U;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_RBUTTONUP :
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button&=~2U;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_MBUTTONUP :
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button&=~4U;
disp->is_event = true;
SetEvent(cimg::Win32attr().wait_event);
break;
case WM_SETCURSOR :
if (disp->visible_cursor) ShowCursor(TRUE);
else ShowCursor(FALSE);
break;
}
return DefWindowProc(window,msg,wParam,lParam);
}
static DWORD WINAPI _events_thread(void* arg) {
CImgDisplay *disp = (CImgDisplay*)(((void**)arg)[0]);
const char *title = (const char*)(((void**)arg)[1]);
MSG msg;
delete[] (void**)arg;
disp->bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
disp->bmi.bmiHeader.biWidth = disp->width;
disp->bmi.bmiHeader.biHeight = -(int)disp->height;
disp->bmi.bmiHeader.biPlanes = 1;
disp->bmi.bmiHeader.biBitCount = 32;
disp->bmi.bmiHeader.biCompression = BI_RGB;
disp->bmi.bmiHeader.biSizeImage = 0;
disp->bmi.bmiHeader.biXPelsPerMeter = 1;
disp->bmi.bmiHeader.biYPelsPerMeter = 1;
disp->bmi.bmiHeader.biClrUsed = 0;
disp->bmi.bmiHeader.biClrImportant = 0;
disp->data = new unsigned int[disp->width*disp->height];
if (!disp->is_fullscreen) { // Normal window
RECT rect;
rect.left = rect.top = 0; rect.right = disp->width-1; rect.bottom = disp->height-1;
AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
const int border1 = (rect.right-rect.left+1-disp->width)/2, border2 = rect.bottom-rect.top+1-disp->height-border1;
disp->window = CreateWindowA("MDICLIENT",title?title:" ",
WS_OVERLAPPEDWINDOW | (disp->is_closed?0:WS_VISIBLE), CW_USEDEFAULT,CW_USEDEFAULT,
disp->width + 2*border1, disp->height + border1 + border2,
0,0,0,&(disp->ccs));
if (!disp->is_closed) {
GetWindowRect(disp->window,&rect);
disp->window_x = rect.left + border1;
disp->window_y = rect.top + border2;
} else disp->window_x = disp->window_y = 0;
} else { // Fullscreen window
const unsigned int sx = screen_dimx(), sy = screen_dimy();
disp->window = CreateWindowA("MDICLIENT",title?title:" ",
WS_POPUP | (disp->is_closed?0:WS_VISIBLE), (sx-disp->width)/2, (sy-disp->height)/2,
disp->width,disp->height,0,0,0,&(disp->ccs));
disp->window_x = disp->window_y = 0;
}
SetForegroundWindow(disp->window);
disp->hdc = GetDC(disp->window);
disp->window_width = disp->width;
disp->window_height = disp->height;
disp->flush();
#ifdef _WIN64
SetWindowLongPtr(disp->window,GWLP_USERDATA,(LONG_PTR)disp);
SetWindowLongPtr(disp->window,GWLP_WNDPROC,(LONG_PTR)_handle_events);
#else
SetWindowLong(disp->window,GWL_USERDATA,(LONG)disp);
SetWindowLong(disp->window,GWL_WNDPROC,(LONG)_handle_events);
#endif
SetEvent(disp->created);
while (GetMessage(&msg,0,0,0)) DispatchMessage(&msg);
return 0;
}
CImgDisplay& _update_window_pos() {
if (!is_closed) {
RECT rect;
rect.left = rect.top = 0; rect.right = width-1; rect.bottom = height-1;
AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1;
GetWindowRect(window,&rect);
window_x = rect.left + border1;
window_y = rect.top + border2;
} else window_x = window_y = -1;
return *this;
}
void _init_fullscreen() {
background_window = 0;
if (is_fullscreen && !is_closed) {
DEVMODE mode;
unsigned int imode = 0, ibest = 0, bestbpp = 0, bw = ~0U, bh = ~0U;
for (mode.dmSize = sizeof(DEVMODE), mode.dmDriverExtra = 0; EnumDisplaySettings(0,imode,&mode); ++imode) {
const unsigned int nw = mode.dmPelsWidth, nh = mode.dmPelsHeight;
if (nw>=width && nh>=height && mode.dmBitsPerPel>=bestbpp && nw<=bw && nh<=bh) {
bestbpp = mode.dmBitsPerPel;
ibest = imode;
bw = nw; bh = nh;
}
}
if (bestbpp) {
curr_mode.dmSize = sizeof(DEVMODE); curr_mode.dmDriverExtra = 0;
EnumDisplaySettings(0,ENUM_CURRENT_SETTINGS,&curr_mode);
EnumDisplaySettings(0,ibest,&mode);
ChangeDisplaySettings(&mode,0);
} else curr_mode.dmSize = 0;
const unsigned int sx = screen_dimx(), sy = screen_dimy();
if (sx!=width || sy!=height) {
CLIENTCREATESTRUCT background_ccs;
background_window = CreateWindowA("MDICLIENT","",WS_POPUP | WS_VISIBLE, 0,0,sx,sy,0,0,0,&background_ccs);
SetForegroundWindow(background_window);
}
} else curr_mode.dmSize = 0;
}
void _desinit_fullscreen() {
if (is_fullscreen) {
if (background_window) DestroyWindow(background_window);
background_window = 0;
if (curr_mode.dmSize) ChangeDisplaySettings(&curr_mode,0);
is_fullscreen = false;
}
}
CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
// Allocate space for window title
const int s = cimg::strlen(ptitle)+1;
char *tmp_title = s?new char[s]:0;
if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));
// Destroy previous window if existing
if (!is_empty()) assign();
// Set display variables
width = cimg::min(dimw,(unsigned int)screen_dimx());
height = cimg::min(dimh,(unsigned int)screen_dimy());
normalization = normalization_type<4?normalization_type:3;
is_fullscreen = fullscreen_flag;
window_x = window_y = 0;
is_closed = closed_flag;
visible_cursor = true;
mouse_tracking = false;
title = tmp_title;
flush();
if (is_fullscreen) _init_fullscreen();
// Create event thread
void *arg = (void*)(new void*[2]);
((void**)arg)[0]=(void*)this;
((void**)arg)[1]=(void*)title;
unsigned long ThreadID = 0;
mutex = CreateMutex(0,FALSE,0);
created = CreateEvent(0,FALSE,FALSE,0);
thread = CreateThread(0,0,_events_thread,arg,0,&ThreadID);
WaitForSingleObject(created,INFINITE);
return *this;
}
CImgDisplay& assign() {
if (is_empty()) return *this;
DestroyWindow(window);
TerminateThread(thread,0);
if (data) delete[] data;
if (title) delete[] title;
if (is_fullscreen) _desinit_fullscreen();
width = height = normalization = window_width = window_height = 0;
window_x = window_y = 0;
is_fullscreen = false;
is_closed = true;
min = max = 0;
title = 0;
flush();
return *this;
}
CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!dimw || !dimh) return assign();
_assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
min = max = 0;
cimg_std::memset(data,0,sizeof(unsigned int)*width*height);
return paint();
}
template<typename T>
CImgDisplay& assign(const CImg<T>& img, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!img) return assign();
CImg<T> tmp;
const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
_assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
if (normalization==2) min = (float)nimg.minmax(max);
return display(nimg);
}
template<typename T>
CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!list) return assign();
CImg<T> tmp;
const CImg<T> img = list.get_append('x','p'),
&nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
_assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
if (normalization==2) min = (float)nimg.minmax(max);
return display(nimg);
}
CImgDisplay& assign(const CImgDisplay& win) {
if (!win) return assign();
_assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
return paint();
}
CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
if (is_empty()) return assign(nwidth,nheight);
const unsigned int
tmpdimx=(nwidth>0)?nwidth:(-nwidth*width/100),
tmpdimy=(nheight>0)?nheight:(-nheight*height/100),
dimx = tmpdimx?tmpdimx:1,
dimy = tmpdimy?tmpdimy:1;
if (window_width!=dimx || window_height!=dimy) {
RECT rect; rect.left = rect.top = 0; rect.right = dimx-1; rect.bottom = dimy-1;
AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
const int cwidth = rect.right-rect.left+1, cheight = rect.bottom-rect.top+1;
SetWindowPos(window,0,0,0,cwidth,cheight,SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS);
}
if (width!=dimx || height!=dimy) {
unsigned int *ndata = new unsigned int[dimx*dimy];
if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
delete[] data;
data = ndata;
bmi.bmiHeader.biWidth = dimx;
bmi.bmiHeader.biHeight = -(int)dimy;
width = dimx;
height = dimy;
}
window_width = dimx; window_height = dimy;
is_resized = false;
if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
if (redraw) return paint();
return *this;
}
CImgDisplay& toggle_fullscreen(const bool redraw=true) {
if (is_empty()) return *this;
if (redraw) {
const unsigned int bufsize = width*height*4;
void *odata = cimg_std::malloc(bufsize);
cimg_std::memcpy(odata,data,bufsize);
assign(width,height,title,normalization,!is_fullscreen,false);
cimg_std::memcpy(data,odata,bufsize);
cimg_std::free(odata);
return paint();
}
return assign(width,height,title,normalization,!is_fullscreen,false);
}
CImgDisplay& show() {
if (is_empty()) return *this;
if (is_closed) {
is_closed = false;
if (is_fullscreen) _init_fullscreen();
ShowWindow(window,SW_SHOW);
_update_window_pos();
}
return paint();
}
CImgDisplay& close() {
if (is_empty()) return *this;
if (!is_closed && !is_fullscreen) {
if (is_fullscreen) _desinit_fullscreen();
ShowWindow(window,SW_HIDE);
is_closed = true;
window_x = window_y = 0;
}
return *this;
}
CImgDisplay& move(const int posx, const int posy) {
if (is_empty()) return *this;
if (!is_fullscreen) {
RECT rect; rect.left = rect.top = 0; rect.right=window_width-1; rect.bottom=window_height-1;
AdjustWindowRect(&rect,WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,false);
const int border1 = (rect.right-rect.left+1-width)/2, border2 = rect.bottom-rect.top+1-height-border1;
SetWindowPos(window,0,posx-border1,posy-border2,0,0,SWP_NOSIZE | SWP_NOZORDER);
} else SetWindowPos(window,0,posx,posy,0,0,SWP_NOSIZE | SWP_NOZORDER);
window_x = posx;
window_y = posy;
is_moved = false;
return show();
}
CImgDisplay& show_mouse() {
if (is_empty()) return *this;
visible_cursor = true;
ShowCursor(TRUE);
SendMessage(window,WM_SETCURSOR,0,0);
return *this;
}
CImgDisplay& hide_mouse() {
if (is_empty()) return *this;
visible_cursor = false;
ShowCursor(FALSE);
SendMessage(window,WM_SETCURSOR,0,0);
return *this;
}
CImgDisplay& set_mouse(const int posx, const int posy) {
if (!is_closed && posx>=0 && posy>=0) {
_update_window_pos();
const int res = (int)SetCursorPos(window_x+posx,window_y+posy);
if (res) { mouse_x = posx; mouse_y = posy; }
}
return *this;
}
CImgDisplay& set_title(const char *format, ...) {
if (is_empty()) return *this;
char tmp[1024] = {0};
va_list ap;
va_start(ap, format);
cimg_std::vsprintf(tmp,format,ap);
va_end(ap);
if (title) delete[] title;
const int s = cimg::strlen(tmp)+1;
title = new char[s];
cimg_std::memcpy(title,tmp,s*sizeof(char));
SetWindowTextA(window, tmp);
return *this;
}
template<typename T>
CImgDisplay& display(const CImg<T>& img) {
if (img.is_empty())
throw CImgArgumentException("CImgDisplay::display() : Cannot display empty image.");
if (is_empty()) assign(img.width,img.height);
return render(img).paint();
}
CImgDisplay& paint() {
if (!is_closed) {
WaitForSingleObject(mutex,INFINITE);
SetDIBitsToDevice(hdc,0,0,width,height,0,0,0,height,data,&bmi,DIB_RGB_COLORS);
ReleaseMutex(mutex);
}
return *this;
}
template<typename T>
CImgDisplay& render(const CImg<T>& img) {
if (is_empty()) return *this;
if (!img)
throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
img.width,img.height,img.depth,img.dim,img.data);
if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
const T
*data1 = img.data,
*data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1,
*data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1;
WaitForSingleObject(mutex,INFINITE);
unsigned int
*const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height],
*ptrd = ndata;
if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
min = max = 0;
switch (img.dim) {
case 1 : {
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)*(data1++);
*(ptrd++) = (val<<16) | (val<<8) | val;
}} break;
case 2 : {
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8);
} break;
default : {
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
}
}
} else {
if (normalization==3) {
if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
else { min = (float)cimg::type<T>::min(); max = (float)cimg::type<T>::max(); }
} else if ((min>max) || normalization==1) min = (float)img.minmax(max);
const float delta = max-min, mm = delta?delta:1.0f;
switch (img.dim) {
case 1 : {
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char val = (unsigned char)(255*(*(data1++)-min)/mm);
*(ptrd++) = (val<<16) | (val<<8) | val;
}} break;
case 2 : {
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char
R = (unsigned char)(255*(*(data1++)-min)/mm),
G = (unsigned char)(255*(*(data2++)-min)/mm);
*(ptrd++) = (R<<16) | (G<<8);
}} break;
default : {
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char
R = (unsigned char)(255*(*(data1++)-min)/mm),
G = (unsigned char)(255*(*(data2++)-min)/mm),
B = (unsigned char)(255*(*(data3++)-min)/mm);
*(ptrd++) = (R<<16) | (G<<8) | B;
}}
}
}
if (ndata!=data) { _render_resize(ndata,img.width,img.height,data,width,height); delete[] ndata; }
ReleaseMutex(mutex);
return *this;
}
template<typename T>
const CImgDisplay& snapshot(CImg<T>& img) const {
if (is_empty()) img.assign();
else {
img.assign(width,height,1,3);
T
*data1 = img.ptr(0,0,0,0),
*data2 = img.ptr(0,0,0,1),
*data3 = img.ptr(0,0,0,2);
unsigned int *ptrs = data;
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned int val = *(ptrs++);
*(data1++) = (unsigned char)(val>>16);
*(data2++) = (unsigned char)((val>>8)&0xFF);
*(data3++) = (unsigned char)(val&0xFF);
}
}
return *this;
}
// MacOSX - Carbon-based display
//-------------------------------
// (Code by Adrien Reboisson && Romain Blei, supervised by Jean-Marie Favreau)
//
#elif cimg_display==3
unsigned int *data; // The bits of the picture
WindowRef carbonWindow; // The opaque carbon window struct associated with the display
MPCriticalRegionID paintCriticalRegion; // Critical section used when drawing
CGColorSpaceRef csr; // Needed for painting
CGDataProviderRef dataProvider; // Needed for painting
CGImageRef imageRef; // The image
UInt32 lastKeyModifiers; // Buffer storing modifiers state
// Define the kind of the queries which can be serialized using the event thread.
typedef enum {
COM_CREATEWINDOW = 0, // Create window query
COM_RELEASEWINDOW, // Release window query
COM_SHOWWINDOW, // Show window query
COM_HIDEWINDOW, // Hide window query
COM_SHOWMOUSE, // Show mouse query
COM_HIDEMOUSE, // Hide mouse query
COM_RESIZEWINDOW, // Resize window query
COM_MOVEWINDOW, // Move window query
COM_SETTITLE, // Set window title query
COM_SETMOUSEPOS // Set cursor position query
} CImgCarbonQueryKind;
// The query destructor send to the event thread.
struct CbSerializedQuery {
CImgDisplay* sender; // Query's sender
CImgCarbonQueryKind kind; // The kind of the query sent to the background thread
short x, y; // X:Y values for move/resize operations
char *c; // Char values for window title
bool createFullScreenWindow; // Boolean value used for full-screen window creation
bool createClosedWindow; // Boolean value used for closed-window creation
bool update; // Boolean value used for resize
- bool success; // Succes or failure of the message, used as return value
+ bool success; // Success or failure of the message, used as return value
CbSerializedQuery(CImgDisplay *s, CImgCarbonQueryKind k):sender(s),kind(k),success(false) {};
inline static CbSerializedQuery BuildReleaseWindowQuery(CImgDisplay* sender) {
return CbSerializedQuery(sender, COM_RELEASEWINDOW);
}
inline static CbSerializedQuery BuildCreateWindowQuery(CImgDisplay* sender, const bool fullscreen, const bool closed) {
CbSerializedQuery q(sender, COM_CREATEWINDOW);
q.createFullScreenWindow = fullscreen;
q.createClosedWindow = closed;
return q;
}
inline static CbSerializedQuery BuildShowWindowQuery(CImgDisplay* sender) {
return CbSerializedQuery(sender, COM_SHOWWINDOW);
}
inline static CbSerializedQuery BuildHideWindowQuery(CImgDisplay* sender) {
return CbSerializedQuery(sender, COM_HIDEWINDOW);
}
inline static CbSerializedQuery BuildShowMouseQuery(CImgDisplay* sender) {
return CbSerializedQuery(sender, COM_SHOWMOUSE);
}
inline static CbSerializedQuery BuildHideMouseQuery(CImgDisplay* sender) {
return CbSerializedQuery(sender, COM_HIDEMOUSE);
}
inline static CbSerializedQuery BuildResizeWindowQuery(CImgDisplay* sender, const int x, const int y, bool update) {
CbSerializedQuery q(sender, COM_RESIZEWINDOW);
q.x = x, q.y = y;
q.update = update;
return q;
}
inline static CbSerializedQuery BuildMoveWindowQuery(CImgDisplay* sender, const int x, const int y) {
CbSerializedQuery q(sender, COM_MOVEWINDOW);
q.x = x, q.y = y;
return q;
}
inline static CbSerializedQuery BuildSetWindowTitleQuery(CImgDisplay* sender, char* c) {
CbSerializedQuery q(sender, COM_SETTITLE);
q.c = c;
return q;
}
inline static CbSerializedQuery BuildSetWindowPosQuery(CImgDisplay* sender, const int x, const int y) {
CbSerializedQuery q(sender, COM_SETMOUSEPOS);
q.x = x, q.y = y;
return q;
}
};
- // Send a serialized query in a synchroneous way.
+ // Send a serialized query in a synchronous way.
// @param c Application Carbon global settings.
// @param m The query to send.
// @result Success/failure of the operation returned by the event thread.
bool _CbSendMsg(cimg::CarbonInfo& c, CbSerializedQuery m) {
MPNotifyQueue(c.com_queue,&m,0,0); // Send the given message
MPWaitOnSemaphore(c.sync_event,kDurationForever); // Wait end of processing notification
return m.success;
}
// Free the window attached to the current display.
// @param c Application Carbon global settings.
// @result Success/failure of the operation.
bool _CbFreeAttachedWindow(cimg::CarbonInfo& c) {
if (!_CbSendMsg(c, CbSerializedQuery::BuildReleaseWindowQuery(this))) // Ask the main thread to free the given window
throw CImgDisplayException("Cannot release window associated with the current display.");
// If a window existed, ask to release it
MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows
--c.windowCount; //Decrement the window count
MPExitCriticalRegion(c.windowListCR); // Unlock the list
return c.windowCount == 0;
}
// Create the window attached to the current display.
// @param c Application Carbon global settings.
// @param title The window title, if any.
- // @param fullscreen Shoud we start in fullscreen mode ?
+ // @param fullscreen Should we start in fullscreen mode ?
// @param create_closed If true, the window is created but not displayed.
// @result Success/failure of the operation.
void _CbCreateAttachedWindow(cimg::CarbonInfo& c, const char* title, const bool fullscreen, const bool create_closed) {
if (!_CbSendMsg(c,CbSerializedQuery::BuildCreateWindowQuery(this,fullscreen,create_closed))) // Ask the main thread to create the window
throw CImgDisplayException("Cannot create the window associated with the current display.");
if (title) set_title(title); // Set the title, if any
// Now we can register the window
MPEnterCriticalRegion(c.windowListCR,kDurationForever); // Lock the list of the windows
++c.windowCount; //Increment the window count
MPExitCriticalRegion(c.windowListCR); // Unlock the list
}
// Destroy graphic objects previously allocated. We free the image, the data provider, then the colorspace.
void _CbFinalizeGraphics() {
CGImageRelease (imageRef); // Release the picture
CGDataProviderRelease(dataProvider); // Release the DP
CGColorSpaceRelease(csr); // Free the cs
}
// Create graphic objects associated to a display. We have to create a colormap, a data provider, and the image.
void _CbInitializeGraphics() {
csr = CGColorSpaceCreateDeviceRGB(); // Create the color space first
if (!csr)
throw CImgDisplayException("CGColorSpaceCreateDeviceRGB() failed.");
// Create the DP
dataProvider = CGDataProviderCreateWithData(0,data,height*width*sizeof(unsigned int),0);
if (!dataProvider)
throw CImgDisplayException("CGDataProviderCreateWithData() failed.");
// ... and finally the image.
if (cimg::endianness())
imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr,
kCGImageAlphaNoneSkipFirst,dataProvider,0,false,kCGRenderingIntentDefault);
else
imageRef = CGImageCreate(width,height,8,32,width*sizeof(unsigned int),csr,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host,dataProvider,0,false,kCGRenderingIntentDefault);
if (!imageRef)
throw CImgDisplayException("CGImageCreate() failed.");
}
// Reinit graphic objects. Free them, then reallocate all.
// This is used when image bounds are changed or when data source get invalid.
void _CbReinitGraphics() {
MPEnterCriticalRegion(paintCriticalRegion, kDurationForever);
_CbFinalizeGraphics();
_CbInitializeGraphics();
MPExitCriticalRegion(paintCriticalRegion);
}
// Convert a point having global coordonates into the window coordonates.
// We use this function to replace the deprecated GlobalToLocal QuickDraw API.
// @param mouseEvent The mouse event which triggered the event handler.
- // @param window The window where the event occured.
+ // @param window The window where the event occurred.
// @param point The modified point struct.
// @result True if the point struct has been converted successfully.
static bool _CbToLocalPointFromMouseEvent(EventRef mouseEvent, WindowRef window, HIPoint* point) {
Rect bounds;
if (GetWindowBounds(window,kWindowStructureRgn,&bounds)==noErr) {
point->x -= bounds.left;
point->y -= bounds.top;
HIViewRef view = NULL;
if (HIViewGetViewForMouseEvent(HIViewGetRoot(window),mouseEvent,&view)==noErr)
return HIViewConvertPoint(point, NULL, view) == noErr;
}
return false;
}
static int screen_dimx() {
return CGDisplayPixelsWide(kCGDirectMainDisplay);
}
static int screen_dimy() {
return CGDisplayPixelsHigh(kCGDirectMainDisplay);
}
CImgDisplay& assign(const unsigned int dimw, const unsigned int dimh, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!dimw || !dimh) return assign();
_assign(dimw,dimh,title,normalization_type,fullscreen_flag,closed_flag);
min = max = 0;
cimg_std::memset(data,0,sizeof(unsigned int)*width*height);
return paint();
}
template<typename T>
CImgDisplay& assign(const CImg<T>& img, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!img) return assign();
CImg<T> tmp;
const CImg<T>& nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
_assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
if (normalization==2) min = (float)nimg.minmax(max);
return display(nimg);
}
template<typename T>
CImgDisplay& assign(const CImgList<T>& list, const char *title=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
if (!list) return assign();
CImg<T> tmp;
const CImg<T> img = list.get_append('x','p'),
&nimg = (img.depth==1)?img:(tmp=img.get_projections2d(img.width/2,img.height/2,img.depth/2));
_assign(nimg.width,nimg.height,title,normalization_type,fullscreen_flag,closed_flag);
if (normalization==2) min = (float)nimg.minmax(max);
return display(nimg);
}
CImgDisplay& assign(const CImgDisplay &win) {
if (!win) return assign();
_assign(win.width,win.height,win.title,win.normalization,win.is_fullscreen,win.is_closed);
cimg_std::memcpy(data,win.data,sizeof(unsigned int)*width*height);
return paint();
}
template<typename T>
CImgDisplay& display(const CImg<T>& img) {
if (is_empty()) assign(img.width,img.height);
return render(img).paint();
}
CImgDisplay& resize(const int nwidth, const int nheight, const bool redraw=true) {
if (!nwidth || !nheight || (is_empty() && (nwidth<0 || nheight<0))) return assign();
if (is_empty()) return assign(nwidth,nheight);
const unsigned int
tmpdimx = (nwidth>0)?nwidth:(-nwidth*width/100),
tmpdimy = (nheight>0)?nheight:(-nheight*height/100),
dimx = tmpdimx?tmpdimx:1,
dimy = tmpdimy?tmpdimy:1;
cimg::CarbonInfo& c = cimg::CarbonAttr();
if ((window_width!=dimx || window_height!=dimy) &&
!_CbSendMsg(c,CbSerializedQuery::BuildResizeWindowQuery(this,dimx,dimy,redraw)))
throw CImgDisplayException("CImgDisplay::resize() : Cannot resize the window associated to the current display.");
if (width!=dimx || height!=dimy) {
unsigned int *ndata = new unsigned int[dimx*dimy];
if (redraw) _render_resize(data,width,height,ndata,dimx,dimy);
else cimg_std::memset(ndata,0x80,sizeof(unsigned int)*dimx*dimy);
unsigned int const* old_data = data;
data = ndata;
delete[] old_data;
_CbReinitGraphics();
}
window_width = width = dimx; window_height = height = dimy;
is_resized = false;
if (is_fullscreen) move((screen_dimx()-width)/2,(screen_dimy()-height)/2);
if (redraw) return paint();
return *this;
}
CImgDisplay& move(const int posx, const int posy) {
if (is_empty()) return *this;
if (!is_fullscreen) {
// If the operation succeeds, window_x and window_y are updated by the event thread
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Send the query
if (!_CbSendMsg(c,CbSerializedQuery::BuildMoveWindowQuery(this,posx,posy)))
throw CImgDisplayException("CImgDisplay::move() : Cannot move the window associated to the current display.");
}
return show();
}
CImgDisplay& set_mouse(const int posx, const int posy) {
if (!is_closed && posx>=0 && posy>=0) {
// If the operation succeeds, mouse_x and mouse_y are updated by the event thread
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Send the query
if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowPosQuery(this,posx,posy)))
throw CImgDisplayException("CImgDisplay::set_mouse() : Cannot set the mouse position to the current display.");
}
return *this;
}
CImgDisplay& hide_mouse() {
if (is_empty()) return *this;
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Send the query
if (!_CbSendMsg(c,CbSerializedQuery::BuildHideMouseQuery(this)))
throw CImgDisplayException("CImgDisplay::hide_mouse() : Cannot hide the mouse associated to the current display.");
return *this;
}
CImgDisplay& show_mouse() {
if (is_empty()) return *this;
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Send the query
if (!_CbSendMsg(c,CbSerializedQuery::BuildShowMouseQuery(this)))
throw CImgDisplayException("CImgDisplay::show_mouse() : Cannot show the mouse associated to the current display.");
return *this;
}
static void wait_all() {
cimg::CarbonInfo& c = cimg::CarbonAttr();
MPWaitOnSemaphore(c.wait_event,kDurationForever);
}
CImgDisplay& show() {
if (is_empty()) return *this;
if (is_closed) {
cimg::CarbonInfo& c = cimg::CarbonAttr();
if (!_CbSendMsg(c,CbSerializedQuery::BuildShowWindowQuery(this)))
throw CImgDisplayException("CImgDisplay::show() : Cannot show the window associated to the current display.");
}
return paint();
}
CImgDisplay& close() {
if (is_empty()) return *this;
if (!is_closed && !is_fullscreen) {
cimg::CarbonInfo& c = cimg::CarbonAttr();
// If the operation succeeds, window_x and window_y are updated on the event thread
if (!_CbSendMsg(c,CbSerializedQuery::BuildHideWindowQuery(this)))
throw CImgDisplayException("CImgDisplay::close() : Cannot hide the window associated to the current display.");
}
return *this;
}
CImgDisplay& set_title(const char *format, ...) {
if (is_empty()) return *this;
char tmp[1024] = {0};
va_list ap;
va_start(ap, format);
cimg_std::vsprintf(tmp,format,ap);
va_end(ap);
if (title) delete[] title;
const int s = cimg::strlen(tmp)+1;
title = new char[s];
cimg_std::memcpy(title,tmp,s*sizeof(char));
cimg::CarbonInfo& c = cimg::CarbonAttr();
if (!_CbSendMsg(c,CbSerializedQuery::BuildSetWindowTitleQuery(this,tmp)))
throw CImgDisplayException("CImgDisplay::set_title() : Cannot set the window title associated to the current display.");
return *this;
}
CImgDisplay& paint() {
if (!is_closed) {
MPEnterCriticalRegion(paintCriticalRegion,kDurationForever);
CGrafPtr portPtr = GetWindowPort(carbonWindow);
CGContextRef currentContext = 0;
QDBeginCGContext(portPtr,&currentContext);
CGContextSetRGBFillColor(currentContext,255,255,255,255);
CGContextFillRect(currentContext,CGRectMake(0,0,window_width,window_height));
CGContextDrawImage(currentContext,CGRectMake(0,int(window_height-height)<0?0:window_height-height,width,height),imageRef);
CGContextFlush(currentContext);
QDEndCGContext(portPtr, &currentContext);
MPExitCriticalRegion(paintCriticalRegion);
}
return *this;
}
template<typename T>
CImgDisplay& render(const CImg<T>& img) {
if (is_empty()) return *this;
if (!img)
throw CImgArgumentException("CImgDisplay::_render_image() : Specified input image (%u,%u,%u,%u,%p) is empty.",
img.width,img.height,img.depth,img.dim,img.data);
if (img.depth!=1) return render(img.get_projections2d(img.width/2,img.height/2,img.depth/2));
const T
*data1 = img.data,
*data2 = (img.dim>=2)?img.ptr(0,0,0,1):data1,
*data3 = (img.dim>=3)?img.ptr(0,0,0,2):data1;
MPEnterCriticalRegion(paintCriticalRegion, kDurationForever);
unsigned int
*const ndata = (img.width==width && img.height==height)?data:new unsigned int[img.width*img.height],
*ptrd = ndata;
if (!normalization || (normalization==3 && cimg::type<T>::string()==cimg::type<unsigned char>::string())) {
min = max = 0;
for (unsigned int xy = img.width*img.height; xy>0; --xy)
*(ptrd++) = ((unsigned char)*(data1++)<<16) | ((unsigned char)*(data2++)<<8) | (unsigned char)*(data3++);
} else {
if (normalization==3) {
if (cimg::type<T>::is_float()) min = (float)img.minmax(max);
else {
min = (float)cimg::type<T>::min();
max = (float)cimg::type<T>::max();
}
} else if ((min>max) || normalization==1) min = (float)img.minmax(max);
const float delta = max-min, mm = delta?delta:1.0f;
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned char
R = (unsigned char)(255*(*(data1++)-min)/mm),
G = (unsigned char)(255*(*(data2++)-min)/mm),
B = (unsigned char)(255*(*(data3++)-min)/mm);
*(ptrd++) = (R<<16) | (G<<8) | (B);
}
}
if (ndata!=data) {
_render_resize(ndata,img.width,img.height,data,width,height);
delete[] ndata;
}
MPExitCriticalRegion(paintCriticalRegion);
return *this;
}
template<typename T>
const CImgDisplay& snapshot(CImg<T>& img) const {
if (is_empty()) img.assign();
else {
img.assign(width,height,1,3);
T
*data1 = img.ptr(0,0,0,0),
*data2 = img.ptr(0,0,0,1),
*data3 = img.ptr(0,0,0,2);
unsigned int *ptrs = data;
for (unsigned int xy = img.width*img.height; xy>0; --xy) {
const unsigned int val = *(ptrs++);
*(data1++) = (unsigned char)(val>>16);
*(data2++) = (unsigned char)((val>>8)&0xFF);
*(data3++) = (unsigned char)(val&0xFF);
}
}
return *this;
}
CImgDisplay& toggle_fullscreen(const bool redraw=true) {
if (is_empty()) return *this;
if (redraw) {
const unsigned int bufsize = width*height*4;
void *odata = cimg_std::malloc(bufsize);
cimg_std::memcpy(odata,data,bufsize);
assign(width,height,title,normalization,!is_fullscreen,false);
cimg_std::memcpy(data,odata,bufsize);
cimg_std::free(odata);
return paint();
}
return assign(width,height,title,normalization,!is_fullscreen,false);
}
static OSStatus CarbonEventHandler(EventHandlerCallRef myHandler, EventRef theEvent, void* userData) {
OSStatus result = eventNotHandledErr;
CImgDisplay* disp = (CImgDisplay*) userData;
(void)myHandler; // Avoid "unused parameter"
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Gets the associated display
if (disp) {
// Window events are always handled
if (GetEventClass(theEvent)==kEventClassWindow) switch (GetEventKind (theEvent)) {
case kEventWindowClose :
disp->mouse_x = disp->mouse_y = -1;
disp->window_x = disp->window_y = 0;
if (disp->button) {
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
disp->button = 0;
}
if (disp->key) {
cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
disp->key = 0;
}
if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
disp->is_closed = true;
HideWindow(disp->carbonWindow);
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
result = noErr;
break;
// There is a lot of case where we have to redraw our window
case kEventWindowBoundsChanging :
case kEventWindowResizeStarted :
case kEventWindowCollapsed : //Not sure it's really needed :-)
break;
case kEventWindowZoomed :
case kEventWindowExpanded :
case kEventWindowResizeCompleted : {
MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever);
// Now we retrieve the new size of the window
Rect newContentRect;
GetWindowBounds(disp->carbonWindow,kWindowContentRgn,&newContentRect);
const unsigned int
nw = (unsigned int)(newContentRect.right - newContentRect.left),
nh = (unsigned int)(newContentRect.bottom - newContentRect.top);
// Then we update CImg internal settings
if (nw && nh && (nw!=disp->width || nh!=disp->height)) {
disp->window_width = nw;
disp->window_height = nh;
disp->mouse_x = disp->mouse_y = -1;
disp->is_resized = true;
}
disp->is_event = true;
MPExitCriticalRegion(disp->paintCriticalRegion);
disp->paint(); // Coords changed, must update the screen
MPSignalSemaphore(c.wait_event);
result = noErr;
} break;
case kEventWindowDragStarted :
case kEventWindowDragCompleted : {
MPEnterCriticalRegion(disp->paintCriticalRegion, kDurationForever);
// Now we retrieve the new size of the window
Rect newContentRect ;
GetWindowBounds(disp->carbonWindow,kWindowStructureRgn,&newContentRect);
const int nx = (int)(newContentRect.left), ny = (int)(newContentRect.top);
// Then we update CImg internal settings
if (nx!=disp->window_x || ny!=disp->window_y) {
disp->window_x = nx;
disp->window_y = ny;
disp->is_moved = true;
}
disp->is_event = true;
MPExitCriticalRegion(disp->paintCriticalRegion);
disp->paint(); // Coords changed, must update the screen
MPSignalSemaphore(c.wait_event);
result = noErr;
} break;
case kEventWindowPaint :
disp->paint();
break;
}
switch (GetEventClass(theEvent)) {
case kEventClassKeyboard : {
if (GetEventKind(theEvent)==kEventRawKeyModifiersChanged) {
// Apple has special keys named "notifiers", we have to convert this (exotic ?) key handling into the regular CImg processing.
UInt32 newModifiers;
if (GetEventParameter(theEvent,kEventParamKeyModifiers,typeUInt32,0,sizeof(UInt32),0,&newModifiers)==noErr) {
int newKeyCode = -1;
UInt32 changed = disp->lastKeyModifiers^newModifiers;
// Find what changed here
if ((changed & rightShiftKey)!=0) newKeyCode = cimg::keySHIFTRIGHT;
if ((changed & shiftKey)!=0) newKeyCode = cimg::keySHIFTLEFT;
// On the Mac, the "option" key = the ALT key
if ((changed & (optionKey | rightOptionKey))!=0) newKeyCode = cimg::keyALTGR;
if ((changed & controlKey)!=0) newKeyCode = cimg::keyCTRLLEFT;
if ((changed & rightControlKey)!=0) newKeyCode = cimg::keyCTRLRIGHT;
if ((changed & cmdKey)!=0) newKeyCode = cimg::keyAPPLEFT;
if ((changed & alphaLock)!=0) newKeyCode = cimg::keyCAPSLOCK;
if (newKeyCode != -1) { // Simulate keystroke
if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
disp->key = (int)newKeyCode;
}
disp->lastKeyModifiers = newModifiers; // Save current state
}
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
}
if (GetEventKind(theEvent)==kEventRawKeyDown || GetEventKind(theEvent)==kEventRawKeyRepeat) {
char keyCode;
if (GetEventParameter(theEvent,kEventParamKeyMacCharCodes,typeChar,0,sizeof(keyCode),0,&keyCode)==noErr) {
disp->update_iskey((unsigned int)keyCode,true);
if (disp->key) cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1);
disp->key = (unsigned int)keyCode;
if (disp->released_key) { cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1); disp->released_key = 0; }
}
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
}
} break;
case kEventClassMouse :
switch (GetEventKind(theEvent)) {
case kEventMouseDragged :
// When you push the main button on the Apple mouse while moving it, you got NO kEventMouseMoved msg,
// but a kEventMouseDragged one. So we merge them here.
case kEventMouseMoved :
HIPoint point;
if (GetEventParameter(theEvent,kEventParamMouseLocation,typeHIPoint,0,sizeof(point),0,&point)==noErr) {
if (_CbToLocalPointFromMouseEvent(theEvent,disp->carbonWindow,&point)) {
disp->mouse_x = (int)point.x;
disp->mouse_y = (int)point.y;
if (disp->mouse_x<0 || disp->mouse_y<0 || disp->mouse_x>=disp->dimx() || disp->mouse_y>=disp->dimy())
disp->mouse_x = disp->mouse_y = -1;
} else disp->mouse_x = disp->mouse_y = -1;
}
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
break;
case kEventMouseDown :
UInt16 btn;
if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) {
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
if (btn==kEventMouseButtonPrimary) disp->button|=1U;
// For those who don't have a multi-mouse button (as me), I think it's better to allow the user
// to emulate a right click by using the Control key
if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)
cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Down]");
if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button|=2U;
if (btn==kEventMouseButtonTertiary) disp->button|=4U;
}
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
break;
case kEventMouseWheelMoved :
EventMouseWheelAxis wheelax;
SInt32 delta;
if (GetEventParameter(theEvent,kEventParamMouseWheelAxis,typeMouseWheelAxis,0,sizeof(wheelax),0,&wheelax)==noErr)
if (wheelax==kEventMouseWheelAxisY) {
if (GetEventParameter(theEvent,kEventParamMouseWheelDelta,typeLongInteger,0,sizeof(delta),0,&delta)==noErr)
if (delta>0) disp->wheel+=delta/120; //FIXME: why 120 ?
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
}
break;
}
}
switch (GetEventClass(theEvent)) {
case kEventClassKeyboard :
if (GetEventKind(theEvent)==kEventRawKeyUp) {
UInt32 keyCode;
if (GetEventParameter(theEvent,kEventParamKeyCode,typeUInt32,0,sizeof(keyCode),0,&keyCode)==noErr) {
disp->update_iskey((unsigned int)keyCode,false);
if (disp->key) { cimg_std::memmove((void*)(disp->keys+1),(void*)disp->keys,512-1); disp->key = 0; }
if (disp->released_key) cimg_std::memmove((void*)(disp->released_keys+1),(void*)disp->released_keys,512-1);
disp->released_key = (int)keyCode;
}
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
}
break;
case kEventClassMouse :
switch (GetEventKind(theEvent)) {
case kEventMouseUp :
UInt16 btn;
if (GetEventParameter(theEvent,kEventParamMouseButton,typeMouseButton,0,sizeof(btn),0,&btn)==noErr) {
cimg_std::memmove((void*)(disp->buttons+1),(void*)disp->buttons,512-1);
if (btn==kEventMouseButtonPrimary) disp->button&=~1U;
// See note in kEventMouseDown handler.
if ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)
cimg::warn("CImgDisplay::CarbonEventHandler() : Will emulate right click now [Up]");
if (btn==kEventMouseButtonSecondary || ((disp->lastKeyModifiers & (controlKey | rightControlKey))!=0)) disp->button&=~2U;
if (btn==kEventMouseButtonTertiary) disp->button&=~2U;
}
disp->is_event = true;
MPSignalSemaphore(c.wait_event);
break;
}
}
}
return (result);
}
static void* _events_thread(void* args) {
(void)args; // Make the compiler happy
cimg::CarbonInfo& c = cimg::CarbonAttr();
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,0);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,0);
MPSignalSemaphore(c.sync_event); // Notify the caller that all goes fine
EventRef theEvent;
EventTargetRef theTarget;
OSStatus err;
CbSerializedQuery* query;
theTarget = GetEventDispatcherTarget();
// Enter in the main loop
while (true) {
pthread_testcancel(); /* Check if cancelation happens */
err = ReceiveNextEvent(0,0,kDurationImmediate,true,&theEvent); // Fetch new events
if (err==noErr) { // Received a carbon event, so process it !
SendEventToEventTarget (theEvent, theTarget);
ReleaseEvent(theEvent);
} else if (err == eventLoopTimedOutErr) { // There is no event to process, so check if there is new messages to process
OSStatus r =MPWaitOnQueue(c.com_queue,(void**)&query,0,0,10*kDurationMillisecond);
if (r!=noErr) continue; //nothing in the queue or an error.., bye
// If we're here, we've something to do now.
if (query) {
switch (query->kind) {
case COM_SETMOUSEPOS : { // change the cursor position
query->success = CGDisplayMoveCursorToPoint(kCGDirectMainDisplay,CGPointMake(query->sender->window_x+query->x,query->sender->window_y+query->y))
== kCGErrorSuccess;
if (query->success) {
query->sender->mouse_x = query->x;
query->sender->mouse_y = query->y;
} else cimg::warn("CImgDisplay::_events_thread() : CGDisplayMoveCursorToPoint failed.");
} break;
case COM_SETTITLE : { // change the title bar caption
CFStringRef windowTitle = CFStringCreateWithCString(0,query->c,kCFStringEncodingMacRoman);
query->success = SetWindowTitleWithCFString(query->sender->carbonWindow,windowTitle)==noErr;
if (!query->success)
cimg::warn("CImgDisplay::_events_thread() : SetWindowTitleWithCFString failed.");
CFRelease(windowTitle);
} break;
case COM_RESIZEWINDOW : { // Resize a window
SizeWindow(query->sender->carbonWindow,query->x,query->y,query->update);
// If the window has been resized successfully, update display informations
query->sender->window_width = query->x;
query->sender->window_height = query->y;
query->success = true;
} break;
case COM_MOVEWINDOW : { // Move a window
MoveWindow(query->sender->carbonWindow,query->x,query->y,false);
query->sender->window_x = query->x;
query->sender->window_y = query->y;
query->sender->is_moved = false;
query->success = true;
} break;
case COM_SHOWMOUSE : { // Show the mouse
query->success = CGDisplayShowCursor(kCGDirectMainDisplay)==noErr;
if (!query->success)
cimg::warn("CImgDisplay::_events_thread() : CGDisplayShowCursor failed.");
} break;
case COM_HIDEMOUSE : { // Hide the mouse
query->success = CGDisplayHideCursor(kCGDirectMainDisplay)==noErr;
if (!query->success)
cimg::warn("CImgDisplay::_events_thread() : CGDisplayHideCursor failed.");
} break;
case COM_SHOWWINDOW : { // We've to show a window
ShowWindow(query->sender->carbonWindow);
query->success = true;
query->sender->is_closed = false;
} break;
case COM_HIDEWINDOW : { // We've to show a window
HideWindow(query->sender->carbonWindow);
query->sender->is_closed = true;
query->sender->window_x = query->sender->window_y = 0;
query->success = true;
} break;
case COM_RELEASEWINDOW : { // We have to release a given window handle
query->success = true;
CFRelease(query->sender->carbonWindow);
} break;
case COM_CREATEWINDOW : { // We have to create a window
query->success = true;
WindowAttributes windowAttrs;
Rect contentRect;
if (query->createFullScreenWindow) {
// To simulate a "true" full screen, we remove menus and close boxes
windowAttrs = (1L << 9); //Why ? kWindowNoTitleBarAttribute seems to be not defined on 10.3
// Define a full screen bound rect
SetRect(&contentRect,0,0,CGDisplayPixelsWide(kCGDirectMainDisplay),CGDisplayPixelsHigh(kCGDirectMainDisplay));
} else { // Set the window size
SetRect(&contentRect,0,0,query->sender->width,query->sender->height); // Window will be centered with RepositionWindow.
// Use default attributes
windowAttrs = kWindowStandardDocumentAttributes | kWindowStandardHandlerAttribute | kWindowInWindowMenuAttribute | kWindowLiveResizeAttribute;
}
// Update window position
if (query->createClosedWindow) query->sender->window_x = query->sender->window_y = 0;
else {
query->sender->window_x = contentRect.left;
query->sender->window_y = contentRect.top;
}
// Update window flags
query->sender->window_width = query->sender->width;
query->sender->window_height = query->sender->height;
query->sender->flush();
// Create the window
if (CreateNewWindow(kDocumentWindowClass,windowAttrs,&contentRect,&query->sender->carbonWindow)!=noErr) {
query->success = false;
cimg::warn("CImgDisplay::_events_thread() : CreateNewWindow() failed.");
}
// Send it to the foreground
if (RepositionWindow(query->sender->carbonWindow,0,kWindowCenterOnMainScreen)!=noErr) query->success = false;
// Show it, if needed
if (!query->createClosedWindow) ShowWindow(query->sender->carbonWindow);
// Associate a valid event handler
EventTypeSpec eventList[] = {
{ kEventClassWindow, kEventWindowClose },
{ kEventClassWindow, kEventWindowResizeStarted },
{ kEventClassWindow, kEventWindowResizeCompleted },
{ kEventClassWindow, kEventWindowDragStarted},
{ kEventClassWindow, kEventWindowDragCompleted },
{ kEventClassWindow, kEventWindowPaint },
{ kEventClassWindow, kEventWindowBoundsChanging },
{ kEventClassWindow, kEventWindowCollapsed },
{ kEventClassWindow, kEventWindowExpanded },
{ kEventClassWindow, kEventWindowZoomed },
{ kEventClassKeyboard, kEventRawKeyDown },
{ kEventClassKeyboard, kEventRawKeyUp },
{ kEventClassKeyboard, kEventRawKeyRepeat },
{ kEventClassKeyboard, kEventRawKeyModifiersChanged },
{ kEventClassMouse, kEventMouseMoved },
{ kEventClassMouse, kEventMouseDown },
{ kEventClassMouse, kEventMouseUp },
{ kEventClassMouse, kEventMouseDragged }
};
// Set up the handler
if (InstallWindowEventHandler(query->sender->carbonWindow,NewEventHandlerUPP(CarbonEventHandler),GetEventTypeCount(eventList),
eventList,(void*)query->sender,0)!=noErr) {
query->success = false;
cimg::warn("CImgDisplay::_events_thread() : InstallWindowEventHandler failed.");
}
// Paint
query->sender->paint();
} break;
default :
cimg::warn("CImgDisplay::_events_thread() : Received unknow code %d.",query->kind);
}
// Signal that the message has been processed
MPSignalSemaphore(c.sync_event);
}
}
}
// If we are here, the application is now finished
pthread_exit(0);
}
CImgDisplay& assign() {
if (is_empty()) return *this;
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Destroy the window associated to the display
_CbFreeAttachedWindow(c);
// Don't destroy the background thread here.
// If you check whether _CbFreeAttachedWindow() returned true,
// - saying that there were no window left on screen - and
// you destroy the background thread here, ReceiveNextEvent won't
// work anymore if you create a new window after. So the
// background thread must be killed (pthread_cancel() + pthread_join())
// only on the application shutdown.
// Finalize graphics
_CbFinalizeGraphics();
// Do some cleanup
if (data) delete[] data;
if (title) delete[] title;
width = height = normalization = window_width = window_height = 0;
window_x = window_y = 0;
is_fullscreen = false;
is_closed = true;
min = max = 0;
title = 0;
flush();
if (MPDeleteCriticalRegion(paintCriticalRegion)!=noErr)
throw CImgDisplayException("CImgDisplay()::assign() : MPDeleteCriticalRegion failed.");
return *this;
}
CImgDisplay& _assign(const unsigned int dimw, const unsigned int dimh, const char *ptitle=0,
const unsigned int normalization_type=3,
const bool fullscreen_flag=false, const bool closed_flag=false) {
cimg::CarbonInfo& c = cimg::CarbonAttr();
// Allocate space for window title
const int s = cimg::strlen(ptitle)+1;
char *tmp_title = s?new char[s]:0;
if (s) cimg_std::memcpy(tmp_title,ptitle,s*sizeof(char));
// Destroy previous window if existing
if (!is_empty()) assign();
// Set display variables
width = cimg::min(dimw,(unsigned int)screen_dimx());
height = cimg::min(dimh,(unsigned int)screen_dimy());
normalization = normalization_type<4?normalization_type:3;
is_fullscreen = fullscreen_flag;
is_closed = closed_flag;
lastKeyModifiers = 0;
title = tmp_title;
flush();
// Create the paint CR
if (MPCreateCriticalRegion(&paintCriticalRegion) != noErr)
throw CImgDisplayException("CImgDisplay::_assign() : MPCreateCriticalRegion() failed.");
// Create the thread if it's not already created
if (c.event_thread==0) {
// Background thread does not exists, so create it !
if (pthread_create(&c.event_thread,0,_events_thread,0)!=0)
throw CImgDisplayException("CImgDisplay::_assign() : pthread_create() failed.");
// Wait for thread initialization
MPWaitOnSemaphore(c.sync_event, kDurationForever);
}
// Init disp. graphics
data = new unsigned int[width*height];
_CbInitializeGraphics();
// Now ask the thread to create the window
_CbCreateAttachedWindow(c,ptitle,fullscreen_flag,closed_flag);
return *this;
}
#endif
};
/*
#--------------------------------------
#
#
#
# Definition of the CImg<T> structure
#
#
#
#--------------------------------------
*/
//! Class representing an image (up to 4 dimensions wide), each pixel being of type \c T.
/**
This is the main class of the %CImg Library. It declares and constructs
an image, allows access to its pixel values, and is able to perform various image operations.
\par Image representation
A %CImg image is defined as an instance of the container \ref CImg<\c T>, which contains a regular grid of pixels,
each pixel value being of type \c T. The image grid can have up to 4 dimensions : width, height, depth
and number of channels.
Usually, the three first dimensions are used to describe spatial coordinates <tt>(x,y,z)</tt>, while the number of channels
is rather used as a vector-valued dimension (it may describe the R,G,B color channels for instance).
If you need a fifth dimension, you can use image lists \ref CImgList<\c T> rather than simple images \ref CImg<\c T>.
Thus, the \ref CImg<\c T> class is able to represent volumetric images of vector-valued pixels,
as well as images with less dimensions (1D scalar signal, 2D color images, ...).
Most member functions of the class CImg<\c T> are designed to handle this maximum case of (3+1) dimensions.
Concerning the pixel value type \c T :
fully supported template types are the basic C++ types : <tt>unsigned char, char, short, unsigned int, int,
unsigned long, long, float, double, ... </tt>.
Typically, fast image display can be done using <tt>CImg<unsigned char></tt> images,
while complex image processing algorithms may be rather coded using <tt>CImg<float></tt> or <tt>CImg<double></tt>
images that have floating-point pixel values. The default value for the template T is \c float.
Using your own template types may be possible. However, you will certainly have to define the complete set
of arithmetic and logical operators for your class.
\par Image structure
The \ref CImg<\c T> structure contains \a six fields :
- \ref width defines the number of \a columns of the image (size along the X-axis).
- \ref height defines the number of \a rows of the image (size along the Y-axis).
- \ref depth defines the number of \a slices of the image (size along the Z-axis).
- \ref dim defines the number of \a channels of the image (size along the V-axis).
- \ref data defines a \a pointer to the \a pixel \a data (of type \c T).
- \ref is_shared is a boolean that tells if the memory buffer \ref data is shared with
another image.
You can access these fields publicly although it is recommended to use the dedicated functions
dimx(), dimy(), dimz(), dimv() and ptr() to do so.
Image dimensions are not limited to a specific range (as long as you got enough available memory).
A value of \e 1 usually means that the corresponding dimension is \a flat.
If one of the dimensions is \e 0, or if the data pointer is null, the image is considered as \e empty.
Empty images should not contain any pixel data and thus, will not be processed by CImg member functions
(a CImgInstanceException will be thrown instead).
Pixel data are stored in memory, in a non interlaced mode (See \ref cimg_storage).
\par Image declaration and construction
Declaring an image can be done by using one of the several available constructors.
Here is a list of the most used :
- Construct images from arbitrary dimensions :
- <tt>CImg<char> img;</tt> declares an empty image.
- <tt>CImg<unsigned char> img(128,128);</tt> declares a 128x128 greyscale image with
\c unsigned \c char pixel values.
- <tt>CImg<double> img(3,3);</tt> declares a 3x3 matrix with \c double coefficients.
- <tt>CImg<unsigned char> img(256,256,1,3);</tt> declares a 256x256x1x3 (color) image
(colors are stored as an image with three channels).
- <tt>CImg<double> img(128,128,128);</tt> declares a 128x128x128 volumetric and greyscale image
(with \c double pixel values).
- <tt>CImg<> img(128,128,128,3);</tt> declares a 128x128x128 volumetric color image
(with \c float pixels, which is the default value of the template parameter \c T).
- \b Note : images pixels are <b>not automatically initialized to 0</b>. You may use the function \ref fill() to
do it, or use the specific constructor taking 5 parameters like this :
<tt>CImg<> img(128,128,128,3,0);</tt> declares a 128x128x128 volumetric color image with all pixel values to 0.
- Construct images from filenames :
- <tt>CImg<unsigned char> img("image.jpg");</tt> reads a JPEG color image from the file "image.jpg".
- <tt>CImg<float> img("analyze.hdr");</tt> reads a volumetric image (ANALYZE7.5 format) from the file "analyze.hdr".
- \b Note : You need to install <a href="http://www.imagemagick.org">ImageMagick</a>
to be able to read common compressed image formats (JPG,PNG, ...) (See \ref cimg_files_io).
- Construct images from C-style arrays :
- <tt>CImg<int> img(data_buffer,256,256);</tt> constructs a 256x256 greyscale image from a \c int* buffer
\c data_buffer (of size 256x256=65536).
- <tt>CImg<unsigned char> img(data_buffer,256,256,1,3,false);</tt> constructs a 256x256 color image
from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels follow each others).
- <tt>CImg<unsigned char> img(data_buffer,256,256,1,3,true);</tt> constructs a 256x256 color image
from a \c unsigned \c char* buffer \c data_buffer (where R,G,B channels are multiplexed).
The complete list of constructors can be found <a href="#constructors">here</a>.
\par Most useful functions
The \ref CImg<\c T> class contains a lot of functions that operates on images.
Some of the most useful are :
- operator()(), operator[]() : allows to access or write pixel values.
- display() : displays the image in a new window.
**/
template<typename T>
struct CImg {
//! Variable representing the width of the instance image (i.e. dimensions along the X-axis).
/**
\remark
- Prefer using the function CImg<T>::dimx() to get information about the width of an image.
- Use function CImg<T>::resize() to set a new width for an image. Setting directly the variable \c width would probably
result in a library crash.
- Empty images have \c width defined to \c 0.
**/
unsigned int width;
//! Variable representing the height of the instance image (i.e. dimensions along the Y-axis).
/**
\remark
- Prefer using the function CImg<T>::dimy() to get information about the height of an image.
- Use function CImg<T>::resize() to set a new height for an image. Setting directly the variable \c height would probably
result in a library crash.
- 1D signals have \c height defined to \c 1.
- Empty images have \c height defined to \c 0.
**/
unsigned int height;
//! Variable representing the depth of the instance image (i.e. dimensions along the Z-axis).
/**
\remark
- Prefer using the function CImg<T>::dimz() to get information about the depth of an image.
- Use function CImg<T>::resize() to set a new depth for an image. Setting directly the variable \c depth would probably
result in a library crash.
- Classical 2D images have \c depth defined to \c 1.
- Empty images have \c depth defined to \c 0.
**/
unsigned int depth;
//! Variable representing the number of channels of the instance image (i.e. dimensions along the V-axis).
/**
\remark
- Prefer using the function CImg<T>::dimv() to get information about the depth of an image.
- Use function CImg<T>::resize() to set a new vector dimension for an image. Setting directly the variable \c dim would probably
result in a library crash.
- Scalar-valued images (one value per pixel) have \c dim defined to \c 1.
- Empty images have \c depth defined to \c 0.
**/
unsigned int dim;
//! Variable telling if pixel buffer of the instance image is shared with another one.
bool is_shared;
//! Pointer to the first pixel of the pixel buffer.
T *data;
//! Iterator type for CImg<T>.
/**
\remark
- An \p iterator is a <tt>T*</tt> pointer (address of a pixel value in the pixel buffer).
- Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL.
**/
typedef T* iterator;
//! Const iterator type for CImg<T>.
/**
\remark
- A \p const_iterator is a <tt>const T*</tt> pointer (address of a pixel value in the pixel buffer).
- Iterators are not directly used in %CImg functions, they have been introduced for compatibility with the STL.
**/
typedef const T* const_iterator;
//! Get value type
typedef T value_type;
- // Define common T-dependant types.
+ // Define common T-dependent types.
typedef typename cimg::superset<T,bool>::type Tbool;
typedef typename cimg::superset<T,unsigned char>::type Tuchar;
typedef typename cimg::superset<T,char>::type Tchar;
typedef typename cimg::superset<T,unsigned short>::type Tushort;
typedef typename cimg::superset<T,short>::type Tshort;
typedef typename cimg::superset<T,unsigned int>::type Tuint;
typedef typename cimg::superset<T,int>::type Tint;
typedef typename cimg::superset<T,unsigned long>::type Tulong;
typedef typename cimg::superset<T,long>::type Tlong;
typedef typename cimg::superset<T,float>::type Tfloat;
typedef typename cimg::superset<T,double>::type Tdouble;
typedef typename cimg::last<T,bool>::type boolT;
typedef typename cimg::last<T,unsigned char>::type ucharT;
typedef typename cimg::last<T,char>::type charT;
typedef typename cimg::last<T,unsigned short>::type ushortT;
typedef typename cimg::last<T,short>::type shortT;
typedef typename cimg::last<T,unsigned int>::type uintT;
typedef typename cimg::last<T,int>::type intT;
typedef typename cimg::last<T,unsigned long>::type ulongT;
typedef typename cimg::last<T,long>::type longT;
typedef typename cimg::last<T,float>::type floatT;
typedef typename cimg::last<T,double>::type doubleT;
//@}
//---------------------------
//
//! \name Plugins
//@{
//---------------------------
#ifdef cimg_plugin
#include cimg_plugin
#endif
#ifdef cimg_plugin1
#include cimg_plugin1
#endif
#ifdef cimg_plugin2
#include cimg_plugin2
#endif
#ifdef cimg_plugin3
#include cimg_plugin3
#endif
#ifdef cimg_plugin4
#include cimg_plugin4
#endif
#ifdef cimg_plugin5
#include cimg_plugin5
#endif
#ifdef cimg_plugin6
#include cimg_plugin6
#endif
#ifdef cimg_plugin7
#include cimg_plugin7
#endif
#ifdef cimg_plugin8
#include cimg_plugin8
#endif
#ifndef cimg_plugin_greycstoration
#define cimg_plugin_greycstoration_count
#endif
#ifndef cimg_plugin_greycstoration_lock
#define cimg_plugin_greycstoration_lock
#endif
#ifndef cimg_plugin_greycstoration_unlock
#define cimg_plugin_greycstoration_unlock
#endif
//@}
//--------------------------------------
//
//! \name Constructors-Destructor-Copy
//@{
//--------------------------------------
//! Destructor.
/**
The destructor destroys the instance image.
\remark
- Destructing an empty or shared image does nothing.
- Otherwise, all memory used to store the pixel data of the instance image is freed.
- When destroying a non-shared image, be sure that every shared instances of the same image are
also destroyed to avoid further access to desallocated memory buffers.
**/
~CImg() {
if (data && !is_shared) delete[] data;
}
//! Default constructor.
/**
The default constructor creates an empty instance image.
\remark
- An empty image does not contain any data and has all of its dimensions \ref width, \ref height, \ref depth, \ref dim
set to 0 as well as its pointer to the pixel buffer \ref data.
- An empty image is non-shared.
**/
CImg():
width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {}
//! Constructs a new image with given size (\p dx,\p dy,\p dz,\p dv).
/**
This constructors create an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T.
\param dx Desired size along the X-axis, i.e. the \ref width of the image.
\param dy Desired size along the Y-axis, i.e. the \ref height of the image.
\param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
\param dv Desired size along the V-axis, i.e. the number of image channels \ref dim.
\remark
- If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the created image is empty
and all has its dimensions set to 0. No memory for pixel data is then allocated.
- This constructor creates only non-shared images.
- Image pixels allocated by this constructor are \b not \b initialized.
Use the constructor CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
to get an image of desired size with pixels set to a particular value.
**/
explicit CImg(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1):
is_shared(false) {
const unsigned long siz = dx*dy*dz*dv;
if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; }
else { width = height = depth = dim = 0; data = 0; }
}
//! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with pixel having a default value \p val.
/**
This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T and sets all pixel
values of the created instance image to \p val.
\param dx Desired size along the X-axis, i.e. the \ref width of the image.
\param dy Desired size along the Y-axis, i.e. the \ref height of the image.
\param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
\param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
\param val Default value for image pixels.
\remark
- This constructor has the same properties as CImg(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
**/
CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val):
is_shared(false) {
const unsigned long siz = dx*dy*dz*dv;
if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(val); }
else { width = height = depth = dim = 0; data = 0; }
}
//! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (int version).
CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
const int val0, const int val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
#define _CImg_stdarg(img,a0,a1,N,t) { \
unsigned int _siz = (unsigned int)N; \
if (_siz--) { \
va_list ap; \
va_start(ap,a1); \
T *ptrd = (img).data; \
*(ptrd++) = (T)a0; \
if (_siz--) { \
*(ptrd++) = (T)a1; \
for (; _siz; --_siz) *(ptrd++) = (T)va_arg(ap,t); \
} \
va_end(ap); \
}}
assign(dx,dy,dz,dv);
_CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int);
}
//! Construct an image with given size (\p dx,\p dy,\p dz,\p dv) and with specified pixel values (double version).
CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
const double val0, const double val1, ...):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
assign(dx,dy,dz,dv);
_CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double);
}
//! Construct an image with given size and with specified values given in a string.
CImg(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
const char *const values, const bool repeat_pattern):is_shared(false) {
const unsigned long siz = dx*dy*dz*dv;
if (siz) { width = dx; height = dy; depth = dz; dim = dv; data = new T[siz]; fill(values,repeat_pattern); }
else { width = height = depth = dim = 0; data = 0; }
}
//! Construct an image from a raw memory buffer.
/**
This constructor creates an instance image of size (\p dx,\p dy,\p dz,\p dv) and fill its pixel buffer by
copying data values from the input raw pixel buffer \p data_buffer.
**/
template<typename t>
CImg(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1, const bool shared=false):is_shared(false) {
if (shared)
throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a (%s*) buffer "
"(different pixel types).",
pixel_type(),CImg<t>::pixel_type());
const unsigned long siz = dx*dy*dz*dv;
if (data_buffer && siz) {
width = dx; height = dy; depth = dz; dim = dv; data = new T[siz];
const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
} else { width = height = depth = dim = 0; data = 0; }
}
#ifndef cimg_use_visualcpp6
CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1, const bool shared=false)
#else
CImg(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
const unsigned int dz, const unsigned int dv, const bool shared)
#endif
{
const unsigned long siz = dx*dy*dz*dv;
if (data_buffer && siz) {
width = dx; height = dy; depth = dz; dim = dv; is_shared = shared;
if (is_shared) data = const_cast<T*>(data_buffer);
else { data = new T[siz]; cimg_std::memcpy(data,data_buffer,siz*sizeof(T)); }
} else { width = height = depth = dim = 0; is_shared = false; data = 0; }
}
//! Default copy constructor.
/**
The default copy constructor creates a new instance image having same dimensions
(\ref width, \ref height, \ref depth, \ref dim) and same pixel values as the input image \p img.
\param img The input image to copy.
\remark
- If the input image \p img is non-shared or have a different template type \p t != \p T,
the default copy constructor allocates a new pixel buffer and copy the pixel data
of \p img into it. In this case, the pointers \ref data to the pixel buffers of the two images are different
and the resulting instance image is non-shared.
- If the input image \p img is shared and has the same template type \p t == \p T,
the default copy constructor does not allocate a new pixel buffer and the resulting instance image
shares its pixel buffer with the input image \p img, which means that modifying pixels of \p img also modifies
the created instance image.
- Copying an image having a different template type \p t != \p T performs a crude static cast conversion of each pixel value from
type \p t to type \p T.
- Copying an image having the same template type \p t == \p T is significantly faster.
**/
template<typename t>
CImg(const CImg<t>& img):is_shared(false) {
const unsigned int siz = img.size();
if (img.data && siz) {
width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz];
const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
} else { width = height = depth = dim = 0; data = 0; }
}
CImg(const CImg<T>& img) {
const unsigned int siz = img.size();
if (img.data && siz) {
width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = img.is_shared;
if (is_shared) data = const_cast<T*>(img.data);
else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); }
} else { width = height = depth = dim = 0; is_shared = false; data = 0; }
}
//! Advanced copy constructor.
/**
The advanced copy constructor - as the default constructor CImg(const CImg< t >&) - creates a new instance image having same dimensions
\ref width, \ref height, \ref depth, \ref dim and same pixel values as the input image \p img.
But it also decides if the created instance image shares its memory with the input image \p img (if the input parameter
\p shared is set to \p true) or not (if the input parameter \p shared is set to \p false).
\param img The input image to copy.
\param shared Boolean flag that decides if the copy is shared on non-shared.
\remark
- It is not possible to create a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
- If a non-shared copy of the input image \p img is created, a new memory buffer is allocated for pixel data.
- If a shared copy of the input image \p img is created, no extra memory is allocated and the pixel buffer of the instance
image is the same as the one used by the input image \p img.
**/
template<typename t>
CImg(const CImg<t>& img, const bool shared):is_shared(false) {
if (shared)
throw CImgArgumentException("CImg<%s>::CImg() : Cannot construct a shared instance image from a CImg<%s> instance "
"(different pixel types).",
pixel_type(),CImg<t>::pixel_type());
const unsigned int siz = img.size();
if (img.data && siz) {
width = img.width; height = img.height; depth = img.depth; dim = img.dim; data = new T[siz];
const t *ptrs = img.data + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
} else { width = height = depth = dim = 0; data = 0; }
}
CImg(const CImg<T>& img, const bool shared) {
const unsigned int siz = img.size();
if (img.data && siz) {
width = img.width; height = img.height; depth = img.depth; dim = img.dim; is_shared = shared;
if (is_shared) data = const_cast<T*>(img.data);
else { data = new T[siz]; cimg_std::memcpy(data,img.data,siz*sizeof(T)); }
} else { width = height = depth = dim = 0; is_shared = false; data = 0; }
}
//! Construct an image using dimensions of another image
template<typename t>
CImg(const CImg<t>& img, const char *const dimensions):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
assign(img,dimensions);
}
//! Construct an image using dimensions of another image, and fill it with a default value
template<typename t>
CImg(const CImg<t>& img, const char *const dimensions, const T val):
width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
assign(img,dimensions).fill(val);
}
//! Construct an image from an image file.
/**
This constructor creates an instance image by reading it from a file.
\param filename Filename of the image file.
\remark
- The image format is deduced from the filename only by looking for the filename extension i.e. without
analyzing the file itself.
- Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
More informations on this topic can be found in cimg_files_io.
- If the filename is not found, a CImgIOException is thrown by this constructor.
**/
explicit CImg(const char *const filename):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
assign(filename);
}
//! Construct an image from the content of a CImgDisplay instance.
explicit CImg(const CImgDisplay &disp):width(0),height(0),depth(0),dim(0),is_shared(false),data(0) {
disp.snapshot(*this);
}
//! In-place version of the default constructor/destructor.
/**
This function replaces the instance image by an empty image.
\remark
- Memory used by the previous content of the instance image is freed if necessary.
- If the instance image was initially shared, it is replaced by a (non-shared) empty image.
- This function is useful to free memory used by an image that is not of use, but which
has been created in the current code scope (i.e. not destroyed yet).
**/
CImg<T>& assign() {
if (data && !is_shared) delete[] data;
width = height = depth = dim = 0; is_shared = false; data = 0;
return *this;
}
//! In-place version of the default constructor.
/**
This function is strictly equivalent to \ref assign() and has been
introduced for having a STL-compliant function name.
**/
CImg<T>& clear() {
return assign();
}
//! In-place version of the previous constructor.
/**
This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T.
\param dx Desired size along the X-axis, i.e. the \ref width of the image.
\param dy Desired size along the Y-axis, i.e. the \ref height of the image.
\param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
\param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
- If one of the input dimension \p dx,\p dy,\p dz or \p dv is set to 0, the instance image becomes empty
and all has its dimensions set to 0. No memory for pixel data is then allocated.
- Memory buffer used to store previous pixel values is freed if necessary.
- If the instance image is shared, this constructor actually does nothing more than verifying
that new and old image dimensions fit.
- Image pixels allocated by this function are \b not \b initialized.
Use the function assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int,const T)
to assign an image of desired size with pixels set to a particular value.
**/
CImg<T>& assign(const unsigned int dx, const unsigned int dy=1, const unsigned int dz=1, const unsigned int dv=1) {
const unsigned long siz = dx*dy*dz*dv;
if (!siz) return assign();
const unsigned long curr_siz = size();
if (siz!=curr_siz) {
if (is_shared)
throw CImgArgumentException("CImg<%s>::assign() : Cannot assign image (%u,%u,%u,%u) to shared instance image (%u,%u,%u,%u,%p).",
pixel_type(),dx,dy,dz,dv,width,height,depth,dim,data);
else { if (data) delete[] data; data = new T[siz]; }
}
width = dx; height = dy; depth = dz; dim = dv;
return *this;
}
//! In-place version of the previous constructor.
/**
This function replaces the instance image by a new image of size (\p dx,\p dy,\p dz,\p dv) with pixels of type \p T
and sets all pixel values of the instance image to \p val.
\param dx Desired size along the X-axis, i.e. the \ref width of the image.
\param dy Desired size along the Y-axis, i.e. the \ref height of the image.
\param dz Desired size along the Z-axis, i.e. the \ref depth of the image.
\param dv Desired size along the V-axis, i.e. the number of image channels \p dim.
\param val Default value for image pixels.
\remark
- This function has the same properties as assign(const unsigned int,const unsigned int,const unsigned int,const unsigned int).
**/
CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv, const T val) {
return assign(dx,dy,dz,dv).fill(val);
}
//! In-place version of the previous constructor.
CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
const int val0, const int val1, ...) {
assign(dx,dy,dz,dv);
_CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,int);
return *this;
}
//! In-place version of the previous constructor.
CImg<T>& assign(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv,
const double val0, const double val1, ...) {
assign(dx,dy,dz,dv);
_CImg_stdarg(*this,val0,val1,dx*dy*dz*dv,double);
return *this;
}
//! In-place version of the previous constructor.
template<typename t>
CImg<T>& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1) {
const unsigned long siz = dx*dy*dz*dv;
if (!data_buffer || !siz) return assign();
assign(dx,dy,dz,dv);
const t *ptrs = data_buffer + siz; cimg_for(*this,ptrd,T) *ptrd = (T)*(--ptrs);
return *this;
}
#ifndef cimg_use_visualcpp6
CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1)
#else
CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
const unsigned int dz, const unsigned int dv)
#endif
{
const unsigned long siz = dx*dy*dz*dv;
if (!data_buffer || !siz) return assign();
const unsigned long curr_siz = size();
if (data_buffer==data && siz==curr_siz) return assign(dx,dy,dz,dv);
if (is_shared || data_buffer+siz<data || data_buffer>=data+size()) {
assign(dx,dy,dz,dv);
if (is_shared) cimg_std::memmove(data,data_buffer,siz*sizeof(T));
else cimg_std::memcpy(data,data_buffer,siz*sizeof(T));
} else {
T *new_data = new T[siz];
cimg_std::memcpy(new_data,data_buffer,siz*sizeof(T));
delete[] data; data = new_data; width = dx; height = dy; depth = dz; dim = dv;
}
return *this;
}
//! In-place version of the previous constructor, allowing to force the shared state of the instance image.
template<typename t>
CImg<T>& assign(const t *const data_buffer, const unsigned int dx, const unsigned int dy,
const unsigned int dz, const unsigned int dv, const bool shared) {
if (shared)
throw CImgArgumentException("CImg<%s>::assign() : Cannot assign buffer (%s*) to shared instance image (%u,%u,%u,%u,%p)"
"(different pixel types).",
pixel_type(),CImg<t>::pixel_type(),width,height,depth,dim,data);
return assign(data_buffer,dx,dy,dz,dv);
}
CImg<T>& assign(const T *const data_buffer, const unsigned int dx, const unsigned int dy,
const unsigned int dz, const unsigned int dv, const bool shared) {
const unsigned long siz = dx*dy*dz*dv;
if (!data_buffer || !siz) return assign();
if (!shared) { if (is_shared) assign(); assign(data_buffer,dx,dy,dz,dv); }
else {
if (!is_shared) {
if (data_buffer+siz<data || data_buffer>=data+size()) assign();
else cimg::warn("CImg<%s>::assign() : Shared instance image has overlapping memory !",
pixel_type());
}
width = dx; height = dy; depth = dz; dim = dv; is_shared = true;
data = const_cast<T*>(data_buffer);
}
return *this;
}
//! In-place version of the default copy constructor.
/**
This function assigns a copy of the input image \p img to the current instance image.
\param img The input image to copy.
\remark
- If the instance image is not shared, the content of the input image \p img is copied into a new buffer
becoming the new pixel buffer of the instance image, while the old pixel buffer is freed if necessary.
- If the instance image is shared, the content of the input image \p img is copied into the current (shared) pixel buffer
of the instance image, modifying then the image referenced by the shared instance image. The instance image still remains shared.
**/
template<typename t>
CImg<T>& assign(const CImg<t>& img) {
return assign(img.data,img.width,img.height,img.depth,img.dim);
}
//! In-place version of the advanced constructor.
/**
This function - as the simpler function assign(const CImg< t >&) - assigns a copy of the input image \p img to the
current instance image. But it also decides if the copy is shared (if the input parameter \p shared is set to \c true)
or non-shared (if the input parameter \p shared is set to \c false).
\param img The input image to copy.
\param shared Boolean flag that decides if the copy is shared or non-shared.
\remark
- It is not possible to assign a shared copy if the input image \p img is empty or has a different pixel type \p t != \p T.
- If a non-shared copy of the input image \p img is assigned, a new memory buffer is allocated for pixel data.
- If a shared copy of the input image \p img is assigned, no extra memory is allocated and the pixel buffer of the instance
image is the same as the one used by the input image \p img.
**/
template<typename t>
CImg<T>& assign(const CImg<t>& img, const bool shared) {
return assign(img.data,img.width,img.height,img.depth,img.dim,shared);
}
//! In-place version of the previous constructor.
template<typename t>
CImg<T>& assign(const CImg<t>& img, const char *const dimensions) {
if (dimensions) {
unsigned int siz[4] = { 0,1,1,1 };
const char *s = dimensions;
char tmp[256] = { 0 }, c = 0;
int val = 0;
for (unsigned int k=0; k<4; ++k) {
const int err = cimg_std::sscanf(s,"%[-0-9]%c",tmp,&c);
if (err>=1) {
const int err = cimg_std::sscanf(s,"%d",&val);
if (err==1) {
int val2 = val<0?-val:(c=='%'?val:-1);
if (val2>=0) {
val = (int)((k==0?img.width:(k==1?img.height:(k==2?img.depth:img.dim)))*val2/100);
if (c!='%' && !val) val = 1;
}
siz[k] = val;
}
s+=cimg::strlen(tmp);
if (c=='%') ++s;
}
if (!err) {
if (!cimg::strncasecmp(s,"x",1)) { ++s; siz[k] = img.width; }
else if (!cimg::strncasecmp(s,"y",1)) { ++s; siz[k] = img.height; }
else if (!cimg::strncasecmp(s,"z",1)) { ++s; siz[k] = img.depth; }
else if (!cimg::strncasecmp(s,"v",1)) { ++s; siz[k] = img.dim; }
else if (!cimg::strncasecmp(s,"dx",2)) { s+=2; siz[k] = img.width; }
else if (!cimg::strncasecmp(s,"dy",2)) { s+=2; siz[k] = img.height; }
else if (!cimg::strncasecmp(s,"dz",2)) { s+=2; siz[k] = img.depth; }
else if (!cimg::strncasecmp(s,"dv",2)) { s+=2; siz[k] = img.dim; }
else if (!cimg::strncasecmp(s,"dimx",4)) { s+=4; siz[k] = img.width; }
else if (!cimg::strncasecmp(s,"dimy",4)) { s+=4; siz[k] = img.height; }
else if (!cimg::strncasecmp(s,"dimz",4)) { s+=4; siz[k] = img.depth; }
else if (!cimg::strncasecmp(s,"dimv",4)) { s+=4; siz[k] = img.dim; }
else if (!cimg::strncasecmp(s,"width",5)) { s+=5; siz[k] = img.width; }
else if (!cimg::strncasecmp(s,"height",6)) { s+=6; siz[k] = img.height; }
else if (!cimg::strncasecmp(s,"depth",5)) { s+=5; siz[k] = img.depth; }
else if (!cimg::strncasecmp(s,"dim",3)) { s+=3; siz[k] = img.dim; }
else { ++s; --k; }
}
}
return assign(siz[0],siz[1],siz[2],siz[3]);
}
return assign();
}
//! In-place version of the previous constructor.
template<typename t>
CImg<T>& assign(const CImg<t>& img, const char *const dimensions, const T val) {
return assign(img,dimensions).fill(val);
}
//! In-place version of the previous constructor.
/**
This function replaces the instance image by the one that have been read from the given file.
\param filename Filename of the image file.
- The image format is deduced from the filename only by looking for the filename extension i.e. without
analyzing the file itself.
- Recognized image formats depend on the tools installed on your system or the external libraries you use to link your code with.
More informations on this topic can be found in cimg_files_io.
- If the filename is not found, a CImgIOException is thrown by this constructor.
**/
CImg<T>& assign(const char *const filename) {
return load(filename);
}
//! In-place version of the previous constructor.
CImg<T>& assign(const CImgDisplay &disp) {
disp.snapshot(*this);
return *this;
}
//! Transfer the content of the instance image into another one in a way that memory copies are avoided if possible.
/**
The instance image is always empty after a call to this function.
**/
template<typename t>
CImg<t>& transfer_to(CImg<t>& img) {
img.assign(*this);
assign();
return img;
}
CImg<T>& transfer_to(CImg<T>& img) {
if (is_shared || img.is_shared) { img.assign(*this); assign(); } else { img.assign(); swap(img); }
return img;
}
//! Swap all fields of two images. Use with care !
CImg<T>& swap(CImg<T>& img) {
cimg::swap(width,img.width);
cimg::swap(height,img.height);
cimg::swap(depth,img.depth);
cimg::swap(dim,img.dim);
cimg::swap(data,img.data);
cimg::swap(is_shared,img.is_shared);
return img;
}
//@}
//-------------------------------------
//
//! \name Image Informations
//@{
//-------------------------------------
//! Return the type of the pixel values.
/**
\return a string describing the type of the image pixels (template parameter \p T).
- The string returned may contains spaces (<tt>"unsigned char"</tt>).
- If the template parameter T does not correspond to a registered type, the string <tt>"unknown"</tt> is returned.
**/
static const char* pixel_type() {
return cimg::type<T>::string();
}
//! Return the total number of pixel values in an image.
/**
- Equivalent to : dimx() * dimy() * dimz() * dimv().
\par example:
\code
CImg<> img(100,100,1,3);
if (img.size()==100*100*3) std::fprintf(stderr,"This statement is true");
\endcode
**/
unsigned long size() const {
return width*height*depth*dim;
}
//! Return the number of columns of the instance image (size along the X-axis, i.e image width).
int dimx() const {
return (int)width;
}
//! Return the number of rows of the instance image (size along the Y-axis, i.e image height).
int dimy() const {
return (int)height;
}
//! Return the number of slices of the instance image (size along the Z-axis).
int dimz() const {
return (int)depth;
}
//! Return the number of vector channels of the instance image (size along the V-axis).
int dimv() const {
return (int)dim;
}
//! Return \c true if image (*this) has the specified width.
bool is_sameX(const unsigned int dx) const {
return (width==dx);
}
//! Return \c true if images \c (*this) and \c img have same width.
template<typename t>
bool is_sameX(const CImg<t>& img) const {
return is_sameX(img.width);
}
//! Return \c true if images \c (*this) and the display \c disp have same width.
bool is_sameX(const CImgDisplay& disp) const {
return is_sameX(disp.width);
}
//! Return \c true if image (*this) has the specified height.
bool is_sameY(const unsigned int dy) const {
return (height==dy);
}
//! Return \c true if images \c (*this) and \c img have same height.
template<typename t>
bool is_sameY(const CImg<t>& img) const {
return is_sameY(img.height);
}
//! Return \c true if images \c (*this) and the display \c disp have same height.
bool is_sameY(const CImgDisplay& disp) const {
return is_sameY(disp.height);
}
//! Return \c true if image (*this) has the specified depth.
bool is_sameZ(const unsigned int dz) const {
return (depth==dz);
}
//! Return \c true if images \c (*this) and \c img have same depth.
template<typename t>
bool is_sameZ(const CImg<t>& img) const {
return is_sameZ(img.depth);
}
//! Return \c true if image (*this) has the specified number of channels.
bool is_sameV(const unsigned int dv) const {
return (dim==dv);
}
//! Return \c true if images \c (*this) and \c img have same dim.
template<typename t>
bool is_sameV(const CImg<t>& img) const {
return is_sameV(img.dim);
}
//! Return \c true if image (*this) has the specified width and height.
bool is_sameXY(const unsigned int dx, const unsigned int dy) const {
return (is_sameX(dx) && is_sameY(dy));
}
//! Return \c true if images have same width and same height.
template<typename t>
bool is_sameXY(const CImg<t>& img) const {
return (is_sameX(img) && is_sameY(img));
}
//! Return \c true if image \c (*this) and the display \c disp have same width and same height.
bool is_sameXY(const CImgDisplay& disp) const {
return (is_sameX(disp) && is_sameY(disp));
}
//! Return \c true if image (*this) has the specified width and depth.
bool is_sameXZ(const unsigned int dx, const unsigned int dz) const {
return (is_sameX(dx) && is_sameZ(dz));
}
//! Return \c true if images have same width and same depth.
template<typename t>
bool is_sameXZ(const CImg<t>& img) const {
return (is_sameX(img) && is_sameZ(img));
}
//! Return \c true if image (*this) has the specified width and number of channels.
bool is_sameXV(const unsigned int dx, const unsigned int dv) const {
return (is_sameX(dx) && is_sameV(dv));
}
//! Return \c true if images have same width and same number of channels.
template<typename t>
bool is_sameXV(const CImg<t>& img) const {
return (is_sameX(img) && is_sameV(img));
}
//! Return \c true if image (*this) has the specified height and depth.
bool is_sameYZ(const unsigned int dy, const unsigned int dz) const {
return (is_sameY(dy) && is_sameZ(dz));
}
//! Return \c true if images have same height and same depth.
template<typename t>
bool is_sameYZ(const CImg<t>& img) const {
return (is_sameY(img) && is_sameZ(img));
}
//! Return \c true if image (*this) has the specified height and number of channels.
bool is_sameYV(const unsigned int dy, const unsigned int dv) const {
return (is_sameY(dy) && is_sameV(dv));
}
//! Return \c true if images have same height and same number of channels.
template<typename t>
bool is_sameYV(const CImg<t>& img) const {
return (is_sameY(img) && is_sameV(img));
}
//! Return \c true if image (*this) has the specified depth and number of channels.
bool is_sameZV(const unsigned int dz, const unsigned int dv) const {
return (is_sameZ(dz) && is_sameV(dv));
}
//! Return \c true if images have same depth and same number of channels.
template<typename t>
bool is_sameZV(const CImg<t>& img) const {
return (is_sameZ(img) && is_sameV(img));
}
//! Return \c true if image (*this) has the specified width, height and depth.
bool is_sameXYZ(const unsigned int dx, const unsigned int dy, const unsigned int dz) const {
return (is_sameXY(dx,dy) && is_sameZ(dz));
}
//! Return \c true if images have same width, same height and same depth.
template<typename t>
bool is_sameXYZ(const CImg<t>& img) const {
return (is_sameXY(img) && is_sameZ(img));
}
//! Return \c true if image (*this) has the specified width, height and depth.
bool is_sameXYV(const unsigned int dx, const unsigned int dy, const unsigned int dv) const {
return (is_sameXY(dx,dy) && is_sameV(dv));
}
//! Return \c true if images have same width, same height and same number of channels.
template<typename t>
bool is_sameXYV(const CImg<t>& img) const {
return (is_sameXY(img) && is_sameV(img));
}
//! Return \c true if image (*this) has the specified width, height and number of channels.
bool is_sameXZV(const unsigned int dx, const unsigned int dz, const unsigned int dv) const {
return (is_sameXZ(dx,dz) && is_sameV(dv));
}
//! Return \c true if images have same width, same depth and same number of channels.
template<typename t>
bool is_sameXZV(const CImg<t>& img) const {
return (is_sameXZ(img) && is_sameV(img));
}
//! Return \c true if image (*this) has the specified height, depth and number of channels.
bool is_sameYZV(const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
return (is_sameYZ(dy,dz) && is_sameV(dv));
}
//! Return \c true if images have same heigth, same depth and same number of channels.
template<typename t>
bool is_sameYZV(const CImg<t>& img) const {
return (is_sameYZ(img) && is_sameV(img));
}
//! Return \c true if image (*this) has the specified width, height, depth and number of channels.
bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
return (is_sameXYZ(dx,dy,dz) && is_sameV(dv));
}
//! Return \c true if images \c (*this) and \c img have same width, same height, same depth and same number of channels.
template<typename t>
bool is_sameXYZV(const CImg<t>& img) const {
return (is_sameXYZ(img) && is_sameV(img));
}
//! Return \c true if current image is empty.
bool is_empty() const {
return !(data && width && height && depth && dim);
}
//! Return \p true if image is not empty.
operator bool() const {
return !is_empty();
}
//! Return an iterator to the first image pixel
iterator begin() {
return data;
}
const_iterator begin() const {
return data;
}
//! Return reference to the first image pixel
const T& first() const {
return *data;
}
T& first() {
return *data;
}
//! Return an iterator pointing after the last image pixel
iterator end() {
return data + size();
}
const_iterator end() const {
return data + size();
}
//! Return a reference to the last image pixel
const T& last() const {
return data[size() - 1];
}
T& last() {
return data[size() - 1];
}
//! Return a pointer to the pixel buffer.
T* ptr() {
return data;
}
const T* ptr() const {
return data;
}
//! Return a pointer to the pixel value located at (\p x,\p y,\p z,\p v).
/**
\param x X-coordinate of the pixel.
\param y Y-coordinate of the pixel.
\param z Z-coordinate of the pixel.
\param v V-coordinate of the pixel.
- - When called without parameters, ptr() returns a pointer to the begining of the pixel buffer.
+ - When called without parameters, ptr() returns a pointer to the beginning of the pixel buffer.
- If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear if
given coordinates are outside the image range (but function performances decrease).
\par example:
\code
CImg<float> img(100,100,1,1,0); // Define a 100x100 greyscale image with float-valued pixels.
float *ptr = ptr(10,10); // Get a pointer to the pixel located at (10,10).
float val = *ptr; // Get the pixel value.
\endcode
**/
#if cimg_debug>=3
T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
const long off = offset(x,y,z,v);
if (off<0 || off>=(long)size()) {
cimg::warn("CImg<%s>::ptr() : Asked for a pointer at coordinates (%u,%u,%u,%u) (offset=%ld), "
"outside image range (%u,%u,%u,%u) (size=%lu)",
pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
return data;
}
return data + off;
}
const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
return const_cast<CImg<T>*>(this)->ptr(x,y,z,v);
}
#else
T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
}
const T* ptr(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
return data + (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
}
#endif
//! Return \c true if the memory buffers of the two images overlaps.
/**
May happen when using shared images.
**/
template<typename t>
bool is_overlapped(const CImg<t>& img) const {
const unsigned long csiz = size(), isiz = img.size();
return !((void*)(data+csiz)<=(void*)img.data || (void*)data>=(void*)(img.data+isiz));
}
//! Return the offset of the pixel coordinates (\p x,\p y,\p z,\p v) with respect to the data pointer \c data.
/**
\param x X-coordinate of the pixel.
\param y Y-coordinate of the pixel.
\param z Z-coordinate of the pixel.
\param v V-coordinate of the pixel.
- No checking is done on the validity of the given coordinates.
\par Example:
\code
CImg<float> img(100,100,1,3,0); // Define a 100x100 color image with float-valued black pixels.
long off = img.offset(10,10,0,2); // Get the offset of the blue value of the pixel located at (10,10).
float val = img[off]; // Get the blue value of the pixel.
\endcode
**/
long offset(const int x, const int y=0, const int z=0, const int v=0) const {
return (long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth;
}
//! Fast access to pixel value for reading or writing.
/**
\param x X-coordinate of the pixel.
\param y Y-coordinate of the pixel.
\param z Z-coordinate of the pixel.
\param v V-coordinate of the pixel.
- If one image dimension is equal to 1, it can be omitted in the coordinate list (see example below).
- If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear
(but function performances decrease).
\par example:
\code
CImg<float> img(100,100,1,3,0); // Define a 100x100 color image with float-valued black pixels.
const float valR = img(10,10,0,0); // Read the red component at coordinates (10,10).
const float valG = img(10,10,0,1); // Read the green component at coordinates (10,10)
const float valB = img(10,10,2); // Read the blue component at coordinates (10,10) (Z-coordinate omitted here).
const float avg = (valR + valG + valB)/3; // Compute average pixel value.
img(10,10,0) = img(10,10,1) = img(10,10,2) = avg; // Replace the pixel (10,10) by the average grey value.
\endcode
**/
#if cimg_debug>=3
T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
const long off = offset(x,y,z,v);
if (!data || off>=(long)size()) {
cimg::warn("CImg<%s>::operator() : Pixel access requested at (%u,%u,%u,%u) (offset=%ld) "
"outside the image range (%u,%u,%u,%u) (size=%lu)",
pixel_type(),x,y,z,v,off,width,height,depth,dim,size());
return *data;
}
else return data[off];
}
const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
return const_cast<CImg<T>*>(this)->operator()(x,y,z,v);
}
#else
T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) {
return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth];
}
const T& operator()(const unsigned int x, const unsigned int y=0, const unsigned int z=0, const unsigned int v=0) const {
return data[(long)x + (long)y*width + (long)z*width*height + (long)v*width*height*depth];
}
#endif
//! Fast access to pixel value for reading or writing, using an offset to the image pixel.
/**
- \param off Offset of the pixel according to the begining of the pixel buffer, given by ptr().
+ \param off Offset of the pixel according to the beginning of the pixel buffer, given by ptr().
- If the macro \c 'cimg_debug'>=3, boundary checking is performed and warning messages may appear
(but function performances decrease).
- As pixel values are aligned in memory, this operator can sometime useful to access values easier than
with operator()() (see example below).
\par example:
\code
CImg<float> vec(1,10); // Define a vector of float values (10 lines, 1 row).
const float val1 = vec(0,4); // Get the fifth element using operator()().
const float val2 = vec[4]; // Get the fifth element using operator[]. Here, val2==val1.
\endcode
**/
#if cimg_debug>=3
T& operator[](const unsigned long off) {
if (!data || off>=size()) {
cimg::warn("CImg<%s>::operator[] : Pixel access requested at offset=%lu "
"outside the image range (%u,%u,%u,%u) (size=%lu)",
pixel_type(),off,width,height,depth,dim,size());
return *data;
}
else return data[off];
}
const T& operator[](const unsigned long off) const {
return const_cast<CImg<T>*>(this)->operator[](off);
}
#else
T& operator[](const unsigned long off) {
return data[off];
}
const T& operator[](const unsigned long off) const {
return data[off];
}
#endif
//! Return a reference to the last image value
T& back() {
return operator()(size()-1);
}
const T& back() const {
return operator()(size()-1);
}
//! Return a reference to the first image value
T& front() {
return *data;
}
const T& front() const {
return *data;
}
//! Return \c true if pixel (x,y,z,v) is inside image boundaries.
bool containsXYZV(const int x, const int y=0, const int z=0, const int v=0) const {
return !is_empty() && x>=0 && x<dimx() && y>=0 && y<dimy() && z>=0 && z<dimz() && v>=0 && v<dimv();
}
//! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z,v).
template<typename t>
bool contains(const T& pixel, t& x, t& y, t& z, t& v) const {
const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim;
const T *const ppixel = &pixel;
if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
unsigned long off = (unsigned long)(ppixel - data);
const unsigned long nv = off/whz;
off%=whz;
const unsigned long nz = off/wh;
off%=wh;
const unsigned long ny = off/width, nx = off%width;
x = (t)nx; y = (t)ny; z = (t)nz; v = (t)nv;
return true;
}
//! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y,z).
template<typename t>
bool contains(const T& pixel, t& x, t& y, t& z) const {
const unsigned long wh = width*height, whz = wh*depth, siz = whz*dim;
const T *const ppixel = &pixel;
if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
unsigned long off = ((unsigned long)(ppixel - data))%whz;
const unsigned long nz = off/wh;
off%=wh;
const unsigned long ny = off/width, nx = off%width;
x = (t)nx; y = (t)ny; z = (t)nz;
return true;
}
//! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x,y).
template<typename t>
bool contains(const T& pixel, t& x, t& y) const {
const unsigned long wh = width*height, siz = wh*depth*dim;
const T *const ppixel = &pixel;
if (is_empty() || ppixel<data || ppixel>=data+siz) return false;
unsigned long off = ((unsigned long)(ppixel - data))%wh;
const unsigned long ny = off/width, nx = off%width;
x = (t)nx; y = (t)ny;
return true;
}
//! Return \c true if specified referenced value is inside image boundaries. If true, returns pixel coordinates in (x).
template<typename t>
bool contains(const T& pixel, t& x) const {
const T *const ppixel = &pixel;
if (is_empty() || ppixel<data || ppixel>=data+size()) return false;
x = (t)(((unsigned long)(ppixel - data))%width);
return true;
}
//! Return \c true if specified referenced value is inside the image boundaries.
bool contains(const T& pixel) const {
const T *const ppixel = &pixel;
return !is_empty() && ppixel>=data && ppixel<data+size();
}
//! Read a pixel value with Dirichlet boundary conditions.
T& at(const int off, const T out_val) {
return (off<0 || off>=(int)size())?(cimg::temporary(out_val)=out_val):(*this)[off];
}
T at(const int off, const T out_val) const {
return (off<0 || off>=(int)size())?out_val:(*this)[off];
}
//! Read a pixel value with Neumann boundary conditions.
T& at(const int off) {
if (!size())
throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.",
pixel_type());
return _at(off);
}
T at(const int off) const {
if (!size())
throw CImgInstanceException("CImg<%s>::at() : Instance image is empty.",
pixel_type());
return _at(off);
}
T& _at(const int off) {
const unsigned int siz = (unsigned int)size();
return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
}
T _at(const int off) const {
const unsigned int siz = (unsigned int)size();
return (*this)[off<0?0:(unsigned int)off>=siz?siz-1:off];
}
//! Read a pixel value with Dirichlet boundary conditions.
T& atXYZV(const int x, const int y, const int z, const int v, const T out_val) {
return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?
(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
}
T atXYZV(const int x, const int y, const int z, const int v, const T out_val) const {
return (x<0 || y<0 || z<0 || v<0 || x>=dimx() || y>=dimy() || z>=dimz() || v>=dimv())?out_val:(*this)(x,y,z,v);
}
//! Read a pixel value with Neumann boundary conditions.
T& atXYZV(const int x, const int y, const int z, const int v) {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.",
pixel_type());
return _atXYZV(x,y,z,v);
}
T atXYZV(const int x, const int y, const int z, const int v) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atXYZV() : Instance image is empty.",
pixel_type());
return _atXYZV(x,y,z,v);
}
T& _atXYZV(const int x, const int y, const int z, const int v) {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),
z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v));
}
T _atXYZV(const int x, const int y, const int z, const int v) const {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),
z<0?0:(z>=dimz()?dimz()-1:z), v<0?0:(v>=dimv()?dimv()-1:v));
}
//! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c x,\c y,\c z).
T& atXYZ(const int x, const int y, const int z, const int v, const T out_val) {
return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?
(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
}
T atXYZ(const int x, const int y, const int z, const int v, const T out_val) const {
return (x<0 || y<0 || z<0 || x>=dimx() || y>=dimy() || z>=dimz())?out_val:(*this)(x,y,z,v);
}
//! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c x,\c y,\c z).
T& atXYZ(const int x, const int y, const int z, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.",
pixel_type());
return _atXYZ(x,y,z,v);
}
T atXYZ(const int x, const int y, const int z, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atXYZ() : Instance image is empty.",
pixel_type());
return _atXYZ(x,y,z,v);
}
T& _atXYZ(const int x, const int y, const int z, const int v=0) {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y),
z<0?0:(z>=dimz()?dimz()-1:z),v);
}
T _atXYZ(const int x, const int y, const int z, const int v=0) const {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y<0?0:(y>=dimy()?dimy()-1:y),
z<0?0:(z>=dimz()?dimz()-1:z),v);
}
//! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c x,\c y).
T& atXY(const int x, const int y, const int z, const int v, const T out_val) {
return (x<0 || y<0 || x>=dimx() || y>=dimy())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
}
T atXY(const int x, const int y, const int z, const int v, const T out_val) const {
return (x<0 || y<0 || x>=dimx() || y>=dimy())?out_val:(*this)(x,y,z,v);
}
//! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c x,\c y).
T& atXY(const int x, const int y, const int z=0, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.",
pixel_type());
return _atXY(x,y,z,v);
}
T atXY(const int x, const int y, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atXY() : Instance image is empty.",
pixel_type());
return _atXY(x,y,z,v);
}
T& _atXY(const int x, const int y, const int z=0, const int v=0) {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v);
}
T _atXY(const int x, const int y, const int z=0, const int v=0) const {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x), y<0?0:(y>=dimy()?dimy()-1:y),z,v);
}
//! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c x).
T& atX(const int x, const int y, const int z, const int v, const T out_val) {
return (x<0 || x>=dimx())?(cimg::temporary(out_val)=out_val):(*this)(x,y,z,v);
}
T atX(const int x, const int y, const int z, const int v, const T out_val) const {
return (x<0 || x>=dimx())?out_val:(*this)(x,y,z,v);
}
//! Read a pixel value with Neumann boundary conditions for the first coordinates (\c x).
T& atX(const int x, const int y=0, const int z=0, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.",
pixel_type());
return _atX(x,y,z,v);
}
T atX(const int x, const int y=0, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::atX() : Instance image is empty.",
pixel_type());
return _atX(x,y,z,v);
}
T& _atX(const int x, const int y=0, const int z=0, const int v=0) {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v);
}
T _atX(const int x, const int y=0, const int z=0, const int v=0) const {
return (*this)(x<0?0:(x>=dimx()?dimx()-1:x),y,z,v);
}
//! Read a pixel value using linear interpolation and Dirichlet boundary conditions.
Tfloat linear_atXYZV(const float fx, const float fy, const float fz, const float fv, const T out_val) const {
const int
x = (int)fx-(fx>=0?0:1), nx = x+1,
y = (int)fy-(fy>=0?0:1), ny = y+1,
z = (int)fz-(fz>=0?0:1), nz = z+1,
v = (int)fv-(fv>=0?0:1), nv = v+1;
const float
dx = fx-x,
dy = fy-y,
dz = fz-z,
dv = fv-v;
const Tfloat
Icccc = (Tfloat)atXYZV(x,y,z,v,out_val), Inccc = (Tfloat)atXYZV(nx,y,z,v,out_val),
Icncc = (Tfloat)atXYZV(x,ny,z,v,out_val), Inncc = (Tfloat)atXYZV(nx,ny,z,v,out_val),
Iccnc = (Tfloat)atXYZV(x,y,nz,v,out_val), Incnc = (Tfloat)atXYZV(nx,y,nz,v,out_val),
Icnnc = (Tfloat)atXYZV(x,ny,nz,v,out_val), Innnc = (Tfloat)atXYZV(nx,ny,nz,v,out_val),
Icccn = (Tfloat)atXYZV(x,y,z,nv,out_val), Inccn = (Tfloat)atXYZV(nx,y,z,nv,out_val),
Icncn = (Tfloat)atXYZV(x,ny,z,nv,out_val), Inncn = (Tfloat)atXYZV(nx,ny,z,nv,out_val),
Iccnn = (Tfloat)atXYZV(x,y,nz,nv,out_val), Incnn = (Tfloat)atXYZV(nx,y,nz,nv,out_val),
Icnnn = (Tfloat)atXYZV(x,ny,nz,nv,out_val), Innnn = (Tfloat)atXYZV(nx,ny,nz,nv,out_val);
return Icccc +
dx*(Inccc-Icccc +
dy*(Icccc+Inncc-Icncc-Inccc +
dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
dz*(Icccc+Incnc-Iccnc-Inccc +
dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
dv*(Icccc+Inccn-Inccc-Icccn)) +
dy*(Icncc-Icccc +
dz*(Icccc+Icnnc-Iccnc-Icncc +
dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
dv*(Icccc+Icncn-Icncc-Icccn)) +
dz*(Iccnc-Icccc +
dv*(Icccc+Iccnn-Iccnc-Icccn)) +
dv*(Icccn-Icccc);
}
//! Read a pixel value using linear interpolation and Neumann boundary conditions.
Tfloat linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::linear_atXYZV() : Instance image is empty.",
pixel_type());
return _linear_atXYZV(fx,fy,fz,fv);
}
Tfloat _linear_atXYZV(const float fx, const float fy=0, const float fz=0, const float fv=0) const {
const float
nfx = fx<0?0:(fx>width-1?width-1:fx),
nfy = fy<0?0:(fy>height-1?height-1:fy),
nfz = fz<0?0:(fz>depth-1?depth-1:fz),
nfv = fv<0?0:(fv>dim-1?dim-1:fv);
const unsigned int
x = (unsigned int)nfx,
y = (unsigned int)nfy,
z = (unsigned int)nfz,
v = (unsigned int)nfv;
const float
dx = nfx-x,
dy = nfy-y,
dz = nfz-z,
dv = nfv-v;
const unsigned int
nx = dx>0?x+1:x,
ny = dy>0?y+1:y,
nz = dz>0?z+1:z,
nv = dv>0?v+1:v;
const Tfloat
Icccc = (Tfloat)(*this)(x,y,z,v), Inccc = (Tfloat)(*this)(nx,y,z,v),
Icncc = (Tfloat)(*this)(x,ny,z,v), Inncc = (Tfloat)(*this)(nx,ny,z,v),
Iccnc = (Tfloat)(*this)(x,y,nz,v), Incnc = (Tfloat)(*this)(nx,y,nz,v),
Icnnc = (Tfloat)(*this)(x,ny,nz,v), Innnc = (Tfloat)(*this)(nx,ny,nz,v),
Icccn = (Tfloat)(*this)(x,y,z,nv), Inccn = (Tfloat)(*this)(nx,y,z,nv),
Icncn = (Tfloat)(*this)(x,ny,z,nv), Inncn = (Tfloat)(*this)(nx,ny,z,nv),
Iccnn = (Tfloat)(*this)(x,y,nz,nv), Incnn = (Tfloat)(*this)(nx,y,nz,nv),
Icnnn = (Tfloat)(*this)(x,ny,nz,nv), Innnn = (Tfloat)(*this)(nx,ny,nz,nv);
return Icccc +
dx*(Inccc-Icccc +
dy*(Icccc+Inncc-Icncc-Inccc +
dz*(Iccnc+Innnc+Icncc+Inccc-Icnnc-Incnc-Icccc-Inncc +
dv*(Iccnn+Innnn+Icncn+Inccn+Icnnc+Incnc+Icccc+Inncc-Icnnn-Incnn-Icccn-Inncn-Iccnc-Innnc-Icncc-Inccc)) +
dv*(Icccn+Inncn+Icncc+Inccc-Icncn-Inccn-Icccc-Inncc)) +
dz*(Icccc+Incnc-Iccnc-Inccc +
dv*(Icccn+Incnn+Iccnc+Inccc-Iccnn-Inccn-Icccc-Incnc)) +
dv*(Icccc+Inccn-Inccc-Icccn)) +
dy*(Icncc-Icccc +
dz*(Icccc+Icnnc-Iccnc-Icncc +
dv*(Icccn+Icnnn+Iccnc+Icncc-Iccnn-Icncn-Icccc-Icnnc)) +
dv*(Icccc+Icncn-Icncc-Icccn)) +
dz*(Iccnc-Icccc +
dv*(Icccc+Iccnn-Iccnc-Icccn)) +
dv*(Icccn-Icccc);
}
//! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first three coordinates).
Tfloat linear_atXYZ(const float fx, const float fy, const float fz, const int v, const T out_val) const {
const int
x = (int)fx-(fx>=0?0:1), nx = x+1,
y = (int)fy-(fy>=0?0:1), ny = y+1,
z = (int)fz-(fz>=0?0:1), nz = z+1;
const float
dx = fx-x,
dy = fy-y,
dz = fz-z;
const Tfloat
Iccc = (Tfloat)atXYZ(x,y,z,v,out_val), Incc = (Tfloat)atXYZ(nx,y,z,v,out_val),
Icnc = (Tfloat)atXYZ(x,ny,z,v,out_val), Innc = (Tfloat)atXYZ(nx,ny,z,v,out_val),
Iccn = (Tfloat)atXYZ(x,y,nz,v,out_val), Incn = (Tfloat)atXYZ(nx,y,nz,v,out_val),
Icnn = (Tfloat)atXYZ(x,ny,nz,v,out_val), Innn = (Tfloat)atXYZ(nx,ny,nz,v,out_val);
return Iccc +
dx*(Incc-Iccc +
dy*(Iccc+Innc-Icnc-Incc +
dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
dz*(Iccc+Incn-Iccn-Incc)) +
dy*(Icnc-Iccc +
dz*(Iccc+Icnn-Iccn-Icnc)) +
dz*(Iccn-Iccc);
}
//! Read a pixel value using linear interpolation and Neumann boundary conditions (first three coordinates).
Tfloat linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::linear_atXYZ() : Instance image is empty.",
pixel_type());
return _linear_atXYZ(fx,fy,fz,v);
}
Tfloat _linear_atXYZ(const float fx, const float fy=0, const float fz=0, const int v=0) const {
const float
nfx = fx<0?0:(fx>width-1?width-1:fx),
nfy = fy<0?0:(fy>height-1?height-1:fy),
nfz = fz<0?0:(fz>depth-1?depth-1:fz);
const unsigned int
x = (unsigned int)nfx,
y = (unsigned int)nfy,
z = (unsigned int)nfz;
const float
dx = nfx-x,
dy = nfy-y,
dz = nfz-z;
const unsigned int
nx = dx>0?x+1:x,
ny = dy>0?y+1:y,
nz = dz>0?z+1:z;
const Tfloat
Iccc = (Tfloat)(*this)(x,y,z,v), Incc = (Tfloat)(*this)(nx,y,z,v),
Icnc = (Tfloat)(*this)(x,ny,z,v), Innc = (Tfloat)(*this)(nx,ny,z,v),
Iccn = (Tfloat)(*this)(x,y,nz,v), Incn = (Tfloat)(*this)(nx,y,nz,v),
Icnn = (Tfloat)(*this)(x,ny,nz,v), Innn = (Tfloat)(*this)(nx,ny,nz,v);
return Iccc +
dx*(Incc-Iccc +
dy*(Iccc+Innc-Icnc-Incc +
dz*(Iccn+Innn+Icnc+Incc-Icnn-Incn-Iccc-Innc)) +
dz*(Iccc+Incn-Iccn-Incc)) +
dy*(Icnc-Iccc +
dz*(Iccc+Icnn-Iccn-Icnc)) +
dz*(Iccn-Iccc);
}
//! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first two coordinates).
Tfloat linear_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const {
const int
x = (int)fx-(fx>=0?0:1), nx = x+1,
y = (int)fy-(fy>=0?0:1), ny = y+1;
const float
dx = fx-x,
dy = fy-y;
const Tfloat
Icc = (Tfloat)atXY(x,y,z,v,out_val), Inc = (Tfloat)atXY(nx,y,z,v,out_val),
Icn = (Tfloat)atXY(x,ny,z,v,out_val), Inn = (Tfloat)atXY(nx,ny,z,v,out_val);
return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
}
//! Read a pixel value using linear interpolation and Neumann boundary conditions (first two coordinates).
Tfloat linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::linear_atXY() : Instance image is empty.",
pixel_type());
return _linear_atXY(fx,fy,z,v);
}
Tfloat _linear_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
const float
nfx = fx<0?0:(fx>width-1?width-1:fx),
nfy = fy<0?0:(fy>height-1?height-1:fy);
const unsigned int
x = (unsigned int)nfx,
y = (unsigned int)nfy;
const float
dx = nfx-x,
dy = nfy-y;
const unsigned int
nx = dx>0?x+1:x,
ny = dy>0?y+1:y;
const Tfloat
Icc = (Tfloat)(*this)(x,y,z,v), Inc = (Tfloat)(*this)(nx,y,z,v),
Icn = (Tfloat)(*this)(x,ny,z,v), Inn = (Tfloat)(*this)(nx,ny,z,v);
return Icc + dx*(Inc-Icc + dy*(Icc+Inn-Icn-Inc)) + dy*(Icn-Icc);
}
//! Read a pixel value using linear interpolation and Dirichlet boundary conditions (first coordinate).
Tfloat linear_atX(const float fx, const int y, const int z, const int v, const T out_val) const {
const int
x = (int)fx-(fx>=0?0:1), nx = x+1;
const float
dx = fx-x;
const Tfloat
Ic = (Tfloat)atX(x,y,z,v,out_val), In = (Tfloat)atXY(nx,y,z,v,out_val);
return Ic + dx*(In-Ic);
}
//! Read a pixel value using linear interpolation and Neumann boundary conditions (first coordinate).
Tfloat linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::linear_atX() : Instance image is empty.",
pixel_type());
return _linear_atX(fx,y,z,v);
}
Tfloat _linear_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
const float
nfx = fx<0?0:(fx>width-1?width-1:fx);
const unsigned int
x = (unsigned int)nfx;
const float
dx = nfx-x;
const unsigned int
nx = dx>0?x+1:x;
const Tfloat
Ic = (Tfloat)(*this)(x,y,z,v), In = (Tfloat)(*this)(nx,y,z,v);
return Ic + dx*(In-Ic);
}
//! Read a pixel value using cubic interpolation and Dirichlet boundary conditions.
Tfloat cubic_atXY(const float fx, const float fy, const int z, const int v, const T out_val) const {
const int
x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2,
y = (int)fy-(fy>=0?0:1), py = y-1, ny = y+1, ay = y+2;
const float
dx = fx-x, dx2 = dx*dx, dx3 = dx2*dx,
dy = fy-y;
const Tfloat
Ipp = (Tfloat)atXY(px,py,z,v,out_val), Icp = (Tfloat)atXY(x,py,z,v,out_val),
Inp = (Tfloat)atXY(nx,py,z,v,out_val), Iap = (Tfloat)atXY(ax,py,z,v,out_val),
Ipc = (Tfloat)atXY(px,y,z,v,out_val), Icc = (Tfloat)atXY(x,y,z,v,out_val),
Inc = (Tfloat)atXY(nx,y,z,v,out_val), Iac = (Tfloat)atXY(ax,y,z,v,out_val),
Ipn = (Tfloat)atXY(px,ny,z,v,out_val), Icn = (Tfloat)atXY(x,ny,z,v,out_val),
Inn = (Tfloat)atXY(nx,ny,z,v,out_val), Ian = (Tfloat)atXY(ax,ny,z,v,out_val),
Ipa = (Tfloat)atXY(px,ay,z,v,out_val), Ica = (Tfloat)atXY(x,ay,z,v,out_val),
Ina = (Tfloat)atXY(nx,ay,z,v,out_val), Iaa = (Tfloat)atXY(ax,ay,z,v,out_val),
valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
u0p = Icp - Ipp,
u1p = Iap - Inp,
ap = 2*(Icp-Inp) + u0p + u1p,
bp = 3*(Inp-Icp) - 2*u0p - u1p,
u0c = Icc - Ipc,
u1c = Iac - Inc,
ac = 2*(Icc-Inc) + u0c + u1c,
bc = 3*(Inc-Icc) - 2*u0c - u1c,
u0n = Icn - Ipn,
u1n = Ian - Inn,
an = 2*(Icn-Inn) + u0n + u1n,
bn = 3*(Inn-Icn) - 2*u0n - u1n,
u0a = Ica - Ipa,
u1a = Iaa - Ina,
aa = 2*(Ica-Ina) + u0a + u1a,
ba = 3*(Ina-Ica) - 2*u0a - u1a,
valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
u0 = valc - valp,
u1 = vala - valn,
a = 2*(valc-valn) + u0 + u1,
b = 3*(valn-valc) - 2*u0 - u1,
val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
return val<valm?valm:(val>valM?valM:val);
}
//! Read a pixel value using cubic interpolation and Neumann boundary conditions.
Tfloat cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::cubic_atXY() : Instance image is empty.",
pixel_type());
return _cubic_atXY(fx,fy,z,v);
}
Tfloat _cubic_atXY(const float fx, const float fy, const int z=0, const int v=0) const {
const float
nfx = fx<0?0:(fx>width-1?width-1:fx),
nfy = fy<0?0:(fy>height-1?height-1:fy);
const int
x = (int)nfx,
y = (int)nfy;
const float
dx = nfx-x, dx2 = dx*dx, dx3 = dx2*dx,
dy = nfy-y;
const int
px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2,
py = y-1<0?0:y-1, ny = dy>0?y+1:y, ay = y+2>=dimy()?dimy()-1:y+2;
const Tfloat
Ipp = (Tfloat)(*this)(px,py,z,v), Icp = (Tfloat)(*this)(x,py,z,v),
Inp = (Tfloat)(*this)(nx,py,z,v), Iap = (Tfloat)(*this)(ax,py,z,v),
Ipc = (Tfloat)(*this)(px,y,z,v), Icc = (Tfloat)(*this)(x,y,z,v),
Inc = (Tfloat)(*this)(nx,y,z,v), Iac = (Tfloat)(*this)(ax,y,z,v),
Ipn = (Tfloat)(*this)(px,ny,z,v), Icn = (Tfloat)(*this)(x,ny,z,v),
Inn = (Tfloat)(*this)(nx,ny,z,v), Ian = (Tfloat)(*this)(ax,ny,z,v),
Ipa = (Tfloat)(*this)(px,ay,z,v), Ica = (Tfloat)(*this)(x,ay,z,v),
Ina = (Tfloat)(*this)(nx,ay,z,v), Iaa = (Tfloat)(*this)(ax,ay,z,v),
valm = cimg::min(cimg::min(Ipp,Icp,Inp,Iap),cimg::min(Ipc,Icc,Inc,Iac),cimg::min(Ipn,Icn,Inn,Ian),cimg::min(Ipa,Ica,Ina,Iaa)),
valM = cimg::max(cimg::max(Ipp,Icp,Inp,Iap),cimg::max(Ipc,Icc,Inc,Iac),cimg::max(Ipn,Icn,Inn,Ian),cimg::max(Ipa,Ica,Ina,Iaa)),
u0p = Icp - Ipp,
u1p = Iap - Inp,
ap = 2*(Icp-Inp) + u0p + u1p,
bp = 3*(Inp-Icp) - 2*u0p - u1p,
u0c = Icc - Ipc,
u1c = Iac - Inc,
ac = 2*(Icc-Inc) + u0c + u1c,
bc = 3*(Inc-Icc) - 2*u0c - u1c,
u0n = Icn - Ipn,
u1n = Ian - Inn,
an = 2*(Icn-Inn) + u0n + u1n,
bn = 3*(Inn-Icn) - 2*u0n - u1n,
u0a = Ica - Ipa,
u1a = Iaa - Ina,
aa = 2*(Ica-Ina) + u0a + u1a,
ba = 3*(Ina-Ica) - 2*u0a - u1a,
valp = ap*dx3 + bp*dx2 + u0p*dx + Icp,
valc = ac*dx3 + bc*dx2 + u0c*dx + Icc,
valn = an*dx3 + bn*dx2 + u0n*dx + Icn,
vala = aa*dx3 + ba*dx2 + u0a*dx + Ica,
u0 = valc - valp,
u1 = vala - valn,
a = 2*(valc-valn) + u0 + u1,
b = 3*(valn-valc) - 2*u0 - u1,
val = a*dy*dy*dy + b*dy*dy + u0*dy + valc;
return val<valm?valm:(val>valM?valM:val);
}
//! Read a pixel value using cubic interpolation and Dirichlet boundary conditions (first coordinates).
Tfloat cubic_atX(const float fx, const int y, const int z, const int v, const T out_val) const {
const int
x = (int)fx-(fx>=0?0:1), px = x-1, nx = x+1, ax = x+2;
const float
dx = fx-x;
const Tfloat
Ip = (Tfloat)atX(px,y,z,v,out_val), Ic = (Tfloat)atX(x,y,z,v,out_val),
In = (Tfloat)atX(nx,y,z,v,out_val), Ia = (Tfloat)atX(ax,y,z,v,out_val),
valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
u0 = Ic - Ip,
u1 = Ia - In,
a = 2*(Ic-In) + u0 + u1,
b = 3*(In-Ic) - 2*u0 - u1,
val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
return val<valm?valm:(val>valM?valM:val);
}
//! Read a pixel value using cubic interpolation and Neumann boundary conditions (first coordinates).
Tfloat cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::cubic_atX() : Instance image is empty.",
pixel_type());
return _cubic_atX(fx,y,z,v);
}
Tfloat _cubic_atX(const float fx, const int y=0, const int z=0, const int v=0) const {
const float
nfx = fx<0?0:(fx>width-1?width-1:fx);
const int
x = (int)nfx;
const float
dx = nfx-x;
const int
px = x-1<0?0:x-1, nx = dx>0?x+1:x, ax = x+2>=dimx()?dimx()-1:x+2;
const Tfloat
Ip = (Tfloat)(*this)(px,y,z,v), Ic = (Tfloat)(*this)(x,y,z,v),
In = (Tfloat)(*this)(nx,y,z,v), Ia = (Tfloat)(*this)(ax,y,z,v),
valm = cimg::min(Ip,In,Ic,Ia), valM = cimg::max(Ip,In,Ic,Ia),
u0 = Ic - Ip,
u1 = Ia - In,
a = 2*(Ic-In) + u0 + u1,
b = 3*(In-Ic) - 2*u0 - u1,
val = a*dx*dx*dx + b*dx*dx + u0*dx + Ic;
return val<valm?valm:(val>valM?valM:val);
}
//! Set a pixel value, with 3D float coordinates, using linear interpolation.
CImg& set_linear_atXYZ(const T& val, const float fx, const float fy=0, const float fz=0, const int v=0,
const bool add=false) {
const int
x = (int)fx-(fx>=0?0:1), nx = x+1,
y = (int)fy-(fy>=0?0:1), ny = y+1,
z = (int)fz-(fz>=0?0:1), nz = z+1;
const float
dx = fx-x,
dy = fy-y,
dz = fz-z;
if (v>=0 && v<dimv()) {
if (z>=0 && z<dimz()) {
if (y>=0 && y<dimy()) {
if (x>=0 && x<dimx()) {
const float w1 = (1-dx)*(1-dy)*(1-dz), w2 = add?1:(1-w1);
(*this)(x,y,z,v) = (T)(w1*val + w2*(*this)(x,y,z,v));
}
if (nx>=0 && nx<dimx()) {
const float w1 = dx*(1-dy)*(1-dz), w2 = add?1:(1-w1);
(*this)(nx,y,z,v) = (T)(w1*val + w2*(*this)(nx,y,z,v));
}
}
if (ny>=0 && ny<dimy()) {
if (x>=0 && x<dimx()) {
const float w1 = (1-dx)*dy*(1-dz), w2 = add?1:(1-w1);
(*this)(x,ny,z,v) = (T)(w1*val + w2*(*this)(x,ny,z,v));
}
if (nx>=0 && nx<dimx()) {
const float w1 = dx*dy*(1-dz), w2 = add?1:(1-w1);
(*this)(nx,ny,z,v) = (T)(w1*val + w2*(*this)(nx,ny,z,v));
}
}
}
if (nz>=0 && nz<dimz()) {
if (y>=0 && y<dimy()) {
if (x>=0 && x<dimx()) {
const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
(*this)(x,y,nz,v) = (T)(w1*val + w2*(*this)(x,y,nz,v));
}
if (nx>=0 && nx<dimx()) {
const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
(*this)(nx,y,nz,v) = (T)(w1*val + w2*(*this)(nx,y,nz,v));
}
}
if (ny>=0 && ny<dimy()) {
if (x>=0 && x<dimx()) {
const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
(*this)(x,ny,nz,v) = (T)(w1*val + w2*(*this)(x,ny,nz,v));
}
if (nx>=0 && nx<dimx()) {
const float w1 = dx*dy, w2 = add?1:(1-w1);
(*this)(nx,ny,nz,v) = (T)(w1*val + w2*(*this)(nx,ny,nz,v));
}
}
}
}
return *this;
}
//! Set a pixel value, with 2D float coordinates, using linear interpolation.
CImg& set_linear_atXY(const T& val, const float fx, const float fy=0, const int z=0, const int v=0,
const bool add=false) {
const int
x = (int)fx-(fx>=0?0:1), nx = x+1,
y = (int)fy-(fy>=0?0:1), ny = y+1;
const float
dx = fx-x,
dy = fy-y;
if (z>=0 && z<dimz() && v>=0 && v<dimv()) {
if (y>=0 && y<dimy()) {
if (x>=0 && x<dimx()) {
const float w1 = (1-dx)*(1-dy), w2 = add?1:(1-w1);
(*this)(x,y,z,v) = (T)(w1*val + w2*(*this)(x,y,z,v));
}
if (nx>=0 && nx<dimx()) {
const float w1 = dx*(1-dy), w2 = add?1:(1-w1);
(*this)(nx,y,z,v) = (T)(w1*val + w2*(*this)(nx,y,z,v));
}
}
if (ny>=0 && ny<dimy()) {
if (x>=0 && x<dimx()) {
const float w1 = (1-dx)*dy, w2 = add?1:(1-w1);
(*this)(x,ny,z,v) = (T)(w1*val + w2*(*this)(x,ny,z,v));
}
if (nx>=0 && nx<dimx()) {
const float w1 = dx*dy, w2 = add?1:(1-w1);
(*this)(nx,ny,z,v) = (T)(w1*val + w2*(*this)(nx,ny,z,v));
}
}
}
return *this;
}
//! Return a reference to the minimum pixel value of the instance image
const T& min() const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::min() : Instance image is empty.",
pixel_type());
const T *ptrmin = data;
T min_value = *ptrmin;
cimg_for(*this,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
return *ptrmin;
}
//! Return a reference to the minimum pixel value of the instance image
T& min() {
if (is_empty())
throw CImgInstanceException("CImg<%s>::min() : Instance image is empty.",
pixel_type());
T *ptrmin = data;
T min_value = *ptrmin;
cimg_for(*this,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
return *ptrmin;
}
//! Return a reference to the maximum pixel value of the instance image
const T& max() const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.",
pixel_type());
const T *ptrmax = data;
T max_value = *ptrmax;
cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
return *ptrmax;
}
//! Return a reference to the maximum pixel value of the instance image
T& max() {
if (is_empty())
throw CImgInstanceException("CImg<%s>::max() : Instance image is empty.",
pixel_type());
T *ptrmax = data;
T max_value = *ptrmax;
cimg_for(*this,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
return *ptrmax;
}
//! Return a reference to the minimum pixel value and return also the maximum pixel value.
template<typename t>
const T& minmax(t& max_val) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.",
pixel_type());
const T *ptrmin = data;
T min_value = *ptrmin, max_value = min_value;
cimg_for(*this,ptr,T) {
const T val = *ptr;
if (val<min_value) { min_value = val; ptrmin = ptr; }
if (val>max_value) max_value = val;
}
max_val = (t)max_value;
return *ptrmin;
}
//! Return a reference to the minimum pixel value and return also the maximum pixel value.
template<typename t>
T& minmax(t& max_val) {
if (is_empty())
throw CImgInstanceException("CImg<%s>::minmax() : Instance image is empty.",
pixel_type());
T *ptrmin = data;
T min_value = *ptrmin, max_value = min_value;
cimg_for(*this,ptr,T) {
const T val = *ptr;
if (val<min_value) { min_value = val; ptrmin = ptr; }
if (val>max_value) max_value = val;
}
max_val = (t)max_value;
return *ptrmin;
}
//! Return a reference to the maximum pixel value and return also the minimum pixel value.
template<typename t>
const T& maxmin(t& min_val) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.",
pixel_type());
const T *ptrmax = data;
T max_value = *ptrmax, min_value = max_value;
cimg_for(*this,ptr,T) {
const T val = *ptr;
if (val>max_value) { max_value = val; ptrmax = ptr; }
if (val<min_value) min_value = val;
}
min_val = (t)min_value;
return *ptrmax;
}
//! Return a reference to the maximum pixel value and return also the minimum pixel value.
template<typename t>
T& maxmin(t& min_val) {
if (is_empty())
throw CImgInstanceException("CImg<%s>::maxmin() : Instance image is empty.",
pixel_type());
T *ptrmax = data;
T max_value = *ptrmax, min_value = max_value;
cimg_for(*this,ptr,T) {
const T val = *ptr;
if (val>max_value) { max_value = val; ptrmax = ptr; }
if (val<min_value) min_value = val;
}
min_val = (t)min_value;
return *ptrmax;
}
//! Return the sum of all the pixel values in an image.
Tfloat sum() const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::sum() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
Tfloat res = 0;
cimg_for(*this,ptr,T) res+=*ptr;
return res;
}
//! Return the mean pixel value of the instance image.
Tfloat mean() const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::mean() : Instance image is empty.",
pixel_type());
Tfloat val = 0;
cimg_for(*this,ptr,T) val+=*ptr;
return val/size();
}
//! Return the variance of the image.
/**
@param variance_method Determines how to calculate the variance
<table border="0">
<tr><td>0</td>
<td>Second moment:
@f$ v = 1/N \sum\limits_{k=1}^{N} (x_k - \bar x)^2
= 1/N \left( \sum\limits_{k=1}^N x_k^2 - \left( \sum\limits_{k=1}^N x_k \right)^2 / N \right) @f$
with @f$ \bar x = 1/N \sum\limits_{k=1}^N x_k \f$</td></tr>
<tr><td>1</td>
<td>Best unbiased estimator: @f$ v = \frac{1}{N-1} \sum\limits_{k=1}^{N} (x_k - \bar x)^2 @f$</td></tr>
<tr><td>2</td>
<td>Least median of squares</td></tr>
<tr><td>3</td>
<td>Least trimmed of squares</td></tr>
</table>
*/
Tfloat variance(const unsigned int variance_method=1) const {
Tfloat foo;
return variancemean(variance_method,foo);
}
//! Return the variance and the mean of the image.
template<typename t>
Tfloat variancemean(const unsigned int variance_method, t& mean) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::variance() : Instance image is empty.",
pixel_type());
Tfloat variance = 0, average = 0;
const unsigned int siz = size();
switch (variance_method) {
case 3 : { // Least trimmed of Squares
CImg<Tfloat> buf(*this);
const unsigned int siz2 = siz>>1;
{ cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; (*ptrs)*=val; average+=val; }}
buf.sort();
Tfloat a = 0;
const Tfloat *ptrs = buf.ptr();
for (unsigned int j = 0; j<siz2; ++j) a+=*(ptrs++);
const Tfloat sig = (Tfloat)(2.6477*cimg_std::sqrt(a/siz2));
variance = sig*sig;
} break;
case 2 : { // Least Median of Squares (MAD)
CImg<Tfloat> buf(*this);
buf.sort();
const unsigned int siz2 = siz>>1;
const Tfloat med_i = buf[siz2];
cimg_for(buf,ptrs,Tfloat) { const Tfloat val = *ptrs; *ptrs = cimg::abs(val - med_i); average+=val; }
buf.sort();
const Tfloat sig = (Tfloat)(1.4828*buf[siz2]);
variance = sig*sig;
} break;
case 1 : { // Least mean square (robust definition)
Tfloat S = 0, S2 = 0;
cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val; S2+=val*val; }
variance = siz>1?(S2 - S*S/siz)/(siz - 1):0;
average = S;
} break;
case 0 :{ // Least mean square (standard definition)
Tfloat S = 0, S2 = 0;
cimg_for(*this,ptr,T) { const Tfloat val = (Tfloat)*ptr; S+=val; S2+=val*val; }
variance = (S2 - S*S/siz)/siz;
average = S;
} break;
default :
throw CImgArgumentException("CImg<%s>::variancemean() : Incorrect parameter 'variance_method = %d' (correct values are 0,1,2 or 3).",
pixel_type(),variance_method);
}
mean = (t)(average/siz);
return variance>0?variance:0;
}
//! Return the kth smallest element of the image.
- // (Adapted from the numerical recipies for CImg)
+ // (Adapted from the numerical recipes for CImg)
T kth_smallest(const unsigned int k) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::kth_smallest() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
CImg<T> arr(*this);
unsigned long l = 0, ir = size()-1;
for (;;) {
if (ir<=l+1) {
if (ir==l+1 && arr[ir]<arr[l]) cimg::swap(arr[l],arr[ir]);
return arr[k];
} else {
const unsigned long mid = (l+ir)>>1;
cimg::swap(arr[mid],arr[l+1]);
if (arr[l]>arr[ir]) cimg::swap(arr[l],arr[ir]);
if (arr[l+1]>arr[ir]) cimg::swap(arr[l+1],arr[ir]);
if (arr[l]>arr[l+1]) cimg::swap(arr[l],arr[l+1]);
unsigned long i = l+1, j = ir;
const T pivot = arr[l+1];
for (;;) {
do ++i; while (arr[i]<pivot);
do --j; while (arr[j]>pivot);
if (j<i) break;
cimg::swap(arr[i],arr[j]);
}
arr[l+1] = arr[j];
arr[j] = pivot;
if (j>=k) ir=j-1;
if (j<=k) l=i;
}
}
return 0;
}
//! Compute a statistics vector (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax).
CImg<T>& stats(const unsigned int variance_method=1) {
return get_stats(variance_method).transfer_to(*this);
}
CImg<Tfloat> get_stats(const unsigned int variance_method=1) const {
if (is_empty()) return CImg<Tfloat>();
const unsigned long siz = size();
const T *const odata = data;
const T *pm = odata, *pM = odata;
Tfloat S = 0, S2 = 0;
T m = *pm, M = m;
cimg_for(*this,ptr,T) {
const T val = *ptr;
const Tfloat fval = (Tfloat)val;
if (val<m) { m = val; pm = ptr; }
if (val>M) { M = val; pM = ptr; }
S+=fval;
S2+=fval*fval;
}
const Tfloat
mean_value = S/siz,
_variance_value = variance_method==0?(S2 - S*S/siz)/siz:
(variance_method==1?(siz>1?(S2 - S*S/siz)/(siz - 1):0):
variance(variance_method)),
variance_value = _variance_value>0?_variance_value:0;
int
xm = 0, ym = 0, zm = 0, vm = 0,
xM = 0, yM = 0, zM = 0, vM = 0;
contains(*pm,xm,ym,zm,vm);
contains(*pM,xM,yM,zM,vM);
return CImg<Tfloat>(1,12).fill((Tfloat)m,(Tfloat)M,mean_value,variance_value,
(Tfloat)xm,(Tfloat)ym,(Tfloat)zm,(Tfloat)vm,
(Tfloat)xM,(Tfloat)yM,(Tfloat)zM,(Tfloat)vM);
}
//! Return the median value of the image.
T median() const {
const unsigned int s = size();
const T res = kth_smallest(s>>1);
return (s%2)?res:((res+kth_smallest((s>>1)-1))/2);
}
//! Compute the MSE (Mean-Squared Error) between two images.
template<typename t>
Tfloat MSE(const CImg<t>& img) const {
if (img.size()!=size())
throw CImgArgumentException("CImg<%s>::MSE() : Instance image (%u,%u,%u,%u) and given image (%u,%u,%u,%u) have different dimensions.",
pixel_type(),width,height,depth,dim,img.width,img.height,img.depth,img.dim);
Tfloat vMSE = 0;
const t* ptr2 = img.end();
cimg_for(*this,ptr1,T) {
const Tfloat diff = (Tfloat)*ptr1 - (Tfloat)*(--ptr2);
vMSE += diff*diff;
}
vMSE/=img.size();
return vMSE;
}
//! Compute the PSNR between two images.
template<typename t>
Tfloat PSNR(const CImg<t>& img, const Tfloat valmax=(Tfloat)255) const {
const Tfloat vMSE = (Tfloat)cimg_std::sqrt(MSE(img));
return (vMSE!=0)?(Tfloat)(20*cimg_std::log10(valmax/vMSE)):(Tfloat)(cimg::type<Tfloat>::max());
}
//! Return the trace of the image, viewed as a matrix.
Tfloat trace() const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::trace() : Instance matrix (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
Tfloat res = 0;
cimg_forX(*this,k) res+=(*this)(k,k);
return res;
}
//! Return the dot product of the current vector/matrix with the vector/matrix \p img.
template<typename t>
Tfloat dot(const CImg<t>& img) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::dot() : Instance object (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
if (!img)
throw CImgArgumentException("CImg<%s>::trace() : Specified argument (%u,%u,%u,%u,%p) is empty.",
pixel_type(),img.width,img.height,img.depth,img.dim,img.data);
const unsigned long nb = cimg::min(size(),img.size());
Tfloat res = 0;
for (unsigned long off = 0; off<nb; ++off) res+=(Tfloat)data[off]*(Tfloat)img[off];
return res;
}
//! Return the determinant of the image, viewed as a matrix.
Tfloat det() const {
if (is_empty() || width!=height || depth!=1 || dim!=1)
throw CImgInstanceException("CImg<%s>::det() : Instance matrix (%u,%u,%u,%u,%p) is not square or is empty.",
pixel_type(),width,height,depth,dim,data);
switch (width) {
case 1 : return (Tfloat)((*this)(0,0));
case 2 : return (Tfloat)((*this)(0,0))*(Tfloat)((*this)(1,1)) - (Tfloat)((*this)(0,1))*(Tfloat)((*this)(1,0));
case 3 : {
const Tfloat
a = (Tfloat)data[0], d = (Tfloat)data[1], g = (Tfloat)data[2],
b = (Tfloat)data[3], e = (Tfloat)data[4], h = (Tfloat)data[5],
c = (Tfloat)data[6], f = (Tfloat)data[7], i = (Tfloat)data[8];
return i*a*e - a*h*f - i*b*d + b*g*f + c*d*h - c*g*e;
}
default : {
CImg<Tfloat> lu(*this);
CImg<uintT> indx;
bool d;
lu._LU(indx,d);
Tfloat res = d?(Tfloat)1:(Tfloat)-1;
cimg_forX(lu,i) res*=lu(i,i);
return res;
}
}
return 0;
}
//! Return the norm of the current vector/matrix. \p ntype = norm type (0=L2, 1=L1, -1=Linf).
Tfloat norm(const int norm_type=2) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::norm() : Instance object (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
Tfloat res = 0;
switch (norm_type) {
case -1 : {
cimg_foroff(*this,off) {
const Tfloat tmp = cimg::abs((Tfloat)data[off]);
if (tmp>res) res = tmp;
}
return res;
} break;
case 1 : {
cimg_foroff(*this,off) res+=cimg::abs((Tfloat)data[off]);
return res;
} break;
case 2 : return (Tfloat)cimg_std::sqrt(dot(*this)); break;
default :
throw CImgArgumentException("CImg<%s>::norm() : Incorrect parameter 'norm_type=%d' (correct values are -1,1 or 2).",
pixel_type(),norm_type);
}
return 0;
}
//! Return a C-string containing the values of the instance image.
CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
if (is_empty()) return CImg<charT>(1,1,1,1,0);
const unsigned int siz = (unsigned int)size();
CImgList<charT> items;
char item[256] = { 0 };
const T *ptrs = ptr();
for (unsigned int off = 0; off<siz-1; ++off) {
cimg_std::sprintf(item,cimg::type<T>::format(),cimg::type<T>::format(*(ptrs++)));
const int l = cimg::strlen(item);
items.insert(CImg<charT>(item,l+1));
items[items.size-1](l) = separator;
}
cimg_std::sprintf(item,cimg::type<T>::format(),cimg::type<T>::format(*ptrs));
items.insert(CImg<charT>(item,cimg::strlen(item)+1));
CImg<ucharT> res = items.get_append('x');
if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
return res;
}
//! Display informations about the image on the standard error output.
/**
\param title Name for the considered image (optional).
\param display_stats Compute and display image statistics (optional).
**/
const CImg<T>& print(const char *title=0, const bool display_stats=true) const {
int xm = 0, ym = 0, zm = 0, vm = 0, xM = 0, yM = 0, zM = 0, vM = 0;
static CImg<doubleT> st;
if (!is_empty() && display_stats) {
st = get_stats();
xm = (int)st[4]; ym = (int)st[5], zm = (int)st[6], vm = (int)st[7];
xM = (int)st[8]; yM = (int)st[9], zM = (int)st[10], vM = (int)st[11];
}
const unsigned long siz = size(), msiz = siz*sizeof(T), siz1 = siz-1;
const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2), width1 = width-1;
char ntitle[64] = { 0 };
if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = (%u,%u,%u,%u) [%lu %s], data = (%s*)%p (%s) = [ ",
title?title:ntitle,(void*)this,width,height,depth,dim,
mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
pixel_type(),(void*)data,is_shared?"shared":"not shared");
if (!is_empty()) cimg_foroff(*this,off) {
cimg_std::fprintf(cimg_stdout,cimg::type<T>::format(),cimg::type<T>::format(data[off]));
if (off!=siz1) cimg_std::fprintf(cimg_stdout,"%s",off%width==width1?" ; ":" ");
if (off==7 && siz>16) { off = siz1-8; if (off!=7) cimg_std::fprintf(cimg_stdout,"... "); }
}
if (!is_empty() && display_stats)
cimg_std::fprintf(cimg_stdout," ], min = %g, max = %g, mean = %g, std = %g, coords(min) = (%u,%u,%u,%u), coords(max) = (%u,%u,%u,%u).\n",
st[0],st[1],st[2],cimg_std::sqrt(st[3]),xm,ym,zm,vm,xM,yM,zM,vM);
else cimg_std::fprintf(cimg_stdout,"%s].\n",is_empty()?"":" ");
return *this;
}
//@}
//------------------------------------------
//
//! \name Arithmetic and Boolean Operators
//@{
//------------------------------------------
//! Assignment operator.
/**
This operator assigns a copy of the input image \p img to the current instance image.
\param img The input image to copy.
\remark
- This operator is strictly equivalent to the function assign(const CImg< t >&) and has exactly the same properties.
**/
template<typename t>
CImg<T>& operator=(const CImg<t>& img) {
return assign(img);
}
CImg<T>& operator=(const CImg<T>& img) {
return assign(img);
}
//! Assign values of a C-array to the instance image.
/**
\param buf Pointer to a C-style array having a size of (at least) <tt>this->size()</tt>.
- Replace pixel values by the content of the array \c buf.
- Warning : the value types in the array and in the image must be the same.
\par example:
\code
float tab[4*4] = { 1,2,3,4, 5,6,7,8, 9,10,11,12, 13,14,15,16 }; // Define a 4x4 matrix in C-style.
CImg<float> matrice(4,4); // Define a 4x4 greyscale image.
matrice = tab; // Fill the image by the values in tab.
\endcode
**/
CImg<T>& operator=(const T *buf) {
return assign(buf,width,height,depth,dim);
}
//! Assign a value to each image pixel of the instance image.
CImg<T>& operator=(const T val) {
return fill(val);
}
//! Operator+
/**
\remark
- This operator can be used to get a non-shared copy of an image.
**/
CImg<T> operator+() const {
return CImg<T>(*this,false);
}
//! Operator+=;
#ifdef cimg_use_visualcpp6
CImg<T>& operator+=(const T val)
#else
template<typename t>
CImg<T>& operator+=(const t val)
#endif
{
cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)+val);
return *this;
}
//! Operator+=
template<typename t>
CImg<T>& operator+=(const CImg<t>& img) {
if (is_overlapped(img)) return *this+=+img;
const unsigned int smin = cimg::min(size(),img.size());
t *ptrs = img.data + smin;
for (T *ptrd = data + smin; ptrd>data; --ptrd, (*ptrd)=(T)((*ptrd)+(*(--ptrs)))) {}
return *this;
}
//! Operator++ (prefix)
CImg<T>& operator++() {
cimg_for(*this,ptr,T) ++(*ptr);
return *this;
}
//! Operator++ (postfix)
CImg<T> operator++(int) {
const CImg<T> copy(*this,false);
++*this;
return copy;
}
//! Operator-.
CImg<T> operator-() const {
return CImg<T>(width,height,depth,dim,0)-=*this;
}
//! Operator-=.
#ifdef cimg_use_visualcpp6
CImg<T>& operator-=(const T val)
#else
template<typename t>
CImg<T>& operator-=(const t val)
#endif
{
cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)-val);
return *this;
}
//! Operator-=.
template<typename t>
CImg<T>& operator-=(const CImg<t>& img) {
if (is_overlapped(img)) return *this-=+img;
const unsigned int smin = cimg::min(size(),img.size());
t *ptrs = img.data+smin;
for (T *ptrd = data+smin; ptrd>data; --ptrd, (*ptrd) = (T)((*ptrd)-(*(--ptrs)))) {}
return *this;
}
//! Operator-- (prefix).
CImg<T>& operator--() {
cimg_for(*this,ptr,T) *ptr = *ptr-(T)1;
return *this;
}
//! Operator-- (postfix).
CImg<T> operator--(int) {
CImg<T> copy(*this,false);
--*this;
return copy;
}
//! Operator*=.
#ifdef cimg_use_visualcpp6
CImg<T>& operator*=(const double val)
#else
template<typename t>
CImg<T>& operator*=(const t val)
#endif
{
cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)*val);
return *this;
}
//! Operator*=.
template<typename t>
CImg<T>& operator*=(const CImg<t>& img) {
return ((*this)*img).transfer_to(*this);
}
//! Operator/=.
#ifdef cimg_use_visualcpp6
CImg<T>& operator/=(const double val)
#else
template<typename t>
CImg<T>& operator/=(const t val)
#endif
{
cimg_for(*this,ptr,T) (*ptr) = (T)((*ptr)/val);
return *this;
}
//! Operator/=.
template<typename t>
CImg<T>& operator/=(const CImg<t>& img) {
return assign(*this*img.get_invert());
}
//! Modulo.
template<typename t>
CImg<typename cimg::superset<T,t>::type> operator%(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false)%=img;
}
//! Modulo.
CImg<T> operator%(const T val) const {
return (+*this)%=val;
}
//! In-place modulo.
CImg<T>& operator%=(const T val) {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg::mod(*ptr,val);
return *this;
}
//! In-place modulo.
template<typename t>
CImg<T>& operator%=(const CImg<t>& img) {
if (is_overlapped(img)) return *this%=+img;
typedef typename cimg::superset<T,t>::type Tt;
const unsigned int smin = cimg::min(size(),img.size());
const t *ptrs = img.data + smin;
for (T *ptrd = data + smin; ptrd>data; ) {
T& val = *(--ptrd);
val = (T)cimg::mod((Tt)val,(Tt)*(--ptrs));
}
return *this;
}
//! Bitwise AND.
template<typename t>
CImg<typename cimg::superset<T,t>::type> operator&(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false)&=img;
}
//! Bitwise AND.
CImg<T> operator&(const T val) const {
return (+*this)&=val;
}
//! In-place bitwise AND.
template<typename t>
CImg<T>& operator&=(const CImg<t>& img) {
if (is_overlapped(img)) return *this&=+img;
const unsigned int smin = cimg::min(size(),img.size());
const t *ptrs = img.data + smin;
for (T *ptrd = data + smin; ptrd>data; ) {
T& val = *(--ptrd);
val = (T)((unsigned long)val & (unsigned long)*(--ptrs));
}
return *this;
}
//! In-place bitwise AND.
CImg<T>& operator&=(const T val) {
cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr & (unsigned long)val);
return *this;
}
//! Bitwise OR.
template<typename t>
CImg<typename cimg::superset<T,t>::type> operator|(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false)|=img;
}
//! Bitwise OR.
CImg<T> operator|(const T val) const {
return (+*this)|=val;
}
//! In-place bitwise OR.
template<typename t>
CImg<T>& operator|=(const CImg<t>& img) {
if (is_overlapped(img)) return *this|=+img;
const unsigned int smin = cimg::min(size(),img.size());
const t *ptrs = img.data + smin;
for (T *ptrd = data + smin; ptrd>data; ) {
T& val = *(--ptrd);
val = (T)((unsigned long)val | (unsigned long)*(--ptrs));
}
return *this;
}
//! In-place bitwise OR.
CImg<T>& operator|=(const T val) {
cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr | (unsigned long)val);
return *this;
}
//! Bitwise XOR.
template<typename t>
CImg<typename cimg::superset<T,t>::type> operator^(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false)^=img;
}
//! Bitwise XOR.
CImg<T> operator^(const T val) const {
return (+*this)^=val;
}
//! In-place bitwise XOR.
template<typename t>
CImg<T>& operator^=(const CImg<t>& img) {
if (is_overlapped(img)) return *this^=+img;
const unsigned int smin = cimg::min(size(),img.size());
const t *ptrs = img.data + smin;
for (T *ptrd = data+smin; ptrd>data; ) {
T& val = *(--ptrd);
val =(T)((unsigned long)val ^ (unsigned long)*(--ptrs));
}
return *this;
}
//! In-place bitwise XOR.
CImg<T>& operator^=(const T val) {
cimg_for(*this,ptr,T) *ptr = (T)((unsigned long)*ptr ^ (unsigned long)val);
return *this;
}
//! Bitwise NOT.
CImg<T> operator~() const {
CImg<T> res(width,height,depth,dim);
const T *ptrs = end();
cimg_for(res,ptrd,T) { const unsigned long val = (unsigned long)*(--ptrs); *ptrd = (T)~val; }
return res;
}
//! Bitwise left shift.
CImg<T>& operator<<=(const int n) {
cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)<<n);
return *this;
}
//! Bitwise left shift.
CImg<T> operator<<(const int n) const {
return (+*this)<<=n;
}
//! Bitwise right shift.
CImg<T>& operator>>=(const int n) {
cimg_for(*this,ptr,T) *ptr = (T)(((long)*ptr)>>n);
return *this;
}
//! Bitwise right shift.
CImg<T> operator>>(const int n) const {
return (+*this)>>=n;
}
//! Boolean equality.
template<typename t>
bool operator==(const CImg<t>& img) const {
const unsigned int siz = size();
bool vequal = true;
if (siz!=img.size()) return false;
t *ptrs = img.data + siz;
for (T *ptrd = data + siz; vequal && ptrd>data; vequal = vequal && ((*(--ptrd))==(*(--ptrs)))) {}
return vequal;
}
//! Boolean difference.
template<typename t>
bool operator!=(const CImg<t>& img) const {
return !((*this)==img);
}
//! Return a list of two images { *this, img }.
template<typename t>
CImgList<typename cimg::superset<T,t>::type> operator<<(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImgList<Tt>(*this,img);
}
//! Return a copy of \p list, where image *this has been inserted at first position.
template<typename t>
CImgList<typename cimg::superset<T,t>::type> operator<<(const CImgList<t>& list) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImgList<Tt>(list).insert(*this,0);
}
//! Return a list of two images { *this, img }.
template<typename t>
CImgList<typename cimg::superset<T,t>::type> operator>>(const CImg<t>& img) const {
return (*this)<<img;
}
- //! Insert an image into the begining of an image list.
+ //! Insert an image into the beginning of an image list.
template<typename t>
CImgList<t>& operator>>(const CImgList<t>& list) const {
return list.insert(*this,0);
}
//! Display an image into a CImgDisplay.
const CImg<T>& operator>>(CImgDisplay& disp) const {
return display(disp);
}
//@}
//---------------------------------------
//
//! \name Usual Mathematics Functions
//@{
//---------------------------------------
//! Apply a R->R function on all pixel values.
template<typename t>
CImg<T>& apply(t& func) {
cimg_for(*this,ptr,T) *ptr = func(*ptr);
return *this;
}
template<typename t>
CImg<T> get_apply(t& func) const {
return (+*this).apply(func);
}
//! Pointwise multiplication between two images.
template<typename t>
CImg<T>& mul(const CImg<t>& img) {
if (is_overlapped(img)) return mul(+img);
t *ptrs = img.data;
T *ptrf = data + cimg::min(size(),img.size());
for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd*(*(ptrs++)));
return *this;
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_mul(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false).mul(img);
}
//! Pointwise division between two images.
template<typename t>
CImg<T>& div(const CImg<t>& img) {
if (is_overlapped(img)) return div(+img);
t *ptrs = img.data;
T *ptrf = data + cimg::min(size(),img.size());
for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)(*ptrd/(*(ptrs++)));
return *this;
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_div(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false).div(img);
}
//! Pointwise max operator between two images.
template<typename t>
CImg<T>& max(const CImg<t>& img) {
if (is_overlapped(img)) return max(+img);
t *ptrs = img.data;
T *ptrf = data + cimg::min(size(),img.size());
for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::max((T)*(ptrs++),*ptrd);
return *this;
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_max(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false).max(img);
}
//! Pointwise max operator between an image and a value.
CImg<T>& max(const T val) {
cimg_for(*this,ptr,T) (*ptr) = cimg::max(*ptr,val);
return *this;
}
CImg<T> get_max(const T val) const {
return (+*this).max(val);
}
//! Pointwise min operator between two images.
template<typename t>
CImg<T>& min(const CImg<t>& img) {
if (is_overlapped(img)) return min(+img);
t *ptrs = img.data;
T *ptrf = data + cimg::min(size(),img.size());
for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = cimg::min((T)*(ptrs++),*ptrd);
return *this;
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_min(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this,false).min(img);
}
//! Pointwise min operator between an image and a value.
CImg<T>& min(const T val) {
cimg_for(*this,ptr,T) (*ptr) = cimg::min(*ptr,val);
return *this;
}
CImg<T> get_min(const T val) const {
return (+*this).min(val);
}
//! Compute the square value of each pixel.
CImg<T>& sqr() {
cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)(val*val); };
return *this;
}
CImg<Tfloat> get_sqr() const {
return CImg<Tfloat>(*this,false).sqr();
}
//! Compute the square root of each pixel value.
CImg<T>& sqrt() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sqrt((double)(*ptr));
return *this;
}
CImg<Tfloat> get_sqrt() const {
return CImg<Tfloat>(*this,false).sqrt();
}
//! Compute the exponential of each pixel value.
CImg<T>& exp() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::exp((double)(*ptr));
return *this;
}
CImg<Tfloat> get_exp() const {
return CImg<Tfloat>(*this,false).exp();
}
//! Compute the log of each pixel value.
CImg<T>& log() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log((double)(*ptr));
return *this;
}
CImg<Tfloat> get_log() const {
return CImg<Tfloat>(*this,false).log();
}
//! Compute the log10 of each pixel value.
CImg<T>& log10() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::log10((double)(*ptr));
return *this;
}
CImg<Tfloat> get_log10() const {
return CImg<Tfloat>(*this,false).log10();
}
//! Compute the power by p of each pixel value.
CImg<T>& pow(const double p) {
if (p==0) return fill(1);
if (p==0.5) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = (T)cimg_std::sqrt((double)val); } return *this; }
if (p==1) return *this;
if (p==2) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val; } return *this; }
if (p==3) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val; } return *this; }
if (p==4) { cimg_for(*this,ptr,T) { const T val = *ptr; *ptr = val*val*val*val; } return *this; }
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::pow((double)(*ptr),p);
return *this;
}
CImg<Tfloat> get_pow(const double p) const {
return CImg<Tfloat>(*this,false).pow(p);
}
//! Compute the power of each pixel value.
template<typename t>
CImg<T>& pow(const CImg<t>& img) {
if (is_overlapped(img)) return pow(+img);
t *ptrs = img.data;
T *ptrf = data + cimg::min(size(),img.size());
for (T* ptrd = data; ptrd<ptrf; ++ptrd) (*ptrd) = (T)cimg_std::pow((double)*ptrd,(double)(*(ptrs++)));
return *this;
}
template<typename t>
CImg<Tfloat> get_pow(const CImg<t>& img) const {
return CImg<Tfloat>(*this,false).pow(img);
}
//! Compute the absolute value of each pixel value.
CImg<T>& abs() {
cimg_for(*this,ptr,T) (*ptr) = cimg::abs(*ptr);
return *this;
}
CImg<Tfloat> get_abs() const {
return CImg<Tfloat>(*this,false).abs();
}
//! Compute the cosinus of each pixel value.
CImg<T>& cos() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::cos((double)(*ptr));
return *this;
}
CImg<Tfloat> get_cos() const {
return CImg<Tfloat>(*this,false).cos();
}
//! Compute the sinus of each pixel value.
CImg<T>& sin() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::sin((double)(*ptr));
return *this;
}
CImg<Tfloat> get_sin() const {
return CImg<Tfloat>(*this,false).sin();
}
//! Compute the tangent of each pixel.
CImg<T>& tan() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::tan((double)(*ptr));
return *this;
}
CImg<Tfloat> get_tan() const {
return CImg<Tfloat>(*this,false).tan();
}
//! Compute the arc-cosine of each pixel value.
CImg<T>& acos() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::acos((double)(*ptr));
return *this;
}
CImg<Tfloat> get_acos() const {
return CImg<Tfloat>(*this,false).acos();
}
//! Compute the arc-sinus of each pixel value.
CImg<T>& asin() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::asin((double)(*ptr));
return *this;
}
CImg<Tfloat> get_asin() const {
return CImg<Tfloat>(*this,false).asin();
}
//! Compute the arc-tangent of each pixel.
CImg<T>& atan() {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg_std::atan((double)(*ptr));
return *this;
}
CImg<Tfloat> get_atan() const {
return CImg<Tfloat>(*this,false).atan();
}
//! Compute image with rounded pixel values.
/**
\param x Rounding precision.
\param rounding_type Roundin type, can be 0 (nearest), 1 (forward), -1(backward).
**/
CImg<T>& round(const float x, const int rounding_type=0) {
cimg_for(*this,ptr,T) (*ptr) = (T)cimg::round(*ptr,x,rounding_type);
return *this;
}
CImg<T> get_round(const float x, const unsigned int rounding_type=0) const {
return (+*this).round(x,rounding_type);
}
//! Fill the instance image with random values between specified range.
CImg<T>& rand(const T val_min, const T val_max) {
const float delta = (float)val_max - (float)val_min;
cimg_for(*this,ptr,T) *ptr = (T)(val_min + cimg::rand()*delta);
return *this;
}
CImg<T> get_rand(const T val_min, const T val_max) const {
return (+*this).rand(val_min,val_max);
}
//@}
//-----------------------------------
//
//! \name Usual Image Transformations
//@{
//-----------------------------------
//! Fill an image by a value \p val.
/**
\param val = fill value
\note All pixel values of the instance image will be initialized by \p val.
**/
CImg<T>& fill(const T val) {
if (is_empty()) return *this;
if (val && sizeof(T)!=1) cimg_for(*this,ptr,T) *ptr = val;
else cimg_std::memset(data,(int)val,size()*sizeof(T));
return *this;
}
CImg<T> get_fill(const T val) const {
return CImg<T>(width,height,depth,dim).fill(val);
}
//! Fill sequentially all pixel values with values \a val0 and \a val1 respectively.
CImg<T>& fill(const T val0, const T val1) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-1;
for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; }
if (ptr!=ptr_end+1) *(ptr++) = val0;
return *this;
}
CImg<T> get_fill(const T val0, const T val1) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1);
}
//! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2.
CImg<T>& fill(const T val0, const T val1, const T val2) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-2;
for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; }
ptr_end+=2;
switch (ptr_end-ptr) {
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2);
}
//! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-3;
for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; }
ptr_end+=3;
switch (ptr_end-ptr) {
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3);
}
//! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-4;
for (ptr = data; ptr<ptr_end; ) { *(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; }
ptr_end+=4;
switch (ptr_end-ptr) {
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4);
}
//! Fill sequentially all pixel values with values \a val0 and \a val1 and \a val2 and \a val3 and \a val4 and \a val5.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-5;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
}
ptr_end+=5;
switch (ptr_end-ptr) {
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-6;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6;
}
ptr_end+=6;
switch (ptr_end-ptr) {
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-7;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3;
*(ptr++) = val4; *(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7;
}
ptr_end+=7;
switch (ptr_end-ptr) {
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-8;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2;
*(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
*(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8;
}
ptr_end+=8;
switch (ptr_end-ptr) {
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-9;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
*(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
}
ptr_end+=9;
switch (ptr_end-ptr) {
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-10;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4;
*(ptr++) = val5; *(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9;
*(ptr++) = val10;
}
ptr_end+=10;
switch (ptr_end-ptr) {
case 10 : *(--ptr_end) = val9;
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-11;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
*(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
}
ptr_end+=11;
switch (ptr_end-ptr) {
case 11 : *(--ptr_end) = val10;
case 10 : *(--ptr_end) = val9;
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-12;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
*(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
*(ptr++) = val12;
}
ptr_end+=12;
switch (ptr_end-ptr) {
case 12 : *(--ptr_end) = val11;
case 11 : *(--ptr_end) = val10;
case 10 : *(--ptr_end) = val9;
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
const T val13) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-13;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
*(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
*(ptr++) = val12; *(ptr++) = val13;
}
ptr_end+=13;
switch (ptr_end-ptr) {
case 13 : *(--ptr_end) = val12;
case 12 : *(--ptr_end) = val11;
case 11 : *(--ptr_end) = val10;
case 10 : *(--ptr_end) = val9;
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
const T val13) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
val13);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
const T val13, const T val14) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-14;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
*(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
*(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14;
}
ptr_end+=14;
switch (ptr_end-ptr) {
case 14 : *(--ptr_end) = val13;
case 13 : *(--ptr_end) = val12;
case 12 : *(--ptr_end) = val11;
case 11 : *(--ptr_end) = val10;
case 10 : *(--ptr_end) = val9;
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
const T val13, const T val14) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
val13,val14);
}
//! Fill sequentially pixel values.
CImg<T>& fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
const T val13, const T val14, const T val15) {
if (is_empty()) return *this;
T *ptr, *ptr_end = end()-15;
for (ptr = data; ptr<ptr_end; ) {
*(ptr++) = val0; *(ptr++) = val1; *(ptr++) = val2; *(ptr++) = val3; *(ptr++) = val4; *(ptr++) = val5;
*(ptr++) = val6; *(ptr++) = val7; *(ptr++) = val8; *(ptr++) = val9; *(ptr++) = val10; *(ptr++) = val11;
*(ptr++) = val12; *(ptr++) = val13; *(ptr++) = val14; *(ptr++) = val15;
}
ptr_end+=15;
switch (ptr_end-ptr) {
case 15 : *(--ptr_end) = val14;
case 14 : *(--ptr_end) = val13;
case 13 : *(--ptr_end) = val12;
case 12 : *(--ptr_end) = val11;
case 11 : *(--ptr_end) = val10;
case 10 : *(--ptr_end) = val9;
case 9 : *(--ptr_end) = val8;
case 8 : *(--ptr_end) = val7;
case 7 : *(--ptr_end) = val6;
case 6 : *(--ptr_end) = val5;
case 5 : *(--ptr_end) = val4;
case 4 : *(--ptr_end) = val3;
case 3 : *(--ptr_end) = val2;
case 2 : *(--ptr_end) = val1;
case 1 : *(--ptr_end) = val0;
}
return *this;
}
CImg<T> get_fill(const T val0, const T val1, const T val2, const T val3, const T val4, const T val5, const T val6,
const T val7, const T val8, const T val9, const T val10, const T val11, const T val12,
const T val13, const T val14, const T val15) const {
return CImg<T>(width,height,depth,dim).fill(val0,val1,val2,val3,val4,val5,val6,val7,val8,val9,val10,val11,val12,
val13,val14,val15);
}
//! Fill image values according to the values found in the specified string.
CImg<T>& fill(const char *const values, const bool repeat_pattern) {
if (is_empty() || !values) return *this;
T *ptrd = data, *ptr_end = data + size();
const char *nvalues = values;
const unsigned int siz = size();
char cval[64] = { 0 }, sep = 0;
int err = 0; double val = 0; unsigned int nb = 0;
while ((err=cimg_std::sscanf(nvalues,"%63[ \n\t0-9e.+-]%c",cval,&sep))>0 &&
cimg_std::sscanf(cval,"%lf",&val)>0 && nb<siz) {
nvalues += cimg::strlen(cval);
*(ptrd++) = (T)val;
++nb;
if (err!=2) break; else ++nvalues;
}
if (repeat_pattern && nb) for (T *ptrs = data; ptrd<ptr_end; ++ptrs) *(ptrd++) = *ptrs;
return *this;
}
CImg<T> get_fill(const char *const values, const bool repeat_pattern) const {
return repeat_pattern?CImg<T>(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern);
}
//! Fill image values according to the values found in the specified image.
template<typename t>
CImg<T>& fill(const CImg<t>& values, const bool repeat_pattern=true) {
if (is_empty() || !values) return *this;
T *ptrd = data, *ptrd_end = ptrd + size();
for (t *ptrs = values.data, *ptrs_end = ptrs + values.size(); ptrs<ptrs_end && ptrd<ptrd_end; ++ptrs) *(ptrd++) = (T)*ptrs;
if (repeat_pattern && ptrd<ptrd_end) for (T *ptrs = data; ptrd<ptrd_end; ++ptrs) *(ptrd++) = *ptrs;
return *this;
}
template<typename t>
CImg<T> get_fill(const CImg<t>& values, const bool repeat_pattern=true) const {
return repeat_pattern?CImg<T>(width,height,depth,dim).fill(values,repeat_pattern):(+*this).fill(values,repeat_pattern);
}
//! Fill image values along the X-axis at the specified pixel position (y,z,v).
CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const int a0, ...) {
#define _cimg_fill1(x,y,z,v,off,siz,t) { \
va_list ap; va_start(ap,a0); T *ptrd = ptr(x,y,z,v); *ptrd = (T)a0; \
for (unsigned int k = 1; k<siz; ++k) { ptrd+=off; *ptrd = (T)va_arg(ap,t); } \
va_end(ap); }
if (y<height && z<depth && v<dim) _cimg_fill1(0,y,z,v,1,width,int);
return *this;
}
CImg<T>& fillX(const unsigned int y, const unsigned int z, const unsigned int v, const double a0, ...) {
if (y<height && z<depth && v<dim) _cimg_fill1(0,y,z,v,1,width,double);
return *this;
}
//! Fill image values along the Y-axis at the specified pixel position (x,z,v).
CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const int a0, ...) {
if (x<width && z<depth && v<dim) _cimg_fill1(x,0,z,v,width,height,int);
return *this;
}
CImg<T>& fillY(const unsigned int x, const unsigned int z, const unsigned int v, const double a0, ...) {
if (x<width && z<depth && v<dim) _cimg_fill1(x,0,z,v,width,height,double);
return *this;
}
//! Fill image values along the Z-axis at the specified pixel position (x,y,v).
CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const int a0, ...) {
const unsigned int wh = width*height;
if (x<width && y<height && v<dim) _cimg_fill1(x,y,0,v,wh,depth,int);
return *this;
}
CImg<T>& fillZ(const unsigned int x, const unsigned int y, const unsigned int v, const double a0, ...) {
const unsigned int wh = width*height;
if (x<width && y<height && v<dim) _cimg_fill1(x,y,0,v,wh,depth,double);
return *this;
}
//! Fill image values along the V-axis at the specified pixel position (x,y,z).
CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const int a0, ...) {
const unsigned int whz = width*height*depth;
if (x<width && y<height && z<depth) _cimg_fill1(x,y,z,0,whz,dim,int);
return *this;
}
CImg<T>& fillV(const unsigned int x, const unsigned int y, const unsigned int z, const double a0, ...) {
const unsigned int whz = width*height*depth;
if (x<width && y<height && z<depth) _cimg_fill1(x,y,z,0,whz,dim,double);
return *this;
}
//! Linear normalization of the pixel values between \a a and \a b.
CImg<T>& normalize(const T a, const T b) {
if (is_empty()) return *this;
const T na = a<b?a:b, nb = a<b?b:a;
T m, M = maxmin(m);
const Tfloat fm = (Tfloat)m, fM = (Tfloat)M;
if (m==M) return fill(0);
if (m!=na || M!=nb) cimg_for(*this,ptr,T) *ptr = (T)((*ptr-fm)/(fM-fm)*(nb-na)+na);
return *this;
}
CImg<T> get_normalize(const T a, const T b) const {
return (+*this).normalize(a,b);
}
//! Cut pixel values between \a a and \a b.
CImg<T>& cut(const T a, const T b) {
if (is_empty()) return *this;
const T na = a<b?a:b, nb = a<b?b:a;
cimg_for(*this,ptr,T) *ptr = (*ptr<na)?na:((*ptr>nb)?nb:*ptr);
return *this;
}
CImg<T> get_cut(const T a, const T b) const {
return (+*this).cut(a,b);
}
//! Quantize pixel values into \n levels.
CImg<T>& quantize(const unsigned int n, const bool keep_range=true) {
if (is_empty()) return *this;
if (!n)
throw CImgArgumentException("CImg<%s>::quantize() : Cannot quantize image to 0 values.",
pixel_type());
Tfloat m, M = (Tfloat)maxmin(m), range = M - m;
if (range>0) {
if (keep_range) cimg_for(*this,ptr,T) {
const unsigned int val = (unsigned int)((*ptr-m)*n/range);
*ptr = (T)(m + cimg::min(val,n-1)*range/n);
} else cimg_for(*this,ptr,T) {
const unsigned int val = (unsigned int)((*ptr-m)*n/range);
*ptr = (T)cimg::min(val,n-1);
}
}
return *this;
}
CImg<T> get_quantize(const unsigned int n, const bool keep_range=true) const {
return (+*this).quantize(n,keep_range);
}
//! Threshold the image.
/**
\param value Threshold value.
\param soft Enable soft thresholding.
\param strict Tells if the threshold is strict.
**/
CImg<T>& threshold(const T value, const bool soft=false, const bool strict=false) {
if (is_empty()) return *this;
if (strict) {
if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>value?(T)(v-value):v<-value?(T)(v+value):(T)0; }
else cimg_for(*this,ptr,T) *ptr = *ptr>value?(T)1:(T)0;
} else {
if (soft) cimg_for(*this,ptr,T) { const T v = *ptr; *ptr = v>=value?(T)(v-value):v<=-value?(T)(v+value):(T)0; }
else cimg_for(*this,ptr,T) *ptr = *ptr>=value?(T)1:(T)0;
}
return *this;
}
CImg<T> get_threshold(const T value, const bool soft=false, const bool strict=false) const {
return (+*this).threshold(value,soft,strict);
}
//! Rotate an image.
/**
\param angle = rotation angle (in degrees).
\param cond = rotation type. can be :
- 0 = zero-value at borders
- 1 = nearest pixel.
- 2 = Fourier style.
\note Returned image will probably have a different size than the instance image *this.
**/
CImg<T>& rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) {
return get_rotate(angle,border_conditions,interpolation).transfer_to(*this);
}
CImg<T> get_rotate(const float angle, const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
if (is_empty()) return *this;
CImg<T> dest;
const float nangle = cimg::mod(angle,360.0f);
if (border_conditions!=1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
const int wm1 = dimx()-1, hm1 = dimy()-1;
const int iangle = (int)nangle/90;
switch (iangle) {
case 1 : {
dest.assign(height,width,depth,dim);
cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(y,hm1-x,z,v);
} break;
case 2 : {
dest.assign(width,height,depth,dim);
cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-x,hm1-y,z,v);
} break;
case 3 : {
dest.assign(height,width,depth,dim);
cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(wm1-y,x,z,v);
} break;
default :
return *this;
}
} else { // generic version
const float
rad = (float)(nangle*cimg::valuePI/180.0),
ca = (float)cimg_std::cos(rad),
sa = (float)cimg_std::sin(rad),
ux = cimg::abs(width*ca), uy = cimg::abs(width*sa),
vx = cimg::abs(height*sa), vy = cimg::abs(height*ca),
w2 = 0.5f*width, h2 = 0.5f*height,
dw2 = 0.5f*(ux+vx), dh2 = 0.5f*(uy+vy);
dest.assign((int)(ux+vx), (int)(uy+vy),depth,dim);
switch (border_conditions) {
case 0 : {
switch (interpolation) {
case 2 : {
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0);
} break;
case 1 : {
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v,0);
} break;
default : {
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v,0);
}
}
} break;
case 1 : {
switch (interpolation) {
case 2 :
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)cubic_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v);
break;
case 1 :
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)linear_atXY(w2 + (x-dw2)*ca + (y-dh2)*sa,h2 - (x-dw2)*sa + (y-dh2)*ca,z,v);
break;
default :
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = atXY((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),(int)(h2 - (x-dw2)*sa + (y-dh2)*ca),z,v);
}
} break;
case 2 : {
switch (interpolation) {
case 2 :
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()),
cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v);
break;
case 1 :
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)linear_atXY(cimg::mod(w2 + (x-dw2)*ca + (y-dh2)*sa,(float)dimx()),
cimg::mod(h2 - (x-dw2)*sa + (y-dh2)*ca,(float)dimy()),z,v);
break;
default :
cimg_forXY(dest,x,y) cimg_forZV(*this,z,v)
dest(x,y,z,v) = (*this)(cimg::mod((int)(w2 + (x-dw2)*ca + (y-dh2)*sa),dimx()),
cimg::mod((int)(h2 - (x-dw2)*sa + (y-dh2)*ca),dimy()),z,v);
}
} break;
default :
throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid border conditions %d (should be 0,1 or 2).",
pixel_type(),border_conditions);
}
}
return dest;
}
//! Rotate an image around a center point (\c cx,\c cy).
/**
\param angle = rotation angle (in degrees).
\param cx = X-coordinate of the rotation center.
\param cy = Y-coordinate of the rotation center.
\param zoom = zoom.
\param cond = rotation type. can be :
- 0 = zero-value at borders
- 1 = repeat image at borders
- 2 = zero-value at borders and linear interpolation
**/
CImg<T>& rotate(const float angle, const float cx, const float cy, const float zoom,
const unsigned int border_conditions=3, const unsigned int interpolation=1) {
return get_rotate(angle,cx,cy,zoom,border_conditions,interpolation).transfer_to(*this);
}
CImg<T> get_rotate(const float angle, const float cx, const float cy, const float zoom,
const unsigned int border_conditions=3, const unsigned int interpolation=1) const {
if (interpolation>2)
throw CImgArgumentException("CImg<%s>::get_rotate() : Invalid interpolation parameter %d (should be {0=none, 1=linear or 2=cubic}).",
pixel_type(),interpolation);
if (is_empty()) return *this;
CImg<T> dest(width,height,depth,dim);
const float nangle = cimg::mod(angle,360.0f);
if (border_conditions!=1 && zoom==1 && cimg::mod(nangle,90.0f)==0) { // optimized version for orthogonal angles
const int iangle = (int)nangle/90;
switch (iangle) {
case 1 : {
dest.fill(0);
const unsigned int
xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
cimg_forZV(dest,z,v) for (unsigned int y = ymin; y<ymax; ++y) for (unsigned int x = xmin; x<xmax; ++x)
dest(x,y,z,v) = (*this)(y-yoff,height-1-x+xoff,z,v);
} break;
case 2 : {
cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = (*this)(width-1-x,height-1-y,z,v);
} break;
case 3 : {
dest.fill(0);
const unsigned int
xmin = cimg::max(0,(dimx()-dimy())/2), xmax = cimg::min(width,xmin+height),
ymin = cimg::max(0,(dimy()-dimx())/2), ymax = cimg::min(height,ymin+width),
xoff = xmin + cimg::min(0,(dimx()-dimy())/2),
yoff = ymin + cimg::min(0,(dimy()-dimx())/2);
cimg_forZV(dest,z,v) for (unsigned int y = ymin; y<ymax; ++y) for (unsigned int x = xmin; x<xmax; ++x)
dest(x,y,z,v) = (*this)(width-1-y+yoff,x-xoff,z,v);
} break;
default :
return *this;
}
} else {
const float
rad = (float)((nangle*cimg::valuePI)/180.0),
ca = (float)cimg_std::cos(rad)/zoom,
sa = (float)cimg_std::sin(rad)/zoom;
switch (border_conditions) { // generic version
case 0 : {
switch (interpolation) {
case 2 : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v,0);
} break;
case 1 : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v,0);
} break;
default : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v,0);
}
}
} break;
case 1 : {
switch (interpolation) {
case 2 : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)cubic_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v);
} break;
case 1 : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)linear_atXY(cx + (x-cx)*ca + (y-cy)*sa,cy - (x-cx)*sa + (y-cy)*ca,z,v);
} break;
default : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = atXY((int)(cx + (x-cx)*ca + (y-cy)*sa),(int)(cy - (x-cx)*sa + (y-cy)*ca),z,v);
}
}
} break;
case 2 : {
switch (interpolation) {
case 2 : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)cubic_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)dimx()),
cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)dimy()),z,v);
} break;
case 1 : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (T)linear_atXY(cimg::mod(cx + (x-cx)*ca + (y-cy)*sa,(float)dimx()),
cimg::mod(cy - (x-cx)*sa + (y-cy)*ca,(float)dimy()),z,v);
} break;
default : {
cimg_forXY(dest,x,y)
cimg_forZV(*this,z,v)
dest(x,y,z,v) = (*this)(cimg::mod((int)(cx + (x-cx)*ca + (y-cy)*sa),dimx()),
cimg::mod((int)(cy - (x-cx)*sa + (y-cy)*ca),dimy()),z,v);
}
}
} break;
default :
throw CImgArgumentException("CImg<%s>::get_rotate() : Incorrect border conditions %d (should be 0,1 or 2).",
pixel_type(),border_conditions);
}
}
return dest;
}
//! Resize an image.
/**
\param pdx Number of columns (new size along the X-axis).
\param pdy Number of rows (new size along the Y-axis).
\param pdz Number of slices (new size along the Z-axis).
\param pdv Number of vector-channels (new size along the V-axis).
\param interpolation_type Method of interpolation :
- -1 = no interpolation : raw memory resizing.
- 0 = no interpolation : additional space is filled according to \p border_condition.
- 1 = bloc interpolation (nearest point).
- 2 = moving average interpolation.
- 3 = linear interpolation.
- 4 = grid interpolation.
- 5 = bi-cubic interpolation.
\param border_condition Border condition type.
\param center Set centering type (only if \p interpolation_type=0).
\note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
**/
CImg<T>& resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100,
const int interpolation_type=1, const int border_condition=-1, const bool center=false) {
if (!pdx || !pdy || !pdz || !pdv) return assign();
const unsigned int
tdx = pdx<0?-pdx*width/100:pdx,
tdy = pdy<0?-pdy*height/100:pdy,
tdz = pdz<0?-pdz*depth/100:pdz,
tdv = pdv<0?-pdv*dim/100:pdv,
dx = tdx?tdx:1,
dy = tdy?tdy:1,
dz = tdz?tdz:1,
dv = tdv?tdv:1;
if (width==dx && height==dy && depth==dz && dim==dv) return *this;
if (interpolation_type==-1 && dx*dy*dz*dv==size()) {
width = dx; height = dy; depth = dz; dim = dv;
return *this;
}
return get_resize(dx,dy,dz,dv,interpolation_type,border_condition,center).transfer_to(*this);
}
CImg<T> get_resize(const int pdx, const int pdy=-100, const int pdz=-100, const int pdv=-100,
const int interpolation_type=1, const int border_condition=-1, const bool center=false) const {
if (!pdx || !pdy || !pdz || !pdv) return CImg<T>();
const unsigned int
tdx = pdx<0?-pdx*width/100:pdx,
tdy = pdy<0?-pdy*height/100:pdy,
tdz = pdz<0?-pdz*depth/100:pdz,
tdv = pdv<0?-pdv*dim/100:pdv,
dx = tdx?tdx:1,
dy = tdy?tdy:1,
dz = tdz?tdz:1,
dv = tdv?tdv:1;
if (width==dx && height==dy && depth==dz && dim==dv) return +*this;
if (is_empty()) return CImg<T>(dx,dy,dz,dv,0);
CImg<T> res;
switch (interpolation_type) {
case -1 : // Raw resizing
cimg_std::memcpy(res.assign(dx,dy,dz,dv,0).data,data,sizeof(T)*cimg::min(size(),(long unsigned int)dx*dy*dz*dv));
break;
case 0 : { // No interpolation
const unsigned int bx = width-1, by = height-1, bz = depth-1, bv = dim-1;
res.assign(dx,dy,dz,dv);
switch (border_condition) {
case 1 : {
if (center) {
const int
x0 = (res.dimx()-dimx())/2,
y0 = (res.dimy()-dimy())/2,
z0 = (res.dimz()-dimz())/2,
v0 = (res.dimv()-dimv())/2,
x1 = x0 + (int)bx,
y1 = y0 + (int)by,
z1 = z0 + (int)bz,
v1 = v0 + (int)bv;
res.draw_image(x0,y0,z0,v0,*this);
cimg_for_outXYZV(res,x0,y0,z0,v0,x1,y1,z1,v1,x,y,z,v) res(x,y,z,v) = _atXYZV(x-x0,y-y0,z-z0,v-v0);
} else {
res.draw_image(*this);
cimg_for_outXYZV(res,0,0,0,0,bx,by,bz,bv,x,y,z,v) res(x,y,z,v) = _atXYZV(x,y,z,v);
}
} break;
case 2 : {
int nx0 = 0, ny0 = 0, nz0 = 0, nv0 = 0;
if (center) {
const int
x0 = (res.dimx()-dimx())/2,
y0 = (res.dimy()-dimy())/2,
z0 = (res.dimz()-dimz())/2,
v0 = (res.dimv()-dimv())/2;
nx0 = x0>0?x0-(1+x0/width)*width:x0;
ny0 = y0>0?y0-(1+y0/height)*height:y0;
nz0 = z0>0?z0-(1+z0/depth)*depth:z0;
nv0 = v0>0?v0-(1+v0/dim)*dim:v0;
}
for (int k = nv0; k<(int)dv; k+=dimv())
for (int z = nz0; z<(int)dz; z+=dimz())
for (int y = ny0; y<(int)dy; y+=dimy())
for (int x = nx0; x<(int)dx; x+=dimx()) res.draw_image(x,y,z,k,*this);
} break;
default : {
res.fill(0);
if (center) res.draw_image((res.dimx()-dimx())/2,(res.dimy()-dimy())/2,(res.dimz()-dimz())/2,(res.dimv()-dimv())/2,*this);
else res.draw_image(*this);
}
}
} break;
case 1 : { // Nearest-neighbor interpolation
res.assign(dx,dy,dz,dv);
unsigned int
*const offx = new unsigned int[dx],
*const offy = new unsigned int[dy+1],
*const offz = new unsigned int[dz+1],
*const offv = new unsigned int[dv+1],
*poffx, *poffy, *poffz, *poffv,
curr, old;
const unsigned int wh = width*height, whd = width*height*depth, rwh = dx*dy, rwhd = dx*dy*dz;
poffx = offx; curr = 0; { cimg_forX(res,x) { old=curr; curr=(x+1)*width/dx; *(poffx++) = (unsigned int)curr-(unsigned int)old; }}
poffy = offy; curr = 0; { cimg_forY(res,y) { old=curr; curr=(y+1)*height/dy; *(poffy++) = width*((unsigned int)curr-(unsigned int)old); }} *poffy=0;
poffz = offz; curr = 0; { cimg_forZ(res,z) { old=curr; curr=(z+1)*depth/dz; *(poffz++) = wh*((unsigned int)curr-(unsigned int)old); }} *poffz=0;
poffv = offv; curr = 0; { cimg_forV(res,k) { old=curr; curr=(k+1)*dim/dv; *(poffv++) = whd*((unsigned int)curr-(unsigned int)old); }} *poffv=0;
T *ptrd = res.data;
const T* ptrv = data;
poffv = offv;
for (unsigned int k=0; k<dv; ) {
const T *ptrz = ptrv;
poffz = offz;
for (unsigned int z=0; z<dz; ) {
const T *ptry = ptrz;
poffy = offy;
for (unsigned int y=0; y<dy; ) {
const T *ptrx = ptry;
poffx = offx;
cimg_forX(res,x) { *(ptrd++) = *ptrx; ptrx+=*(poffx++); }
++y;
unsigned int dy = *(poffy++);
for (;!dy && y<dy; cimg_std::memcpy(ptrd, ptrd-dx, sizeof(T)*dx), ++y, ptrd+=dx, dy=*(poffy++)) {}
ptry+=dy;
}
++z;
unsigned int dz = *(poffz++);
for (;!dz && z<dz; cimg_std::memcpy(ptrd, ptrd-rwh, sizeof(T)*rwh), ++z, ptrd+=rwh, dz=*(poffz++)) {}
ptrz+=dz;
}
++k;
unsigned int dv = *(poffv++);
for (;!dv && k<dv; cimg_std::memcpy(ptrd, ptrd-rwhd, sizeof(T)*rwhd), ++k, ptrd+=rwhd, dv=*(poffv++)) {}
ptrv+=dv;
}
delete[] offx; delete[] offy; delete[] offz; delete[] offv;
} break;
case 2 : { // Moving average
bool instance_first = true;
if (dx!=width) {
CImg<Tfloat> tmp(dx,height,depth,dim,0);
for (unsigned int a = width*dx, b = width, c = dx, s = 0, t = 0; a; ) {
const unsigned int d = cimg::min(b,c);
a-=d; b-=d; c-=d;
cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)+=(Tfloat)(*this)(s,y,z,v)*d;
if (!b) { cimg_forYZV(tmp,y,z,v) tmp(t,y,z,v)/=width; ++t; b = width; }
if (!c) { ++s; c = dx; }
}
tmp.transfer_to(res);
instance_first = false;
}
if (dy!=height) {
CImg<Tfloat> tmp(dx,dy,depth,dim,0);
for (unsigned int a = height*dy, b = height, c = dy, s = 0, t = 0; a; ) {
const unsigned int d = cimg::min(b,c);
a-=d; b-=d; c-=d;
if (instance_first) cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)(*this)(x,s,z,v)*d;
else cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)+=(Tfloat)res(x,s,z,v)*d;
if (!b) { cimg_forXZV(tmp,x,z,v) tmp(x,t,z,v)/=height; ++t; b = height; }
if (!c) { ++s; c = dy; }
}
tmp.transfer_to(res);
instance_first = false;
}
if (dz!=depth) {
CImg<Tfloat> tmp(dx,dy,dz,dim,0);
for (unsigned int a = depth*dz, b = depth, c = dz, s = 0, t = 0; a; ) {
const unsigned int d = cimg::min(b,c);
a-=d; b-=d; c-=d;
if (instance_first) cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)(*this)(x,y,s,v)*d;
else cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)+=(Tfloat)res(x,y,s,v)*d;
if (!b) { cimg_forXYV(tmp,x,y,v) tmp(x,y,t,v)/=depth; ++t; b = depth; }
if (!c) { ++s; c = dz; }
}
tmp.transfer_to(res);
instance_first = false;
}
if (dv!=dim) {
CImg<Tfloat> tmp(dx,dy,dz,dv,0);
for (unsigned int a = dim*dv, b = dim, c = dv, s = 0, t = 0; a; ) {
const unsigned int d = cimg::min(b,c);
a-=d; b-=d; c-=d;
if (instance_first) cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)(*this)(x,y,z,s)*d;
else cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)+=(Tfloat)res(x,y,z,s)*d;
if (!b) { cimg_forXYZ(tmp,x,y,z) tmp(x,y,z,t)/=dim; ++t; b = dim; }
if (!c) { ++s; c = dv; }
}
tmp.transfer_to(res);
instance_first = false;
}
} break;
case 3 : { // Linear interpolation
const unsigned int dimmax = cimg::max(dx,dy,dz,dv);
const float
sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
sv = (border_condition<0 && dv>dim )?(dv>1?(dim-1.0f)/(dv-1) :0):(float)dim/dv;
unsigned int *const off = new unsigned int[dimmax], *poff;
float *const foff = new float[dimmax], *pfoff, old, curr;
CImg<T> resx, resy, resz, resv;
T *ptrd;
if (dx!=width) {
if (width==1) resx = get_resize(dx,height,depth,dim,1,0);
else {
resx.assign(dx,height,depth,dim);
curr = old = 0; poff = off; pfoff = foff;
cimg_forX(resx,x) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sx; *(poff++) = (unsigned int)curr-(unsigned int)old; }
ptrd = resx.data;
const T *ptrs0 = data;
cimg_forYZV(resx,y,z,k) {
poff = off; pfoff = foff;
const T *ptrs = ptrs0, *const ptrsmax = ptrs0 + (width-1);
cimg_forX(resx,x) {
const float alpha = *(pfoff++);
const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+1):(border_condition?val1:(T)0);
*(ptrd++) = (T)((1-alpha)*val1 + alpha*val2);
ptrs+=*(poff++);
}
ptrs0+=width;
}
}
} else resx.assign(*this,true);
if (dy!=height) {
if (height==1) resy = resx.get_resize(dx,dy,depth,dim,1,0);
else {
resy.assign(dx,dy,depth,dim);
curr = old = 0; poff = off; pfoff = foff;
cimg_forY(resy,y) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sy; *(poff++) = dx*((unsigned int)curr-(unsigned int)old); }
cimg_forXZV(resy,x,z,k) {
ptrd = resy.ptr(x,0,z,k);
const T *ptrs = resx.ptr(x,0,z,k), *const ptrsmax = ptrs + (height-1)*dx;
poff = off; pfoff = foff;
cimg_forY(resy,y) {
const float alpha = *(pfoff++);
const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+dx):(border_condition?val1:(T)0);
*ptrd = (T)((1-alpha)*val1 + alpha*val2);
ptrd+=dx;
ptrs+=*(poff++);
}
}
}
resx.assign();
} else resy.assign(resx,true);
if (dz!=depth) {
if (depth==1) resz = resy.get_resize(dx,dy,dz,dim,1,0);
else {
const unsigned int wh = dx*dy;
resz.assign(dx,dy,dz,dim);
curr = old = 0; poff = off; pfoff = foff;
cimg_forZ(resz,z) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sz; *(poff++) = wh*((unsigned int)curr-(unsigned int)old); }
cimg_forXYV(resz,x,y,k) {
ptrd = resz.ptr(x,y,0,k);
const T *ptrs = resy.ptr(x,y,0,k), *const ptrsmax = ptrs + (depth-1)*wh;
poff = off; pfoff = foff;
cimg_forZ(resz,z) {
const float alpha = *(pfoff++);
const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+wh):(border_condition?val1:(T)0);
*ptrd = (T)((1-alpha)*val1 + alpha*val2);
ptrd+=wh;
ptrs+=*(poff++);
}
}
}
resy.assign();
} else resz.assign(resy,true);
if (dv!=dim) {
if (dim==1) resv = resz.get_resize(dx,dy,dz,dv,1,0);
else {
const unsigned int whd = dx*dy*dz;
resv.assign(dx,dy,dz,dv);
curr = old = 0; poff = off; pfoff = foff;
cimg_forV(resv,k) { *(pfoff++) = curr-(unsigned int)curr; old = curr; curr+=sv; *(poff++) = whd*((unsigned int)curr-(unsigned int)old); }
cimg_forXYZ(resv,x,y,z) {
ptrd = resv.ptr(x,y,z,0);
const T *ptrs = resz.ptr(x,y,z,0), *const ptrsmax = ptrs + (dim-1)*whd;
poff = off; pfoff = foff;
cimg_forV(resv,k) {
const float alpha = *(pfoff++);
const T val1 = *ptrs, val2 = ptrs<ptrsmax?*(ptrs+whd):(border_condition?val1:(T)0);
*ptrd = (T)((1-alpha)*val1 + alpha*val2);
ptrd+=whd;
ptrs+=*(poff++);
}
}
}
resz.assign();
} else resv.assign(resz,true);
delete[] off; delete[] foff;
return resv.is_shared?(resz.is_shared?(resy.is_shared?(resx.is_shared?(+(*this)):resx):resy):resz):resv;
} break;
case 4 : { // Grid filling
res.assign(dx,dy,dz,dv,0);
cimg_forXYZV(*this,x,y,z,k) res(x*dx/width,y*dy/height,z*dz/depth,k*dv/dim) = (*this)(x,y,z,k);
} break;
case 5 : { // Cubic interpolation
const float
sx = (border_condition<0 && dx>width )?(dx>1?(width-1.0f)/(dx-1) :0):(float)width/dx,
sy = (border_condition<0 && dy>height)?(dy>1?(height-1.0f)/(dy-1):0):(float)height/dy,
sz = (border_condition<0 && dz>depth )?(dz>1?(depth-1.0f)/(dz-1) :0):(float)depth/dz,
sv = (border_condition<0 && dv>dim )?(dv>1?(dim-1.0f)/(dv-1) :0):(float)dim/dv;
res.assign(dx,dy,dz,dv);
T *ptrd = res.ptr();
float cx, cy, cz, ck = 0;
cimg_forV(res,k) { cz = 0;
cimg_forZ(res,z) { cy = 0;
cimg_forY(res,y) { cx = 0;
cimg_forX(res,x) {
*(ptrd++) = (T)(border_condition?_cubic_atXY(cx,cy,(int)cz,(int)ck):cubic_atXY(cx,cy,(int)cz,(int)ck,0));
cx+=sx;
} cy+=sy;
} cz+=sz;
} ck+=sv;
}
} break;
default : // Invalid interpolation method
throw CImgArgumentException("CImg<%s>::resize() : Invalid interpolation_type %d "
"(should be { -1=raw, 0=zero, 1=nearest, 2=average, 3=linear, 4=grid, 5=bicubic}).",
pixel_type(),interpolation_type);
}
return res;
}
//! Resize an image.
/**
\param src Image giving the geometry of the resize.
\param interpolation_type Interpolation method :
- 1 = raw memory
- 0 = no interpolation : additional space is filled with 0.
- 1 = bloc interpolation (nearest point).
- 2 = mosaic : image is repeated if necessary.
- 3 = linear interpolation.
- 4 = grid interpolation.
- 5 = bi-cubic interpolation.
\param border_condition Border condition type.
\note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
**/
template<typename t>
CImg<T>& resize(const CImg<t>& src, const int interpolation_type=1,
const int border_condition=-1, const bool center=false) {
return resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center);
}
template<typename t>
CImg<T> get_resize(const CImg<t>& src, const int interpolation_type=1,
const int border_condition=-1, const bool center=false) const {
return get_resize(src.width,src.height,src.depth,src.dim,interpolation_type,border_condition,center);
}
//! Resize an image.
/**
\param disp = Display giving the geometry of the resize.
\param interpolation_type = Resizing type :
- 0 = no interpolation : additional space is filled with 0.
- 1 = bloc interpolation (nearest point).
- 2 = mosaic : image is repeated if necessary.
- 3 = linear interpolation.
- 4 = grid interpolation.
- 5 = bi-cubic interpolation.
- 6 = moving average (best quality for photographs)
\param border_condition Border condition type.
\note If pd[x,y,z,v]<0, it corresponds to a percentage of the original size (the default value is -100).
**/
CImg<T>& resize(const CImgDisplay& disp, const int interpolation_type=1,
const int border_condition=-1, const bool center=false) {
return resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center);
}
CImg<T> get_resize(const CImgDisplay& disp, const int interpolation_type=1,
const int border_condition=-1, const bool center=false) const {
return get_resize(disp.width,disp.height,depth,dim,interpolation_type,border_condition,center);
}
//! Half-resize an image, using a special optimized filter.
CImg<T>& resize_halfXY() {
return get_resize_halfXY().transfer_to(*this);
}
CImg<T> get_resize_halfXY() const {
if (is_empty()) return *this;
const Tfloat mask[9] = { 0.07842776544f, 0.1231940459f, 0.07842776544f,
0.1231940459f, 0.1935127547f, 0.1231940459f,
0.07842776544f, 0.1231940459f, 0.07842776544f };
T I[9] = { 0 };
CImg<T> dest(width/2,height/2,depth,dim);
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I)
if (x%2 && y%2) dest(x/2,y/2,z,k) = (T)
(I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
return dest;
}
//! Upscale an image by a factor 2x.
/**
Use anisotropic upscaling algorithm described at
http://scale2x.sourceforge.net/algorithm.html
**/
CImg<T>& resize_doubleXY() {
return get_resize_doubleXY().transfer_to(*this);
}
CImg<T> get_resize_doubleXY() const {
#define _cimg_gs2x_for3(bound,i) \
for (int i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1; \
_n1##i<(int)(bound) || i==--_n1##i; \
_p1##i = i++, ++_n1##i, ptrd1+=(res).width, ptrd2+=(res).width)
#define _cimg_gs2x_for3x3(img,x,y,z,v,I) \
_cimg_gs2x_for3((img).height,y) for (int x = 0, \
_p1##x = 0, \
_n1##x = (int)( \
(I[1] = (img)(0,_p1##y,z,v)), \
(I[3] = I[4] = (img)(0,y,z,v)), \
(I[7] = (img)(0,_n1##y,z,v)), \
1>=(img).width?(int)((img).width)-1:1); \
(_n1##x<(int)((img).width) && ( \
(I[2] = (img)(_n1##x,_p1##y,z,v)), \
(I[5] = (img)(_n1##x,y,z,v)), \
(I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
x==--_n1##x; \
I[1] = I[2], \
I[3] = I[4], I[4] = I[5], \
I[7] = I[8], \
_p1##x = x++, ++_n1##x)
if (is_empty()) return *this;
CImg<T> res(2*width,2*height,depth,dim);
CImg_3x3(I,T);
cimg_forZV(*this,z,k) {
T
*ptrd1 = res.ptr(0,0,0,k),
*ptrd2 = ptrd1 + res.width;
_cimg_gs2x_for3x3(*this,x,y,0,k,I) {
if (Icp!=Icn && Ipc!=Inc) {
*(ptrd1++) = Ipc==Icp?Ipc:Icc;
*(ptrd1++) = Icp==Inc?Inc:Icc;
*(ptrd2++) = Ipc==Icn?Ipc:Icc;
*(ptrd2++) = Icn==Inc?Inc:Icc;
} else { *(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc; }
}
}
return res;
}
//! Upscale an image by a factor 3x.
/**
Use anisotropic upscaling algorithm described at
http://scale2x.sourceforge.net/algorithm.html
**/
CImg<T>& resize_tripleXY() {
return get_resize_tripleXY().transfer_to(*this);
}
CImg<T> get_resize_tripleXY() const {
#define _cimg_gs3x_for3(bound,i) \
for (int i = 0, _p1##i = 0, \
_n1##i = 1>=(bound)?(int)(bound)-1:1; \
_n1##i<(int)(bound) || i==--_n1##i; \
_p1##i = i++, ++_n1##i, ptrd1+=2*(res).width, ptrd2+=2*(res).width, ptrd3+=2*(res).width)
#define _cimg_gs3x_for3x3(img,x,y,z,v,I) \
_cimg_gs3x_for3((img).height,y) for (int x = 0, \
_p1##x = 0, \
_n1##x = (int)( \
(I[0] = I[1] = (img)(0,_p1##y,z,v)), \
(I[3] = I[4] = (img)(0,y,z,v)), \
(I[6] = I[7] = (img)(0,_n1##y,z,v)), \
1>=(img).width?(int)((img).width)-1:1); \
(_n1##x<(int)((img).width) && ( \
(I[2] = (img)(_n1##x,_p1##y,z,v)), \
(I[5] = (img)(_n1##x,y,z,v)), \
(I[8] = (img)(_n1##x,_n1##y,z,v)),1)) || \
x==--_n1##x; \
I[0] = I[1], I[1] = I[2], \
I[3] = I[4], I[4] = I[5], \
I[6] = I[7], I[7] = I[8], \
_p1##x = x++, ++_n1##x)
if (is_empty()) return *this;
CImg<T> res(3*width,3*height,depth,dim);
CImg_3x3(I,T);
cimg_forZV(*this,z,k) {
T
*ptrd1 = res.ptr(0,0,0,k),
*ptrd2 = ptrd1 + res.width,
*ptrd3 = ptrd2 + res.width;
_cimg_gs3x_for3x3(*this,x,y,0,k,I) {
if (Icp != Icn && Ipc != Inc) {
*(ptrd1++) = Ipc==Icp?Ipc:Icc;
*(ptrd1++) = (Ipc==Icp && Icc!=Inp) || (Icp==Inc && Icc!=Ipp)?Icp:Icc;
*(ptrd1++) = Icp==Inc?Inc:Icc;
*(ptrd2++) = (Ipc==Icp && Icc!=Ipn) || (Ipc==Icn && Icc!=Ipp)?Ipc:Icc;
*(ptrd2++) = Icc;
*(ptrd2++) = (Icp==Inc && Icc!=Inn) || (Icn==Inc && Icc!=Inp)?Inc:Icc;
*(ptrd3++) = Ipc==Icn?Ipc:Icc;
*(ptrd3++) = (Ipc==Icn && Icc!=Inn) || (Icn==Inc && Icc!=Ipn)?Icn:Icc;
*(ptrd3++) = Icn==Inc?Inc:Icc;
} else {
*(ptrd1++) = Icc; *(ptrd1++) = Icc; *(ptrd1++) = Icc;
*(ptrd2++) = Icc; *(ptrd2++) = Icc; *(ptrd2++) = Icc;
*(ptrd3++) = Icc; *(ptrd3++) = Icc; *(ptrd3++) = Icc;
}
}
}
return res;
}
// Warp an image.
template<typename t>
CImg<T>& warp(const CImg<t>& warp, const bool relative=false,
const bool interpolation=true, const unsigned int border_conditions=0) {
return get_warp(warp,relative,interpolation,border_conditions).transfer_to(*this);
}
template<typename t>
CImg<T> get_warp(const CImg<t>& warp, const bool relative=false,
const bool interpolation=true, const unsigned int border_conditions=0) const {
if (is_empty() || !warp) return *this;
if (!is_sameXYZ(warp))
throw CImgArgumentException("CImg<%s>::warp() : Instance image (%u,%u,%u,%u,%p) and warping field (%u,%u,%u,%u,%p) "
"have different XYZ dimensions.",
pixel_type(),width,height,depth,dim,data,
warp.width,warp.height,warp.depth,warp.dim,warp.data);
CImg<T> res(width,height,depth,dim);
switch (warp.dim) {
case 1 : // 1D warping.
if (relative) { // Relative warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atX(cimg::mod(x-(float)warp(x,y,z,0),(float)width),y,z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atX(x-(float)warp(x,y,z,0),y,z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atX(x-(float)warp(x,y,z,0),y,z,v,0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),y,z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atX(x-(int)warp(x,y,z,0),y,z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atX(x-(int)warp(x,y,z,0),y,z,v,0);
}
}
} else { // Absolute warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atX(cimg::mod((float)warp(x,y,z,0),(float)width),y,z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atX((float)warp(x,y,z,0),y,z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atX((float)warp(x,y,z,0),y,z,v,0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),y,z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atX((int)warp(x,y,z,0),y,z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atX((int)warp(x,y,z,0),y,z,v,0);
}
}
}
break;
case 2 : // 2D warping
if (relative) { // Relative warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXY(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
cimg::mod(y-(float)warp(x,y,z,1),(float)height),z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atXY(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z,v,0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
cimg::mod(y-(int)warp(x,y,z,1),(int)height),z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atXY(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z,v,0);
}
}
} else { // Absolute warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXY(cimg::mod((float)warp(x,y,z,0),(float)width),
cimg::mod((float)warp(x,y,z,1),(float)height),z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atXY((float)warp(x,y,z,0),(float)warp(x,y,z,1),z,v,0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
cimg::mod((int)warp(x,y,z,1),(int)depth),z,v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atXY((int)warp(x,y,z,0),(int)warp(x,y,z,1),z,v,0);
}
}
}
break;
case 3 : // 3D warping
if (relative) { // Relative warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
cimg::mod(y-(float)warp(x,y,z,1),(float)height),
cimg::mod(z-(float)warp(x,y,z,2),(float)depth),v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atXYZ(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v,0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
cimg::mod(y-(int)warp(x,y,z,1),(int)height),
cimg::mod(z-(int)warp(x,y,z,2),(int)depth),v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v,0);
}
}
} else { // Absolute warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZ(cimg::mod((float)warp(x,y,z,0),(float)width),
cimg::mod((float)warp(x,y,z,1),(float)height),
cimg::mod((float)warp(x,y,z,2),(float)depth),v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atXYZ((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),v,0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
cimg::mod((int)warp(x,y,z,1),(int)height),
cimg::mod((int)warp(x,y,z,2),(int)depth),v);
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v);
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atXYZ((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),v,0);
}
}
}
break;
default : // 4D warping
if (relative) { // Relative warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod(x-(float)warp(x,y,z,0),(float)width),
cimg::mod(y-(float)warp(x,y,z,1),(float)height),
cimg::mod(z-(float)warp(x,y,z,2),(float)depth),
cimg::mod(z-(float)warp(x,y,z,3),(float)dim));
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3));
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atXYZV(x-(float)warp(x,y,z,0),y-(float)warp(x,y,z,1),z-(float)warp(x,y,z,2),v-(float)warp(x,y,z,3),0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod(x-(int)warp(x,y,z,0),(int)width),
cimg::mod(y-(int)warp(x,y,z,1),(int)height),
cimg::mod(z-(int)warp(x,y,z,2),(int)depth),
cimg::mod(v-(int)warp(x,y,z,3),(int)dim));
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atXYZV(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3));
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atXYZ(x-(int)warp(x,y,z,0),y-(int)warp(x,y,z,1),z-(int)warp(x,y,z,2),v-(int)warp(x,y,z,3),0);
}
}
} else { // Absolute warp coordinates
if (interpolation) switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZV(cimg::mod((float)warp(x,y,z,0),(float)width),
cimg::mod((float)warp(x,y,z,1),(float)height),
cimg::mod((float)warp(x,y,z,2),(float)depth),
cimg::mod((float)warp(x,y,z,3),(float)dim));
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)_linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3));
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (T)linear_atXYZV((float)warp(x,y,z,0),(float)warp(x,y,z,1),(float)warp(x,y,z,2),(float)warp(x,y,z,3),0);
}
} else switch (border_conditions) {
case 2 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = (*this)(cimg::mod((int)warp(x,y,z,0),(int)width),
cimg::mod((int)warp(x,y,z,1),(int)height),
cimg::mod((int)warp(x,y,z,2),(int)depth),
cimg::mod((int)warp(x,y,z,3),(int)dim));
} break;
case 1 : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = _atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3));
} break;
default : {
cimg_forXYZV(*this,x,y,z,v)
res(x,y,z,v) = atXYZV((int)warp(x,y,z,0),(int)warp(x,y,z,1),(int)warp(x,y,z,2),(int)warp(x,y,z,3),0);
}
}
}
}
return res;
}
// Permute axes order (internal).
template<typename t>
CImg<t> _get_permute_axes(const char *permut, const t&) const {
if (is_empty() || !permut) return CImg<t>(*this,false);
CImg<t> res;
const T* ptrs = data;
if (!cimg::strncasecmp(permut,"xyzv",4)) return (+*this);
if (!cimg::strncasecmp(permut,"xyvz",4)) {
res.assign(width,height,dim,depth);
cimg_forXYZV(*this,x,y,z,v) res(x,y,v,z) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"xzyv",4)) {
res.assign(width,depth,height,dim);
cimg_forXYZV(*this,x,y,z,v) res(x,z,y,v) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"xzvy",4)) {
res.assign(width,depth,dim,height);
cimg_forXYZV(*this,x,y,z,v) res(x,z,v,y) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"xvyz",4)) {
res.assign(width,dim,height,depth);
cimg_forXYZV(*this,x,y,z,v) res(x,v,y,z) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"xvzy",4)) {
res.assign(width,dim,depth,height);
cimg_forXYZV(*this,x,y,z,v) res(x,v,z,y) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"yxzv",4)) {
res.assign(height,width,depth,dim);
cimg_forXYZV(*this,x,y,z,v) res(y,x,z,v) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"yxvz",4)) {
res.assign(height,width,dim,depth);
cimg_forXYZV(*this,x,y,z,v) res(y,x,v,z) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"yzxv",4)) {
res.assign(height,depth,width,dim);
cimg_forXYZV(*this,x,y,z,v) res(y,z,x,v) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"yzvx",4)) {
res.assign(height,depth,dim,width);
switch (width) {
case 1 : {
t *ptrR = res.ptr(0,0,0,0);
for (unsigned long siz = height*depth*dim; siz; --siz) {
*(ptrR++) = (t)*(ptrs++);
}
} break;
case 2 : {
t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1);
for (unsigned long siz = height*depth*dim; siz; --siz) {
*(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++);
}
} break;
case 3 : { // Optimization for the classical conversion from interleaved RGB to planar RGB
t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2);
for (unsigned long siz = height*depth*dim; siz; --siz) {
*(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++);
}
} break;
case 4 : { // Optimization for the classical conversion from interleaved RGBA to planar RGBA
t *ptrR = res.ptr(0,0,0,0), *ptrG = res.ptr(0,0,0,1), *ptrB = res.ptr(0,0,0,2), *ptrA = res.ptr(0,0,0,3);
for (unsigned long siz = height*depth*dim; siz; --siz) {
*(ptrR++) = (t)*(ptrs++); *(ptrG++) = (t)*(ptrs++); *(ptrB++) = (t)*(ptrs++); *(ptrA++) = (t)*(ptrs++);
}
} break;
default : {
cimg_forXYZV(*this,x,y,z,v) res(y,z,v,x) = *(ptrs++);
return res;
}
}
}
if (!cimg::strncasecmp(permut,"yvxz",4)) {
res.assign(height,dim,width,depth);
cimg_forXYZV(*this,x,y,z,v) res(y,v,x,z) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"yvzx",4)) {
res.assign(height,dim,depth,width);
cimg_forXYZV(*this,x,y,z,v) res(y,v,z,x) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"zxyv",4)) {
res.assign(depth,width,height,dim);
cimg_forXYZV(*this,x,y,z,v) res(z,x,y,v) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"zxvy",4)) {
res.assign(depth,width,dim,height);
cimg_forXYZV(*this,x,y,z,v) res(z,x,v,y) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"zyxv",4)) {
res.assign(depth,height,width,dim);
cimg_forXYZV(*this,x,y,z,v) res(z,y,x,v) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"zyvx",4)) {
res.assign(depth,height,dim,width);
cimg_forXYZV(*this,x,y,z,v) res(z,y,v,x) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"zvxy",4)) {
res.assign(depth,dim,width,height);
cimg_forXYZV(*this,x,y,z,v) res(z,v,x,y) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"zvyx",4)) {
res.assign(depth,dim,height,width);
cimg_forXYZV(*this,x,y,z,v) res(z,v,y,x) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"vxyz",4)) {
res.assign(dim,width,height,depth);
switch (dim) {
case 1 : {
const T *ptrR = ptr(0,0,0,0);
t *ptrd = res.ptr();
for (unsigned long siz = width*height*depth; siz; --siz) {
*(ptrd++) = (t)*(ptrR++);
}
} break;
case 2 : {
const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1);
t *ptrd = res.ptr();
for (unsigned long siz = width*height*depth; siz; --siz) {
*(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++);
}
} break;
case 3 : { // Optimization for the classical conversion from planar RGB to interleaved RGB
const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2);
t *ptrd = res.ptr();
for (unsigned long siz = width*height*depth; siz; --siz) {
*(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++);
}
} break;
case 4 : { // Optimization for the classical conversion from planar RGBA to interleaved RGBA
const T *ptrR = ptr(0,0,0,0), *ptrG = ptr(0,0,0,1), *ptrB = ptr(0,0,0,2), *ptrA = ptr(0,0,0,3);
t *ptrd = res.ptr();
for (unsigned long siz = width*height*depth; siz; --siz) {
*(ptrd++) = (t)*(ptrR++); *(ptrd++) = (t)*(ptrG++); *(ptrd++) = (t)*(ptrB++); *(ptrd++) = (t)*(ptrA++);
}
} break;
default : {
cimg_forXYZV(*this,x,y,z,v) res(v,x,y,z) = (t)*(ptrs++);
}
}
}
if (!cimg::strncasecmp(permut,"vxzy",4)) {
res.assign(dim,width,depth,height);
cimg_forXYZV(*this,x,y,z,v) res(v,x,z,y) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"vyxz",4)) {
res.assign(dim,height,width,depth);
cimg_forXYZV(*this,x,y,z,v) res(v,y,x,z) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"vyzx",4)) {
res.assign(dim,height,depth,width);
cimg_forXYZV(*this,x,y,z,v) res(v,y,z,x) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"vzxy",4)) {
res.assign(dim,depth,width,height);
cimg_forXYZV(*this,x,y,z,v) res(v,z,x,y) = (t)*(ptrs++);
}
if (!cimg::strncasecmp(permut,"vzyx",4)) {
res.assign(dim,depth,height,width);
cimg_forXYZV(*this,x,y,z,v) res(v,z,y,x) = (t)*(ptrs++);
}
if (!res)
throw CImgArgumentException("CImg<%s>::permute_axes() : Invalid input permutation '%s'.",
pixel_type(),permut);
return res;
}
//! Permute axes order.
/**
This function permutes image axes.
\param permut = String describing the permutation (4 characters).
**/
CImg<T>& permute_axes(const char *order) {
return get_permute_axes(order).transfer_to(*this);
}
CImg<T> get_permute_axes(const char *order) const {
const T foo = (T)0;
return _get_permute_axes(order,foo);
}
//! Invert endianness.
CImg<T>& invert_endianness() {
cimg::invert_endianness(data,size());
return *this;
}
CImg<T> get_invert_endianness() const {
return (+*this).invert_endianness();
}
//! Mirror an image along the specified axis.
CImg<T>& mirror(const char axis) {
if (is_empty()) return *this;
T *pf, *pb, *buf = 0;
switch (cimg::uncase(axis)) {
case 'x' : {
pf = data; pb = ptr(width-1);
const unsigned int width2 = width/2;
for (unsigned int yzv = 0; yzv<height*depth*dim; ++yzv) {
for (unsigned int x = 0; x<width2; ++x) { const T val = *pf; *(pf++) = *pb; *(pb--) = val; }
pf+=width - width2;
pb+=width + width2;
}
} break;
case 'y' : {
buf = new T[width];
pf = data; pb = ptr(0,height-1);
const unsigned int height2 = height/2;
for (unsigned int zv=0; zv<depth*dim; ++zv) {
for (unsigned int y=0; y<height2; ++y) {
cimg_std::memcpy(buf,pf,width*sizeof(T));
cimg_std::memcpy(pf,pb,width*sizeof(T));
cimg_std::memcpy(pb,buf,width*sizeof(T));
pf+=width;
pb-=width;
}
pf+=width*(height - height2);
pb+=width*(height + height2);
}
} break;
case 'z' : {
buf = new T[width*height];
pf = data; pb = ptr(0,0,depth-1);
const unsigned int depth2 = depth/2;
cimg_forV(*this,v) {
for (unsigned int z=0; z<depth2; ++z) {
cimg_std::memcpy(buf,pf,width*height*sizeof(T));
cimg_std::memcpy(pf,pb,width*height*sizeof(T));
cimg_std::memcpy(pb,buf,width*height*sizeof(T));
pf+=width*height;
pb-=width*height;
}
pf+=width*height*(depth - depth2);
pb+=width*height*(depth + depth2);
}
} break;
case 'v' : {
buf = new T[width*height*depth];
pf = data; pb = ptr(0,0,0,dim-1);
const unsigned int dim2 = dim/2;
for (unsigned int v=0; v<dim2; ++v) {
cimg_std::memcpy(buf,pf,width*height*depth*sizeof(T));
cimg_std::memcpy(pf,pb,width*height*depth*sizeof(T));
cimg_std::memcpy(pb,buf,width*height*depth*sizeof(T));
pf+=width*height*depth;
pb-=width*height*depth;
}
} break;
default :
throw CImgArgumentException("CImg<%s>::mirror() : unknow axis '%c', must be 'x','y','z' or 'v'.",
pixel_type(),axis);
}
if (buf) delete[] buf;
return *this;
}
CImg<T> get_mirror(const char axis) const {
return (+*this).mirror(axis);
}
//! Translate the image.
/**
\param deltax Amount of displacement along the X-axis.
\param deltay Amount of displacement along the Y-axis.
\param deltaz Amount of displacement along the Z-axis.
\param deltav Amount of displacement along the V-axis.
\param border_condition Border condition.
- \c border_condition can be :
- 0 : Zero border condition (Dirichlet).
- 1 : Nearest neighbors (Neumann).
- 2 : Repeat Pattern (Fourier style).
**/
CImg<T>& translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
const int border_condition=0) {
if (is_empty()) return *this;
if (deltax) // Translate along X-axis
switch (border_condition) {
case 0 :
if (cimg::abs(deltax)>=dimx()) return fill(0);
if (deltax>0) cimg_forYZV(*this,y,z,k) {
cimg_std::memmove(ptr(0,y,z,k),ptr(deltax,y,z,k),(width-deltax)*sizeof(T));
cimg_std::memset(ptr(width-deltax,y,z,k),0,deltax*sizeof(T));
} else cimg_forYZV(*this,y,z,k) {
cimg_std::memmove(ptr(-deltax,y,z,k),ptr(0,y,z,k),(width+deltax)*sizeof(T));
cimg_std::memset(ptr(0,y,z,k),0,-deltax*sizeof(T));
}
break;
case 1 :
if (deltax>0) {
const int ndeltax = (deltax>=dimx())?width-1:deltax;
if (!ndeltax) return *this;
cimg_forYZV(*this,y,z,k) {
cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
T *ptrd = ptr(width-1,y,z,k);
const T val = *ptrd;
for (int l = 0; l<ndeltax-1; ++l) *(--ptrd) = val;
}
} else {
const int ndeltax = (-deltax>=dimx())?width-1:-deltax;
if (!ndeltax) return *this;
cimg_forYZV(*this,y,z,k) {
cimg_std::memmove(ptr(ndeltax,y,z,k),ptr(0,y,z,k),(width-ndeltax)*sizeof(T));
T *ptrd = ptr(0,y,z,k);
const T val = *ptrd;
for (int l = 0; l<ndeltax-1; ++l) *(++ptrd) = val;
}
}
break;
case 2 : {
const int ml = cimg::mod(deltax,dimx()), ndeltax = (ml<=dimx()/2)?ml:(ml-dimx());
if (!ndeltax) return *this;
T* buf = new T[cimg::abs(ndeltax)];
if (ndeltax>0) cimg_forYZV(*this,y,z,k) {
cimg_std::memcpy(buf,ptr(0,y,z,k),ndeltax*sizeof(T));
cimg_std::memmove(ptr(0,y,z,k),ptr(ndeltax,y,z,k),(width-ndeltax)*sizeof(T));
cimg_std::memcpy(ptr(width-ndeltax,y,z,k),buf,ndeltax*sizeof(T));
} else cimg_forYZV(*this,y,z,k) {
cimg_std::memcpy(buf,ptr(width+ndeltax,y,z,k),-ndeltax*sizeof(T));
cimg_std::memmove(ptr(-ndeltax,y,z,k),ptr(0,y,z,k),(width+ndeltax)*sizeof(T));
cimg_std::memcpy(ptr(0,y,z,k),buf,-ndeltax*sizeof(T));
}
delete[] buf;
} break;
}
if (deltay) // Translate along Y-axis
switch (border_condition) {
case 0 :
if (cimg::abs(deltay)>=dimy()) return fill(0);
if (deltay>0) cimg_forZV(*this,z,k) {
cimg_std::memmove(ptr(0,0,z,k),ptr(0,deltay,z,k),width*(height-deltay)*sizeof(T));
cimg_std::memset(ptr(0,height-deltay,z,k),0,width*deltay*sizeof(T));
} else cimg_forZV(*this,z,k) {
cimg_std::memmove(ptr(0,-deltay,z,k),ptr(0,0,z,k),width*(height+deltay)*sizeof(T));
cimg_std::memset(ptr(0,0,z,k),0,-deltay*width*sizeof(T));
}
break;
case 1 :
if (deltay>0) {
const int ndeltay = (deltay>=dimy())?height-1:deltay;
if (!ndeltay) return *this;
cimg_forZV(*this,z,k) {
cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
T *ptrd = ptr(0,height-ndeltay,z,k), *ptrs = ptr(0,height-1,z,k);
for (int l = 0; l<ndeltay-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
}
} else {
const int ndeltay = (-deltay>=dimy())?height-1:-deltay;
if (!ndeltay) return *this;
cimg_forZV(*this,z,k) {
cimg_std::memmove(ptr(0,ndeltay,z,k),ptr(0,0,z,k),width*(height-ndeltay)*sizeof(T));
T *ptrd = ptr(0,1,z,k), *ptrs = ptr(0,0,z,k);
for (int l = 0; l<ndeltay-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*sizeof(T)); ptrd+=width; }
}
}
break;
case 2 : {
const int ml = cimg::mod(deltay,dimy()), ndeltay = (ml<=dimy()/2)?ml:(ml-dimy());
if (!ndeltay) return *this;
T* buf = new T[width*cimg::abs(ndeltay)];
if (ndeltay>0) cimg_forZV(*this,z,k) {
cimg_std::memcpy(buf,ptr(0,0,z,k),width*ndeltay*sizeof(T));
cimg_std::memmove(ptr(0,0,z,k),ptr(0,ndeltay,z,k),width*(height-ndeltay)*sizeof(T));
cimg_std::memcpy(ptr(0,height-ndeltay,z,k),buf,width*ndeltay*sizeof(T));
} else cimg_forZV(*this,z,k) {
cimg_std::memcpy(buf,ptr(0,height+ndeltay,z,k),-ndeltay*width*sizeof(T));
cimg_std::memmove(ptr(0,-ndeltay,z,k),ptr(0,0,z,k),width*(height+ndeltay)*sizeof(T));
cimg_std::memcpy(ptr(0,0,z,k),buf,-ndeltay*width*sizeof(T));
}
delete[] buf;
} break;
}
if (deltaz) // Translate along Z-axis
switch (border_condition) {
case 0 :
if (cimg::abs(deltaz)>=dimz()) return fill(0);
if (deltaz>0) cimg_forV(*this,k) {
cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,deltaz,k),width*height*(depth-deltaz)*sizeof(T));
cimg_std::memset(ptr(0,0,depth-deltaz,k),0,width*height*deltaz*sizeof(T));
} else cimg_forV(*this,k) {
cimg_std::memmove(ptr(0,0,-deltaz,k),ptr(0,0,0,k),width*height*(depth+deltaz)*sizeof(T));
cimg_std::memset(ptr(0,0,0,k),0,-deltaz*width*height*sizeof(T));
}
break;
case 1 :
if (deltaz>0) {
const int ndeltaz = (deltaz>=dimz())?depth-1:deltaz;
if (!ndeltaz) return *this;
cimg_forV(*this,k) {
cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
T *ptrd = ptr(0,0,depth-ndeltaz,k), *ptrs = ptr(0,0,depth-1,k);
for (int l = 0; l<ndeltaz-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
}
} else {
const int ndeltaz = (-deltaz>=dimz())?depth-1:-deltaz;
if (!ndeltaz) return *this;
cimg_forV(*this,k) {
cimg_std::memmove(ptr(0,0,ndeltaz,k),ptr(0,0,0,k),width*height*(depth-ndeltaz)*sizeof(T));
T *ptrd = ptr(0,0,1,k), *ptrs = ptr(0,0,0,k);
for (int l = 0; l<ndeltaz-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*sizeof(T)); ptrd+=width*height; }
}
}
break;
case 2 : {
const int ml = cimg::mod(deltaz,dimz()), ndeltaz = (ml<=dimz()/2)?ml:(ml-dimz());
if (!ndeltaz) return *this;
T* buf = new T[width*height*cimg::abs(ndeltaz)];
if (ndeltaz>0) cimg_forV(*this,k) {
cimg_std::memcpy(buf,ptr(0,0,0,k),width*height*ndeltaz*sizeof(T));
cimg_std::memmove(ptr(0,0,0,k),ptr(0,0,ndeltaz,k),width*height*(depth-ndeltaz)*sizeof(T));
cimg_std::memcpy(ptr(0,0,depth-ndeltaz,k),buf,width*height*ndeltaz*sizeof(T));
} else cimg_forV(*this,k) {
cimg_std::memcpy(buf,ptr(0,0,depth+ndeltaz,k),-ndeltaz*width*height*sizeof(T));
cimg_std::memmove(ptr(0,0,-ndeltaz,k),ptr(0,0,0,k),width*height*(depth+ndeltaz)*sizeof(T));
cimg_std::memcpy(ptr(0,0,0,k),buf,-ndeltaz*width*height*sizeof(T));
}
delete[] buf;
} break;
}
if (deltav) // Translate along V-axis
switch (border_condition) {
case 0 :
if (cimg::abs(deltav)>=dimv()) return fill(0);
if (deltav>0) {
cimg_std::memmove(data,ptr(0,0,0,deltav),width*height*depth*(dim-deltav)*sizeof(T));
cimg_std::memset(ptr(0,0,0,dim-deltav),0,width*height*depth*deltav*sizeof(T));
} else cimg_forV(*this,k) {
cimg_std::memmove(ptr(0,0,0,-deltav),data,width*height*depth*(dim+deltav)*sizeof(T));
cimg_std::memset(data,0,-deltav*width*height*depth*sizeof(T));
}
break;
case 1 :
if (deltav>0) {
const int ndeltav = (deltav>=dimv())?dim-1:deltav;
if (!ndeltav) return *this;
cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
T *ptrd = ptr(0,0,0,dim-ndeltav), *ptrs = ptr(0,0,0,dim-1);
for (int l = 0; l<ndeltav-1; ++l) { cimg_std::memcpy(ptrd,ptrs,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
} else {
const int ndeltav = (-deltav>=dimv())?dim-1:-deltav;
if (!ndeltav) return *this;
cimg_std::memmove(ptr(0,0,0,ndeltav),data,width*height*depth*(dim-ndeltav)*sizeof(T));
T *ptrd = ptr(0,0,0,1);
for (int l = 0; l<ndeltav-1; ++l) { cimg_std::memcpy(ptrd,data,width*height*depth*sizeof(T)); ptrd+=width*height*depth; }
}
break;
case 2 : {
const int ml = cimg::mod(deltav,dimv()), ndeltav = (ml<=dimv()/2)?ml:(ml-dimv());
if (!ndeltav) return *this;
T* buf = new T[width*height*depth*cimg::abs(ndeltav)];
if (ndeltav>0) {
cimg_std::memcpy(buf,data,width*height*depth*ndeltav*sizeof(T));
cimg_std::memmove(data,ptr(0,0,0,ndeltav),width*height*depth*(dim-ndeltav)*sizeof(T));
cimg_std::memcpy(ptr(0,0,0,dim-ndeltav),buf,width*height*depth*ndeltav*sizeof(T));
} else {
cimg_std::memcpy(buf,ptr(0,0,0,dim+ndeltav),-ndeltav*width*height*depth*sizeof(T));
cimg_std::memmove(ptr(0,0,0,-ndeltav),data,width*height*depth*(dim+ndeltav)*sizeof(T));
cimg_std::memcpy(data,buf,-ndeltav*width*height*depth*sizeof(T));
}
delete[] buf;
} break;
}
return *this;
}
CImg<T> get_translate(const int deltax, const int deltay=0, const int deltaz=0, const int deltav=0,
const int border_condition=0) const {
return (+*this).translate(deltax,deltay,deltaz,deltav,border_condition);
}
//! Get a square region of the image.
/**
\param x0 = X-coordinate of the upper-left crop rectangle corner.
\param y0 = Y-coordinate of the upper-left crop rectangle corner.
\param z0 = Z-coordinate of the upper-left crop rectangle corner.
\param v0 = V-coordinate of the upper-left crop rectangle corner.
\param x1 = X-coordinate of the lower-right crop rectangle corner.
\param y1 = Y-coordinate of the lower-right crop rectangle corner.
\param z1 = Z-coordinate of the lower-right crop rectangle corner.
\param v1 = V-coordinate of the lower-right crop rectangle corner.
\param border_condition = Dirichlet (false) or Neumann border conditions.
**/
CImg<T>& crop(const int x0, const int y0, const int z0, const int v0,
const int x1, const int y1, const int z1, const int v1,
const bool border_condition=false) {
return get_crop(x0,y0,z0,v0,x1,y1,z1,v1,border_condition).transfer_to(*this);
}
CImg<T> get_crop(const int x0, const int y0, const int z0, const int v0,
const int x1, const int y1, const int z1, const int v1,
const bool border_condition=false) const {
if (is_empty()) return *this;
const int
nx0 = x0<x1?x0:x1, nx1 = x0^x1^nx0,
ny0 = y0<y1?y0:y1, ny1 = y0^y1^ny0,
nz0 = z0<z1?z0:z1, nz1 = z0^z1^nz0,
nv0 = v0<v1?v0:v1, nv1 = v0^v1^nv0;
CImg<T> dest(1U+nx1-nx0,1U+ny1-ny0,1U+nz1-nz0,1U+nv1-nv0);
if (nx0<0 || nx1>=dimx() || ny0<0 || ny1>=dimy() || nz0<0 || nz1>=dimz() || nv0<0 || nv1>=dimv()) {
if (border_condition) cimg_forXYZV(dest,x,y,z,v) dest(x,y,z,v) = _atXYZV(nx0+x,ny0+y,nz0+z,nv0+v);
else dest.fill(0).draw_image(-nx0,-ny0,-nz0,-nv0,*this);
} else dest.draw_image(-nx0,-ny0,-nz0,-nv0,*this);
return dest;
}
//! Get a rectangular part of the instance image.
/**
\param x0 = X-coordinate of the upper-left crop rectangle corner.
\param y0 = Y-coordinate of the upper-left crop rectangle corner.
\param z0 = Z-coordinate of the upper-left crop rectangle corner.
\param x1 = X-coordinate of the lower-right crop rectangle corner.
\param y1 = Y-coordinate of the lower-right crop rectangle corner.
\param z1 = Z-coordinate of the lower-right crop rectangle corner.
\param border_condition = determine the type of border condition if
some of the desired region is outside the image.
**/
CImg<T>& crop(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const bool border_condition=false) {
return crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
}
CImg<T> get_crop(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const bool border_condition=false) const {
return get_crop(x0,y0,z0,0,x1,y1,z1,dim-1,border_condition);
}
//! Get a rectangular part of the instance image.
/**
\param x0 = X-coordinate of the upper-left crop rectangle corner.
\param y0 = Y-coordinate of the upper-left crop rectangle corner.
\param x1 = X-coordinate of the lower-right crop rectangle corner.
\param y1 = Y-coordinate of the lower-right crop rectangle corner.
\param border_condition = determine the type of border condition if
some of the desired region is outside the image.
**/
CImg<T>& crop(const int x0, const int y0,
const int x1, const int y1,
const bool border_condition=false) {
return crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
}
CImg<T> get_crop(const int x0, const int y0,
const int x1, const int y1,
const bool border_condition=false) const {
return get_crop(x0,y0,0,0,x1,y1,depth-1,dim-1,border_condition);
}
//! Get a rectangular part of the instance image.
/**
\param x0 = X-coordinate of the upper-left crop rectangle corner.
\param x1 = X-coordinate of the lower-right crop rectangle corner.
\param border_condition = determine the type of border condition if
some of the desired region is outside the image.
**/
CImg<T>& crop(const int x0, const int x1, const bool border_condition=false) {
return crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
}
CImg<T> get_crop(const int x0, const int x1, const bool border_condition=false) const {
return get_crop(x0,0,0,0,x1,height-1,depth-1,dim-1,border_condition);
}
//! Autocrop an image, regarding of the specified backround value.
CImg<T>& autocrop(const T value, const char *const axes="vzyx") {
if (is_empty()) return *this;
const int lmax = cimg::strlen(axes);
for (int l = 0; l<lmax; ++l) autocrop(value,axes[l]);
return *this;
}
CImg<T> get_autocrop(const T value, const char *const axes="vzyx") const {
return (+*this).autocrop(value,axes);
}
//! Autocrop an image, regarding of the specified backround color.
CImg<T>& autocrop(const T *const color, const char *const axes="zyx") {
if (is_empty()) return *this;
const int lmax = cimg::strlen(axes);
for (int l = 0; l<lmax; ++l) autocrop(color,axes[l]);
return *this;
}
CImg<T> get_autocrop(const T *const color, const char *const axes="zyx") const {
return (+*this).autocrop(color,axes);
}
//! Autocrop an image, regarding of the specified backround color.
template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char *const axes="zyx") {
return get_autocrop(color,axes).transfer_to(*this);
}
template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char *const axes="zyx") const {
return get_autocrop(color.data,axes);
}
//! Autocrop an image along specified axis, regarding of the specified backround value.
CImg<T>& autocrop(const T value, const char axis) {
return get_autocrop(value,axis).transfer_to(*this);
}
CImg<T> get_autocrop(const T value, const char axis) const {
if (is_empty()) return *this;
CImg<T> res;
const CImg<intT> coords = _get_autocrop(value,axis);
switch (cimg::uncase(axis)) {
case 'x' : {
const int x0 = coords[0], x1 = coords[1];
if (x0>=0 && x1>=0) res = get_crop(x0,x1);
} break;
case 'y' : {
const int y0 = coords[0], y1 = coords[1];
if (y0>=0 && y1>=0) res = get_crop(0,y0,width-1,y1);
} break;
case 'z' : {
const int z0 = coords[0], z1 = coords[1];
if (z0>=0 && z1>=0) res = get_crop(0,0,z0,width-1,height-1,z1);
} break;
case 'v' : {
const int v0 = coords[0], v1 = coords[1];
if (v0>=0 && v1>=0) res = get_crop(0,0,0,v0,width-1,height-1,depth-1,v1);
} break;
}
return res;
}
//! Autocrop an image along specified axis, regarding of the specified backround color.
CImg<T>& autocrop(const T *const color, const char axis) {
return get_autocrop(color,axis).transfer_to(*this);
}
CImg<T> get_autocrop(const T *const color, const char axis) const {
if (is_empty()) return *this;
CImg<T> res;
switch (cimg::uncase(axis)) {
case 'x' : {
int x0 = width, x1 = -1;
cimg_forV(*this,k) {
const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
const int nx0 = coords[0], nx1 = coords[1];
if (nx0>=0 && nx1>=0) { x0 = cimg::min(x0,nx0); x1 = cimg::max(x1,nx1); }
}
if (x0<=x1) res = get_crop(x0,x1);
} break;
case 'y' : {
int y0 = height, y1 = -1;
cimg_forV(*this,k) {
const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
const int ny0 = coords[0], ny1 = coords[1];
if (ny0>=0 && ny1>=0) { y0 = cimg::min(y0,ny0); y1 = cimg::max(y1,ny1); }
}
if (y0<=y1) res = get_crop(0,y0,width-1,y1);
} break;
case 'z' : {
int z0 = depth, z1 = -1;
cimg_forV(*this,k) {
const CImg<intT> coords = get_shared_channel(k)._get_autocrop(color[k],axis);
const int nz0 = coords[0], nz1 = coords[1];
if (nz0>=0 && nz1>=0) { z0 = cimg::min(z0,nz0); z1 = cimg::max(z1,nz1); }
}
if (z0<=z1) res = get_crop(0,0,z0,width-1,height-1,z1);
} break;
default :
throw CImgArgumentException("CImg<%s>::autocrop() : Invalid axis '%c', must be 'x','y' or 'z'.",
pixel_type(),axis);
}
return res;
}
//! Autocrop an image along specified axis, regarding of the specified backround color.
template<typename t> CImg<T>& autocrop(const CImg<t>& color, const char axis) {
return get_autocrop(color,axis).transfer_to(*this);
}
template<typename t> CImg<T> get_autocrop(const CImg<t>& color, const char axis) const {
return get_autocrop(color.data,axis);
}
CImg<intT> _get_autocrop(const T value, const char axis) const {
CImg<intT> res;
int x0 = -1, y0 = -1, z0 = -1, v0 = -1, x1 = -1, y1 = -1, z1 = -1, v1 = -1;
switch (cimg::uncase(axis)) {
case 'x' : {
cimg_forX(*this,x) cimg_forYZV(*this,y,z,v)
if ((*this)(x,y,z,v)!=value) { x0 = x; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
if (x0>=0) {
for (int x = dimx()-1; x>=0; --x) cimg_forYZV(*this,y,z,v)
if ((*this)(x,y,z,v)!=value) { x1 = x; x = 0; y = dimy(); z = dimz(); v = dimv(); }
}
res = CImg<intT>::vector(x0,x1);
} break;
case 'y' : {
cimg_forY(*this,y) cimg_forXZV(*this,x,z,v)
if ((*this)(x,y,z,v)!=value) { y0 = y; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
if (y0>=0) {
for (int y = dimy()-1; y>=0; --y) cimg_forXZV(*this,x,z,v)
if ((*this)(x,y,z,v)!=value) { y1 = y; x = dimx(); y = 0; z = dimz(); v = dimv(); }
}
res = CImg<intT>::vector(y0,y1);
} break;
case 'z' : {
cimg_forZ(*this,z) cimg_forXYV(*this,x,y,v)
if ((*this)(x,y,z,v)!=value) { z0 = z; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
if (z0>=0) {
for (int z = dimz()-1; z>=0; --z) cimg_forXYV(*this,x,y,v)
if ((*this)(x,y,z,v)!=value) { z1 = z; x = dimx(); y = dimy(); z = 0; v = dimv(); }
}
res = CImg<intT>::vector(z0,z1);
} break;
case 'v' : {
cimg_forV(*this,v) cimg_forXYZ(*this,x,y,z)
if ((*this)(x,y,z,v)!=value) { v0 = v; x = dimx(); y = dimy(); z = dimz(); v = dimv(); }
if (v0>=0) {
for (int v = dimv()-1; v>=0; --v) cimg_forXYZ(*this,x,y,z)
if ((*this)(x,y,z,v)!=value) { v1 = v; x = dimx(); y = dimy(); z = dimz(); v = 0; }
}
res = CImg<intT>::vector(v0,v1);
} break;
default :
throw CImgArgumentException("CImg<%s>::autocrop() : unknow axis '%c', must be 'x','y','z' or 'v'",
pixel_type(),axis);
}
return res;
}
//! Get a set of columns.
CImg<T>& columns(const unsigned int x0, const unsigned int x1) {
return get_columns(x0,x1).transfer_to(*this);
}
CImg<T> get_columns(const unsigned int x0, const unsigned int x1) const {
return get_crop((int)x0,0,0,0,(int)x1,dimy()-1,dimz()-1,dimv()-1);
}
//! Get one column.
CImg<T>& column(const unsigned int x0) {
return columns(x0,x0);
}
CImg<T> get_column(const unsigned int x0) const {
return get_columns(x0,x0);
}
//! Get a set of lines.
CImg<T>& lines(const unsigned int y0, const unsigned int y1) {
return get_lines(y0,y1).transfer_to(*this);
}
CImg<T> get_lines(const unsigned int y0, const unsigned int y1) const {
return get_crop(0,(int)y0,0,0,dimx()-1,(int)y1,dimz()-1,dimv()-1);
}
//! Get a line.
CImg<T>& line(const unsigned int y0) {
return lines(y0,y0);
}
CImg<T> get_line(const unsigned int y0) const {
return get_lines(y0,y0);
}
//! Get a set of slices.
CImg<T>& slices(const unsigned int z0, const unsigned int z1) {
return get_slices(z0,z1).transfer_to(*this);
}
CImg<T> get_slices(const unsigned int z0, const unsigned int z1) const {
return get_crop(0,0,(int)z0,0,dimx()-1,dimy()-1,(int)z1,dimv()-1);
}
//! Get a slice.
CImg<T>& slice(const unsigned int z0) {
return slices(z0,z0);
}
CImg<T> get_slice(const unsigned int z0) const {
return get_slices(z0,z0);
}
//! Get a set of channels.
CImg<T>& channels(const unsigned int v0, const unsigned int v1) {
return get_channels(v0,v1).transfer_to(*this);
}
CImg<T> get_channels(const unsigned int v0, const unsigned int v1) const {
return get_crop(0,0,0,(int)v0,dimx()-1,dimy()-1,dimz()-1,(int)v1);
}
//! Get a channel.
CImg<T>& channel(const unsigned int v0) {
return channels(v0,v0);
}
CImg<T> get_channel(const unsigned int v0) const {
return get_channels(v0,v0);
}
//! Get a shared-memory image referencing a set of points of the instance image.
CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) {
const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
}
const CImg<T> get_shared_points(const unsigned int x0, const unsigned int x1,
const unsigned int y0=0, const unsigned int z0=0, const unsigned int v0=0) const {
const unsigned long beg = offset(x0,y0,z0,v0), end = offset(x1,y0,z0,v0);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_points() : Cannot return a shared-memory subset (%u->%u,%u,%u,%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),x0,x1,y0,z0,v0,width,height,depth,dim);
return CImg<T>(data+beg,x1-x0+1,1,1,1,true);
}
//! Return a shared-memory image referencing a set of lines of the instance image.
CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
const unsigned int z0=0, const unsigned int v0=0) {
const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
}
const CImg<T> get_shared_lines(const unsigned int y0, const unsigned int y1,
const unsigned int z0=0, const unsigned int v0=0) const {
const unsigned long beg = offset(0,y0,z0,v0), end = offset(0,y1,z0,v0);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_lines() : Cannot return a shared-memory subset (0->%u,%u->%u,%u,%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),width-1,y0,y1,z0,v0,width,height,depth,dim);
return CImg<T>(data+beg,width,y1-y0+1,1,1,true);
}
//! Return a shared-memory image referencing one particular line (y0,z0,v0) of the instance image.
CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) {
return get_shared_lines(y0,y0,z0,v0);
}
const CImg<T> get_shared_line(const unsigned int y0, const unsigned int z0=0, const unsigned int v0=0) const {
return get_shared_lines(y0,y0,z0,v0);
}
//! Return a shared memory image referencing a set of planes (z0->z1,v0) of the instance image.
CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) {
const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
}
const CImg<T> get_shared_planes(const unsigned int z0, const unsigned int z1, const unsigned int v0=0) const {
const unsigned long beg = offset(0,0,z0,v0), end = offset(0,0,z1,v0);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_planes() : Cannot return a shared-memory subset (0->%u,0->%u,%u->%u,%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),width-1,height-1,z0,z1,v0,width,height,depth,dim);
return CImg<T>(data+beg,width,height,z1-z0+1,1,true);
}
//! Return a shared-memory image referencing one plane (z0,v0) of the instance image.
CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) {
return get_shared_planes(z0,z0,v0);
}
const CImg<T> get_shared_plane(const unsigned int z0, const unsigned int v0=0) const {
return get_shared_planes(z0,z0,v0);
}
//! Return a shared-memory image referencing a set of channels (v0->v1) of the instance image.
CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) {
const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
}
const CImg<T> get_shared_channels(const unsigned int v0, const unsigned int v1) const {
const unsigned long beg = offset(0,0,0,v0), end = offset(0,0,0,v1);
if (beg>end || beg>=size() || end>=size())
throw CImgArgumentException("CImg<%s>::get_shared_channels() : Cannot return a shared-memory subset (0->%u,0->%u,0->%u,%u->%u) from "
"a (%u,%u,%u,%u) image.",
pixel_type(),width-1,height-1,depth-1,v0,v1,width,height,depth,dim);
return CImg<T>(data+beg,width,height,depth,v1-v0+1,true);
}
//! Return a shared-memory image referencing one channel v0 of the instance image.
CImg<T> get_shared_channel(const unsigned int v0) {
return get_shared_channels(v0,v0);
}
const CImg<T> get_shared_channel(const unsigned int v0) const {
return get_shared_channels(v0,v0);
}
//! Return a shared version of the instance image.
CImg<T> get_shared() {
return CImg<T>(data,width,height,depth,dim,true);
}
const CImg<T> get_shared() const {
return CImg<T>(data,width,height,depth,dim,true);
}
//! Return a 2D representation of a 3D image, with three slices.
CImg<T>& projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
const int dx=-100, const int dy=-100, const int dz=-100) {
return get_projections2d(x0,y0,z0,dx,dy,dz).transfer_to(*this);
}
CImg<T> get_projections2d(const unsigned int x0, const unsigned int y0, const unsigned int z0,
const int dx=-100, const int dy=-100, const int dz=-100) const {
if (is_empty()) return *this;
const unsigned int
nx0 = (x0>=width)?width-1:x0,
ny0 = (y0>=height)?height-1:y0,
nz0 = (z0>=depth)?depth-1:z0;
CImg<T>
imgxy(width,height,1,dim),
imgzy(depth,height,1,dim),
imgxz(width,depth,1,dim);
{ cimg_forXYV(*this,x,y,k) imgxy(x,y,k) = (*this)(x,y,nz0,k); }
{ cimg_forYZV(*this,y,z,k) imgzy(z,y,k) = (*this)(nx0,y,z,k); }
{ cimg_forXZV(*this,x,z,k) imgxz(x,z,k) = (*this)(x,ny0,z,k); }
imgxy.resize(dx,dy,1,dim,1);
imgzy.resize(dz,dy,1,dim,1);
imgxz.resize(dx,dz,1,dim,1);
return CImg<T>(imgxy.width+imgzy.width,imgxy.height+imgxz.height,1,dim,0).
draw_image(imgxy).draw_image(imgxy.width,imgzy).draw_image(0,imgxy.height,imgxz);
}
//! Compute the image histogram.
/**
The histogram H of an image I is a 1D-function where H(x) is the number of
- occurences of the value x in I.
+ occurrences of the value x in I.
\param nblevels = Number of different levels of the computed histogram.
For classical images, this value is 256. You should specify more levels
if you are working with CImg<float> or images with high range of pixel values.
\param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
won't be counted.
\param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
won't be counted.
\note If val_min==val_max==0 (default values), the function first estimates the minimum and maximum
pixel values of the current image, then uses these values for the histogram computation.
\result The histogram is returned as a 1D CImg<float> image H, having a size of (nblevels,1,1,1) such that
- H(0) and H(nblevels-1) are respectively equal to the number of occurences of the values val_min and val_max in I.
+ H(0) and H(nblevels-1) are respectively equal to the number of occurrences of the values val_min and val_max in I.
\note Histogram computation always returns a 1D function. Histogram of multi-valued (such as color) images
are not multi-dimensional.
**/
CImg<T>& histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) {
return get_histogram(nblevels,val_min,val_max).transfer_to(*this);
}
CImg<floatT> get_histogram(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
if (is_empty()) return CImg<floatT>();
if (!nblevels)
throw CImgArgumentException("CImg<%s>::get_histogram() : Can't compute an histogram with 0 levels",
pixel_type());
T vmin = val_min, vmax = val_max;
CImg<floatT> res(nblevels,1,1,1,0);
if (vmin>=vmax && vmin==0) vmin = minmax(vmax);
if (vmin<vmax) cimg_for(*this,ptr,T) {
const int pos = (int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
if (pos>=0 && pos<(int)nblevels) ++res[pos];
} else res[0]+=size();
return res;
}
//! Compute the histogram-equalized version of the instance image.
/**
The histogram equalization is a classical image processing algorithm that enhances the image contrast
by expanding its histogram.
\param nblevels = Number of different levels of the computed histogram.
For classical images, this value is 256. You should specify more levels
if you are working with CImg<float> or images with high range of pixel values.
\param val_min = Minimum value considered for the histogram computation. All pixel values lower than val_min
won't be changed.
\param val_max = Maximum value considered for the histogram computation. All pixel values higher than val_max
won't be changed.
\note If val_min==val_max==0 (default values), the function acts on all pixel values of the image.
\return A new image with same size is returned, where pixels have been equalized.
**/
CImg<T>& equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) {
if (is_empty()) return *this;
T vmin = val_min, vmax = val_max;
if (vmin==vmax && vmin==0) vmin = minmax(vmax);
if (vmin<vmax) {
CImg<floatT> hist = get_histogram(nblevels,vmin,vmax);
float cumul = 0;
cimg_forX(hist,pos) { cumul+=hist[pos]; hist[pos]=cumul; }
cimg_for(*this,ptr,T) {
const int pos = (unsigned int)((*ptr-vmin)*(nblevels-1)/(vmax-vmin));
if (pos>=0 && pos<(int)nblevels) *ptr = (T)(vmin + (vmax-vmin)*hist[pos]/size());
}
}
return *this;
}
CImg<T> get_equalize(const unsigned int nblevels, const T val_min=(T)0, const T val_max=(T)0) const {
return (+*this).equalize(nblevels,val_min,val_max);
}
//! Get a label map of disconnected regions with same intensities.
CImg<T>& label_regions() {
return get_label_regions().transfer_to(*this);
}
CImg<uintT> get_label_regions() const {
#define _cimg_get_label_test(p,q) { \
flag = true; \
const T *ptr1 = ptr(x,y) + siz, *ptr2 = ptr(p,q) + siz; \
for (unsigned int i = dim; flag && i; --i) { ptr1-=wh; ptr2-=wh; flag = (*ptr1==*ptr2); } \
}
if (depth>1)
throw CImgInstanceException("CImg<%s>::label_regions() : Instance image must be a 2D image");
CImg<uintT> res(width,height,depth,1,0);
unsigned int label = 1;
const unsigned int wh = width*height, siz = width*height*dim;
const int W1 = dimx()-1, H1 = dimy()-1;
bool flag;
cimg_forXY(*this,x,y) {
bool done = false;
if (y) {
_cimg_get_label_test(x,y-1);
if (flag) {
const unsigned int lab = (res(x,y) = res(x,y-1));
done = true;
if (x && res(x-1,y)!=lab) {
_cimg_get_label_test(x-1,y);
if (flag) {
const unsigned int lold = res(x-1,y), *const cptr = res.ptr(x,y);
for (unsigned int *ptr = res.ptr(); ptr<cptr; ++ptr) if (*ptr==lold) *ptr = lab;
}
}
}
}
if (x && !done) { _cimg_get_label_test(x-1,y); if (flag) { res(x,y) = res(x-1,y); done = true; }}
if (!done) res(x,y) = label++;
}
{ for (int y = H1; y>=0; --y) for (int x=W1; x>=0; --x) {
bool done = false;
if (y<H1) {
_cimg_get_label_test(x,y+1);
if (flag) {
const unsigned int lab = (res(x,y) = res(x,y+1));
done = true;
if (x<W1 && res(x+1,y)!=lab) {
_cimg_get_label_test(x+1,y);
if (flag) {
const unsigned int lold = res(x+1,y), *const cptr = res.ptr(x,y);
for (unsigned int *ptr = res.ptr()+res.size()-1; ptr>cptr; --ptr) if (*ptr==lold) *ptr = lab;
}
}
}
}
if (x<W1 && !done) { _cimg_get_label_test(x+1,y); if (flag) res(x,y) = res(x+1,y); done = true; }
}}
const unsigned int lab0 = res.max()+1;
label = lab0;
cimg_foroff(res,off) { // Relabel regions
const unsigned int lab = res[off];
if (lab<lab0) { cimg_for(res,ptr,unsigned int) if (*ptr==lab) *ptr = label; ++label; }
}
return (res-=lab0);
}
//! Compute the scalar image of vector norms.
/**
When dealing with vector-valued images (i.e images with dimv()>1), this function computes the L1,L2 or Linf norm of each
vector-valued pixel.
\param norm_type = Type of the norm being computed (1 = L1, 2 = L2, -1 = Linf).
\return A scalar-valued image CImg<float> with size (dimx(),dimy(),dimz(),1), where each pixel is the norm
of the corresponding pixels in the original vector-valued image.
**/
CImg<T>& pointwise_norm(int norm_type=2) {
return get_pointwise_norm(norm_type).transfer_to(*this);
}
CImg<Tfloat> get_pointwise_norm(int norm_type=2) const {
if (is_empty()) return *this;
if (dim==1) return get_abs();
CImg<Tfloat> res(width,height,depth);
switch (norm_type) {
case -1 : { // Linf norm
cimg_forXYZ(*this,x,y,z) {
Tfloat n = 0; cimg_forV(*this,v) {
const Tfloat tmp = (Tfloat)cimg::abs((*this)(x,y,z,v));
if (tmp>n) n=tmp; res(x,y,z) = n;
}
}
} break;
case 1 : { // L1 norm
cimg_forXYZ(*this,x,y,z) {
Tfloat n = 0; cimg_forV(*this,v) n+=cimg::abs((*this)(x,y,z,v)); res(x,y,z) = n;
}
} break;
default : { // L2 norm
cimg_forXYZ(*this,x,y,z) {
Tfloat n = 0; cimg_forV(*this,v) n+=(*this)(x,y,z,v)*(*this)(x,y,z,v); res(x,y,z) = (Tfloat)cimg_std::sqrt((double)n);
}
}
}
return res;
}
//! Compute the image of normalized vectors.
/**
When dealing with vector-valued images (i.e images with dimv()>1), this function return the image of normalized vectors
(unit vectors). Null vectors are unchanged. The L2-norm is computed for the normalization.
\return A new vector-valued image with same size, where each vector-valued pixels have been normalized.
**/
CImg<T>& pointwise_orientation() {
cimg_forXYZ(*this,x,y,z) {
float n = 0;
cimg_forV(*this,v) n+=(float)((*this)(x,y,z,v)*(*this)(x,y,z,v));
n = (float)cimg_std::sqrt(n);
if (n>0) cimg_forV(*this,v) (*this)(x,y,z,v) = (T)((*this)(x,y,z,v)/n);
else cimg_forV(*this,v) (*this)(x,y,z,v) = 0;
}
return *this;
}
CImg<Tfloat> get_pointwise_orientation() const {
if (is_empty()) return *this;
return CImg<Tfloat>(*this,false).pointwise_orientation();
}
//! Split image into a list.
CImgList<T> get_split(const char axis, const unsigned int nb=0) const {
if (is_empty()) return CImgList<T>();
CImgList<T> res;
switch (cimg::uncase(axis)) {
case 'x' : {
if (nb>width)
throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'x' into %u images.",
pixel_type(),width,height,depth,dim,data,nb);
res.assign(nb?nb:width);
const unsigned int delta = (unsigned int)cimg::round((float)width/res.size,1);
unsigned int l, x;
for (l = 0, x = 0; l<res.size-1; ++l, x+=delta) res[l] = get_crop(x,0,0,0,x+delta-1,height-1,depth-1,dim-1);
res[res.size-1] = get_crop(x,0,0,0,width-1,height-1,depth-1,dim-1);
} break;
case 'y' : {
if (nb>height)
throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'y' into %u images.",
pixel_type(),width,height,depth,dim,data,nb);
res.assign(nb?nb:height);
const unsigned int delta = (unsigned int)cimg::round((float)height/res.size,1);
unsigned int l, y;
for (l = 0, y = 0; l<res.size-1; ++l, y+=delta) res[l] = get_crop(0,y,0,0,width-1,y+delta-1,depth-1,dim-1);
res[res.size-1] = get_crop(0,y,0,0,width-1,height-1,depth-1,dim-1);
} break;
case 'z' : {
if (nb>depth)
throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'z' into %u images.",
pixel_type(),width,height,depth,dim,data,nb);
res.assign(nb?nb:depth);
const unsigned int delta = (unsigned int)cimg::round((float)depth/res.size,1);
unsigned int l, z;
for (l = 0, z = 0; l<res.size-1; ++l, z+=delta) res[l] = get_crop(0,0,z,0,width-1,height-1,z+delta-1,dim-1);
res[res.size-1] = get_crop(0,0,z,0,width-1,height-1,depth-1,dim-1);
} break;
case 'v' : {
if (nb>dim)
throw CImgArgumentException("CImg<%s>::get_split() : Cannot split instance image (%u,%u,%u,%u,%p) along 'v' into %u images.",
pixel_type(),width,height,depth,dim,data,nb);
res.assign(nb?nb:dim);
const unsigned int delta = (unsigned int)cimg::round((float)dim/res.size,1);
unsigned int l, v;
for (l = 0, v = 0; l<res.size-1; ++l, v+=delta) res[l] = get_crop(0,0,0,v,width-1,height-1,depth-1,v+delta-1);
res[res.size-1] = get_crop(0,0,0,v,width-1,height-1,depth-1,dim-1);
} break;
default :
throw CImgArgumentException("CImg<%s>::get_split() : Unknow axis '%c', must be 'x','y','z' or 'v'",
pixel_type(),axis);
}
return res;
}
// Split image into a list of vectors, according to a given splitting value.
CImgList<T> get_split(const T value, const bool keep_values, const bool shared) const {
CImgList<T> res;
const T *ptr0 = data, *const ptr_end = data + size();
while (ptr0<ptr_end) {
const T *ptr1 = ptr0;
while (ptr1<ptr_end && *ptr1==value) ++ptr1;
const unsigned int siz0 = ptr1 - ptr0;
if (siz0 && keep_values) res.insert(CImg<T>(ptr0,1,siz0,1,1,shared));
ptr0 = ptr1;
while (ptr1<ptr_end && *ptr1!=value) ++ptr1;
const unsigned int siz1 = ptr1 - ptr0;
if (siz1) res.insert(CImg<T>(ptr0,1,siz1,1,1,shared),~0U,shared);
ptr0 = ptr1;
}
return res;
}
//! Append an image to another one.
CImg<T>& append(const CImg<T>& img, const char axis, const char align='p') {
if (!img) return *this;
if (is_empty()) return (*this=img);
return get_append(img,axis,align).transfer_to(*this);
}
CImg<T> get_append(const CImg<T>& img, const char axis, const char align='p') const {
if (!img) return *this;
if (is_empty()) return img;
CImgList<T> temp(2);
temp[0].width = width; temp[0].height = height; temp[0].depth = depth;
temp[0].dim = dim; temp[0].data = data;
temp[1].width = img.width; temp[1].height = img.height; temp[1].depth = img.depth;
temp[1].dim = img.dim; temp[1].data = img.data;
const CImg<T> res = temp.get_append(axis,align);
temp[0].width = temp[0].height = temp[0].depth = temp[0].dim = 0; temp[0].data = 0;
temp[1].width = temp[1].height = temp[1].depth = temp[1].dim = 0; temp[1].data = 0;
return res;
}
//! Compute the list of images, corresponding to the XY-gradients of an image.
/**
\param scheme = Numerical scheme used for the gradient computation :
- -1 = Backward finite differences
- 0 = Centered finite differences
- 1 = Forward finite differences
- 2 = Using Sobel masks
- 3 = Using rotation invariant masks
- 4 = Using Deriche recusrsive filter.
**/
CImgList<Tfloat> get_gradient(const char *const axes=0, const int scheme=3) const {
CImgList<Tfloat> grad(2,width,height,depth,dim);
bool threed = false;
if (axes) {
for (unsigned int a = 0; axes[a]; ++a) {
const char axis = cimg::uncase(axes[a]);
switch (axis) {
case 'x' : case 'y' : break;
case 'z' : threed = true; break;
default :
throw CImgArgumentException("CImg<%s>::get_gradient() : Unknown specified axis '%c'.",
pixel_type(),axis);
}
}
} else threed = (depth>1);
if (threed) {
grad.insert(1); grad[2].assign(width,height,depth,dim);
switch (scheme) { // Compute 3D gradient
case -1 : { // backward finite differences
CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = (Tfloat)Iccc - Ipcc;
grad[1](x,y,z,k) = (Tfloat)Iccc - Icpc;
grad[2](x,y,z,k) = (Tfloat)Iccc - Iccp;
}
} break;
case 1 : { // forward finite differences
CImg_2x2x2(I,T);
cimg_forV(*this,k) cimg_for2x2x2(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = (Tfloat)Incc - Iccc;
grad[1](x,y,z,k) = (Tfloat)Icnc - Iccc;
grad[2](x,y,z,k) = (Tfloat)Iccn - Iccc;
}
} break;
case 4 : { // using Deriche filter with low standard variation
grad[0] = get_deriche(0,1,'x');
grad[1] = get_deriche(0,1,'y');
grad[2] = get_deriche(0,1,'z');
} break;
default : { // central finite differences
CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = 0.5f*((Tfloat)Incc - Ipcc);
grad[1](x,y,z,k) = 0.5f*((Tfloat)Icnc - Icpc);
grad[2](x,y,z,k) = 0.5f*((Tfloat)Iccn - Iccp);
}
}
}
} else switch (scheme) { // Compute 2D-gradient
case -1 : { // backward finite differences
CImg_3x3(I,T);
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = (Tfloat)Icc - Ipc;
grad[1](x,y,z,k) = (Tfloat)Icc - Icp;
}
} break;
case 1 : { // forward finite differences
CImg_2x2(I,T);
cimg_forZV(*this,z,k) cimg_for2x2(*this,x,y,z,k,I) {
grad[0](x,y,0,k) = (Tfloat)Inc - Icc;
grad[1](x,y,z,k) = (Tfloat)Icn - Icc;
}
} break;
case 2 : { // using Sobel mask
CImg_3x3(I,T);
const Tfloat a = 1, b = 2;
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
}
} break;
case 3 : { // using rotation invariant mask
CImg_3x3(I,T);
const Tfloat a = (Tfloat)(0.25f*(2-cimg_std::sqrt(2.0f))), b = (Tfloat)(0.5f*(cimg_std::sqrt(2.0f)-1));
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = -a*Ipp - b*Ipc - a*Ipn + a*Inp + b*Inc + a*Inn;
grad[1](x,y,z,k) = -a*Ipp - b*Icp - a*Inp + a*Ipn + b*Icn + a*Inn;
}
} break;
case 4 : { // using Deriche filter with low standard variation
grad[0] = get_deriche(0,1,'x');
grad[1] = get_deriche(0,1,'y');
} break;
default : { // central finite differences
CImg_3x3(I,T);
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) {
grad[0](x,y,z,k) = 0.5f*((Tfloat)Inc - Ipc);
grad[1](x,y,z,k) = 0.5f*((Tfloat)Icn - Icp);
}
}
}
if (!axes) return grad;
CImgList<Tfloat> res;
for (unsigned int l = 0; axes[l]; ++l) {
const char axis = cimg::uncase(axes[l]);
switch (axis) {
case 'x' : res.insert(grad[0]); break;
case 'y' : res.insert(grad[1]); break;
case 'z' : res.insert(grad[2]); break;
}
}
grad.assign();
return res;
}
//! Compute the structure tensor field of an image.
CImg<T>& structure_tensor(const bool central_scheme=false) {
return get_structure_tensor(central_scheme).transfer_to(*this);
}
CImg<Tfloat> get_structure_tensor(const bool central_scheme=false) const {
if (is_empty()) return *this;
CImg<Tfloat> res;
if (depth>1) { // 3D version
res.assign(width,height,depth,6,0);
CImg_3x3x3(I,T);
if (central_scheme) cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // classical central finite differences
const Tfloat
ix = 0.5f*((Tfloat)Incc - Ipcc),
iy = 0.5f*((Tfloat)Icnc - Icpc),
iz = 0.5f*((Tfloat)Iccn - Iccp);
res(x,y,z,0)+=ix*ix;
res(x,y,z,1)+=ix*iy;
res(x,y,z,2)+=ix*iz;
res(x,y,z,3)+=iy*iy;
res(x,y,z,4)+=iy*iz;
res(x,y,z,5)+=iz*iz;
} else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) { // Precise forward/backward finite differences
const Tfloat
ixf = (Tfloat)Incc - Iccc, ixb = (Tfloat)Iccc - Ipcc,
iyf = (Tfloat)Icnc - Iccc, iyb = (Tfloat)Iccc - Icpc,
izf = (Tfloat)Iccn - Iccc, izb = (Tfloat)Iccc - Iccp;
res(x,y,z,0) += 0.5f*(ixf*ixf + ixb*ixb);
res(x,y,z,1) += 0.25f*(ixf*iyf + ixf*iyb + ixb*iyf + ixb*iyb);
res(x,y,z,2) += 0.25f*(ixf*izf + ixf*izb + ixb*izf + ixb*izb);
res(x,y,z,3) += 0.5f*(iyf*iyf + iyb*iyb);
res(x,y,z,4) += 0.25f*(iyf*izf + iyf*izb + iyb*izf + iyb*izb);
res(x,y,z,5) += 0.5f*(izf*izf + izb*izb);
}
} else { // 2D version
res.assign(width,height,depth,3,0);
CImg_3x3(I,T);
if (central_scheme) cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // classical central finite differences
const Tfloat
ix = 0.5f*((Tfloat)Inc - Ipc),
iy = 0.5f*((Tfloat)Icn - Icp);
res(x,y,0,0)+=ix*ix;
res(x,y,0,1)+=ix*iy;
res(x,y,0,2)+=iy*iy;
} else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) { // Precise forward/backward finite differences
const Tfloat
ixf = (Tfloat)Inc - Icc, ixb = (Tfloat)Icc - Ipc,
iyf = (Tfloat)Icn - Icc, iyb = (Tfloat)Icc - Icp;
res(x,y,0,0) += 0.5f*(ixf*ixf+ixb*ixb);
res(x,y,0,1) += 0.25f*(ixf*iyf+ixf*iyb+ixb*iyf+ixb*iyb);
res(x,y,0,2) += 0.5f*(iyf*iyf+iyb*iyb);
}
}
return res;
}
//! Get components of the Hessian matrix of an image.
CImgList<Tfloat> get_hessian(const char *const axes=0) const {
const char *naxes = axes, *const def_axes2d = "xxxyyy", *const def_axes3d = "xxxyxzyyyzzz";
if (!axes) naxes = depth>1?def_axes3d:def_axes2d;
CImgList<Tfloat> res;
const int lmax = cimg::strlen(naxes);
if (lmax%2)
throw CImgArgumentException("CImg<%s>::get_hessian() : Incomplete parameter axes = '%s'.",
pixel_type(),naxes);
res.assign(lmax/2,width,height,depth,dim);
if (!cimg::strcasecmp(naxes,def_axes3d)) { // Default 3D version
CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
res[0](x,y,z,k) = (Tfloat)Ipcc + Incc - 2*Iccc; // Ixx
res[1](x,y,z,k) = 0.25f*((Tfloat)Ippc + Innc - Ipnc - Inpc); // Ixy
res[2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp); // Ixz
res[3](x,y,z,k) = (Tfloat)Icpc + Icnc - 2*Iccc; // Iyy
res[4](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp); // Iyz
res[5](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc; // Izz
}
} else if (!cimg::strcasecmp(naxes,def_axes2d)) { // Default 2D version
CImg_3x3(I,T);
cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
res[0](x,y,0,k) = (Tfloat)Ipc + Inc - 2*Icc; // Ixx
res[1](x,y,0,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp); // Ixy
res[2](x,y,0,k) = (Tfloat)Icp + Icn - 2*Icc; // Iyy
}
} else for (int l = 0; l<lmax; ) { // Version with custom axes.
const int l2 = l/2;
char axis1 = naxes[l++], axis2 = naxes[l++];
if (axis1>axis2) cimg::swap(axis1,axis2);
bool valid_axis = false;
if (axis1=='x' && axis2=='x') { // Ixx
valid_axis = true; CImg_3x3(I,T);
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Ipc + Inc - 2*Icc;
}
else if (axis1=='x' && axis2=='y') { // Ixy
valid_axis = true; CImg_3x3(I,T);
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipp + Inn - Ipn - Inp);
}
else if (axis1=='x' && axis2=='z') { // Ixz
valid_axis = true; CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Ipcp + Incn - Ipcn - Incp);
}
else if (axis1=='y' && axis2=='y') { // Iyy
valid_axis = true; CImg_3x3(I,T);
cimg_forZV(*this,z,k) cimg_for3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Icp + Icn - 2*Icc;
}
else if (axis1=='y' && axis2=='z') { // Iyz
valid_axis = true; CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = 0.25f*((Tfloat)Icpp + Icnn - Icpn - Icnp);
}
else if (axis1=='z' && axis2=='z') { // Izz
valid_axis = true; CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) res[l2](x,y,z,k) = (Tfloat)Iccn + Iccp - 2*Iccc;
}
else if (!valid_axis) throw CImgArgumentException("CImg<%s>::get_hessian() : Invalid parameter axes = '%s'.",
pixel_type(),naxes);
}
return res;
}
//! Compute distance function from 0-valued isophotes by the application of an Hamilton-Jacobi PDE.
CImg<T>& distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) {
if (is_empty()) return *this;
CImg<Tfloat> veloc(*this);
for (unsigned int iter = 0; iter<nb_iter; ++iter) {
veloc.fill(0);
if (depth>1) { // 3D version
CImg_3x3x3(I,T);
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) if (band_size<=0 || cimg::abs(Iccc)<band_size) {
const Tfloat
gx = 0.5f*((Tfloat)Incc - Ipcc),
gy = 0.5f*((Tfloat)Icnc - Icpc),
gz = 0.5f*((Tfloat)Iccn - Iccp),
sgn = -cimg::sign((Tfloat)Iccc),
ix = gx*sgn>0?(Tfloat)Incc - Iccc:(Tfloat)Iccc - Ipcc,
iy = gy*sgn>0?(Tfloat)Icnc - Iccc:(Tfloat)Iccc - Icpc,
iz = gz*sgn>0?(Tfloat)Iccn - Iccc:(Tfloat)Iccc - Iccp,
ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy + gz*gz),
ngx = gx/ng,
ngy = gy/ng,
ngz = gz/ng;
veloc(x,y,z,k) = sgn*(ngx*ix + ngy*iy + ngz*iz - 1);
}
} else { // 2D version
CImg_3x3(I,T);
cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) if (band_size<=0 || cimg::abs(Icc)<band_size) {
const Tfloat
gx = 0.5f*((Tfloat)Inc - Ipc),
gy = 0.5f*((Tfloat)Icn - Icp),
sgn = -cimg::sign((Tfloat)Icc),
ix = gx*sgn>0?(Tfloat)Inc - Icc:(Tfloat)Icc - Ipc,
iy = gy*sgn>0?(Tfloat)Icn - Icc:(Tfloat)Icc - Icp,
ng = 1e-5f + (Tfloat)cimg_std::sqrt(gx*gx + gy*gy),
ngx = gx/ng,
ngy = gy/ng;
veloc(x,y,k) = sgn*(ngx*ix + ngy*iy - 1);
}
}
float m, M = (float)veloc.maxmin(m), xdt = precision/(float)cimg::max(cimg::abs(m),cimg::abs(M));
*this+=(veloc*=xdt);
}
return *this;
}
CImg<Tfloat> get_distance_hamilton(const unsigned int nb_iter, const float band_size=0, const float precision=0.5f) const {
return CImg<Tfloat>(*this,false).distance_hamilton(nb_iter,band_size,precision);
}
//! Compute the Euclidean distance map to a shape of specified isovalue.
CImg<T>& distance(const T isovalue,
const float sizex=1, const float sizey=1, const float sizez=1,
const bool compute_sqrt=true) {
return get_distance(isovalue,sizex,sizey,sizez,compute_sqrt).transfer_to(*this);
}
CImg<floatT> get_distance(const T isovalue,
const float sizex=1, const float sizey=1, const float sizez=1,
const bool compute_sqrt=true) const {
if (is_empty()) return *this;
const int dx = dimx(), dy = dimy(), dz = dimz();
CImg<floatT> res(dx,dy,dz,dim);
const float maxdist = (float)cimg_std::sqrt((float)dx*dx + dy*dy + dz*dz);
cimg_forV(*this,k) {
bool is_isophote = false;
if (depth>1) { // 3D version
{ cimg_forYZ(*this,y,z) {
if ((*this)(0,y,z,k)==isovalue) { is_isophote = true; res(0,y,z,k) = 0; } else res(0,y,z,k) = maxdist;
for (int x = 1; x<dx; ++x) if ((*this)(x,y,z,k)==isovalue) { is_isophote = true; res(x,y,z,k) = 0; }
else res(x,y,z,k) = res(x-1,y,z,k) + sizex;
{ for (int x = dx-2; x>=0; --x) if (res(x+1,y,z,k)<res(x,y,z,k)) res(x,y,z,k) = res(x+1,y,z,k) + sizex; }
}}
if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type<float>::max()); continue; }
CImg<floatT> tmp(cimg::max(dy,dz));
CImg<intT> s(tmp.width), t(s.width);
{ cimg_forXZ(*this,x,z) {
{ cimg_forY(*this,y) tmp[y] = res(x,y,z,k); }
int q = s[0] = t[0] = 0;
{ for (int y = 1; y<dy; ++y) {
const float val = tmp[y], val2 = val*val;
while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizey)>_distance_f(t[q],y,val2,sizey)) --q;
if (q<0) { q = 0; s[0] = y; }
else {
const int w = 1 + _distance_sep(s[q],y,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizey);
if (w<dy) { s[++q] = y; t[q] = w; }
}
}}
{ for (int y = dy - 1; y>=0; --y) {
res(x,y,z,k) = _distance_f(y,s[q],cimg::sqr(tmp[s[q]]),sizey);
if (y==t[q]) --q;
}}
}}
{ cimg_forXY(*this,x,y) {
{ cimg_forZ(*this,z) tmp[z] = res(x,y,z,k); }
int q = s[0] = t[0] = 0;
{ for (int z = 1; z<dz; ++z) {
const float val = tmp[z];
while (q>=0 && _distance_f(t(q),s[q],tmp[s[q]],sizez)>_distance_f(t[q],z,tmp[z],sizez)) --q;
if (q<0) { q = 0; s[0] = z; }
else {
const int w = 1 + _distance_sep(s[q],z,(int)tmp[s[q]],(int)val,sizez);
if (w<dz) { s[++q] = z; t[q] = w; }
}
}}
{ for (int z = dz - 1; z>=0; --z) {
const float val = _distance_f(z,s[q],tmp[s[q]],sizez);
res(x,y,z,k) = compute_sqrt?(float)cimg_std::sqrt(val):val;
if (z==t[q]) --q;
}}
}}
} else { // 2D version (with small optimizations)
cimg_forX(*this,x) {
const T *ptrs = ptr(x,0,0,k);
float *ptrd = res.ptr(x,0,0,k), d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:maxdist;
for (int y = 1; y<dy; ++y) { ptrs+=width; ptrd+=width; d = *ptrd = *ptrs==isovalue?(is_isophote=true),0:d+sizey; }
{ for (int y = dy - 2; y>=0; --y) { ptrd-=width; if (d<*ptrd) *ptrd = (d+=sizey); else d = *ptrd; }}
}
if (!is_isophote) { res.get_shared_channel(k).fill(cimg::type<float>::max()); continue; }
CImg<floatT> tmp(dx);
CImg<intT> s(dx), t(dx);
cimg_forY(*this,y) {
float *ptmp = tmp.ptr();
cimg_std::memcpy(ptmp,res.ptr(0,y,0,k),sizeof(float)*dx);
int q = s[0] = t[0] = 0;
for (int x = 1; x<dx; ++x) {
const float val = *(++ptmp), val2 = val*val;
while (q>=0 && _distance_f(t[q],s[q],cimg::sqr(tmp[s[q]]),sizex)>_distance_f(t[q],x,val2,sizex)) --q;
if (q<0) { q = 0; s[0] = x; }
else {
const int w = 1 + _distance_sep(s[q],x,(int)cimg::sqr(tmp[s[q]]),(int)val2,sizex);
if (w<dx) { q++; s[q] = x; t[q] = w; }
}
}
float *pres = res.ptr(0,y,0,k) + width;
{ for (int x = dx - 1; x>=0; --x) {
const float val = _distance_f(x,s[q],cimg::sqr(tmp[s[q]]),sizex);
*(--pres) = compute_sqrt?(float)cimg_std::sqrt(val):val;
if (x==t[q]) --q;
}}
}
}
}
return res;
}
static float _distance_f(const int x, const int i, const float gi2, const float fact) {
const float xmi = fact*((float)x - i);
return xmi*xmi + gi2;
}
static int _distance_sep(const int i, const int u, const int gi2, const int gu2, const float fact) {
const float fact2 = fact*fact;
return (int)(fact2*(u*u - i*i) + gu2 - gi2)/(int)(2*fact2*(u - i));
}
//! Compute minimal path in a graph, using the Dijkstra algorithm.
/**
\param distance An object having operator()(unsigned int i, unsigned int j) which returns distance between two nodes (i,j).
\param nb_nodes Number of graph nodes.
\param starting_node Indice of the starting node.
\param ending_node Indice of the ending node (set to ~0U to ignore ending node).
\param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
\return Array of distances of each node to the starting node.
**/
template<typename tf, typename t>
static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
const unsigned int starting_node, const unsigned int ending_node,
CImg<t>& previous) {
CImg<T> dist(1,nb_nodes,1,1,cimg::type<T>::max());
dist(starting_node) = 0;
previous.assign(1,nb_nodes,1,1,(t)-1);
previous(starting_node) = (t)starting_node;
CImg<uintT> Q(nb_nodes);
cimg_forX(Q,u) Q(u) = u;
cimg::swap(Q(starting_node),Q(0));
unsigned int sizeQ = nb_nodes;
while (sizeQ) {
// Update neighbors from minimal vertex
const unsigned int umin = Q(0);
if (umin==ending_node) sizeQ = 0;
else {
const T dmin = dist(umin);
const T infty = cimg::type<T>::max();
for (unsigned int q=1; q<sizeQ; ++q) {
const unsigned int v = Q(q);
const T d = (T)distance(v,umin);
if (d<infty) {
const T alt = dmin + d;
if (alt<dist(v)) {
dist(v) = alt;
previous(v) = (t)umin;
const T distpos = dist(Q(q));
for (unsigned int pos = q, par = 0; pos && distpos<dist(Q(par=(pos+1)/2-1)); pos=par) cimg::swap(Q(pos),Q(par));
}
}
}
// Remove minimal vertex from queue
Q(0) = Q(--sizeQ);
const T distpos = dist(Q(0));
for (unsigned int pos = 0, left = 0, right = 0;
((right=2*(pos+1),(left=right-1))<sizeQ && distpos>dist(Q(left))) || (right<sizeQ && distpos>dist(Q(right)));) {
if (right<sizeQ) {
if (dist(Q(left))<dist(Q(right))) { cimg::swap(Q(pos),Q(left)); pos = left; }
else { cimg::swap(Q(pos),Q(right)); pos = right; }
} else { cimg::swap(Q(pos),Q(left)); pos = left; }
}
}
}
return dist;
}
//! Return minimal path in a graph, using the Dijkstra algorithm.
template<typename tf, typename t>
static CImg<T> dijkstra(const tf& distance, const unsigned int nb_nodes,
const unsigned int starting_node, const unsigned int ending_node=~0U) {
CImg<uintT> foo;
return dijkstra(distance,nb_nodes,starting_node,ending_node,foo);
}
//! Return minimal path in a graph, using the Dijkstra algorithm.
/**
Instance image corresponds to the adjacency matrix of the graph.
\param starting_node Indice of the starting node.
\param previous Array that gives the previous node indice in the path to the starting node (optional parameter).
\return Array of distances of each node to the starting node.
**/
template<typename t>
CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) {
return get_dijkstra(starting_node,ending_node,previous).transfer_to(*this);
}
template<typename t>
CImg<T> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node, CImg<t>& previous) const {
if (width!=height || depth!=1 || dim!=1)
throw CImgInstanceException("CImg<%s>::dijkstra() : Instance image (%u,%u,%u,%u,%p) is not a graph adjacency matrix",
pixel_type(),width,height,depth,dim,data);
return dijkstra(*this,width,starting_node,ending_node,previous);
}
//! Return minimal path in a graph, using the Dijkstra algorithm.
CImg<T>& dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) {
return get_dijkstra(starting_node,ending_node).transfer_to(*this);
}
CImg<Tfloat> get_dijkstra(const unsigned int starting_node, const unsigned int ending_node=~0U) const {
CImg<uintT> foo;
return get_dijkstra(starting_node,ending_node,foo);
}
//@}
//-------------------------------------
//
//! \name Meshes and Triangulations
//@{
//-------------------------------------
//! Return a 3D centered cube.
template<typename tf>
static CImg<floatT> cube3d(CImgList<tf>& primitives, const float size=100) {
const double s = size/2.0;
primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
return CImg<floatT>(8,3,1,1,
-s,s,s,-s,-s,s,s,-s,
-s,-s,s,s,-s,-s,s,s,
-s,-s,-s,-s,s,s,s,s);
}
//! Return a 3D centered cuboid.
template<typename tf>
static CImg<floatT> cuboid3d(CImgList<tf>& primitives, const float sizex=200,
const float sizey=100, const float sizez=100) {
const double sx = sizex/2.0, sy = sizey/2.0, sz = sizez/2.0;
primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 0,1,5,4, 3,7,6,2, 0,4,7,3, 1,2,6,5);
return CImg<floatT>(8,3,1,1,
-sx,sx,sx,-sx,-sx,sx,sx,-sx,
-sy,-sy,sy,sy,-sy,-sy,sy,sy,
-sz,-sz,-sz,-sz,sz,sz,sz,sz);
}
//! Return a 3D centered cone.
template<typename tf>
static CImg<floatT> cone3d(CImgList<tf>& primitives, const float radius=50, const float height=100,
const unsigned int subdivisions=24, const bool symetrize=false) {
primitives.assign();
if (!subdivisions) return CImg<floatT>();
const double r = (double)radius, h = (double)height/2;
CImgList<floatT> points(2,1,3,1,1,
0.0,0.0,h,
0.0,0.0,-h);
const float delta = 360.0f/subdivisions, nh = symetrize?0:-(float)h;
for (float angle = 0; angle<360; angle+=delta) {
const float a = (float)(angle*cimg::valuePI/180);
points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),nh));
}
const unsigned int nbr = points.size-2;
for (unsigned int p = 0; p<nbr; ++p) {
const unsigned int curr = 2+p, next = 2+((p+1)%nbr);
primitives.insert(CImg<tf>::vector(1,next,curr)).
insert(CImg<tf>::vector(0,curr,next));
}
return points.get_append('x');
}
//! Return a 3D centered cylinder.
template<typename tf>
static CImg<floatT> cylinder3d(CImgList<tf>& primitives, const float radius=50, const float height=100,
const unsigned int subdivisions=24) {
primitives.assign();
if (!subdivisions) return CImg<floatT>();
const double r = (double)radius, h = (double)height/2;
CImgList<floatT> points(2,1,3,1,1,
0.0,0.0,-h,
0.0,0.0,h);
const float delta = 360.0f/subdivisions;
for (float angle = 0; angle<360; angle+=delta) {
const float a = (float)(angle*cimg::valuePI/180);
points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),-(float)h));
points.insert(CImg<floatT>::vector((float)(r*cimg_std::cos(a)),(float)(r*cimg_std::sin(a)),(float)h));
}
const unsigned int nbr = (points.size-2)/2;
for (unsigned int p = 0; p<nbr; ++p) {
const unsigned int curr = 2+2*p, next = 2+(2*((p+1)%nbr));
primitives.insert(CImg<tf>::vector(0,next,curr)).
insert(CImg<tf>::vector(1,curr+1,next+1)).
insert(CImg<tf>::vector(curr,next,next+1,curr+1));
}
return points.get_append('x');
}
//! Return a 3D centered torus.
template<typename tf>
static CImg<floatT> torus3d(CImgList<tf>& primitives, const float radius1=100, const float radius2=30,
const unsigned int subdivisions1=24, const unsigned int subdivisions2=12) {
primitives.assign();
if (!subdivisions1 || !subdivisions2) return CImg<floatT>();
CImgList<floatT> points;
for (unsigned int v = 0; v<subdivisions1; ++v) {
const float
beta = (float)(v*2*cimg::valuePI/subdivisions1),
xc = radius1*(float)cimg_std::cos(beta),
yc = radius1*(float)cimg_std::sin(beta);
for (unsigned int u=0; u<subdivisions2; ++u) {
const float
alpha = (float)(u*2*cimg::valuePI/subdivisions2),
x = xc + radius2*(float)(cimg_std::cos(alpha)*cimg_std::cos(beta)),
y = yc + radius2*(float)(cimg_std::cos(alpha)*cimg_std::sin(beta)),
z = radius2*(float)cimg_std::sin(alpha);
points.insert(CImg<floatT>::vector(x,y,z));
}
}
for (unsigned int vv = 0; vv<subdivisions1; ++vv) {
const unsigned int nv = (vv+1)%subdivisions1;
for (unsigned int uu = 0; uu<subdivisions2; ++uu) {
const unsigned int nu = (uu+1)%subdivisions2, svv = subdivisions2*vv, snv = subdivisions2*nv;
primitives.insert(CImg<tf>::vector(svv+nu,svv+uu,snv+uu));
primitives.insert(CImg<tf>::vector(svv+nu,snv+uu,snv+nu));
}
}
return points.get_append('x');
}
//! Return a 3D centered XY plane.
template<typename tf>
static CImg<floatT> plane3d(CImgList<tf>& primitives, const float sizex=100, const float sizey=100,
const unsigned int subdivisionsx=3, const unsigned int subdivisionsy=3,
const bool double_sided=false) {
primitives.assign();
if (!subdivisionsx || !subdivisionsy) return CImg<floatT>();
CImgList<floatT> points;
const unsigned int w = subdivisionsx + 1, h = subdivisionsy + 1;
const float w2 = subdivisionsx/2.0f, h2 = subdivisionsy/2.0f, fx = (float)sizex/w, fy = (float)sizey/h;
for (unsigned int yy = 0; yy<h; ++yy)
for (unsigned int xx = 0; xx<w; ++xx)
points.insert(CImg<floatT>::vector(fx*(xx-w2),fy*(yy-h2),0));
for (unsigned int y = 0; y<subdivisionsy; ++y) for (unsigned int x = 0; x<subdivisionsx; ++x) {
const int off1 = x+y*w, off2 = x+1+y*w, off3 = x+1+(y+1)*w, off4 = x+(y+1)*w;
primitives.insert(CImg<tf>::vector(off1,off4,off3,off2));
if (double_sided) primitives.insert(CImg<tf>::vector(off1,off2,off3,off4));
}
return points.get_append('x');
}
//! Return a 3D centered sphere.
template<typename tf>
static CImg<floatT> sphere3d(CImgList<tf>& primitives, const float radius=50, const unsigned int subdivisions=3) {
// Create initial icosahedron
primitives.assign();
if (!subdivisions) return CImg<floatT>();
const double tmp = (1+cimg_std::sqrt(5.0f))/2, a = 1.0/cimg_std::sqrt(1+tmp*tmp), b = tmp*a;
CImgList<floatT> points(12,1,3,1,1, b,a,0.0, -b,a,0.0, -b,-a,0.0, b,-a,0.0, a,0.0,b, a,0.0,-b,
-a,0.0,-b, -a,0.0,b, 0.0,b,a, 0.0,-b,a, 0.0,-b,-a, 0.0,b,-a);
primitives.assign(20,1,3,1,1, 4,8,7, 4,7,9, 5,6,11, 5,10,6, 0,4,3, 0,3,5, 2,7,1, 2,1,6,
8,0,11, 8,11,1, 9,10,3, 9,2,10, 8,4,0, 11,0,5, 4,9,3,
5,3,10, 7,8,1, 6,1,11, 7,2,9, 6,10,2);
// Recurse subdivisions
for (unsigned int i = 0; i<subdivisions; ++i) {
const unsigned int L = primitives.size;
for (unsigned int l = 0; l<L; ++l) {
const unsigned int
p0 = (unsigned int)primitives(0,0), p1 = (unsigned int)primitives(0,1), p2 = (unsigned int)primitives(0,2);
const float
x0 = points(p0,0), y0 = points(p0,1), z0 = points(p0,2),
x1 = points(p1,0), y1 = points(p1,1), z1 = points(p1,2),
x2 = points(p2,0), y2 = points(p2,1), z2 = points(p2,2),
tnx0 = (x0+x1)/2, tny0 = (y0+y1)/2, tnz0 = (z0+z1)/2, nn0 = (float)cimg_std::sqrt(tnx0*tnx0+tny0*tny0+tnz0*tnz0),
tnx1 = (x0+x2)/2, tny1 = (y0+y2)/2, tnz1 = (z0+z2)/2, nn1 = (float)cimg_std::sqrt(tnx1*tnx1+tny1*tny1+tnz1*tnz1),
tnx2 = (x1+x2)/2, tny2 = (y1+y2)/2, tnz2 = (z1+z2)/2, nn2 = (float)cimg_std::sqrt(tnx2*tnx2+tny2*tny2+tnz2*tnz2),
nx0 = tnx0/nn0, ny0 = tny0/nn0, nz0 = tnz0/nn0,
nx1 = tnx1/nn1, ny1 = tny1/nn1, nz1 = tnz1/nn1,
nx2 = tnx2/nn2, ny2 = tny2/nn2, nz2 = tnz2/nn2;
int i0 = -1, i1 = -1, i2 = -1;
cimglist_for(points,p) {
const float x = (float)points(p,0), y = (float)points(p,1), z = (float)points(p,2);
if (x==nx0 && y==ny0 && z==nz0) i0 = p;
if (x==nx1 && y==ny1 && z==nz1) i1 = p;
if (x==nx2 && y==ny2 && z==nz2) i2 = p;
}
if (i0<0) { points.insert(CImg<floatT>::vector(nx0,ny0,nz0)); i0 = points.size-1; }
if (i1<0) { points.insert(CImg<floatT>::vector(nx1,ny1,nz1)); i1 = points.size-1; }
if (i2<0) { points.insert(CImg<floatT>::vector(nx2,ny2,nz2)); i2 = points.size-1; }
primitives.remove(0);
primitives.insert(CImg<tf>::vector(p0,i0,i1)).
insert(CImg<tf>::vector((tf)i0,(tf)p1,(tf)i2)).
insert(CImg<tf>::vector((tf)i1,(tf)i2,(tf)p2)).
insert(CImg<tf>::vector((tf)i1,(tf)i0,(tf)i2));
}
}
return points.get_append('x')*=radius;
}
//! Return a 3D centered ellipsoid.
template<typename tf, typename t>
static CImg<floatT> ellipsoid3d(CImgList<tf>& primitives, const CImg<t>& tensor,
const unsigned int subdivisions=3) {
primitives.assign();
if (!subdivisions) return CImg<floatT>();
typedef typename cimg::superset<t,float>::type tfloat;
CImg<tfloat> S,V;
tensor.symmetric_eigen(S,V);
const tfloat l0 = S[0], l1 = S[1], l2 = S[2];
CImg<floatT> points = sphere(primitives,subdivisions);
cimg_forX(points,p) {
points(p,0) = (float)(points(p,0)*l0);
points(p,1) = (float)(points(p,1)*l1);
points(p,2) = (float)(points(p,2)*l2);
}
V.transpose();
points = V*points;
return points;
}
//! Return a 3D elevation object of the instance image.
template<typename tf, typename tc, typename te>
CImg<floatT> get_elevation3d(CImgList<tf>& primitives, CImgList<tc>& colors, const CImg<te>& elevation) const {
primitives.assign();
colors.assign();
if (is_empty()) return *this;
if (depth>1)
throw CImgInstanceException("CImg<%s>::get_elevation3d() : Instance image (%u,%u,%u,%u,%p) is not a 2D image.",
pixel_type(),width,height,depth,dim,data);
if (!is_sameXY(elevation))
throw CImgArgumentException("CImg<%s>::get_elevation3d() : Elevation image (%u,%u,%u,%u,%p) and instance image (%u,%u,%u,%u,%p) "
"have different sizes.",pixel_type(),
elevation.width,elevation.height,elevation.depth,elevation.dim,elevation.data,
width,height,depth,dim,data,pixel_type());
float m, M = (float)maxmin(m);
if (M==m) ++M;
const unsigned int w = width + 1, h = height + 1;
CImg<floatT> points(w*h,3);
cimg_forXY(*this,x,y) {
const int yw = y*w, xpyw = x + yw, xpyww = xpyw + w;
points(xpyw,0) = points(xpyw+1,0) = points(xpyww+1,0) = points(xpyww,0) = (float)x;
points(xpyw,1) = points(xpyw+1,1) = points(xpyww+1,1) = points(xpyww,1) = (float)y;
points(xpyw,2) = points(xpyw+1,2) = points(xpyww+1,2) = points(xpyww,2) = (float)elevation(x,y);
primitives.insert(CImg<tf>::vector(xpyw,xpyw+1,xpyww+1,xpyww));
const unsigned char
r = (unsigned char)(((*this)(x,y,0) - m)*255/(M-m)),
g = dim>1?(unsigned char)(((*this)(x,y,1) - m)*255/(M-m)):r,
b = dim>2?(unsigned char)(((*this)(x,y,2) - m)*255/(M-m)):(dim>1?0:r);
colors.insert(CImg<tc>::vector((tc)r,(tc)g,(tc)b));
}
return points;
}
// Inner routine used by the Marching square algorithm.
template<typename t>
static int _marching_squares_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
const unsigned int x, const unsigned int nx) {
switch (edge) {
case 0 : return (int)indices1(x,0);
case 1 : return (int)indices1(nx,1);
case 2 : return (int)indices2(x,0);
case 3 : return (int)indices1(x,1);
}
return 0;
}
//! Polygonize an implicit 2D function by the marching squares algorithm.
template<typename tf, typename tfunc>
static CImg<floatT> marching_squares(CImgList<tf>& primitives, const tfunc& func, const float isovalue,
const float x0, const float y0,
const float x1, const float y1,
const float resx, const float resy) {
static unsigned int edges[16] = { 0x0, 0x9, 0x3, 0xa, 0x6, 0xf, 0x5, 0xc, 0xc, 0x5, 0xf, 0x6, 0xa, 0x3, 0x9, 0x0 };
static int segments[16][4] = { { -1,-1,-1,-1 }, { 0,3,-1,-1 }, { 0,1,-1,-1 }, { 1,3,-1,-1 },
{ 1,2,-1,-1 }, { 0,1,2,3 }, { 0,2,-1,-1 }, { 2,3,-1,-1 },
{ 2,3,-1,-1 }, { 0,2,-1,-1}, { 0,3,1,2 }, { 1,2,-1,-1 },
{ 1,3,-1,-1 }, { 0,1,-1,-1}, { 0,3,-1,-1}, { -1,-1,-1,-1 } };
const unsigned int
nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1;
if (!nxm1 || !nym1) return CImg<floatT>();
primitives.assign();
CImgList<floatT> points;
CImg<intT> indices1(nx,1,1,2,-1), indices2(nx,1,1,2);
CImg<floatT> values1(nx), values2(nx);
float X = 0, Y = 0, nX = 0, nY = 0;
// Fill first line with values
cimg_forX(values1,x) { values1(x) = (float)func(X,Y); X+=resx; }
// Run the marching squares algorithm
Y = y0; nY = Y + resy;
for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y=nY, nY+=resy) {
X = x0; nX = X + resx;
indices2.fill(-1);
for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X=nX, nX+=resx) {
// Determine cube configuration
const float
val0 = values1(xi), val1 = values1(nxi),
val2 = values2(nxi) = (float)func(nX,nY),
val3 = values2(xi) = (float)func(X,nY);
const unsigned int configuration = (val0<isovalue?1:0) | (val1<isovalue?2:0) | (val2<isovalue?4:0) | (val3<isovalue?8:0),
edge = edges[configuration];
// Compute intersection points
if (edge) {
if ((edge&1) && indices1(xi,0)<0) {
const float Xi = X + (isovalue-val0)*resx/(val1-val0);
indices1(xi,0) = points.size;
points.insert(CImg<floatT>::vector(Xi,Y));
}
if ((edge&2) && indices1(nxi,1)<0) {
const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
indices1(nxi,1) = points.size;
points.insert(CImg<floatT>::vector(nX,Yi));
}
if ((edge&4) && indices2(xi,0)<0) {
const float Xi = X + (isovalue-val3)*resx/(val2-val3);
indices2(xi,0) = points.size;
points.insert(CImg<floatT>::vector(Xi,nY));
}
if ((edge&8) && indices1(xi,1)<0) {
const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
indices1(xi,1) = points.size;
points.insert(CImg<floatT>::vector(X,Yi));
}
// Create segments
for (int *segment = segments[configuration]; *segment!=-1; ) {
const unsigned int p0 = *(segment++), p1 = *(segment++);
const tf
i0 = (tf)(_marching_squares_indice(p0,indices1,indices2,xi,nxi)),
i1 = (tf)(_marching_squares_indice(p1,indices1,indices2,xi,nxi));
primitives.insert(CImg<tf>::vector(i0,i1));
}
}
}
values1.swap(values2);
indices1.swap(indices2);
}
return points.get_append('x');
}
// Inner routine used by the Marching cube algorithm.
template<typename t>
static int _marching_cubes_indice(const unsigned int edge, const CImg<t>& indices1, const CImg<t>& indices2,
const unsigned int x, const unsigned int y, const unsigned int nx, const unsigned int ny) {
switch (edge) {
case 0 : return indices1(x,y,0);
case 1 : return indices1(nx,y,1);
case 2 : return indices1(x,ny,0);
case 3 : return indices1(x,y,1);
case 4 : return indices2(x,y,0);
case 5 : return indices2(nx,y,1);
case 6 : return indices2(x,ny,0);
case 7 : return indices2(x,y,1);
case 8 : return indices1(x,y,2);
case 9 : return indices1(nx,y,2);
case 10 : return indices1(nx,ny,2);
case 11 : return indices1(x,ny,2);
}
return 0;
}
//! Polygonize an implicit function
// This function uses the Marching Cubes Tables published on the web page :
// http://astronomy.swin.edu.au/~pbourke/modelling/polygonise/
template<typename tf, typename tfunc>
static CImg<floatT> marching_cubes(CImgList<tf>& primitives,
const tfunc& func, const float isovalue,
const float x0, const float y0, const float z0,
const float x1, const float y1, const float z1,
const float resx, const float resy, const float resz,
const bool invert_faces=false) {
static unsigned int edges[256] = {
0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 };
static int triangles[256][16] =
{{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
{ 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
{ 3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
{ 3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
{ 8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1 },
{ 3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1 }, { 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1 },
{ 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
{ 5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1 },
{ 9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1 }, { 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1 },
{ 10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1 },
{ 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1 }, { 5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1 }, { 1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1 }, { 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1 },
{ 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1 }, { 2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1 },
{ 7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
{ 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1 }, { 11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1 }, { 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1 },
{ 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1 }, { 11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1 },
{ 2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1 },
{ 6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1 },
{ 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1 }, { 6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1 },
{ 5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1 },
{ 6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1 },
{ 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1 }, { 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1 },
{ 3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1 }, { 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1 },
{ 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1 }, { 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1 },
{ 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1 }, { 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1 },
{ 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1 }, { 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1 },
{ 10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1 },
{ 10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1 },
{ 1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1 },
{ 0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1 },
{ 10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1 },
{ 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1 }, { 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1 },
{ 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1 }, { 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1 },
{ 3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1 }, { 6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1 }, { 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1 },
{ 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1 }, { 10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1 },
{ 7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1 }, { 7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1 }, { 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1 },
{ 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1 }, { 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1 },
{ 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1 }, { 0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1 }, { 7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
{ 10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 },
{ 2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1 },
{ 7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
{ 2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1 }, { 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1 },
{ 10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1 },
{ 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1 }, { 7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1 },
{ 6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
{ 8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, { 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1 },
{ 6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1 }, { 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1 },
{ 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1 }, { 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1 },
{ 8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1 }, { 1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1 },
{ 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1 }, { 10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1 },
{ 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1 }, { 10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1 },
{ 5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1 },
{ 9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1 }, { 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1 },
{ 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1 }, { 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1 },
{ 7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1 },
{ 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1 }, { 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1 },
{ 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1 }, { 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1 },
{ 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1 }, { 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1 },
{ 6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1 },
{ 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1 }, { 6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1 }, { 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1 },
{ 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1 }, { 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1 },
{ 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1 }, { 9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1 }, { 1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1 }, { 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1 },
{ 0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1 },
{ 5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1 }, { 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1 },
{ 11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1 },
{ 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1 }, { 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1 },
{ 2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1 }, { 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1 },
{ 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1 }, { 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1 },
{ 1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1 }, { 9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1 },
{ 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1 }, { 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1 },
{ 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1 }, { 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1 },
{ 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1 }, { 9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1 }, { 5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1 },
{ 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1 }, { 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1 },
{ 8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1 }, { 9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1 },
{ 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1 }, { 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1 },
{ 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1 }, { 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1 },
{ 11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1 }, { 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1 },
{ 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1 }, { 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1 },
{ 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1 }, { 1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1 }, { 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1 },
{ 4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1 }, { 3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1 }, { 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1 },
{ 0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1 }, { 9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1 }, { 1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { 0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 },
{ 0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }};
const unsigned int
nx = (unsigned int)((x1-x0+1)/resx), nxm1 = nx-1,
ny = (unsigned int)((y1-y0+1)/resy), nym1 = ny-1,
nz = (unsigned int)((z1-z0+1)/resz), nzm1 = nz-1;
if (!nxm1 || !nym1 || !nzm1) return CImg<floatT>();
primitives.assign();
CImgList<floatT> points;
CImg<intT> indices1(nx,ny,1,3,-1), indices2(indices1);
CImg<floatT> values1(nx,ny), values2(nx,ny);
float X = 0, Y = 0, Z = 0, nX = 0, nY = 0, nZ = 0;
// Fill the first plane with function values
Y = y0;
cimg_forY(values1,y) {
X = x0;
cimg_forX(values1,x) { values1(x,y) = (float)func(X,Y,z0); X+=resx; }
Y+=resy;
}
// Run Marching Cubes algorithm
Z = z0; nZ = Z + resz;
for (unsigned int zi = 0; zi<nzm1; ++zi, Z = nZ, nZ+=resz) {
Y = y0; nY = Y + resy;
indices2.fill(-1);
for (unsigned int yi = 0, nyi = 1; yi<nym1; ++yi, ++nyi, Y = nY, nY+=resy) {
X = x0; nX = X + resx;
for (unsigned int xi = 0, nxi = 1; xi<nxm1; ++xi, ++nxi, X = nX, nX+=resx) {
// Determine cube configuration
const float
val0 = values1(xi,yi), val1 = values1(nxi,yi), val2 = values1(nxi,nyi), val3 = values1(xi,nyi),
val4 = values2(xi,yi) = (float)func(X,Y,nZ),
val5 = values2(nxi,yi) = (float)func(nX,Y,nZ),
val6 = values2(nxi,nyi) = (float)func(nX,nY,nZ),
val7 = values2(xi,nyi) = (float)func(X,nY,nZ);
const unsigned int configuration =
(val0<isovalue?1:0) | (val1<isovalue?2:0) | (val2<isovalue?4:0) | (val3<isovalue?8:0) |
(val4<isovalue?16:0) | (val5<isovalue?32:0) | (val6<isovalue?64:0) | (val7<isovalue?128:0),
edge = edges[configuration];
// Compute intersection points
if (edge) {
if ((edge&1) && indices1(xi,yi,0)<0) {
const float Xi = X + (isovalue-val0)*resx/(val1-val0);
indices1(xi,yi,0) = points.size;
points.insert(CImg<floatT>::vector(Xi,Y,Z));
}
if ((edge&2) && indices1(nxi,yi,1)<0) {
const float Yi = Y + (isovalue-val1)*resy/(val2-val1);
indices1(nxi,yi,1) = points.size;
points.insert(CImg<floatT>::vector(nX,Yi,Z));
}
if ((edge&4) && indices1(xi,nyi,0)<0) {
const float Xi = X + (isovalue-val3)*resx/(val2-val3);
indices1(xi,nyi,0) = points.size;
points.insert(CImg<floatT>::vector(Xi,nY,Z));
}
if ((edge&8) && indices1(xi,yi,1)<0) {
const float Yi = Y + (isovalue-val0)*resy/(val3-val0);
indices1(xi,yi,1) = points.size;
points.insert(CImg<floatT>::vector(X,Yi,Z));
}
if ((edge&16) && indices2(xi,yi,0)<0) {
const float Xi = X + (isovalue-val4)*resx/(val5-val4);
indices2(xi,yi,0) = points.size;
points.insert(CImg<floatT>::vector(Xi,Y,nZ));
}
if ((edge&32) && indices2(nxi,yi,1)<0) {
const float Yi = Y + (isovalue-val5)*resy/(val6-val5);
indices2(nxi,yi,1) = points.size;
points.insert(CImg<floatT>::vector(nX,Yi,nZ));
}
if ((edge&64) && indices2(xi,nyi,0)<0) {
const float Xi = X + (isovalue-val7)*resx/(val6-val7);
indices2(xi,nyi,0) = points.size;
points.insert(CImg<floatT>::vector(Xi,nY,nZ));
}
if ((edge&128) && indices2(xi,yi,1)<0) {
const float Yi = Y + (isovalue-val4)*resy/(val7-val4);
indices2(xi,yi,1) = points.size;
points.insert(CImg<floatT>::vector(X,Yi,nZ));
}
if ((edge&256) && indices1(xi,yi,2)<0) {
const float Zi = Z+ (isovalue-val0)*resz/(val4-val0);
indices1(xi,yi,2) = points.size;
points.insert(CImg<floatT>::vector(X,Y,Zi));
}
if ((edge&512) && indices1(nxi,yi,2)<0) {
const float Zi = Z + (isovalue-val1)*resz/(val5-val1);
indices1(nxi,yi,2) = points.size;
points.insert(CImg<floatT>::vector(nX,Y,Zi));
}
if ((edge&1024) && indices1(nxi,nyi,2)<0) {
const float Zi = Z + (isovalue-val2)*resz/(val6-val2);
indices1(nxi,nyi,2) = points.size;
points.insert(CImg<floatT>::vector(nX,nY,Zi));
}
if ((edge&2048) && indices1(xi,nyi,2)<0) {
const float Zi = Z + (isovalue-val3)*resz/(val7-val3);
indices1(xi,nyi,2) = points.size;
points.insert(CImg<floatT>::vector(X,nY,Zi));
}
// Create triangles
for (int *triangle = triangles[configuration]; *triangle!=-1; ) {
const unsigned int p0 = *(triangle++), p1 = *(triangle++), p2 = *(triangle++);
const tf
i0 = (tf)(_marching_cubes_indice(p0,indices1,indices2,xi,yi,nxi,nyi)),
i1 = (tf)(_marching_cubes_indice(p1,indices1,indices2,xi,yi,nxi,nyi)),
i2 = (tf)(_marching_cubes_indice(p2,indices1,indices2,xi,yi,nxi,nyi));
if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
else primitives.insert(CImg<tf>::vector(i0,i2,i1));
}
}
}
}
cimg::swap(values1,values2);
cimg::swap(indices1,indices2);
}
return points.get_append('x');
}
struct _marching_squares_func {
const CImg<T>& ref;
explicit _marching_squares_func(const CImg<T>& pref):ref(pref) {}
float operator()(const float x, const float y) const {
return (float)ref((int)x,(int)y);
}
};
struct _marching_cubes_func {
const CImg<T>& ref;
explicit _marching_cubes_func(const CImg<T>& pref):ref(pref) {}
float operator()(const float x, const float y, const float z) const {
return (float)ref((int)x,(int)y,(int)z);
}
};
struct _marching_squares_func_float {
const CImg<T>& ref;
explicit _marching_squares_func_float(const CImg<T>& pref):ref(pref) {}
float operator()(const float x, const float y) const {
return (float)ref._linear_atXY(x,y);
}
};
struct _marching_cubes_func_float {
const CImg<T>& ref;
explicit _marching_cubes_func_float(const CImg<T>& pref):ref(pref) {}
float operator()(const float x, const float y, const float z) const {
return (float)ref._linear_atXYZ(x,y,z);
}
};
//! Compute a vectorization of an implicit function.
template<typename tf>
CImg<floatT> get_isovalue3d(CImgList<tf>& primitives, const float isovalue,
const float resx=1, const float resy=1, const float resz=1,
const bool invert_faces=false) const {
primitives.assign();
if (is_empty()) return *this;
if (dim>1)
throw CImgInstanceException("CImg<%s>::get_isovalue3d() : Instance image (%u,%u,%u,%u,%p) is not a scalar image.",
pixel_type(),width,height,depth,dim,data);
CImg<floatT> points;
if (depth>1) {
if (resx==1 && resy==1 && resz==1) {
const _marching_cubes_func func(*this);
points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces);
} else {
const _marching_cubes_func_float func(*this);
points = marching_cubes(primitives,func,isovalue,0,0,0,dimx()-1.0f,dimy()-1.0f,dimz()-1.0f,resx,resy,resz,invert_faces);
}
} else {
if (resx==1 && resy==1) {
const _marching_squares_func func(*this);
points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy);
} else {
const _marching_squares_func_float func(*this);
points = marching_squares(primitives,func,isovalue,0,0,dimx()-1.0f,dimy()-1.0f,resx,resy);
}
if (points) points.resize(-100,3,1,1,0);
}
return points;
}
//! Translate a 3D object.
CImg<T>& translate_object3d(const float tx, const float ty=0, const float tz=0) {
get_shared_line(0)+=tx; get_shared_line(1)+=ty; get_shared_line(2)+=tz;
return *this;
}
CImg<Tfloat> get_translate_object3d(const float tx, const float ty=0, const float tz=0) const {
return CImg<Tfloat>(*this,false).translate_object3d(tx,ty,tz);
}
//! Translate a 3D object so that it becomes centered.
CImg<T>& translate_object3d() {
CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
xcoords-=(xm + xM)/2; ycoords-=(ym + yM)/2; zcoords-=(zm + zM)/2;
return *this;
}
CImg<Tfloat> get_translate_object3d() const {
return CImg<Tfloat>(*this,false).translate_object3d();
}
//! Resize a 3D object.
CImg<T>& resize_object3d(const float sx, const float sy=-100, const float sz=-100) {
CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
if (xm<xM) { if (sx>0) xcoords*=sx/(xM-xm); else xcoords*=-sx/100; }
if (ym<yM) { if (sy>0) ycoords*=sy/(yM-ym); else ycoords*=-sy/100; }
if (zm<zM) { if (sz>0) zcoords*=sz/(zM-zm); else zcoords*=-sz/100; }
return *this;
}
CImg<Tfloat> get_resize_object3d(const float sx, const float sy=-100, const float sz=-100) const {
return CImg<Tfloat>(*this,false).resize_object3d(sx,sy,sz);
}
// Resize a 3D object so that its max dimension if one.
CImg<T> resize_object3d() const {
CImg<T> xcoords = get_shared_line(0), ycoords = get_shared_line(1), zcoords = get_shared_line(2);
float xm, xM = (float)xcoords.maxmin(xm), ym, yM = (float)ycoords.maxmin(ym), zm, zM = (float)zcoords.maxmin(zm);
const float dx = xM - xm, dy = yM - ym, dz = zM - zm, dmax = cimg::max(dx,dy,dz);
if (dmax>0) { xcoords/=dmax; ycoords/=dmax; zcoords/=dmax; }
return *this;
}
CImg<Tfloat> get_resize_object3d() const {
return CImg<Tfloat>(*this,false).resize_object3d();
}
//! Append a 3D object to another one.
template<typename tf, typename tp, typename tff>
CImg<T>& append_object3d(CImgList<tf>& primitives, const CImg<tp>& obj_points, const CImgList<tff>& obj_primitives) {
const unsigned int P = width;
append(obj_points,'x');
const unsigned int N = primitives.size;
primitives.insert(obj_primitives);
for (unsigned int i = N; i<primitives.size; ++i) {
CImg<tf> &p = primitives[i];
if (p.size()!=5) p+=P;
else { p[0]+=P; if (p[2]==0) p[1]+=P; }
}
return *this;
}
//@}
//----------------------------
//
//! \name Color bases
//@{
//----------------------------
//! Return a default indexed color palette with 256 (R,G,B) entries.
/**
The default color palette is used by %CImg when displaying images on 256 colors displays.
It consists in the quantification of the (R,G,B) color space using 3:3:2 bits for color coding
(i.e 8 levels for the Red and Green and 4 levels for the Blue).
\return a 1x256x1x3 color image defining the palette entries.
**/
static CImg<Tuchar> default_LUT8() {
static CImg<Tuchar> palette;
if (!palette) {
palette.assign(1,256,1,3);
for (unsigned int index = 0, r = 16; r<256; r+=32)
for (unsigned int g = 16; g<256; g+=32)
for (unsigned int b = 32; b<256; b+=64) {
palette(0,index,0) = (Tuchar)r;
palette(0,index,1) = (Tuchar)g;
palette(0,index++,2) = (Tuchar)b;
}
}
return palette;
}
//! Return a rainbow color palette with 256 (R,G,B) entries.
static CImg<Tuchar> rainbow_LUT8() {
static CImg<Tuchar> palette;
if (!palette) {
CImg<Tint> tmp(1,256,1,3,1);
tmp.get_shared_channel(0).sequence(0,359);
palette = tmp.HSVtoRGB();
}
return palette;
}
//! Return a contrasted color palette with 256 (R,G,B) entries.
static CImg<Tuchar> contrast_LUT8() {
static const unsigned char pal[] = {
217,62,88,75,1,237,240,12,56,160,165,116,1,1,204,2,15,248,148,185,133,141,46,246,222,116,16,5,207,226,
17,114,247,1,214,53,238,0,95,55,233,235,109,0,17,54,33,0,90,30,3,0,94,27,19,0,68,212,166,130,0,15,7,119,
238,2,246,198,0,3,16,10,13,2,25,28,12,6,2,99,18,141,30,4,3,140,12,4,30,233,7,10,0,136,35,160,168,184,20,
233,0,1,242,83,90,56,180,44,41,0,6,19,207,5,31,214,4,35,153,180,75,21,76,16,202,218,22,17,2,136,71,74,
81,251,244,148,222,17,0,234,24,0,200,16,239,15,225,102,230,186,58,230,110,12,0,7,129,249,22,241,37,219,
1,3,254,210,3,212,113,131,197,162,123,252,90,96,209,60,0,17,0,180,249,12,112,165,43,27,229,77,40,195,12,
87,1,210,148,47,80,5,9,1,137,2,40,57,205,244,40,8,252,98,0,40,43,206,31,187,0,180,1,69,70,227,131,108,0,
223,94,228,35,248,243,4,16,0,34,24,2,9,35,73,91,12,199,51,1,249,12,103,131,20,224,2,70,32,
233,1,165,3,8,154,246,233,196,5,0,6,183,227,247,195,208,36,0,0,226,160,210,198,69,153,210,1,23,8,192,2,4,
137,1,0,52,2,249,241,129,0,0,234,7,238,71,7,32,15,157,157,252,158,2,250,6,13,30,11,162,0,199,21,11,27,224,
4,157,20,181,111,187,218,3,0,11,158,230,196,34,223,22,248,135,254,210,157,219,0,117,239,3,255,4,227,5,247,
11,4,3,188,111,11,105,195,2,0,14,1,21,219,192,0,183,191,113,241,1,12,17,248,0,48,7,19,1,254,212,0,239,246,
0,23,0,250,165,194,194,17,3,253,0,24,6,0,141,167,221,24,212,2,235,243,0,0,205,1,251,133,204,28,4,6,1,10,
141,21,74,12,236,254,228,19,1,0,214,1,186,13,13,6,13,16,27,209,6,216,11,207,251,59,32,9,155,23,19,235,143,
116,6,213,6,75,159,23,6,0,228,4,10,245,249,1,7,44,234,4,102,174,0,19,239,103,16,15,18,8,214,22,4,47,244,
255,8,0,251,173,1,212,252,250,251,252,6,0,29,29,222,233,246,5,149,0,182,180,13,151,0,203,183,0,35,149,0,
235,246,254,78,9,17,203,73,11,195,0,3,5,44,0,0,237,5,106,6,130,16,214,20,168,247,168,4,207,11,5,1,232,251,
129,210,116,231,217,223,214,27,45,38,4,177,186,249,7,215,172,16,214,27,249,230,236,2,34,216,217,0,175,30,
243,225,244,182,20,212,2,226,21,255,20,0,2,13,62,13,191,14,76,64,20,121,4,118,0,216,1,147,0,2,210,1,215,
95,210,236,225,184,46,0,248,24,11,1,9,141,250,243,9,221,233,160,11,147,2,55,8,23,12,253,9,0,54,0,231,6,3,
141,8,2,246,9,180,5,11,8,227,8,43,110,242,1,130,5,97,36,10,6,219,86,133,11,108,6,1,5,244,67,19,28,0,174,
154,16,127,149,252,188,196,196,228,244,9,249,0,0,0,37,170,32,250,0,73,255,23,3,224,234,38,195,198,0,255,87,
33,221,174,31,3,0,189,228,6,153,14,144,14,108,197,0,9,206,245,254,3,16,253,178,248,0,95,125,8,0,3,168,21,
23,168,19,50,240,244,185,0,1,144,10,168,31,82,1,13 };
static const CImg<Tuchar> palette(pal,1,256,1,3,false);
return palette;
}
//! Convert (R,G,B) color image to indexed color image.
template<typename t>
CImg<T>& RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) {
return get_RGBtoLUT(palette,dithering,indexing).transfer_to(*this);
}
template<typename t>
CImg<t> get_RGBtoLUT(const CImg<t>& palette, const bool dithering=true, const bool indexing=false) const {
if (is_empty()) return CImg<t>();
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoLUT() : Input image dimension is dim=%u, "
"should be a (R,G,B) image.",
pixel_type(),dim);
if (palette.data && palette.dim!=3)
throw CImgArgumentException("CImg<%s>::RGBtoLUT() : Given palette dimension is dim=%u, "
"should be a (R,G,B) palette",
pixel_type(),palette.dim);
CImg<t> res(width,height,depth,indexing?1:3);
float *line1 = new float[3*width], *line2 = new float[3*width];
t *pRd = res.ptr(0,0,0,0), *pGd = indexing?pRd:res.ptr(0,0,0,1), *pBd = indexing?pRd:res.ptr(0,0,0,2);
cimg_forZ(*this,z) {
const T *pRs = ptr(0,0,z,0), *pGs = ptr(0,0,z,1), *pBs = ptr(0,0,z,2);
float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); }
cimg_forY(*this,y) {
cimg::swap(line1,line2);
if (y<dimy()-1) {
const int ny = y + 1;
const T *pRs = ptr(0,ny,z,0), *pGs = ptr(0,ny,z,1), *pBs = ptr(0,ny,z,2);
float *ptrd = line2; cimg_forX(*this,x) { *(ptrd++) = (float)*(pRs++); *(ptrd++) = (float)*(pGs++); *(ptrd++) = (float)*(pBs++); }
}
float *ptr1 = line1, *ptr2 = line2;
cimg_forX(*this,x) {
float R = *(ptr1++), G = *(ptr1++), B = *(ptr1++);
R = R<0?0:(R>255?255:R); G = G<0?0:(G>255?255:G); B = B<0?0:(B>255?255:B);
t Rbest = 0, Gbest = 0, Bbest = 0;
int best_index = 0;
if (palette) { // find best match in given color palette
const t *pRs = palette.ptr(0,0,0,0), *pGs = palette.ptr(0,0,0,1), *pBs = palette.ptr(0,0,0,2);
const unsigned int Npal = palette.width*palette.height*palette.depth;
float min = cimg::type<float>::max();
for (unsigned int off = 0; off<Npal; ++off) {
const t Rp = *(pRs++), Gp = *(pGs++), Bp = *(pBs++);
const float error = cimg::sqr((float)Rp-(float)R) + cimg::sqr((float)Gp-(float)G) + cimg::sqr((float)Bp-(float)B);
if (error<min) { min = error; best_index = off; Rbest = Rp; Gbest = Gp; Bbest = Bp; }
}
} else {
Rbest = (t)((unsigned char)R&0xe0); Gbest = (t)((unsigned char)G&0xe0); Bbest = (t)((unsigned char)B&0xc0);
best_index = (unsigned char)Rbest | ((unsigned char)Gbest>>3) | ((unsigned char)Bbest>>6);
}
if (indexing) *(pRd++) = (t)best_index; else { *(pRd++) = Rbest; *(pGd++) = Gbest; *(pBd++) = Bbest; }
if (dithering) { // apply dithering to neighborhood pixels if needed
const float dR = (float)(R-Rbest), dG = (float)(G-Gbest), dB = (float)(B-Bbest);
if (x<dimx()-1) { *(ptr1++)+= dR*7/16; *(ptr1++)+= dG*7/16; *(ptr1++)+= dB*7/16; ptr1-=3; }
if (y<dimy()-1) {
*(ptr2++)+= dR*5/16; *(ptr2++)+= dG*5/16; *ptr2+= dB*5/16; ptr2-=2;
if (x>0) { *(--ptr2)+= dB*3/16; *(--ptr2)+= dG*3/16; *(--ptr2)+= dR*3/16; ptr2+=3; }
if (x<dimx()-1) { ptr2+=3; *(ptr2++)+= dR/16; *(ptr2++)+= dG/16; *ptr2+= dB/16; ptr2-=5; }
}
}
ptr2+=3;
}
}
}
delete[] line1; delete[] line2;
return res;
}
//! Convert color pixels from (R,G,B) to match the default palette.
CImg<T>& RGBtoLUT(const bool dithering=true, const bool indexing=false) {
return get_RGBtoLUT(dithering,indexing).transfer_to(*this);
}
CImg<Tuchar> get_RGBtoLUT(const bool dithering=true, const bool indexing=false) const {
static const CImg<Tuchar> empty;
return get_RGBtoLUT(empty,dithering,indexing);
}
//! Convert an indexed image to a (R,G,B) image using the specified color palette.
CImg<T>& LUTtoRGB(const CImg<T>& palette) {
return get_LUTtoRGB(palette).transfer_to(*this);
}
template<typename t>
CImg<t> get_LUTtoRGB(const CImg<t>& palette) const {
if (is_empty()) return CImg<t>();
if (dim!=1)
throw CImgInstanceException("CImg<%s>::LUTtoRGB() : Input image dimension is dim=%u, "
"should be a LUT image",
pixel_type(),dim);
if (palette.data && palette.dim!=3)
throw CImgArgumentException("CImg<%s>::LUTtoRGB() : Given palette dimension is dim=%u, "
"should be a (R,G,B) palette",
pixel_type(),palette.dim);
const CImg<t> pal = palette.data?palette:CImg<t>(default_LUT8());
CImg<t> res(width,height,depth,3);
const t *pRs = pal.ptr(0,0,0,0), *pGs = pal.ptr(0,0,0,1), *pBs = pal.ptr(0,0,0,2);
t *pRd = res.ptr(0,0,0,1), *pGd = pRd + width*height*depth, *pBd = pGd + width*height*depth;
const unsigned int Npal = palette.width*palette.height*palette.depth;
cimg_for(*this,ptr,T) {
const unsigned int index = ((unsigned int)*ptr)%Npal;
*(--pRd) = pRs[index]; *(--pGd) = pGs[index]; *(--pBd) = pBs[index];
}
return res;
}
//! Convert an indexed image (with the default palette) to a (R,G,B) image.
CImg<T>& LUTtoRGB() {
return get_LUTtoRGB().transfer_to(*this);
}
CImg<Tuchar> get_LUTtoRGB() const {
static const CImg<Tuchar> empty;
return get_LUTtoRGB(empty);
}
//! Convert color pixels from (R,G,B) to (H,S,V).
CImg<T>& RGBtoHSV() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoHSV() : Input image dimension is dim=%u, "
"should be a (R,G,B) image.",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1,
G = (Tfloat)*p2,
B = (Tfloat)*p3,
nR = (R<0?0:(R>255?255:R))/255,
nG = (G<0?0:(G>255?255:G))/255,
nB = (B<0?0:(B>255?255:B))/255,
m = cimg::min(nR,nG,nB),
M = cimg::max(nR,nG,nB);
Tfloat H = 0, S = 0;
if (M!=m) {
const Tfloat
f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
i = (Tfloat)((nR==m)?3:((nG==m)?5:1));
H = (i-f/(M-m));
if (H>=6) H-=6;
H*=60;
S = (M-m)/M;
}
*(p1++) = (T)H;
*(p2++) = (T)S;
*(p3++) = (T)M;
}
return *this;
}
CImg<Tfloat> get_RGBtoHSV() const {
return CImg<Tfloat>(*this,false).RGBtoHSV();
}
//! Convert color pixels from (H,S,V) to (R,G,B).
CImg<T>& HSVtoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::HSVtoRGB() : Input image dimension is dim=%u, "
"should be a (H,S,V) image",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
Tfloat
H = (Tfloat)*p1,
S = (Tfloat)*p2,
V = (Tfloat)*p3,
R = 0, G = 0, B = 0;
if (H==0 && S==0) R = G = B = V;
else {
H/=60;
const int i = (int)cimg_std::floor(H);
const Tfloat
f = (i&1)?(H-i):(1-H+i),
m = V*(1-S),
n = V*(1-S*f);
switch (i) {
case 6 :
case 0 : R = V; G = n; B = m; break;
case 1 : R = n; G = V; B = m; break;
case 2 : R = m; G = V; B = n; break;
case 3 : R = m; G = n; B = V; break;
case 4 : R = n; G = m; B = V; break;
case 5 : R = V; G = m; B = n; break;
}
}
R*=255; G*=255; B*=255;
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tuchar> get_HSVtoRGB() const {
return CImg<Tuchar>(*this,false).HSVtoRGB();
}
//! Convert color pixels from (R,G,B) to (H,S,L).
CImg<T>& RGBtoHSL() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoHSL() : Input image dimension is dim=%u, "
"should be a (R,G,B) image.",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1,
G = (Tfloat)*p2,
B = (Tfloat)*p3,
nR = (R<0?0:(R>255?255:R))/255,
nG = (G<0?0:(G>255?255:G))/255,
nB = (B<0?0:(B>255?255:B))/255,
m = cimg::min(nR,nG,nB),
M = cimg::max(nR,nG,nB),
L = (m+M)/2;
Tfloat H = 0, S = 0;
if (M==m) H = S = 0;
else {
const Tfloat
f = (nR==m)?(nG-nB):((nG==m)?(nB-nR):(nR-nG)),
i = (nR==m)?3.0f:((nG==m)?5.0f:1.0f);
H = (i-f/(M-m));
if (H>=6) H-=6;
H*=60;
S = (2*L<=1)?((M-m)/(M+m)):((M-m)/(2-M-m));
}
*(p1++) = (T)H;
*(p2++) = (T)S;
*(p3++) = (T)L;
}
return *this;
}
CImg<Tfloat> get_RGBtoHSL() const {
return CImg< Tfloat>(*this,false).RGBtoHSL();
}
//! Convert color pixels from (H,S,L) to (R,G,B).
CImg<T>& HSLtoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::HSLtoRGB() : Input image dimension is dim=%u, "
"should be a (H,S,V) image",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
H = (Tfloat)*p1,
S = (Tfloat)*p2,
L = (Tfloat)*p3,
q = 2*L<1?L*(1+S):(L+S-L*S),
p = 2*L-q,
h = H/360,
tr = h + 1.0f/3,
tg = h,
tb = h - 1.0f/3,
ntr = tr<0?tr+1:(tr>1?tr-1:tr),
ntg = tg<0?tg+1:(tg>1?tg-1:tg),
ntb = tb<0?tb+1:(tb>1?tb-1:tb),
R = 255*(6*ntr<1?p+(q-p)*6*ntr:(2*ntr<1?q:(3*ntr<2?p+(q-p)*6*(2.0f/3-ntr):p))),
G = 255*(6*ntg<1?p+(q-p)*6*ntg:(2*ntg<1?q:(3*ntg<2?p+(q-p)*6*(2.0f/3-ntg):p))),
B = 255*(6*ntb<1?p+(q-p)*6*ntb:(2*ntb<1?q:(3*ntb<2?p+(q-p)*6*(2.0f/3-ntb):p)));
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tuchar> get_HSLtoRGB() const {
return CImg<Tuchar>(*this,false).HSLtoRGB();
}
//! Convert color pixels from (R,G,B) to (H,S,I).
//! Reference: "Digital Image Processing, 2nd. edition", R. Gonzalez and R. Woods. Prentice Hall, 2002.
CImg<T>& RGBtoHSI() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoHSI() : Input image dimension is dim=%u, "
"should be a (R,G,B) image.",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1,
G = (Tfloat)*p2,
B = (Tfloat)*p3,
nR = (R<0?0:(R>255?255:R))/255,
nG = (G<0?0:(G>255?255:G))/255,
nB = (B<0?0:(B>255?255:B))/255,
m = cimg::min(nR,nG,nB),
theta = (Tfloat)(cimg_std::acos(0.5f*((nR-nG)+(nR-nB))/cimg_std::sqrt(cimg_std::pow(nR-nG,2)+(nR-nB)*(nG-nB)))*180/cimg::valuePI),
sum = nR + nG + nB;
Tfloat H = 0, S = 0, I = 0;
if (theta>0) H = (nB<=nG)?theta:360-theta;
if (sum>0) S = 1 - 3/sum*m;
I = sum/3;
*(p1++) = (T)H;
*(p2++) = (T)S;
*(p3++) = (T)I;
}
return *this;
}
CImg<Tfloat> get_RGBtoHSI() const {
return CImg<Tfloat>(*this,false).RGBtoHSI();
}
//! Convert color pixels from (H,S,I) to (R,G,B).
CImg<T>& HSItoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::HSItoRGB() : Input image dimension is dim=%u, "
"should be a (H,S,I) image",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
Tfloat
H = (Tfloat)*p1,
S = (Tfloat)*p2,
I = (Tfloat)*p3,
a = I*(1-S),
R = 0, G = 0, B = 0;
if (H<120) {
B = a;
R = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
G = 3*I-(R+B);
} else if (H<240) {
H-=120;
R = a;
G = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
B = 3*I-(R+G);
} else {
H-=240;
G = a;
B = (Tfloat)(I*(1+S*cimg_std::cos(H*cimg::valuePI/180)/cimg_std::cos((60-H)*cimg::valuePI/180)));
R = 3*I-(G+B);
}
R*=255; G*=255; B*=255;
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tfloat> get_HSItoRGB() const {
return CImg< Tuchar>(*this,false).HSItoRGB();
}
//! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8.
CImg<T>& RGBtoYCbCr() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoYCbCr() : Input image dimension is dim=%u, "
"should be a (R,G,B) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1,
G = (Tfloat)*p2,
B = (Tfloat)*p3,
Y = (66*R + 129*G + 25*B + 128)/256 + 16,
Cb = (-38*R - 74*G + 112*B + 128)/256 + 128,
Cr = (112*R - 94*G - 18*B + 128)/256 + 128;
*(p1++) = (T)(Y<0?0:(Y>255?255:Y));
*(p2++) = (T)(Cb<0?0:(Cb>255?255:Cb));
*(p3++) = (T)(Cr<0?0:(Cr>255?255:Cr));
}
return *this;
}
CImg<Tuchar> get_RGBtoYCbCr() const {
return CImg<Tuchar>(*this,false).RGBtoYCbCr();
}
//! Convert color pixels from (R,G,B) to (Y,Cb,Cr)_8.
CImg<T>& YCbCrtoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::YCbCrtoRGB() : Input image dimension is dim=%u, "
"should be a (Y,Cb,Cr)_8 image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
Y = (Tfloat)*p1 - 16,
Cb = (Tfloat)*p2 - 128,
Cr = (Tfloat)*p3 - 128,
R = (298*Y + 409*Cr + 128)/256,
G = (298*Y - 100*Cb - 208*Cr + 128)/256,
B = (298*Y + 516*Cb + 128)/256;
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tuchar> get_YCbCrtoRGB() const {
return CImg<Tuchar>(*this,false).YCbCrtoRGB();
}
//! Convert color pixels from (R,G,B) to (Y,U,V).
CImg<T>& RGBtoYUV() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoYUV() : Input image dimension is dim=%u, "
"should be a (R,G,B) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1/255,
G = (Tfloat)*p2/255,
B = (Tfloat)*p3/255,
Y = 0.299f*R + 0.587f*G + 0.114f*B;
*(p1++) = (T)Y;
*(p2++) = (T)(0.492f*(B-Y));
*(p3++) = (T)(0.877*(R-Y));
}
return *this;
}
CImg<Tfloat> get_RGBtoYUV() const {
return CImg<Tfloat>(*this,false).RGBtoYUV();
}
//! Convert color pixels from (Y,U,V) to (R,G,B).
CImg<T>& YUVtoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::YUVtoRGB() : Input image dimension is dim=%u, "
"should be a (Y,U,V) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
Y = (Tfloat)*p1,
U = (Tfloat)*p2,
V = (Tfloat)*p3,
R = (Y + 1.140f*V)*255,
G = (Y - 0.395f*U - 0.581f*V)*255,
B = (Y + 2.032f*U)*255;
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tuchar> get_YUVtoRGB() const {
return CImg< Tuchar>(*this,false).YUVtoRGB();
}
//! Convert color pixels from (R,G,B) to (C,M,Y).
CImg<T>& RGBtoCMY() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoCMY() : Input image dimension is dim=%u, "
"should be a (R,G,B) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1/255,
G = (Tfloat)*p2/255,
B = (Tfloat)*p3/255;
*(p1++) = (T)(1 - R);
*(p2++) = (T)(1 - G);
*(p3++) = (T)(1 - B);
}
return *this;
}
CImg<Tfloat> get_RGBtoCMY() const {
return CImg<Tfloat>(*this,false).RGBtoCMY();
}
//! Convert (C,M,Y) pixels of a color image into the (R,G,B) color space.
CImg<T>& CMYtoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::CMYtoRGB() : Input image dimension is dim=%u, "
"should be a (C,M,Y) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
C = (Tfloat)*p1,
M = (Tfloat)*p2,
Y = (Tfloat)*p3,
R = 255*(1 - C),
G = 255*(1 - M),
B = 255*(1 - Y);
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tuchar> get_CMYtoRGB() const {
return CImg<Tuchar>(*this,false).CMYtoRGB();
}
//! Convert color pixels from (C,M,Y) to (C,M,Y,K).
CImg<T>& CMYtoCMYK() {
return get_CMYtoCMYK().transfer_to(*this);
}
CImg<Tfloat> get_CMYtoCMYK() const {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::CMYtoCMYK() : Input image dimension is dim=%u, "
"should be a (C,M,Y) image (dim=3)",
pixel_type(),dim);
CImg<Tfloat> res(width,height,depth,4);
const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2);
Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2), *pd4 = res.ptr(0,0,0,3);
for (unsigned long N = width*height*depth; N; --N) {
Tfloat
C = (Tfloat)*(ps1++),
M = (Tfloat)*(ps2++),
Y = (Tfloat)*(ps3++),
K = cimg::min(C,M,Y);
if (K==1) C = M = Y = 0;
else { const Tfloat K1 = 1 - K; C = (C - K)/K1; M = (M - K)/K1; Y = (Y - K)/K1; }
*(pd1++) = C;
*(pd2++) = M;
*(pd3++) = Y;
*(pd4++) = K;
}
return res;
}
//! Convert (C,M,Y,K) pixels of a color image into the (C,M,Y) color space.
CImg<T>& CMYKtoCMY() {
return get_CMYKtoCMY().transfer_to(*this);
}
CImg<Tfloat> get_CMYKtoCMY() const {
if (is_empty()) return *this;
if (dim!=4)
throw CImgInstanceException("CImg<%s>::CMYKtoCMY() : Input image dimension is dim=%u, "
"should be a (C,M,Y,K) image (dim=4)",
pixel_type(),dim);
CImg<Tfloat> res(width,height,depth,3);
const T *ps1 = ptr(0,0,0,0), *ps2 = ptr(0,0,0,1), *ps3 = ptr(0,0,0,2), *ps4 = ptr(0,0,0,3);
Tfloat *pd1 = res.ptr(0,0,0,0), *pd2 = res.ptr(0,0,0,1), *pd3 = res.ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
C = (Tfloat)*ps1,
M = (Tfloat)*ps2,
Y = (Tfloat)*ps3,
K = (Tfloat)*ps4,
K1 = 1 - K;
*(pd1++) = C*K1 + K;
*(pd2++) = M*K1 + K;
*(pd3++) = Y*K1 + K;
}
return res;
}
//! Convert color pixels from (R,G,B) to (X,Y,Z)_709.
CImg<T>& RGBtoXYZ() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoXYZ() : Input image dimension is dim=%u, "
"should be a (R,G,B) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
R = (Tfloat)*p1/255,
G = (Tfloat)*p2/255,
B = (Tfloat)*p3/255;
*(p1++) = (T)(0.412453f*R + 0.357580f*G + 0.180423f*B);
*(p2++) = (T)(0.212671f*R + 0.715160f*G + 0.072169f*B);
*(p3++) = (T)(0.019334f*R + 0.119193f*G + 0.950227f*B);
}
return *this;
}
CImg<Tfloat> get_RGBtoXYZ() const {
return CImg<Tfloat>(*this,false).RGBtoXYZ();
}
//! Convert (X,Y,Z)_709 pixels of a color image into the (R,G,B) color space.
CImg<T>& XYZtoRGB() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::XYZtoRGB() : Input image dimension is dim=%u, "
"should be a (X,Y,Z) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
X = (Tfloat)*p1*255,
Y = (Tfloat)*p2*255,
Z = (Tfloat)*p3*255,
R = 3.240479f*X - 1.537150f*Y - 0.498535f*Z,
G = -0.969256f*X + 1.875992f*Y + 0.041556f*Z,
B = 0.055648f*X - 0.204043f*Y + 1.057311f*Z;
*(p1++) = (T)(R<0?0:(R>255?255:R));
*(p2++) = (T)(G<0?0:(G>255?255:G));
*(p3++) = (T)(B<0?0:(B>255?255:B));
}
return *this;
}
CImg<Tuchar> get_XYZtoRGB() const {
return CImg<Tuchar>(*this,false).XYZtoRGB();
}
//! Convert (X,Y,Z)_709 pixels of a color image into the (L*,a*,b*) color space.
CImg<T>& XYZtoLab() {
#define _cimg_Labf(x) ((x)>=0.008856f?(cimg_std::pow(x,(Tfloat)1/3)):(7.787f*(x)+16.0f/116))
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::XYZtoLab() : Input image dimension is dim=%u, "
"should be a (X,Y,Z) image (dim=3)",
pixel_type(),dim);
const Tfloat
Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
X = (Tfloat)*p1,
Y = (Tfloat)*p2,
Z = (Tfloat)*p3,
XXn = X/Xn, YYn = Y/Yn, ZZn = Z/Zn,
fX = (Tfloat)_cimg_Labf(XXn),
fY = (Tfloat)_cimg_Labf(YYn),
fZ = (Tfloat)_cimg_Labf(ZZn);
*(p1++) = (T)(116*fY - 16);
*(p2++) = (T)(500*(fX - fY));
*(p3++) = (T)(200*(fY - fZ));
}
return *this;
}
CImg<Tfloat> get_XYZtoLab() const {
return CImg<Tfloat>(*this,false).XYZtoLab();
}
//! Convert (L,a,b) pixels of a color image into the (X,Y,Z) color space.
CImg<T>& LabtoXYZ() {
#define _cimg_Labfi(x) ((x)>=0.206893f?((x)*(x)*(x)):(((x)-16.0f/116)/7.787f))
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::LabtoXYZ() : Input image dimension is dim=%u, "
"should be a (X,Y,Z) image (dim=3)",
pixel_type(),dim);
const Tfloat
Xn = (Tfloat)(0.412453f + 0.357580f + 0.180423f),
Yn = (Tfloat)(0.212671f + 0.715160f + 0.072169f),
Zn = (Tfloat)(0.019334f + 0.119193f + 0.950227f);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
L = (Tfloat)*p1,
a = (Tfloat)*p2,
b = (Tfloat)*p3,
cY = (L + 16)/116,
Y = (Tfloat)(Yn*_cimg_Labfi(cY)),
pY = (Tfloat)cimg_std::pow(Y/Yn,(Tfloat)1/3),
cX = a/500 + pY,
X = Xn*cX*cX*cX,
cZ = pY - b/200,
Z = Zn*cZ*cZ*cZ;
*(p1++) = (T)(X);
*(p2++) = (T)(Y);
*(p3++) = (T)(Z);
}
return *this;
}
CImg<Tfloat> get_LabtoXYZ() const {
return CImg<Tfloat>(*this,false).LabtoXYZ();
}
//! Convert (X,Y,Z)_709 pixels of a color image into the (x,y,Y) color space.
CImg<T>& XYZtoxyY() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::XYZtoxyY() : Input image dimension is dim=%u, "
"should be a (X,Y,Z) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
X = (Tfloat)*p1,
Y = (Tfloat)*p2,
Z = (Tfloat)*p3,
sum = (X+Y+Z),
nsum = sum>0?sum:1;
*(p1++) = (T)(X/nsum);
*(p2++) = (T)(Y/nsum);
*(p3++) = (T)Y;
}
return *this;
}
CImg<Tfloat> get_XYZtoxyY() const {
return CImg<Tfloat>(*this,false).XYZtoxyY();
}
//! Convert (x,y,Y) pixels of a color image into the (X,Y,Z)_709 color space.
CImg<T>& xyYtoXYZ() {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::xyYtoXYZ() : Input image dimension is dim=%u, "
"should be a (x,y,Y) image (dim=3)",
pixel_type(),dim);
T *p1 = ptr(0,0,0,0), *p2 = ptr(0,0,0,1), *p3 = ptr(0,0,0,2);
for (unsigned long N = width*height*depth; N; --N) {
const Tfloat
px = (Tfloat)*p1,
py = (Tfloat)*p2,
Y = (Tfloat)*p3,
ny = py>0?py:1;
*(p1++) = (T)(px*Y/ny);
*(p2++) = (T)Y;
*(p3++) = (T)((1-px-py)*Y/ny);
}
return *this;
}
CImg<Tfloat> get_xyYtoXYZ() const {
return CImg<Tfloat>(*this,false).xyYtoXYZ();
}
//! Convert a (R,G,B) image to a (L,a,b) one.
CImg<T>& RGBtoLab() {
return RGBtoXYZ().XYZtoLab();
}
CImg<Tfloat> get_RGBtoLab() const {
return CImg<Tfloat>(*this,false).RGBtoLab();
}
//! Convert a (L,a,b) image to a (R,G,B) one.
CImg<T>& LabtoRGB() {
return LabtoXYZ().XYZtoRGB();
}
CImg<Tuchar> get_LabtoRGB() const {
return CImg<Tuchar>(*this,false).LabtoRGB();
}
//! Convert a (R,G,B) image to a (x,y,Y) one.
CImg<T>& RGBtoxyY() {
return RGBtoXYZ().XYZtoxyY();
}
CImg<Tfloat> get_RGBtoxyY() const {
return CImg<Tfloat>(*this,false).RGBtoxyY();
}
//! Convert a (x,y,Y) image to a (R,G,B) one.
CImg<T>& xyYtoRGB() {
return xyYtoXYZ().XYZtoRGB();
}
CImg<Tuchar> get_xyYtoRGB() const {
return CImg<Tuchar>(*this,false).xyYtoRGB();
}
//! Convert a (R,G,B) image to a (C,M,Y,K) one.
CImg<T>& RGBtoCMYK() {
return RGBtoCMY().CMYtoCMYK();
}
CImg<Tfloat> get_RGBtoCMYK() const {
return CImg<Tfloat>(*this,false).RGBtoCMYK();
}
//! Convert a (C,M,Y,K) image to a (R,G,B) one.
CImg<T>& CMYKtoRGB() {
return CMYKtoCMY().CMYtoRGB();
}
CImg<Tuchar> get_CMYKtoRGB() const {
return CImg<Tuchar>(*this,false).CMYKtoRGB();
}
//! Convert a (R,G,B) image to a Bayer-coded representation.
/**
\note First (upper-left) pixel if the red component of the pixel color.
**/
CImg<T>& RGBtoBayer() {
return get_RGBtoBayer().transfer_to(*this);
}
CImg<T> get_RGBtoBayer() const {
if (is_empty()) return *this;
if (dim!=3)
throw CImgInstanceException("CImg<%s>::RGBtoBayer() : Input image dimension is dim=%u, "
"should be a (R,G,B) image (dim=3)",
pixel_type(),dim);
CImg<T> res(width,height,depth,1);
const T *pR = ptr(0,0,0,0), *pG = ptr(0,0,0,1), *pB = ptr(0,0,0,2);
T *ptrd = res.data;
cimg_forXYZ(*this,x,y,z) {
if (y%2) {
if (x%2) *(ptrd++) = *pB;
else *(ptrd++) = *pG;
} else {
if (x%2) *(ptrd++) = *pG;
else *(ptrd++) = *pR;
}
++pR; ++pG; ++pB;
}
return res;
}
//! Convert a Bayer-coded image to a (R,G,B) color image.
CImg<T>& BayertoRGB(const unsigned int interpolation_type=3) {
return get_BayertoRGB(interpolation_type).transfer_to(*this);
}
CImg<Tuchar> get_BayertoRGB(const unsigned int interpolation_type=3) const {
if (is_empty()) return *this;
if (dim!=1)
throw CImgInstanceException("CImg<%s>::BayertoRGB() : Input image dimension is dim=%u, "
"should be a Bayer image (dim=1)",
pixel_type(),dim);
CImg<Tuchar> res(width,height,depth,3);
CImg_3x3(I,T);
Tuchar *pR = res.ptr(0,0,0,0), *pG = res.ptr(0,0,0,1), *pB = res.ptr(0,0,0,2);
switch (interpolation_type) {
case 3 : { // Edge-directed
CImg_3x3(R,T);
CImg_3x3(G,T);
CImg_3x3(B,T);
cimg_forXYZ(*this,x,y,z) {
const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
cimg_get3x3(*this,x,y,z,0,I);
if (y%2) {
if (x%2) {
const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
*pG = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
} else *pG = (Tuchar)Icc;
} else {
if (x%2) *pG = (Tuchar)Icc;
else {
const Tfloat alpha = cimg::sqr((Tfloat)Inc - Ipc), beta = cimg::sqr((Tfloat)Icn - Icp), cx = 1/(1+alpha), cy = 1/(1+beta);
*pG = (Tuchar)((cx*(Inc+Ipc) + cy*(Icn+Icp))/(2*(cx+cy)));
}
}
++pG;
}
cimg_forXYZ(*this,x,y,z) {
const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
cimg_get3x3(*this,x,y,z,0,I);
cimg_get3x3(res,x,y,z,1,G);
if (y%2) {
if (x%2) *pB = (Tuchar)Icc;
else { *pR = (Tuchar)((Icn+Icp)/2); *pB = (Tuchar)((Inc+Ipc)/2); }
} else {
if (x%2) { *pR = (Tuchar)((Inc+Ipc)/2); *pB = (Tuchar)((Icn+Icp)/2); }
else *pR = (Tuchar)Icc;
}
++pR; ++pB;
}
pR = res.ptr(0,0,0,0);
pG = res.ptr(0,0,0,1);
pB = res.ptr(0,0,0,2);
cimg_forXYZ(*this,x,y,z) {
const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
cimg_get3x3(res,x,y,z,0,R);
cimg_get3x3(res,x,y,z,1,G);
cimg_get3x3(res,x,y,z,2,B);
if (y%2) {
if (x%2) {
const float alpha = (float)cimg::sqr(Rnc-Rpc), beta = (float)cimg::sqr(Rcn-Rcp), cx = 1/(1+alpha), cy = 1/(1+beta);
*pR = (Tuchar)((cx*(Rnc+Rpc) + cy*(Rcn+Rcp))/(2*(cx+cy)));
}
} else {
if (!(x%2)) {
const float alpha = (float)cimg::sqr(Bnc-Bpc), beta = (float)cimg::sqr(Bcn-Bcp), cx = 1/(1+alpha), cy = 1/(1+beta);
*pB = (Tuchar)((cx*(Bnc+Bpc) + cy*(Bcn+Bcp))/(2*(cx+cy)));
}
}
++pR; ++pG; ++pB;
}
} break;
case 2 : { // Linear interpolation
cimg_forXYZ(*this,x,y,z) {
const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
cimg_get3x3(*this,x,y,z,0,I);
if (y%2) {
if (x%2) { *pR = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); *pG = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *pB = (Tuchar)Icc; }
else { *pR = (Tuchar)((Icp+Icn)/2); *pG = (Tuchar)Icc; *pB = (Tuchar)((Inc+Ipc)/2); }
} else {
if (x%2) { *pR = (Tuchar)((Ipc+Inc)/2); *pG = (Tuchar)Icc; *pB = (Tuchar)((Icn+Icp)/2); }
else { *pR = (Tuchar)Icc; *pG = (Tuchar)((Inc+Ipc+Icn+Icp)/4); *pB = (Tuchar)((Ipp+Inn+Ipn+Inp)/4); }
}
++pR; ++pG; ++pB;
}
} break;
case 1 : { // Nearest neighbor interpolation
cimg_forXYZ(*this,x,y,z) {
const int _p1x = x?x-1:1, _p1y = y?y-1:1, _n1x = x<dimx()-1?x+1:x-1, _n1y = y<dimy()-1?y+1:y-1;
cimg_get3x3(*this,x,y,z,0,I);
if (y%2) {
if (x%2) { *pR = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); *pG = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *pB = (Tuchar)Icc; }
else { *pR = (Tuchar)cimg::min(Icn,Icp); *pG = (Tuchar)Icc; *pB = (Tuchar)cimg::min(Inc,Ipc); }
} else {
if (x%2) { *pR = (Tuchar)cimg::min(Inc,Ipc); *pG = (Tuchar)Icc; *pB = (Tuchar)cimg::min(Icn,Icp); }
else { *pR = (Tuchar)Icc; *pG = (Tuchar)cimg::min(Inc,Ipc,Icn,Icp); *pB = (Tuchar)cimg::min(Ipp,Inn,Ipn,Inp); }
}
++pR; ++pG; ++pB;
}
} break;
default : { // 0-filling interpolation
const T *ptrs = data;
res.fill(0);
cimg_forXYZ(*this,x,y,z) {
const T val = *(ptrs++);
if (y%2) { if (x%2) *pB = val; else *pG = val; } else { if (x%2) *pG = val; else *pR = val; }
++pR; ++pG; ++pB;
}
}
}
return res;
}
//@}
//-------------------
//
//! \name Drawing
//@{
//-------------------
// The following _draw_scanline() routines are *non user-friendly functions*, used only for internal purpose.
// Pre-requisites : x0<x1, y-coordinate is valid, col is valid.
template<typename tc>
CImg<T>& _draw_scanline(const int x0, const int x1, const int y,
const tc *const color, const float opacity=1,
const float brightness=1, const bool init=false) {
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
static float nopacity = 0, copacity = 0;
static unsigned int whz = 0;
static const tc *col = 0;
if (init) {
nopacity = cimg::abs(opacity);
copacity = 1 - cimg::max(opacity,0);
whz = width*height*depth;
} else {
const int nx0 = x0>0?x0:0, nx1 = x1<dimx()?x1:dimx()-1, dx = nx1 - nx0;
if (dx>=0) {
col = color;
const unsigned int off = whz-dx-1;
T *ptrd = ptr(nx0,y);
if (opacity>=1) { // ** Opaque drawing **
if (brightness==1) { // Brightness==1
if (sizeof(T)!=1) cimg_forV(*this,k) {
const T val = (T)*(col++);
for (int x = dx; x>=0; --x) *(ptrd++) = val;
ptrd+=off;
} else cimg_forV(*this,k) {
const T val = (T)*(col++);
cimg_std::memset(ptrd,(int)val,dx+1);
ptrd+=whz;
}
} else if (brightness<1) { // Brightness<1
if (sizeof(T)!=1) cimg_forV(*this,k) {
const T val = (T)(*(col++)*brightness);
for (int x = dx; x>=0; --x) *(ptrd++) = val;
ptrd+=off;
} else cimg_forV(*this,k) {
const T val = (T)(*(col++)*brightness);
cimg_std::memset(ptrd,(int)val,dx+1);
ptrd+=whz;
}
} else { // Brightness>1
if (sizeof(T)!=1) cimg_forV(*this,k) {
const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
for (int x = dx; x>=0; --x) *(ptrd++) = val;
ptrd+=off;
} else cimg_forV(*this,k) {
const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
cimg_std::memset(ptrd,(int)val,dx+1);
ptrd+=whz;
}
}
} else { // ** Transparent drawing **
if (brightness==1) { // Brightness==1
cimg_forV(*this,k) {
const T val = (T)*(col++);
for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
ptrd+=off;
}
} else if (brightness<=1) { // Brightness<1
cimg_forV(*this,k) {
const T val = (T)(*(col++)*brightness);
for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
ptrd+=off;
}
} else { // Brightness>1
cimg_forV(*this,k) {
const T val = (T)((2-brightness)**(col++) + (brightness-1)*maxval);
for (int x = dx; x>=0; --x) { *ptrd = (T)(val*nopacity + *ptrd*copacity); ++ptrd; }
ptrd+=off;
}
}
}
}
}
return *this;
}
template<typename tc>
CImg<T>& _draw_scanline(const tc *const color, const float opacity=1) {
return _draw_scanline(0,0,0,color,opacity,0,true);
}
//! Draw a 2D colored point (pixel).
/**
\param x0 X-coordinate of the point.
\param y0 Y-coordinate of the point.
\param color Pointer to \c dimv() consecutive values, defining the color values.
\param opacity Drawing opacity (optional).
\note
- Clipping is supported.
- To set pixel values without clipping needs, you should use the faster CImg::operator()() function.
\par Example:
\code
CImg<unsigned char> img(100,100,1,3,0);
const unsigned char color[] = { 255,128,64 };
img.draw_point(50,50,color);
\endcode
**/
template<typename tc>
CImg<T>& draw_point(const int x0, const int y0,
const tc *const color, const float opacity=1) {
return draw_point(x0,y0,0,color,opacity);
}
//! Draw a 2D colored point (pixel).
template<typename tc>
CImg<T>& draw_point(const int x0, const int y0,
const CImg<tc>& color, const float opacity=1) {
return draw_point(x0,y0,color.data,opacity);
}
//! Draw a 3D colored point (voxel).
template<typename tc>
CImg<T>& draw_point(const int x0, const int y0, const int z0,
const tc *const color, const float opacity=1) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_point() : Specified color is (null)",
pixel_type());
if (x0>=0 && y0>=0 && z0>=0 && x0<dimx() && y0<dimy() && z0<dimz()) {
const unsigned int whz = width*height*depth;
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
T *ptrd = ptr(x0,y0,z0,0);
const tc *col = color;
if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
}
return *this;
}
//! Draw a 3D colored point (voxel).
template<typename tc>
CImg<T>& draw_point(const int x0, const int y0, const int z0,
const CImg<tc>& color, const float opacity=1) {
return draw_point(x0,y0,z0,color.data,opacity);
}
// Draw a cloud of colored point (internal).
template<typename t, typename tc>
CImg<T>& _draw_point(const t& points, const unsigned int W, const unsigned int H,
const tc *const color, const float opacity) {
if (is_empty() || !points || !W) return *this;
switch (H) {
case 0 : case 1 :
throw CImgArgumentException("CImg<%s>::draw_point() : Given list of points is not valid.",
pixel_type());
case 2 : {
for (unsigned int i = 0; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1);
draw_point(x,y,color,opacity);
}
} break;
default : {
for (unsigned int i = 0; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
draw_point(x,y,z,color,opacity);
}
}
}
return *this;
}
//! Draw a cloud of colored points.
/**
\param points Coordinates of vertices, stored as a list of vectors.
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param opacity Drawing opacity (optional).
\note
- This function uses several call to the single CImg::draw_point() procedure,
depending on the vectors size in \p points.
\par Example:
\code
CImg<unsigned char> img(100,100,1,3,0);
const unsigned char color[] = { 255,128,64 };
CImgList<int> points;
points.insert(CImg<int>::vector(0,0)).
.insert(CImg<int>::vector(70,10)).
.insert(CImg<int>::vector(80,60)).
.insert(CImg<int>::vector(10,90));
img.draw_point(points,color);
\endcode
**/
template<typename t, typename tc>
CImg<T>& draw_point(const CImgList<t>& points,
const tc *const color, const float opacity=1) {
unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
return _draw_point(points,points.size,H,color,opacity);
}
//! Draw a cloud of colored points.
template<typename t, typename tc>
CImg<T>& draw_point(const CImgList<t>& points,
const CImg<tc>& color, const float opacity=1) {
return draw_point(points,color.data,opacity);
}
//! Draw a cloud of colored points.
/**
\note
- Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
(sequence of vectors aligned along the x-axis).
**/
template<typename t, typename tc>
CImg<T>& draw_point(const CImg<t>& points,
const tc *const color, const float opacity=1) {
return _draw_point(points,points.width,points.height,color,opacity);
}
//! Draw a cloud of colored points.
template<typename t, typename tc>
CImg<T>& draw_point(const CImg<t>& points,
const CImg<tc>& color, const float opacity=1) {
return draw_point(points,color.data,opacity);
}
//! Draw a 2D colored line.
/**
\param x0 X-coordinate of the starting line point.
\param y0 Y-coordinate of the starting line point.
\param x1 X-coordinate of the ending line point.
\param y1 Y-coordinate of the ending line point.
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param opacity Drawing opacity (optional).
\param pattern An integer whose bits describe the line pattern (optional).
\param init_hatch Flag telling if a reinitialization of the hash state must be done (optional).
\note
- Clipping is supported.
- Line routine uses Bresenham's algorithm.
- Set \p init_hatch = false to draw consecutive hatched segments without breaking the line pattern.
\par Example:
\code
CImg<unsigned char> img(100,100,1,3,0);
const unsigned char color[] = { 255,128,64 };
img.draw_line(40,40,80,70,color);
\endcode
**/
template<typename tc>
CImg<T>& draw_line(const int x0, const int y0,
const int x1, const int y1,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",
pixel_type());
static unsigned int hatch = ~0U - (~0U>>1);
if (init_hatch) hatch = ~0U - (~0U>>1);
const bool xdir = x0<x1, ydir = y0<y1;
int
nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
&xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
&xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
&xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
&xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
if (xright<0 || xleft>=dimx()) return *this;
if (xleft<0) { yleft-=xleft*(yright - yleft)/(xright - xleft); xleft = 0; }
if (xright>=dimx()) { yright-=(xright - dimx())*(yright - yleft)/(xright - xleft); xright = dimx()-1; }
if (ydown<0 || yup>=dimy()) return *this;
if (yup<0) { xup-=yup*(xdown - xup)/(ydown - yup); yup = 0; }
if (ydown>=dimy()) { xdown-=(ydown - dimy())*(xdown - xup)/(ydown - yup); ydown = dimy()-1; }
T *ptrd0 = ptr(nx0,ny0);
int dx = xright - xleft, dy = ydown - yup;
const bool steep = dy>dx;
if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
const int
offx = (nx0<nx1?1:-1)*(steep?width:1),
offy = (ny0<ny1?1:-1)*(steep?1:width),
wh = width*height;
if (opacity>=1) {
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) { T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
}
} else {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) {
T *ptrd = ptrd0; const tc* col = color;
cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
T *ptrd = ptrd0; const tc* col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
}
}
return *this;
}
//! Draw a 2D colored line.
template<typename tc>
CImg<T>& draw_line(const int x0, const int y0,
const int x1, const int y1,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_line(x0,y0,x1,y1,color.data,opacity,pattern,init_hatch);
}
//! Draw a 2D colored line, with z-buffering.
template<typename tc>
CImg<T>& draw_line(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
if (!is_empty() && z0>0 && z1>0) {
if (!color)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null).",
pixel_type());
static unsigned int hatch = ~0U - (~0U>>1);
if (init_hatch) hatch = ~0U - (~0U>>1);
const bool xdir = x0<x1, ydir = y0<y1;
int
nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
&xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
&xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
&xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
&xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
float
Z0 = 1/z0, Z1 = 1/z1, nz0 = Z0, nz1 = Z1, dz = Z1 - Z0,
&zleft = xdir?nz0:nz1,
&zright = xdir?nz1:nz0,
&zup = ydir?nz0:nz1,
&zdown = ydir?nz1:nz0;
if (xright<0 || xleft>=dimx()) return *this;
if (xleft<0) {
const int D = xright - xleft;
yleft-=xleft*(yright - yleft)/D;
zleft-=xleft*(zright - zleft)/D;
xleft = 0;
}
if (xright>=dimx()) {
const int d = xright - dimx(), D = xright - xleft;
yright-=d*(yright - yleft)/D;
zright-=d*(zright - zleft)/D;
xright = dimx()-1;
}
if (ydown<0 || yup>=dimy()) return *this;
if (yup<0) {
const int D = ydown - yup;
xup-=yup*(xdown - xup)/D;
zup-=yup*(zdown - zup)/D;
yup = 0;
}
if (ydown>=dimy()) {
const int d = ydown - dimy(), D = ydown - yup;
xdown-=d*(xdown - xup)/D;
zdown-=d*(zdown - zup)/D;
ydown = dimy()-1;
}
T *ptrd0 = ptr(nx0,ny0);
float *ptrz = zbuffer + nx0 + ny0*width;
int dx = xright - xleft, dy = ydown - yup;
const bool steep = dy>dx;
if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
const int
offx = (nx0<nx1?1:-1)*(steep?width:1),
offy = (ny0<ny1?1:-1)*(steep?1:width),
wh = width*height,
ndx = dx>0?dx:1;
if (opacity>=1) {
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz && pattern&hatch) {
*ptrz = z;
T *ptrd = ptrd0; const tc *col = color;
cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz) {
*ptrz = z;
T *ptrd = ptrd0; const tc *col = color;
cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=wh; }
}
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
}
} else {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz && pattern&hatch) {
*ptrz = z;
T *ptrd = ptrd0; const tc *col = color;
cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz) {
*ptrz = z;
T *ptrd = ptrd0; const tc *col = color;
cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=wh; }
}
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
}
}
}
return *this;
}
//! Draw a 2D colored line, with z-buffering.
template<typename tc>
CImg<T>& draw_line(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
}
//! Draw a 3D colored line.
template<typename tc>
CImg<T>& draw_line(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified color is (null)",
pixel_type());
static unsigned int hatch = ~0U - (~0U>>1);
if (init_hatch) hatch = ~0U - (~0U>>1);
int nx0 = x0, ny0 = y0, nz0 = z0, nx1 = x1, ny1 = y1, nz1 = z1;
if (nx0>nx1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
if (nx1<0 || nx0>=dimx()) return *this;
if (nx0<0) { const int D = 1 + nx1 - nx0; ny0-=nx0*(1 + ny1 - ny0)/D; nz0-=nx0*(1 + nz1 - nz0)/D; nx0 = 0; }
if (nx1>=dimx()) { const int d = nx1-dimx(), D = 1 + nx1 - nx0; ny1+=d*(1 + ny0 - ny1)/D; nz1+=d*(1 + nz0 - nz1)/D; nx1 = dimx()-1; }
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
if (ny1<0 || ny0>=dimy()) return *this;
if (ny0<0) { const int D = 1 + ny1 - ny0; nx0-=ny0*(1 + nx1 - nx0)/D; nz0-=ny0*(1 + nz1 - nz0)/D; ny0 = 0; }
if (ny1>=dimy()) { const int d = ny1-dimy(), D = 1 + ny1 - ny0; nx1+=d*(1 + nx0 - nx1)/D; nz1+=d*(1 + nz0 - nz1)/D; ny1 = dimy()-1; }
if (nz0>nz1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
if (nz1<0 || nz0>=dimz()) return *this;
if (nz0<0) { const int D = 1 + nz1 - nz0; nx0-=nz0*(1 + nx1 - nx0)/D; ny0-=nz0*(1 + ny1 - ny0)/D; nz0 = 0; }
if (nz1>=dimz()) { const int d = nz1-dimz(), D = 1 + nz1 - nz0; nx1+=d*(1 + nx0 - nx1)/D; ny1+=d*(1 + ny0 - ny1)/D; nz1 = dimz()-1; }
const unsigned int dmax = cimg::max(cimg::abs(nx1 - nx0),cimg::abs(ny1 - ny0),nz1 - nz0), whz = width*height*depth;
const float px = (nx1 - nx0)/(float)dmax, py = (ny1 - ny0)/(float)dmax, pz = (nz1 - nz0)/(float)dmax;
float x = (float)nx0, y = (float)ny0, z = (float)nz0;
if (opacity>=1) for (unsigned int t = 0; t<=dmax; ++t) {
if (!(~pattern) || (~pattern && pattern&hatch)) {
T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z);
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
}
x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
} else {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
for (unsigned int t = 0; t<=dmax; ++t) {
if (!(~pattern) || (~pattern && pattern&hatch)) {
T* ptrd = ptr((unsigned int)x,(unsigned int)y,(unsigned int)z);
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; }
}
x+=px; y+=py; z+=pz; if (pattern) { hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1); }
}
}
return *this;
}
//! Draw a 3D colored line.
template<typename tc>
CImg<T>& draw_line(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_line(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern,init_hatch);
}
//! Draw a 2D textured line.
/**
\param x0 X-coordinate of the starting line point.
\param y0 Y-coordinate of the starting line point.
\param x1 X-coordinate of the ending line point.
\param y1 Y-coordinate of the ending line point.
\param texture Texture image defining the pixel colors.
\param tx0 X-coordinate of the starting texture point.
\param ty0 Y-coordinate of the starting texture point.
\param tx1 X-coordinate of the ending texture point.
\param ty1 Y-coordinate of the ending texture point.
\param opacity Drawing opacity (optional).
\param pattern An integer whose bits describe the line pattern (optional).
\param init_hatch Flag telling if the hash variable must be reinitialized (optional).
\note
- Clipping is supported but not for texture coordinates.
- Line routine uses the well known Bresenham's algorithm.
\par Example:
\code
CImg<unsigned char> img(100,100,1,3,0), texture("texture256x256.ppm");
const unsigned char color[] = { 255,128,64 };
img.draw_line(40,40,80,70,texture,0,0,255,255);
\endcode
**/
template<typename tc>
CImg<T>& draw_line(const int x0, const int y0,
const int x1, const int y1,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
if (is_empty()) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_line(x0,y0,x1,y1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
static unsigned int hatch = ~0U - (~0U>>1);
if (init_hatch) hatch = ~0U - (~0U>>1);
const bool xdir = x0<x1, ydir = y0<y1;
int
dtx = tx1-tx0, dty = ty1-ty0,
nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
tnx0 = tx0, tnx1 = tx1, tny0 = ty0, tny1 = ty1,
&xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1, &xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
&txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
&xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1, &xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0,
&txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
if (xright<0 || xleft>=dimx()) return *this;
if (xleft<0) {
const int D = xright - xleft;
yleft-=xleft*(yright - yleft)/D;
txleft-=xleft*(txright - txleft)/D;
tyleft-=xleft*(tyright - tyleft)/D;
xleft = 0;
}
if (xright>=dimx()) {
const int d = xright - dimx(), D = xright - xleft;
yright-=d*(yright - yleft)/D;
txright-=d*(txright - txleft)/D;
tyright-=d*(tyright - tyleft)/D;
xright = dimx()-1;
}
if (ydown<0 || yup>=dimy()) return *this;
if (yup<0) {
const int D = ydown - yup;
xup-=yup*(xdown - xup)/D;
txup-=yup*(txdown - txup)/D;
tyup-=yup*(tydown - tyup)/D;
yup = 0;
}
if (ydown>=dimy()) {
const int d = ydown - dimy(), D = ydown - yup;
xdown-=d*(xdown - xup)/D;
txdown-=d*(txdown - txup)/D;
tydown-=d*(tydown - tyup)/D;
ydown = dimy()-1;
}
T *ptrd0 = ptr(nx0,ny0);
int dx = xright - xleft, dy = ydown - yup;
const bool steep = dy>dx;
if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
const int
offx = (nx0<nx1?1:-1)*(steep?width:1),
offy = (ny0<ny1?1:-1)*(steep?1:width),
wh = width*height,
ndx = dx>0?dx:1;
if (opacity>=1) {
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) {
T *ptrd = ptrd0;
const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
T *ptrd = ptrd0;
const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
cimg_forV(*this,k) { *ptrd = (T)texture(tx,ty,0,k); ptrd+=wh; }
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
}
} else {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
T *ptrd = ptrd0;
if (pattern&hatch) {
const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
T *ptrd = ptrd0;
const int tx = tx0 + x*dtx/ndx, ty = ty0 + x*dty/ndx;
cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture(tx,ty,0,k) + *ptrd*copacity); ptrd+=wh; }
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
}
}
return *this;
}
//! Draw a 2D textured line, with perspective correction.
template<typename tc>
CImg<T>& draw_line(const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
if (is_empty() && z0<=0 && z1<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_line(x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
static unsigned int hatch = ~0U - (~0U>>1);
if (init_hatch) hatch = ~0U - (~0U>>1);
const bool xdir = x0<x1, ydir = y0<y1;
int
nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
&xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
&xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
&xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
&xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
float
Tx0 = tx0/z0, Tx1 = tx1/z1,
Ty0 = ty0/z0, Ty1 = ty1/z1,
Z0 = 1/z0, Z1 = 1/z1,
dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
&zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
&zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
&zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
&zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
if (xright<0 || xleft>=dimx()) return *this;
if (xleft<0) {
const int D = xright - xleft;
yleft-=xleft*(yright - yleft)/D;
zleft-=xleft*(zright - zleft)/D;
txleft-=xleft*(txright - txleft)/D;
tyleft-=xleft*(tyright - tyleft)/D;
xleft = 0;
}
if (xright>=dimx()) {
const int d = xright - dimx(), D = xright - xleft;
yright-=d*(yright - yleft)/D;
zright-=d*(zright - zleft)/D;
txright-=d*(txright - txleft)/D;
tyright-=d*(tyright - tyleft)/D;
xright = dimx()-1;
}
if (ydown<0 || yup>=dimy()) return *this;
if (yup<0) {
const int D = ydown - yup;
xup-=yup*(xdown - xup)/D;
zup-=yup*(zdown - zup)/D;
txup-=yup*(txdown - txup)/D;
tyup-=yup*(tydown - tyup)/D;
yup = 0;
}
if (ydown>=dimy()) {
const int d = ydown - dimy(), D = ydown - yup;
xdown-=d*(xdown - xup)/D;
zdown-=d*(zdown - zup)/D;
txdown-=d*(txdown - txup)/D;
tydown-=d*(tydown - tyup)/D;
ydown = dimy()-1;
}
T *ptrd0 = ptr(nx0,ny0);
int dx = xright - xleft, dy = ydown - yup;
const bool steep = dy>dx;
if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
const int
offx = (nx0<nx1?1:-1)*(steep?width:1),
offy = (ny0<ny1?1:-1)*(steep?1:width),
wh = width*height,
ndx = dx>0?dx:1;
if (opacity>=1) {
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) {
const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
}
} else {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) {
const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx, tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0;
cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
ptrd0+=offx;
if ((error-=dy)<0) { ptrd0+=offy; error+=dx; }
}
}
return *this;
}
//! Draw a 2D textured line, with z-buffering and perspective correction.
template<typename tc>
CImg<T>& draw_line(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
if (!is_empty() && z0>0 && z1>0) {
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_line(zbuffer,x0,y0,z0,x1,y1,z1,+texture,tx0,ty0,tx1,ty1,opacity,pattern,init_hatch);
static unsigned int hatch = ~0U - (~0U>>1);
if (init_hatch) hatch = ~0U - (~0U>>1);
const bool xdir = x0<x1, ydir = y0<y1;
int
nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1,
&xleft = xdir?nx0:nx1, &yleft = xdir?ny0:ny1,
&xright = xdir?nx1:nx0, &yright = xdir?ny1:ny0,
&xup = ydir?nx0:nx1, &yup = ydir?ny0:ny1,
&xdown = ydir?nx1:nx0, &ydown = ydir?ny1:ny0;
float
Tx0 = tx0/z0, Tx1 = tx1/z1,
Ty0 = ty0/z0, Ty1 = ty1/z1,
Z0 = 1/z0, Z1 = 1/z1,
dz = Z1 - Z0, dtx = Tx1 - Tx0, dty = Ty1 - Ty0,
tnx0 = Tx0, tnx1 = Tx1, tny0 = Ty0, tny1 = Ty1, nz0 = Z0, nz1 = Z1,
&zleft = xdir?nz0:nz1, &txleft = xdir?tnx0:tnx1, &tyleft = xdir?tny0:tny1,
&zright = xdir?nz1:nz0, &txright = xdir?tnx1:tnx0, &tyright = xdir?tny1:tny0,
&zup = ydir?nz0:nz1, &txup = ydir?tnx0:tnx1, &tyup = ydir?tny0:tny1,
&zdown = ydir?nz1:nz0, &txdown = ydir?tnx1:tnx0, &tydown = ydir?tny1:tny0;
if (xright<0 || xleft>=dimx()) return *this;
if (xleft<0) {
const int D = xright - xleft;
yleft-=xleft*(yright - yleft)/D;
zleft-=xleft*(zright - zleft)/D;
txleft-=xleft*(txright - txleft)/D;
tyleft-=xleft*(tyright - tyleft)/D;
xleft = 0;
}
if (xright>=dimx()) {
const int d = xright - dimx(), D = xright - xleft;
yright-=d*(yright - yleft)/D;
zright-=d*(zright - zleft)/D;
txright-=d*(txright - txleft)/D;
tyright-=d*(tyright - tyleft)/D;
xright = dimx()-1;
}
if (ydown<0 || yup>=dimy()) return *this;
if (yup<0) {
const int D = ydown - yup;
xup-=yup*(xdown - xup)/D;
zup-=yup*(zdown - zup)/D;
txup-=yup*(txdown - txup)/D;
tyup-=yup*(tydown - tyup)/D;
yup = 0;
}
if (ydown>=dimy()) {
const int d = ydown - dimy(), D = ydown - yup;
xdown-=d*(xdown - xup)/D;
zdown-=d*(zdown - zup)/D;
txdown-=d*(txdown - txup)/D;
tydown-=d*(tydown - tyup)/D;
ydown = dimy()-1;
}
T *ptrd0 = ptr(nx0,ny0);
float *ptrz = zbuffer + nx0 + ny0*width;
int dx = xright - xleft, dy = ydown - yup;
const bool steep = dy>dx;
if (steep) cimg::swap(nx0,ny0,nx1,ny1,dx,dy);
const int
offx = (nx0<nx1?1:-1)*(steep?width:1),
offy = (ny0<ny1?1:-1)*(steep?1:width),
wh = width*height,
ndx = dx>0?dx:1;
if (opacity>=1) {
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz) {
*ptrz = z;
const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
}
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz) {
*ptrz = z;
const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)texture((int)(tx/z),(int)(ty/z),0,k); ptrd+=wh; }
}
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
}
} else {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (~pattern) for (int error = dx>>1, x = 0; x<=dx; ++x) {
if (pattern&hatch) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz) {
*ptrz = z;
const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
}
}
hatch>>=1; if (!hatch) hatch = ~0U - (~0U>>1);
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offy; error+=dx; }
} else for (int error = dx>>1, x = 0; x<=dx; ++x) {
const float z = Z0 + x*dz/ndx;
if (z>*ptrz) {
*ptrz = z;
const float tx = Tx0 + x*dtx/ndx, ty = Ty0 + x*dty/ndx;
T *ptrd = ptrd0; cimg_forV(*this,k) { *ptrd = (T)(nopacity*texture((int)(tx/z),(int)(ty/z),0,k) + *ptrd*copacity); ptrd+=wh; }
}
ptrd0+=offx; ptrz+=offx;
if ((error-=dy)<0) { ptrd0+=offy; ptrz+=offx; error+=dx; }
}
}
}
return *this;
}
// Inner routine for drawing set of consecutive lines with generic type for coordinates.
template<typename t, typename tc>
CImg<T>& _draw_line(const t& points, const unsigned int W, const unsigned int H,
const tc *const color, const float opacity,
const unsigned int pattern, const bool init_hatch) {
if (is_empty() || !points || W<2) return *this;
bool ninit_hatch = init_hatch;
switch (H) {
case 0 : case 1 :
throw CImgArgumentException("CImg<%s>::draw_line() : Given list of points is not valid.",
pixel_type());
case 2 : {
const int x0 = (int)points(0,0), y0 = (int)points(0,1);
int ox = x0, oy = y0;
for (unsigned int i = 1; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1);
draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = x; oy = y;
}
} break;
default : {
const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
int ox = x0, oy = y0, oz = z0;
for (unsigned int i = 1; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = x; oy = y; oz = z;
}
}
}
return *this;
}
//! Draw a set of consecutive colored lines in the instance image.
/**
\param points Coordinates of vertices, stored as a list of vectors.
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param opacity Drawing opacity (optional).
\param pattern An integer whose bits describe the line pattern (optional).
\param init_hatch If set to true, init hatch motif.
\note
- This function uses several call to the single CImg::draw_line() procedure,
depending on the vectors size in \p points.
\par Example:
\code
CImg<unsigned char> img(100,100,1,3,0);
const unsigned char color[] = { 255,128,64 };
CImgList<int> points;
points.insert(CImg<int>::vector(0,0)).
.insert(CImg<int>::vector(70,10)).
.insert(CImg<int>::vector(80,60)).
.insert(CImg<int>::vector(10,90));
img.draw_line(points,color);
\endcode
**/
template<typename t, typename tc>
CImg<T>& draw_line(const CImgList<t>& points,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
return _draw_line(points,points.size,H,color,opacity,pattern,init_hatch);
}
//! Draw a set of consecutive colored lines in the instance image.
template<typename t, typename tc>
CImg<T>& draw_line(const CImgList<t>& points,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_line(points,color.data,opacity,pattern,init_hatch);
}
//! Draw a set of consecutive colored lines in the instance image.
/**
\note
- Similar to the previous function, where the N vertex coordinates are stored as a Nx2 or Nx3 image
(sequence of vectors aligned along the x-axis).
**/
template<typename t, typename tc>
CImg<T>& draw_line(const CImg<t>& points,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return _draw_line(points,points.width,points.height,color,opacity,pattern,init_hatch);
}
//! Draw a set of consecutive colored lines in the instance image.
template<typename t, typename tc>
CImg<T>& draw_line(const CImg<t>& points,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_line(points,color.data,opacity,pattern,init_hatch);
}
// Inner routine for a drawing filled polygon with generic type for coordinates.
template<typename t, typename tc>
CImg<T>& _draw_polygon(const t& points, const unsigned int N,
const tc *const color, const float opacity) {
if (is_empty() || !points || N<3) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_polygon() : Specified color is (null).",
pixel_type());
_draw_scanline(color,opacity);
int xmin = (int)(~0U>>1), xmax = 0, ymin = (int)(~0U>>1), ymax = 0;
{ for (unsigned int p = 0; p<N; ++p) {
const int x = (int)points(p,0), y = (int)points(p,1);
if (x<xmin) xmin = x;
if (x>xmax) xmax = x;
if (y<ymin) ymin = y;
if (y>ymax) ymax = y;
}}
if (xmax<0 || xmin>=dimx() || ymax<0 || ymin>=dimy()) return *this;
const unsigned int
nymin = ymin<0?0:(unsigned int)ymin,
nymax = ymax>=dimy()?height-1:(unsigned int)ymax,
dy = 1 + nymax - nymin;
CImg<intT> X(1+2*N,dy,1,1,0), tmp;
int cx = (int)points(0,0), cy = (int)points(0,1);
for (unsigned int cp = 0, p = 0; p<N; ++p) {
const unsigned int np = (p!=N-1)?p+1:0, ap = (np!=N-1)?np+1:0;
const int
nx = (int)points(np,0), ny = (int)points(np,1), ay = (int)points(ap,1),
y0 = cy - nymin, y1 = ny - nymin;
if (y0!=y1) {
const int countermin = ((ny<ay && cy<ny) || (ny>ay && cy>ny))?1:0;
for (int x = cx, y = y0, _sx = 1, _sy = 1,
_dx = nx>cx?nx-cx:((_sx=-1),cx-nx),
_dy = y1>y0?y1-y0:((_sy=-1),y0-y1),
_counter = ((_dx-=_dy?_dy*(_dx/_dy):0),_dy),
_err = _dx>>1,
_rx = _dy?(nx-cx)/_dy:0;
_counter>=countermin;
--_counter, y+=_sy, x+=_rx + ((_err-=_dx)<0?_err+=_dy,_sx:0))
if (y>=0 && y<(int)dy) X(++X(0,y),y) = x;
cp = np; cx = nx; cy = ny;
} else {
const int pp = (cp?cp-1:N-1), py = (int)points(pp,1);
if ((cy>py && ay>cy) || (cy<py && ay<cy)) X(++X(0,y0),y0) = nx;
if (cy!=ay) { cp = np; cx = nx; cy = ny; }
}
}
for (int y = 0; y<(int)dy; ++y) {
tmp.assign(X.ptr(1,y),X(0,y),1,1,1,true).sort();
for (int i = 1; i<=X(0,y); ) {
const int xb = X(i++,y), xe = X(i++,y);
_draw_scanline(xb,xe,nymin+y,color,opacity);
}
}
return *this;
}
//! Draw a filled polygon in the instance image.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImgList<t>& points,
const tc *const color, const float opacity=1) {
if (!points.is_sameY(2))
throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
pixel_type());
return _draw_polygon(points,points.size,color,opacity);
}
//! Draw a filled polygon in the instance image.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImgList<t>& points,
const CImg<tc>& color, const float opacity=1) {
return draw_polygon(points,color.data,opacity);
}
//! Draw a filled polygon in the instance image.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImg<t>& points,
const tc *const color, const float opacity=1) {
if (points.height<2)
throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
pixel_type());
return _draw_polygon(points,points.width,color,opacity);
}
//! Draw a filled polygon in the instance image.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImg<t>& points,
const CImg<tc>& color, const float opacity=1) {
return draw_polygon(points,color.data,opacity);
}
// Inner routine for drawing an outlined polygon with generic point coordinates.
template<typename t, typename tc>
CImg<T>& _draw_polygon(const t& points, const unsigned int W, const unsigned int H,
const tc *const color, const float opacity,
const unsigned int pattern) {
if (is_empty() || !points || W<3) return *this;
bool ninit_hatch = true;
switch (H) {
case 0 : case 1 :
throw CImgArgumentException("CImg<%s>::draw_polygon() : Given list of points is not valid.",
pixel_type());
case 2 : {
const int x0 = (int)points(0,0), y0 = (int)points(0,1);
int ox = x0, oy = y0;
for (unsigned int i = 1; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1);
draw_line(ox,oy,x,y,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = x; oy = y;
}
draw_line(ox,oy,x0,y0,color,opacity,pattern,false);
} break;
default : {
const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
int ox = x0, oy = y0, oz = z0;
for (unsigned int i = 1; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
draw_line(ox,oy,oz,x,y,z,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = x; oy = y; oz = z;
}
draw_line(ox,oy,oz,x0,y0,z0,color,opacity,pattern,false);
}
}
return *this;
}
//! Draw a polygon outline.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImgList<t>& points,
const tc *const color, const float opacity,
const unsigned int pattern) {
unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()));
return _draw_polygon(points,points.size,H,color,opacity,pattern);
}
//! Draw a polygon outline.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImgList<t>& points,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_polygon(points,color.data,opacity,pattern);
}
//! Draw a polygon outline.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImg<t>& points,
const tc *const color, const float opacity,
const unsigned int pattern) {
return _draw_polygon(points,points.width,points.height,color,opacity,pattern);
}
//! Draw a polygon outline.
template<typename t, typename tc>
CImg<T>& draw_polygon(const CImg<t>& points,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_polygon(points,color.data,opacity,pattern);
}
//! Draw a cubic spline curve in the instance image.
/**
\param x0 X-coordinate of the starting curve point
\param y0 Y-coordinate of the starting curve point
\param u0 X-coordinate of the starting velocity
\param v0 Y-coordinate of the starting velocity
\param x1 X-coordinate of the ending curve point
\param y1 Y-coordinate of the ending curve point
\param u1 X-coordinate of the ending velocity
\param v1 Y-coordinate of the ending velocity
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param precision Curve drawing precision (optional).
\param opacity Drawing opacity (optional).
\param pattern An integer whose bits describe the line pattern (optional).
\param init_hatch If \c true, init hatch motif.
\note
- The curve is a 2D cubic Bezier spline, from the set of specified starting/ending points
and corresponding velocity vectors.
- The spline is drawn as a serie of connected segments. The \p precision parameter sets the
average number of pixels in each drawn segment.
- A cubic Bezier curve is sometimes defined by a set of 4 points { (\p x0,\p y0), (\p xa,\p ya), (\p xb,\p yb), (\p x1,\p y1) }
where (\p x0,\p y0) is the starting point, (\p x1,\p y1) is the ending point and (\p xa,\p ya), (\p xb,\p yb) are two
\e control points.
The starting and ending velocities (\p u0,\p v0) and (\p u1,\p v1) can be deduced easily from the control points as
\p u0 = (\p xa - \p x0), \p v0 = (\p ya - \p y0), \p u1 = (\p x1 - \p xb) and \p v1 = (\p y1 - \p yb).
\par Example:
\code
CImg<unsigned char> img(100,100,1,3,0);
const unsigned char color[] = { 255,255,255 };
img.draw_spline(30,30,0,100,90,40,0,-100,color);
\endcode
**/
template<typename tc>
CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
const int x1, const int y1, const float u1, const float v1,
const tc *const color, const float opacity=1,
const float precision=4, const unsigned int pattern=~0U,
const bool init_hatch=true) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",
pixel_type());
bool ninit_hatch = init_hatch;
const float
dx = (float)(x1 - x0),
dy = (float)(y1 - y0),
dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
ax = -2*dx + u0 + u1,
bx = 3*dx - 2*u0 - u1,
ay = -2*dy + v0 + v1,
by = 3*dy - 2*v0 - v1,
xprecision = dmax>0?precision/dmax:1.0f,
tmax = 1 + (dmax>0?xprecision:0.0f);
int ox = x0, oy = y0;
for (float t = 0; t<tmax; t+=xprecision) {
const float
t2 = t*t,
t3 = t2*t;
const int
nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
ny = (int)(ay*t3 + by*t2 + v0*t + y0);
draw_line(ox,oy,nx,ny,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = nx; oy = ny;
}
return *this;
}
//! Draw a cubic spline curve in the instance image.
template<typename tc>
CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
const int x1, const int y1, const float u1, const float v1,
const CImg<tc>& color, const float opacity=1,
const float precision=4, const unsigned int pattern=~0U,
const bool init_hatch=true) {
return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,color.data,opacity,precision,pattern,init_hatch);
}
//! Draw a cubic spline curve in the instance image (for volumetric images).
/**
\note
- Similar to CImg::draw_spline() for a 3D spline in a volumetric image.
**/
template<typename tc>
CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
const tc *const color, const float opacity=1,
const float precision=4, const unsigned int pattern=~0U,
const bool init_hatch=true) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_spline() : Specified color is (null)",
pixel_type());
bool ninit_hatch = init_hatch;
const float
dx = (float)(x1 - x0),
dy = (float)(y1 - y0),
dz = (float)(z1 - z0),
dmax = cimg::max(cimg::abs(dx),cimg::abs(dy),cimg::abs(dz)),
ax = -2*dx + u0 + u1,
bx = 3*dx - 2*u0 - u1,
ay = -2*dy + v0 + v1,
by = 3*dy - 2*v0 - v1,
az = -2*dz + w0 + w1,
bz = 3*dz - 2*w0 - w1,
xprecision = dmax>0?precision/dmax:1.0f,
tmax = 1 + (dmax>0?xprecision:0.0f);
int ox = x0, oy = y0, oz = z0;
for (float t = 0; t<tmax; t+=xprecision) {
const float
t2 = t*t,
t3 = t2*t;
const int
nx = (int)(ax*t3 + bx*t2 + u0*t + x0),
ny = (int)(ay*t3 + by*t2 + v0*t + y0),
nz = (int)(az*t3 + bz*t2 + w0*t + z0);
draw_line(ox,oy,oz,nx,ny,nz,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = nx; oy = ny; oz = nz;
}
return *this;
}
//! Draw a cubic spline curve in the instance image (for volumetric images).
template<typename tc>
CImg<T>& draw_spline(const int x0, const int y0, const int z0, const float u0, const float v0, const float w0,
const int x1, const int y1, const int z1, const float u1, const float v1, const float w1,
const CImg<tc>& color, const float opacity=1,
const float precision=4, const unsigned int pattern=~0U,
const bool init_hatch=true) {
return draw_spline(x0,y0,z0,u0,v0,w0,x1,y1,z1,u1,v1,w1,color.data,opacity,precision,pattern,init_hatch);
}
//! Draw a cubic spline curve in the instance image.
/**
\param x0 X-coordinate of the starting curve point
\param y0 Y-coordinate of the starting curve point
\param u0 X-coordinate of the starting velocity
\param v0 Y-coordinate of the starting velocity
\param x1 X-coordinate of the ending curve point
\param y1 Y-coordinate of the ending curve point
\param u1 X-coordinate of the ending velocity
\param v1 Y-coordinate of the ending velocity
\param texture Texture image defining line pixel colors.
\param tx0 X-coordinate of the starting texture point.
\param ty0 Y-coordinate of the starting texture point.
\param tx1 X-coordinate of the ending texture point.
\param ty1 Y-coordinate of the ending texture point.
\param precision Curve drawing precision (optional).
\param opacity Drawing opacity (optional).
\param pattern An integer whose bits describe the line pattern (optional).
\param init_hatch if \c true, reinit hatch motif.
**/
template<typename t>
CImg<T>& draw_spline(const int x0, const int y0, const float u0, const float v0,
const int x1, const int y1, const float u1, const float v1,
const CImg<t>& texture,
const int tx0, const int ty0, const int tx1, const int ty1,
const float opacity=1,
const float precision=4, const unsigned int pattern=~0U,
const bool init_hatch=true) {
if (is_empty()) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_line() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_spline(x0,y0,u0,v0,x1,y1,u1,v1,+texture,tx0,ty0,tx1,ty1,precision,opacity,pattern,init_hatch);
bool ninit_hatch = true;
const float
dx = (float)(x1 - x0),
dy = (float)(y1 - y0),
dmax = cimg::max(cimg::abs(dx),cimg::abs(dy)),
ax = -2*dx + u0 + u1,
bx = 3*dx - 2*u0 - u1,
ay = -2*dy + v0 + v1,
by = 3*dy - 2*v0 - v1,
xprecision = dmax>0?precision/dmax:1.0f,
tmax = 1 + (dmax>0?xprecision:0.0f);
int ox = x0, oy = y0, otx = tx0, oty = ty0;
for (float t1 = 0; t1<tmax; t1+=xprecision) {
const float
t2 = t1*t1,
t3 = t2*t1;
const int
nx = (int)(ax*t3 + bx*t2 + u0*t1 + x0),
ny = (int)(ay*t3 + by*t2 + v0*t1 + y0),
ntx = tx0 + (int)((tx1-tx0)*t1/tmax),
nty = ty0 + (int)((ty1-ty0)*t1/tmax);
draw_line(ox,oy,nx,ny,texture,otx,oty,ntx,nty,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = nx; oy = ny; otx = ntx; oty = nty;
}
return *this;
}
// Draw a set of connected spline curves in the instance image (internal).
template<typename tp, typename tt, typename tc>
CImg<T>& _draw_spline(const tp& points, const tt& tangents, const unsigned int W, const unsigned int H,
const tc *const color, const float opacity,
const bool close_set, const float precision,
const unsigned int pattern, const bool init_hatch) {
if (is_empty() || !points || !tangents || W<2) return *this;
bool ninit_hatch = init_hatch;
switch (H) {
case 0 : case 1 :
throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.",
pixel_type());
case 2 : {
const int x0 = (int)points(0,0), y0 = (int)points(0,1);
const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1);
int ox = x0, oy = y0;
float ou = u0, ov = v0;
for (unsigned int i = 1; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1);
const float u = (float)tangents(i,0), v = (float)tangents(i,1);
draw_spline(ox,oy,ou,ov,x,y,u,v,color,precision,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = x; oy = y; ou = u; ov = v;
}
if (close_set) draw_spline(ox,oy,ou,ov,x0,y0,u0,v0,color,precision,opacity,pattern,false);
} break;
default : {
const int x0 = (int)points(0,0), y0 = (int)points(0,1), z0 = (int)points(0,2);
const float u0 = (float)tangents(0,0), v0 = (float)tangents(0,1), w0 = (float)tangents(0,2);
int ox = x0, oy = y0, oz = z0;
float ou = u0, ov = v0, ow = w0;
for (unsigned int i = 1; i<W; ++i) {
const int x = (int)points(i,0), y = (int)points(i,1), z = (int)points(i,2);
const float u = (float)tangents(i,0), v = (float)tangents(i,1), w = (float)tangents(i,2);
draw_spline(ox,oy,oz,ou,ov,ow,x,y,z,u,v,w,color,opacity,pattern,ninit_hatch);
ninit_hatch = false;
ox = x; oy = y; oz = z; ou = u; ov = v; ow = w;
}
if (close_set) draw_spline(ox,oy,oz,ou,ov,ow,x0,y0,z0,u0,v0,w0,color,precision,opacity,pattern,false);
}
}
return *this;
}
// Draw a set of connected spline curves in the instance image (internal).
template<typename tp, typename tc>
CImg<T>& _draw_spline(const tp& points, const unsigned int W, const unsigned int H,
const tc *const color, const float opacity,
const bool close_set, const float precision,
const unsigned int pattern, const bool init_hatch) {
if (is_empty() || !points || W<2) return *this;
CImg<Tfloat> tangents;
switch (H) {
case 0 : case 1 :
throw CImgArgumentException("CImg<%s>::draw_spline() : Given list of points or tangents is not valid.",
pixel_type());
case 2 : {
tangents.assign(W,H);
for (unsigned int p = 0; p<W; ++p) {
const unsigned int
p0 = close_set?(p+W-1)%W:(p?p-1:0),
p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
const float
x = (float)points(p,0),
y = (float)points(p,1),
x0 = (float)points(p0,0),
y0 = (float)points(p0,1),
x1 = (float)points(p1,0),
y1 = (float)points(p1,1),
u0 = x - x0,
v0 = y - y0,
n0 = 1e-8f + (float)cimg_std::sqrt(u0*u0 + v0*v0),
u1 = x1 - x,
v1 = y1 - y,
n1 = 1e-8f + (float)cimg_std::sqrt(u1*u1 + v1*v1),
u = u0/n0 + u1/n1,
v = v0/n0 + v1/n1,
n = 1e-8f + (float)cimg_std::sqrt(u*u + v*v),
fact = 0.5f*(n0 + n1);
tangents(p,0) = (Tfloat)(fact*u/n);
tangents(p,1) = (Tfloat)(fact*v/n);
}
} break;
default : {
tangents.assign(W,H);
for (unsigned int p = 0; p<W; ++p) {
const unsigned int
p0 = close_set?(p+W-1)%W:(p?p-1:0),
p1 = close_set?(p+1)%W:(p+1<W?p+1:p);
const float
x = (float)points(p,0),
y = (float)points(p,1),
z = (float)points(p,2),
x0 = (float)points(p0,0),
y0 = (float)points(p0,1),
z0 = (float)points(p0,2),
x1 = (float)points(p1,0),
y1 = (float)points(p1,1),
z1 = (float)points(p1,2),
u0 = x - x0,
v0 = y - y0,
w0 = z - z0,
n0 = 1e-8f + (float)cimg_std::sqrt(u0*u0 + v0*v0 + w0*w0),
u1 = x1 - x,
v1 = y1 - y,
w1 = z1 - z,
n1 = 1e-8f + (float)cimg_std::sqrt(u1*u1 + v1*v1 + w1*w1),
u = u0/n0 + u1/n1,
v = v0/n0 + v1/n1,
w = w0/n0 + w1/n1,
n = 1e-8f + (float)cimg_std::sqrt(u*u + v*v + w*w),
fact = 0.5f*(n0 + n1);
tangents(p,0) = (Tfloat)(fact*u/n);
tangents(p,1) = (Tfloat)(fact*v/n);
tangents(p,2) = (Tfloat)(fact*w/n);
}
}
}
return _draw_spline(points,tangents,W,H,color,opacity,close_set,precision,pattern,init_hatch);
}
//! Draw a set of consecutive colored splines in the instance image.
template<typename tp, typename tt, typename tc>
CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
const tc *const color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
unsigned int H = ~0U; cimglist_for(points,p) H = cimg::min(H,(unsigned int)(points[p].size()),(unsigned int)(tangents[p].size()));
return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.size,H);
}
//! Draw a set of consecutive colored splines in the instance image.
template<typename tp, typename tt, typename tc>
CImg<T>& draw_spline(const CImgList<tp>& points, const CImgList<tt>& tangents,
const CImg<tc>& color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch);
}
//! Draw a set of consecutive colored splines in the instance image.
template<typename tp, typename tt, typename tc>
CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
const tc *const color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return _draw_spline(points,tangents,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height);
}
//! Draw a set of consecutive colored splines in the instance image.
template<typename tp, typename tt, typename tc>
CImg<T>& draw_spline(const CImg<tp>& points, const CImg<tt>& tangents,
const CImg<tc>& color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_spline(points,tangents,color.data,opacity,close_set,precision,pattern,init_hatch);
}
//! Draw a set of consecutive colored splines in the instance image.
template<typename t, typename tc>
CImg<T>& draw_spline(const CImgList<t>& points,
const tc *const color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
unsigned int H = ~0U;
cimglist_for(points,p) { const unsigned int s = points[p].size(); if (s<H) H = s; }
return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.size,H);
}
//! Draw a set of consecutive colored splines in the instance image.
template<typename t, typename tc>
CImg<T>& draw_spline(const CImgList<t>& points,
CImg<tc>& color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch);
}
//! Draw a set of consecutive colored lines in the instance image.
template<typename t, typename tc>
CImg<T>& draw_spline(const CImg<t>& points,
const tc *const color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return _draw_spline(points,color,opacity,close_set,precision,pattern,init_hatch,points.width,points.height);
}
//! Draw a set of consecutive colored lines in the instance image.
template<typename t, typename tc>
CImg<T>& draw_spline(const CImg<t>& points,
const CImg<tc>& color, const float opacity=1,
const bool close_set=false, const float precision=4,
const unsigned int pattern=~0U, const bool init_hatch=true) {
return draw_spline(points,color.data,opacity,close_set,precision,pattern,init_hatch);
}
//! Draw a colored arrow in the instance image.
/**
\param x0 X-coordinate of the starting arrow point (tail).
\param y0 Y-coordinate of the starting arrow point (tail).
\param x1 X-coordinate of the ending arrow point (head).
\param y1 Y-coordinate of the ending arrow point (head).
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param angle Aperture angle of the arrow head (optional).
\param length Length of the arrow head. If negative, describes a percentage of the arrow length (optional).
\param opacity Drawing opacity (optional).
\param pattern An integer whose bits describe the line pattern (optional).
\note
- Clipping is supported.
**/
template<typename tc>
CImg<T>& draw_arrow(const int x0, const int y0,
const int x1, const int y1,
const tc *const color, const float opacity=1,
const float angle=30, const float length=-10,
const unsigned int pattern=~0U) {
if (is_empty()) return *this;
const float u = (float)(x0 - x1), v = (float)(y0 - y1), sq = u*u + v*v,
deg = (float)(angle*cimg::valuePI/180), ang = (sq>0)?(float)cimg_std::atan2(v,u):0.0f,
l = (length>=0)?length:-length*(float)cimg_std::sqrt(sq)/100;
if (sq>0) {
const float
cl = (float)cimg_std::cos(ang - deg), sl = (float)cimg_std::sin(ang - deg),
cr = (float)cimg_std::cos(ang + deg), sr = (float)cimg_std::sin(ang + deg);
const int
xl = x1 + (int)(l*cl), yl = y1 + (int)(l*sl),
xr = x1 + (int)(l*cr), yr = y1 + (int)(l*sr),
xc = x1 + (int)((l+1)*(cl+cr))/2, yc = y1 + (int)((l+1)*(sl+sr))/2;
draw_line(x0,y0,xc,yc,color,opacity,pattern).draw_triangle(x1,y1,xl,yl,xr,yr,color,opacity);
} else draw_point(x0,y0,color,opacity);
return *this;
}
//! Draw a colored arrow in the instance image.
template<typename tc>
CImg<T>& draw_arrow(const int x0, const int y0,
const int x1, const int y1,
const CImg<tc>& color, const float opacity=1,
const float angle=30, const float length=-10,
const unsigned int pattern=~0U) {
return draw_arrow(x0,y0,x1,y1,color.data,opacity,angle,length,pattern);
}
//! Draw an image.
/**
\param sprite Sprite image.
\param x0 X-coordinate of the sprite position.
\param y0 Y-coordinate of the sprite position.
\param z0 Z-coordinate of the sprite position.
\param v0 V-coordinate of the sprite position.
\param opacity Drawing opacity (optional).
\note
- Clipping is supported.
**/
template<typename t>
CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
const CImg<t>& sprite, const float opacity=1) {
if (is_empty()) return *this;
if (!sprite)
throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity);
const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
const int
lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
const t
*ptrs = sprite.data -
(bx?x0:0) -
(by?y0*sprite.dimx():0) -
(bz?z0*sprite.dimx()*sprite.dimy():0) -
(bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
const unsigned int
offX = width - lX, soffX = sprite.width - lX,
offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY),
offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ);
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (lX>0 && lY>0 && lZ>0 && lV>0) {
T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
for (int v = 0; v<lV; ++v) {
for (int z = 0; z<lZ; ++z) {
for (int y = 0; y<lY; ++y) {
if (opacity>=1) for (int x = 0; x<lX; ++x) *(ptrd++) = (T)*(ptrs++);
else for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
ptrd+=offX; ptrs+=soffX;
}
ptrd+=offY; ptrs+=soffY;
}
ptrd+=offZ; ptrs+=soffZ;
}
}
return *this;
}
#ifndef cimg_use_visualcpp6
- // Otimized version (internal).
+ // Optimized version (internal).
CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
const CImg<T>& sprite, const float opacity=1) {
if (is_empty()) return *this;
if (!sprite)
throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,opacity);
const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
const int
lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
const T
*ptrs = sprite.data -
(bx?x0:0) -
(by?y0*sprite.dimx():0) -
(bz?z0*sprite.dimx()*sprite.dimy():0) -
(bv?v0*sprite.dimx()*sprite.dimy()*sprite.dimz():0);
const unsigned int
offX = width - lX, soffX = sprite.width - lX,
offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY),
offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ),
slX = lX*sizeof(T);
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
if (lX>0 && lY>0 && lZ>0 && lV>0) {
T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
for (int v = 0; v<lV; ++v) {
for (int z = 0; z<lZ; ++z) {
if (opacity>=1) for (int y = 0; y<lY; ++y) { cimg_std::memcpy(ptrd,ptrs,slX); ptrd+=width; ptrs+=sprite.width; }
else for (int y = 0; y<lY; ++y) {
for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*(*(ptrs++)) + *ptrd*copacity); ++ptrd; }
ptrd+=offX; ptrs+=soffX;
}
ptrd+=offY; ptrs+=soffY;
}
ptrd+=offZ; ptrs+=soffZ;
}
}
return *this;
}
#endif
//! Draw an image.
template<typename t>
CImg<T>& draw_image(const int x0, const int y0, const int z0,
const CImg<t>& sprite, const float opacity=1) {
return draw_image(x0,y0,z0,0,sprite,opacity);
}
//! Draw an image.
template<typename t>
CImg<T>& draw_image(const int x0, const int y0,
const CImg<t>& sprite, const float opacity=1) {
return draw_image(x0,y0,0,sprite,opacity);
}
//! Draw an image.
template<typename t>
CImg<T>& draw_image(const int x0,
const CImg<t>& sprite, const float opacity=1) {
return draw_image(x0,0,sprite,opacity);
}
//! Draw an image.
template<typename t>
CImg<T>& draw_image(const CImg<t>& sprite, const float opacity=1) {
return draw_image(0,sprite,opacity);
}
//! Draw a sprite image in the instance image (masked version).
/**
\param sprite Sprite image.
\param mask Mask image.
\param x0 X-coordinate of the sprite position in the instance image.
\param y0 Y-coordinate of the sprite position in the instance image.
\param z0 Z-coordinate of the sprite position in the instance image.
\param v0 V-coordinate of the sprite position in the instance image.
\param mask_valmax Maximum pixel value of the mask image \c mask (optional).
\param opacity Drawing opacity.
\note
- Pixel values of \c mask set the opacity of the corresponding pixels in \c sprite.
- Clipping is supported.
- Dimensions along x,y and z of \p sprite and \p mask must be the same.
**/
template<typename ti, typename tm>
CImg<T>& draw_image(const int x0, const int y0, const int z0, const int v0,
const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
const float mask_valmax=1) {
if (is_empty()) return *this;
if (!sprite)
throw CImgArgumentException("CImg<%s>::draw_image() : Specified sprite image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),sprite.width,sprite.height,sprite.depth,sprite.dim,sprite.data);
if (!mask)
throw CImgArgumentException("CImg<%s>::draw_image() : Specified mask image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
if (is_overlapped(sprite)) return draw_image(x0,y0,z0,v0,+sprite,mask,opacity,mask_valmax);
if (is_overlapped(mask)) return draw_image(x0,y0,z0,v0,sprite,+mask,opacity,mask_valmax);
if (mask.width!=sprite.width || mask.height!=sprite.height || mask.depth!=sprite.depth)
throw CImgArgumentException("CImg<%s>::draw_image() : Mask dimension is (%u,%u,%u,%u), while sprite is (%u,%u,%u,%u)",
pixel_type(),mask.width,mask.height,mask.depth,mask.dim,sprite.width,sprite.height,sprite.depth,sprite.dim);
const bool bx = (x0<0), by = (y0<0), bz = (z0<0), bv = (v0<0);
const int
lX = sprite.dimx() - (x0 + sprite.dimx()>dimx()?x0 + sprite.dimx() - dimx():0) + (bx?x0:0),
lY = sprite.dimy() - (y0 + sprite.dimy()>dimy()?y0 + sprite.dimy() - dimy():0) + (by?y0:0),
lZ = sprite.dimz() - (z0 + sprite.dimz()>dimz()?z0 + sprite.dimz() - dimz():0) + (bz?z0:0),
lV = sprite.dimv() - (v0 + sprite.dimv()>dimv()?v0 + sprite.dimv() - dimv():0) + (bv?v0:0);
const int
coff = -(bx?x0:0)-(by?y0*mask.dimx():0)-(bz?z0*mask.dimx()*mask.dimy():0)-(bv?v0*mask.dimx()*mask.dimy()*mask.dimz():0),
ssize = mask.dimx()*mask.dimy()*mask.dimz();
const ti *ptrs = sprite.data + coff;
const tm *ptrm = mask.data + coff;
const unsigned int
offX = width - lX, soffX = sprite.width - lX,
offY = width*(height - lY), soffY = sprite.width*(sprite.height - lY),
offZ = width*height*(depth - lZ), soffZ = sprite.width*sprite.height*(sprite.depth - lZ);
if (lX>0 && lY>0 && lZ>0 && lV>0) {
T *ptrd = ptr(x0<0?0:x0,y0<0?0:y0,z0<0?0:z0,v0<0?0:v0);
for (int v = 0; v<lV; ++v) {
ptrm = mask.data + (ptrm - mask.data)%ssize;
for (int z = 0; z<lZ; ++z) {
for (int y = 0; y<lY; ++y) {
for (int x=0; x<lX; ++x) {
const float mopacity = (float)(*(ptrm++)*opacity),
nopacity = cimg::abs(mopacity), copacity = mask_valmax - cimg::max(mopacity,0);
*ptrd = (T)((nopacity*(*(ptrs++)) + *ptrd*copacity)/mask_valmax);
++ptrd;
}
ptrd+=offX; ptrs+=soffX; ptrm+=soffX;
}
ptrd+=offY; ptrs+=soffY; ptrm+=soffY;
}
ptrd+=offZ; ptrs+=soffZ; ptrm+=soffZ;
}
}
return *this;
}
//! Draw an image.
template<typename ti, typename tm>
CImg<T>& draw_image(const int x0, const int y0, const int z0,
const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
const float mask_valmax=1) {
return draw_image(x0,y0,z0,0,sprite,mask,opacity,mask_valmax);
}
//! Draw an image.
template<typename ti, typename tm>
CImg<T>& draw_image(const int x0, const int y0,
const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
const float mask_valmax=1) {
return draw_image(x0,y0,0,sprite,mask,opacity,mask_valmax);
}
//! Draw an image.
template<typename ti, typename tm>
CImg<T>& draw_image(const int x0,
const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
const float mask_valmax=1) {
return draw_image(x0,0,sprite,mask,opacity,mask_valmax);
}
//! Draw an image.
template<typename ti, typename tm>
CImg<T>& draw_image(const CImg<ti>& sprite, const CImg<tm>& mask, const float opacity=1,
const float mask_valmax=1) {
return draw_image(0,sprite,mask,opacity,mask_valmax);
}
//! Draw a 4D filled rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0,\c v0)-(\c x1,\c y1,\c z1,\c v1).
/**
\param x0 X-coordinate of the upper-left rectangle corner.
\param y0 Y-coordinate of the upper-left rectangle corner.
\param z0 Z-coordinate of the upper-left rectangle corner.
\param v0 V-coordinate of the upper-left rectangle corner.
\param x1 X-coordinate of the lower-right rectangle corner.
\param y1 Y-coordinate of the lower-right rectangle corner.
\param z1 Z-coordinate of the lower-right rectangle corner.
\param v1 V-coordinate of the lower-right rectangle corner.
\param val Scalar value used to fill the rectangle area.
\param opacity Drawing opacity (optional).
\note
- Clipping is supported.
**/
CImg<T>& draw_rectangle(const int x0, const int y0, const int z0, const int v0,
const int x1, const int y1, const int z1, const int v1,
const T val, const float opacity=1) {
if (is_empty()) return *this;
const bool bx = (x0<x1), by = (y0<y1), bz = (z0<z1), bv = (v0<v1);
const int
nx0 = bx?x0:x1, nx1 = bx?x1:x0,
ny0 = by?y0:y1, ny1 = by?y1:y0,
nz0 = bz?z0:z1, nz1 = bz?z1:z0,
nv0 = bv?v0:v1, nv1 = bv?v1:v0;
const int
lX = (1 + nx1 - nx0) + (nx1>=dimx()?dimx() - 1 - nx1:0) + (nx0<0?nx0:0),
lY = (1 + ny1 - ny0) + (ny1>=dimy()?dimy() - 1 - ny1:0) + (ny0<0?ny0:0),
lZ = (1 + nz1 - nz0) + (nz1>=dimz()?dimz() - 1 - nz1:0) + (nz0<0?nz0:0),
lV = (1 + nv1 - nv0) + (nv1>=dimv()?dimv() - 1 - nv1:0) + (nv0<0?nv0:0);
const unsigned int offX = width - lX, offY = width*(height - lY), offZ = width*height*(depth - lZ);
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
T *ptrd = ptr(nx0<0?0:nx0,ny0<0?0:ny0,nz0<0?0:nz0,nv0<0?0:nv0);
if (lX>0 && lY>0 && lZ>0 && lV>0)
for (int v = 0; v<lV; ++v) {
for (int z = 0; z<lZ; ++z) {
for (int y = 0; y<lY; ++y) {
if (opacity>=1) {
if (sizeof(T)!=1) { for (int x = 0; x<lX; ++x) *(ptrd++) = val; ptrd+=offX; }
else { cimg_std::memset(ptrd,(int)val,lX); ptrd+=width; }
} else { for (int x = 0; x<lX; ++x) { *ptrd = (T)(nopacity*val + *ptrd*copacity); ++ptrd; } ptrd+=offX; }
}
ptrd+=offY;
}
ptrd+=offZ;
}
return *this;
}
//! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
/**
\param x0 X-coordinate of the upper-left rectangle corner.
\param y0 Y-coordinate of the upper-left rectangle corner.
\param z0 Z-coordinate of the upper-left rectangle corner.
\param x1 X-coordinate of the lower-right rectangle corner.
\param y1 Y-coordinate of the lower-right rectangle corner.
\param z1 Z-coordinate of the lower-right rectangle corner.
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param opacity Drawing opacity (optional).
\note
- Clipping is supported.
**/
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const tc *const color, const float opacity=1) {
if (!color)
throw CImgArgumentException("CImg<%s>::draw_rectangle : specified color is (null)",
pixel_type());
cimg_forV(*this,k) draw_rectangle(x0,y0,z0,k,x1,y1,z1,k,color[k],opacity);
return *this;
}
//! Draw a 3D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0,\c z0)-(\c x1,\c y1,\c z1).
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const CImg<tc>& color, const float opacity=1) {
return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity);
}
//! Draw a 3D outlined colored rectangle in the instance image.
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const tc *const color, const float opacity,
const unsigned int pattern) {
return draw_line(x0,y0,z0,x1,y0,z0,color,opacity,pattern,true).
draw_line(x1,y0,z0,x1,y1,z0,color,opacity,pattern,false).
draw_line(x1,y1,z0,x0,y1,z0,color,opacity,pattern,false).
draw_line(x0,y1,z0,x0,y0,z0,color,opacity,pattern,false).
draw_line(x0,y0,z1,x1,y0,z1,color,opacity,pattern,true).
draw_line(x1,y0,z1,x1,y1,z1,color,opacity,pattern,false).
draw_line(x1,y1,z1,x0,y1,z1,color,opacity,pattern,false).
draw_line(x0,y1,z1,x0,y0,z1,color,opacity,pattern,false).
draw_line(x0,y0,z0,x0,y0,z1,color,opacity,pattern,true).
draw_line(x1,y0,z0,x1,y0,z1,color,opacity,pattern,true).
draw_line(x1,y1,z0,x1,y1,z1,color,opacity,pattern,true).
draw_line(x0,y1,z0,x0,y1,z1,color,opacity,pattern,true);
}
//! Draw a 3D outlined colored rectangle in the instance image.
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_rectangle(x0,y0,z0,x1,y1,z1,color.data,opacity,pattern);
}
//! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
/**
\param x0 X-coordinate of the upper-left rectangle corner.
\param y0 Y-coordinate of the upper-left rectangle corner.
\param x1 X-coordinate of the lower-right rectangle corner.
\param y1 Y-coordinate of the lower-right rectangle corner.
\param color Pointer to \c dimv() consecutive values of type \c T, defining the drawing color.
\param opacity Drawing opacity (optional).
\note
- Clipping is supported.
**/
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0,
const int x1, const int y1,
const tc *const color, const float opacity=1) {
return draw_rectangle(x0,y0,0,x1,y1,depth-1,color,opacity);
}
//! Draw a 2D filled colored rectangle in the instance image, at coordinates (\c x0,\c y0)-(\c x1,\c y1).
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0,
const int x1, const int y1,
const CImg<tc>& color, const float opacity=1) {
return draw_rectangle(x0,y0,x1,y1,color.data,opacity);
}
//! Draw a 2D outlined colored rectangle.
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0,
const int x1, const int y1,
const tc *const color, const float opacity,
const unsigned int pattern) {
if (is_empty()) return *this;
if (y0==y1) return draw_line(x0,y0,x1,y0,color,opacity,pattern,true);
if (x0==x1) return draw_line(x0,y0,x0,y1,color,opacity,pattern,true);
const bool bx = (x0<x1), by = (y0<y1);
const int
nx0 = bx?x0:x1, nx1 = bx?x1:x0,
ny0 = by?y0:y1, ny1 = by?y1:y0;
if (ny1==ny0+1) return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false);
return draw_line(nx0,ny0,nx1,ny0,color,opacity,pattern,true).
draw_line(nx1,ny0+1,nx1,ny1-1,color,opacity,pattern,false).
draw_line(nx1,ny1,nx0,ny1,color,opacity,pattern,false).
draw_line(nx0,ny1-1,nx0,ny0+1,color,opacity,pattern,false);
}
//! Draw a 2D outlined colored rectangle.
template<typename tc>
CImg<T>& draw_rectangle(const int x0, const int y0,
const int x1, const int y1,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_rectangle(x0,y0,x1,y1,color.data,opacity,pattern);
}
// Inner macro for drawing triangles.
#define _cimg_for_triangle1(img,xl,xr,y,x0,y0,x1,y1,x2,y2) \
for (int y = y0<0?0:y0, \
xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
_sxn=1, \
_sxr=1, \
_sxl=1, \
_dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
_dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
_dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
_dyn = y2-y1, \
_dyr = y2-y0, \
_dyl = y1-y0, \
_counter = (_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
_dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
_dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
cimg::min((int)(img).height-y-1,y2-y)), \
_errn = _dyn/2, \
_errr = _dyr/2, \
_errl = _dyl/2, \
_rxn = _dyn?(x2-x1)/_dyn:0, \
_rxr = _dyr?(x2-x0)/_dyr:0, \
_rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
(_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn); \
_counter>=0; --_counter, ++y, \
xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
xl+=(y!=y1)?_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0): \
(_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
#define _cimg_for_triangle2(img,xl,cl,xr,cr,y,x0,y0,c0,x1,y1,c1,x2,y2,c2) \
for (int y = y0<0?0:y0, \
xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
_sxn=1, _scn=1, \
_sxr=1, _scr=1, \
_sxl=1, _scl=1, \
_dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
_dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
_dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
_dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
_dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
_dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
_dyn = y2-y1, \
_dyr = y2-y0, \
_dyl = y1-y0, \
_counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
_dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
_dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
_dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
_dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
_dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
cimg::min((int)(img).height-y-1,y2-y)), \
_errn = _dyn/2, _errcn = _errn, \
_errr = _dyr/2, _errcr = _errr, \
_errl = _dyl/2, _errcl = _errl, \
_rxn = _dyn?(x2-x1)/_dyn:0, \
_rcn = _dyn?(c2-c1)/_dyn:0, \
_rxr = _dyr?(x2-x0)/_dyr:0, \
_rcr = _dyr?(c2-c0)/_dyr:0, \
_rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
(_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
_rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
(_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ); \
_counter>=0; --_counter, ++y, \
xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
(_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
#define _cimg_for_triangle3(img,xl,txl,tyl,xr,txr,tyr,y,x0,y0,tx0,ty0,x1,y1,tx1,ty1,x2,y2,tx2,ty2) \
for (int y = y0<0?0:y0, \
xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
_sxn=1, _stxn=1, _styn=1, \
_sxr=1, _stxr=1, _styr=1, \
_sxl=1, _stxl=1, _styl=1, \
_dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
_dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
_dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
_dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
_dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
_dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
_dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
_dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
_dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
_dyn = y2-y1, \
_dyr = y2-y0, \
_dyl = y1-y0, \
_counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
_dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
_dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
_dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
_dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
_dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
_dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
_dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
_dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
cimg::min((int)(img).height-y-1,y2-y)), \
_errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, \
_errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, \
_errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, \
_rxn = _dyn?(x2-x1)/_dyn:0, \
_rtxn = _dyn?(tx2-tx1)/_dyn:0, \
_rtyn = _dyn?(ty2-ty1)/_dyn:0, \
_rxr = _dyr?(x2-x0)/_dyr:0, \
_rtxr = _dyr?(tx2-tx0)/_dyr:0, \
_rtyr = _dyr?(ty2-ty0)/_dyr:0, \
_rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
(_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
_rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
(_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
_rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
(_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
_counter>=0; --_counter, ++y, \
xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
(_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1,\
_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
#define _cimg_for_triangle4(img,xl,cl,txl,tyl,xr,cr,txr,tyr,y,x0,y0,c0,tx0,ty0,x1,y1,c1,tx1,ty1,x2,y2,c2,tx2,ty2) \
for (int y = y0<0?0:y0, \
xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
cr = y0>=0?c0:(c0-y0*(c2-c0)/(y2-y0)), \
txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
cl = y1>=0?(y0>=0?(y0==y1?c1:c0):(c0-y0*(c1-c0)/(y1-y0))):(c1-y1*(c2-c1)/(y2-y1)), \
txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
_sxn=1, _scn=1, _stxn=1, _styn=1, \
_sxr=1, _scr=1, _stxr=1, _styr=1, \
_sxl=1, _scl=1, _stxl=1, _styl=1, \
_dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), \
_dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), \
_dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), \
_dcn = c2>c1?c2-c1:(_scn=-1,c1-c2), \
_dcr = c2>c0?c2-c0:(_scr=-1,c0-c2), \
_dcl = c1>c0?c1-c0:(_scl=-1,c0-c1), \
_dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
_dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
_dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
_dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
_dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
_dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
_dyn = y2-y1, \
_dyr = y2-y0, \
_dyl = y1-y0, \
_counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
_dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
_dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
_dcn-=_dyn?_dyn*(_dcn/_dyn):0, \
_dcr-=_dyr?_dyr*(_dcr/_dyr):0, \
_dcl-=_dyl?_dyl*(_dcl/_dyl):0, \
_dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
_dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
_dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
_dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
_dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
_dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
cimg::min((int)(img).height-y-1,y2-y)), \
_errn = _dyn/2, _errcn = _errn, _errtxn = _errn, _errtyn = _errn, \
_errr = _dyr/2, _errcr = _errr, _errtxr = _errr, _errtyr = _errr, \
_errl = _dyl/2, _errcl = _errl, _errtxl = _errl, _errtyl = _errl, \
_rxn = _dyn?(x2-x1)/_dyn:0, \
_rcn = _dyn?(c2-c1)/_dyn:0, \
_rtxn = _dyn?(tx2-tx1)/_dyn:0, \
_rtyn = _dyn?(ty2-ty1)/_dyn:0, \
_rxr = _dyr?(x2-x0)/_dyr:0, \
_rcr = _dyr?(c2-c0)/_dyr:0, \
_rtxr = _dyr?(tx2-tx0)/_dyr:0, \
_rtyr = _dyr?(ty2-ty0)/_dyr:0, \
_rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
(_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
_rcl = (y0!=y1 && y1>0)?(_dyl?(c1-c0)/_dyl:0): \
(_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcn ), \
_rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
(_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
_rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
(_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ); \
_counter>=0; --_counter, ++y, \
xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
cr+=_rcr+((_errcr-=_dcr)<0?_errcr+=_dyr,_scr:0), \
txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
xl+=(y!=y1)?(cl+=_rcl+((_errcl-=_dcl)<0?(_errcl+=_dyl,_scl):0), \
txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
(_errcl=_errcn, _dcl=_dcn, _dyl=_dyn, _scl=_scn, _rcl=_rcn, cl=c1, \
_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
#define _cimg_for_triangle5(img,xl,txl,tyl,lxl,lyl,xr,txr,tyr,lxr,lyr,y,x0,y0,tx0,ty0,lx0,ly0,x1,y1,tx1,ty1,lx1,ly1,x2,y2,tx2,ty2,lx2,ly2) \
for (int y = y0<0?0:y0, \
xr = y0>=0?x0:(x0-y0*(x2-x0)/(y2-y0)), \
txr = y0>=0?tx0:(tx0-y0*(tx2-tx0)/(y2-y0)), \
tyr = y0>=0?ty0:(ty0-y0*(ty2-ty0)/(y2-y0)), \
lxr = y0>=0?lx0:(lx0-y0*(lx2-lx0)/(y2-y0)), \
lyr = y0>=0?ly0:(ly0-y0*(ly2-ly0)/(y2-y0)), \
xl = y1>=0?(y0>=0?(y0==y1?x1:x0):(x0-y0*(x1-x0)/(y1-y0))):(x1-y1*(x2-x1)/(y2-y1)), \
txl = y1>=0?(y0>=0?(y0==y1?tx1:tx0):(tx0-y0*(tx1-tx0)/(y1-y0))):(tx1-y1*(tx2-tx1)/(y2-y1)), \
tyl = y1>=0?(y0>=0?(y0==y1?ty1:ty0):(ty0-y0*(ty1-ty0)/(y1-y0))):(ty1-y1*(ty2-ty1)/(y2-y1)), \
lxl = y1>=0?(y0>=0?(y0==y1?lx1:lx0):(lx0-y0*(lx1-lx0)/(y1-y0))):(lx1-y1*(lx2-lx1)/(y2-y1)), \
lyl = y1>=0?(y0>=0?(y0==y1?ly1:ly0):(ly0-y0*(ly1-ly0)/(y1-y0))):(ly1-y1*(ly2-ly1)/(y2-y1)), \
_sxn=1, _stxn=1, _styn=1, _slxn=1, _slyn=1, \
_sxr=1, _stxr=1, _styr=1, _slxr=1, _slyr=1, \
_sxl=1, _stxl=1, _styl=1, _slxl=1, _slyl=1, \
_dxn = x2>x1?x2-x1:(_sxn=-1,x1-x2), _dyn = y2-y1, \
_dxr = x2>x0?x2-x0:(_sxr=-1,x0-x2), _dyr = y2-y0, \
_dxl = x1>x0?x1-x0:(_sxl=-1,x0-x1), _dyl = y1-y0, \
_dtxn = tx2>tx1?tx2-tx1:(_stxn=-1,tx1-tx2), \
_dtxr = tx2>tx0?tx2-tx0:(_stxr=-1,tx0-tx2), \
_dtxl = tx1>tx0?tx1-tx0:(_stxl=-1,tx0-tx1), \
_dtyn = ty2>ty1?ty2-ty1:(_styn=-1,ty1-ty2), \
_dtyr = ty2>ty0?ty2-ty0:(_styr=-1,ty0-ty2), \
_dtyl = ty1>ty0?ty1-ty0:(_styl=-1,ty0-ty1), \
_dlxn = lx2>lx1?lx2-lx1:(_slxn=-1,lx1-lx2), \
_dlxr = lx2>lx0?lx2-lx0:(_slxr=-1,lx0-lx2), \
_dlxl = lx1>lx0?lx1-lx0:(_slxl=-1,lx0-lx1), \
_dlyn = ly2>ly1?ly2-ly1:(_slyn=-1,ly1-ly2), \
_dlyr = ly2>ly0?ly2-ly0:(_slyr=-1,ly0-ly2), \
_dlyl = ly1>ly0?ly1-ly0:(_slyl=-1,ly0-ly1), \
_counter =(_dxn-=_dyn?_dyn*(_dxn/_dyn):0, \
_dxr-=_dyr?_dyr*(_dxr/_dyr):0, \
_dxl-=_dyl?_dyl*(_dxl/_dyl):0, \
_dtxn-=_dyn?_dyn*(_dtxn/_dyn):0, \
_dtxr-=_dyr?_dyr*(_dtxr/_dyr):0, \
_dtxl-=_dyl?_dyl*(_dtxl/_dyl):0, \
_dtyn-=_dyn?_dyn*(_dtyn/_dyn):0, \
_dtyr-=_dyr?_dyr*(_dtyr/_dyr):0, \
_dtyl-=_dyl?_dyl*(_dtyl/_dyl):0, \
_dlxn-=_dyn?_dyn*(_dlxn/_dyn):0, \
_dlxr-=_dyr?_dyr*(_dlxr/_dyr):0, \
_dlxl-=_dyl?_dyl*(_dlxl/_dyl):0, \
_dlyn-=_dyn?_dyn*(_dlyn/_dyn):0, \
_dlyr-=_dyr?_dyr*(_dlyr/_dyr):0, \
_dlyl-=_dyl?_dyl*(_dlyl/_dyl):0, \
cimg::min((int)(img).height-y-1,y2-y)), \
_errn = _dyn/2, _errtxn = _errn, _errtyn = _errn, _errlxn = _errn, _errlyn = _errn, \
_errr = _dyr/2, _errtxr = _errr, _errtyr = _errr, _errlxr = _errr, _errlyr = _errr, \
_errl = _dyl/2, _errtxl = _errl, _errtyl = _errl, _errlxl = _errl, _errlyl = _errl, \
_rxn = _dyn?(x2-x1)/_dyn:0, \
_rtxn = _dyn?(tx2-tx1)/_dyn:0, \
_rtyn = _dyn?(ty2-ty1)/_dyn:0, \
_rlxn = _dyn?(lx2-lx1)/_dyn:0, \
_rlyn = _dyn?(ly2-ly1)/_dyn:0, \
_rxr = _dyr?(x2-x0)/_dyr:0, \
_rtxr = _dyr?(tx2-tx0)/_dyr:0, \
_rtyr = _dyr?(ty2-ty0)/_dyr:0, \
_rlxr = _dyr?(lx2-lx0)/_dyr:0, \
_rlyr = _dyr?(ly2-ly0)/_dyr:0, \
_rxl = (y0!=y1 && y1>0)?(_dyl?(x1-x0)/_dyl:0): \
(_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxn), \
_rtxl = (y0!=y1 && y1>0)?(_dyl?(tx1-tx0)/_dyl:0): \
(_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxn ), \
_rtyl = (y0!=y1 && y1>0)?(_dyl?(ty1-ty0)/_dyl:0): \
(_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyn ), \
_rlxl = (y0!=y1 && y1>0)?(_dyl?(lx1-lx0)/_dyl:0): \
(_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxn ), \
_rlyl = (y0!=y1 && y1>0)?(_dyl?(ly1-ly0)/_dyl:0): \
(_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyn ); \
_counter>=0; --_counter, ++y, \
xr+=_rxr+((_errr-=_dxr)<0?_errr+=_dyr,_sxr:0), \
txr+=_rtxr+((_errtxr-=_dtxr)<0?_errtxr+=_dyr,_stxr:0), \
tyr+=_rtyr+((_errtyr-=_dtyr)<0?_errtyr+=_dyr,_styr:0), \
lxr+=_rlxr+((_errlxr-=_dlxr)<0?_errlxr+=_dyr,_slxr:0), \
lyr+=_rlyr+((_errlyr-=_dlyr)<0?_errlyr+=_dyr,_slyr:0), \
xl+=(y!=y1)?(txl+=_rtxl+((_errtxl-=_dtxl)<0?(_errtxl+=_dyl,_stxl):0), \
tyl+=_rtyl+((_errtyl-=_dtyl)<0?(_errtyl+=_dyl,_styl):0), \
lxl+=_rlxl+((_errlxl-=_dlxl)<0?(_errlxl+=_dyl,_slxl):0), \
lyl+=_rlyl+((_errlyl-=_dlyl)<0?(_errlyl+=_dyl,_slyl):0), \
_rxl+((_errl-=_dxl)<0?(_errl+=_dyl,_sxl):0)): \
(_errtxl=_errtxn, _dtxl=_dtxn, _dyl=_dyn, _stxl=_stxn, _rtxl=_rtxn, txl=tx1, \
_errtyl=_errtyn, _dtyl=_dtyn, _dyl=_dyn, _styl=_styn, _rtyl=_rtyn, tyl=ty1, \
_errlxl=_errlxn, _dlxl=_dlxn, _dyl=_dyn, _slxl=_slxn, _rlxl=_rlxn, lxl=lx1, \
_errlyl=_errlyn, _dlyl=_dlyn, _dyl=_dyn, _slyl=_slyn, _rlyl=_rlyn, lyl=ly1, \
_errl=_errn, _dxl=_dxn, _dyl=_dyn, _sxl=_sxn, _rxl=_rxn, x1-xl))
// Draw a colored triangle (inner routine, uses bresenham's algorithm).
template<typename tc>
CImg<T>& _draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const tc *const color, const float opacity,
const float brightness) {
_draw_scanline(color,opacity);
const float nbrightness = brightness<0?0:(brightness>2?2:brightness);
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2);
if (ny0<dimy() && ny2>=0) {
if ((nx1 - nx0)*(ny2 - ny0) - (nx2 - nx0)*(ny1 - ny0)<0)
_cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xl,xr,y,color,opacity,nbrightness);
else
_cimg_for_triangle1(*this,xl,xr,y,nx0,ny0,nx1,ny1,nx2,ny2) _draw_scanline(xr,xl,y,color,opacity,nbrightness);
}
return *this;
}
//! Draw a 2D filled colored triangle.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const tc *const color, const float opacity=1) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
pixel_type());
_draw_triangle(x0,y0,x1,y1,x2,y2,color,opacity,1);
return *this;
}
//! Draw a 2D filled colored triangle.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& color, const float opacity=1) {
return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity);
}
//! Draw a 2D outlined colored triangle.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const tc *const color, const float opacity,
const unsigned int pattern) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
pixel_type());
draw_line(x0,y0,x1,y1,color,opacity,pattern,true).
draw_line(x1,y1,x2,y2,color,opacity,pattern,false).
draw_line(x2,y2,x0,y0,color,opacity,pattern,false);
return *this;
}
//! Draw a 2D outlined colored triangle.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opacity,pattern);
}
//! Draw a 2D filled colored triangle, with z-buffering.
template<typename tc>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const tc *const color, const float opacity=1,
const float brightness=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
pixel_type());
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float
nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
nbrightness = brightness<0?0:(brightness>2?2:brightness);
const int whz = width*height*depth, offx = dim*whz;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2);
if (ny0>=dimy() || ny2<0) return *this;
float
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
_cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
if (y==ny1) { zl = nz1; pzl = pzn; }
int xleft = xleft0, xright = xright0;
float zleft = zl, zright = zr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright);
const int dx = xright - xleft;
const float pentez = (zright - zleft)/dx;
if (xleft<0 && dx) zleft-=xleft*(zright - zleft)/dx;
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
float *ptrz = zbuffer + xleft + y*width;
if (opacity>=1) {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; }
ptrd-=offx;
}
zleft+=pentez;
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nbrightness*(*col++)); ptrd+=whz; }
ptrd-=offx;
}
zleft+=pentez;
} else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval); ptrd+=whz; }
ptrd-=offx;
}
zleft+=pentez;
}
} else {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity**(col++) + *ptrd*copacity); ptrd+=whz; }
ptrd-=offx;
}
zleft+=pentez;
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color; cimg_forV(*this,k) { *ptrd = (T)(nopacity*nbrightness**(col++) + *ptrd*copacity); ptrd+=whz; }
ptrd-=offx;
}
zleft+=pentez;
} else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color;
cimg_forV(*this,k) {
const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz;
}
ptrd-=offx;
}
zleft+=pentez;
}
}
zr+=pzr; zl+=pzl;
}
return *this;
}
//! Draw a 2D filled colored triangle, with z-buffering.
template<typename tc>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& color, const float opacity=1,
const float brightness=1) {
return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opacity,brightness);
}
//! Draw a 2D Gouraud-shaded colored triangle.
/**
\param x0 = X-coordinate of the first corner in the instance image.
\param y0 = Y-coordinate of the first corner in the instance image.
\param x1 = X-coordinate of the second corner in the instance image.
\param y1 = Y-coordinate of the second corner in the instance image.
\param x2 = X-coordinate of the third corner in the instance image.
\param y2 = Y-coordinate of the third corner in the instance image.
\param color = array of dimv() values of type \c T, defining the global drawing color.
\param brightness0 = brightness of the first corner (in [0,2]).
\param brightness1 = brightness of the second corner (in [0,2]).
\param brightness2 = brightness of the third corner (in [0,2]).
\param opacity = opacity of the drawing.
\note Clipping is supported.
**/
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const tc *const color,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
pixel_type());
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nc0,nc1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nc0,nc2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nc1,nc2);
if (ny0>=dimy() || ny2<0) return *this;
_cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
if (xright<xleft) cimg::swap(xleft,xright,cleft,cright);
const int
dx = xright - xleft,
dc = cright>cleft?cright - cleft:cleft - cright,
rc = dx?(cright - cleft)/dx:0,
sc = cright>cleft?1:-1,
ndc = dc-(dx?dx*(dc/dx):0);
int errc = dx>>1;
if (xleft<0 && dx) cleft-=xleft*(cright - cleft)/dx;
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y);
if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
const tc *col = color;
cimg_forV(*this,k) {
*ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
ptrd+=whz;
}
ptrd-=offx;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
} else for (int x = xleft; x<=xright; ++x) {
const tc *col = color;
cimg_forV(*this,k) {
const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz;
}
ptrd-=offx;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
}
}
return *this;
}
//! Draw a 2D Gouraud-shaded colored triangle.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& color,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,brightness0,brightness1,brightness2,opacity);
}
//! Draw a 2D Gouraud-shaded colored triangle, with z-buffering.
template<typename tc>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const tc *const color,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
pixel_type());
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, offx = dim*whz;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nz0,nz1,nc0,nc1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nz0,nz2,nc0,nc2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nz1,nz2,nc1,nc2);
if (ny0>=dimy() || ny2<0) return *this;
float
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
_cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
if (y==ny1) { zl = nz1; pzl = pzn; }
int xleft = xleft0, xright = xright0, cleft = cleft0, cright = cright0;
float zleft = zl, zright = zr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,cleft,cright);
const int
dx = xright - xleft,
dc = cright>cleft?cright - cleft:cleft - cright,
rc = dx?(cright-cleft)/dx:0,
sc = cright>cleft?1:-1,
ndc = dc-(dx?dx*(dc/dx):0);
const float pentez = (zright - zleft)/dx;
int errc = dx>>1;
if (xleft<0 && dx) {
cleft-=xleft*(cright - cleft)/dx;
zleft-=xleft*(zright - zleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T *ptrd = ptr(xleft,y);
float *ptrz = zbuffer + xleft + y*width;
if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color;
cimg_forV(*this,k) {
*ptrd = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
ptrd+=whz;
}
ptrd-=offx;
}
zleft+=pentez;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
} else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tc *col = color;
cimg_forV(*this,k) {
const T val = (T)(cleft<256?cleft**(col++)/256:((512-cleft)**(col++)+(cleft-256)*maxval)/256);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz;
}
ptrd-=offx;
}
zleft+=pentez;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
}
zr+=pzr; zl+=pzl;
}
return *this;
}
//! Draw a Gouraud triangle with z-buffer consideration.
template<typename tc>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& color,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,brightness0,brightness1,brightness2,opacity);
}
//! Draw a 2D textured triangle.
/**
\param x0 = X-coordinate of the first corner in the instance image.
\param y0 = Y-coordinate of the first corner in the instance image.
\param x1 = X-coordinate of the second corner in the instance image.
\param y1 = Y-coordinate of the second corner in the instance image.
\param x2 = X-coordinate of the third corner in the instance image.
\param y2 = Y-coordinate of the third corner in the instance image.
\param texture = texture image used to fill the triangle.
\param tx0 = X-coordinate of the first corner in the texture image.
\param ty0 = Y-coordinate of the first corner in the texture image.
\param tx1 = X-coordinate of the second corner in the texture image.
\param ty1 = Y-coordinate of the second corner in the texture image.
\param tx2 = X-coordinate of the third corner in the texture image.
\param ty2 = Y-coordinate of the third corner in the texture image.
\param opacity = opacity of the drawing.
\param brightness = brightness of the drawing (in [0,2]).
\note Clipping is supported, but texture coordinates do not support clipping.
**/
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const float opacity=1,
const float brightness=1) {
if (is_empty()) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float
nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
nbrightness = brightness<0?0:(brightness>2?2:brightness);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2);
if (ny0>=dimy() || ny2<0) return *this;
_cimg_for_triangle3(*this,xleft0,txleft0,tyleft0,xright0,txright0,tyright0,y,
nx0,ny0,ntx0,nty0,nx1,ny1,ntx1,nty1,nx2,ny2,ntx2,nty2) {
int
xleft = xleft0, xright = xright0,
txleft = txleft0, txright = txright0,
tyleft = tyleft0, tyright = tyright0;
if (xright<xleft) cimg::swap(xleft,xright,txleft,txright,tyleft,tyright);
const int
dx = xright - xleft,
dtx = txright>txleft?txright - txleft:txleft - txright,
dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
rtx = dx?(txright - txleft)/dx:0,
rty = dx?(tyright - tyleft)/dx:0,
stx = txright>txleft?1:-1,
sty = tyright>tyleft?1:-1,
ndtx = dtx - (dx?dx*(dtx/dx):0),
ndty = dty - (dx?dx*(dty/dx):0);
int errtx = dx>>1, errty = errtx;
if (xleft<0 && dx) {
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)*col;
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)(nbrightness**col);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
} else for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
}
} else {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)(nopacity**col + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
} else for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
const T val = (T)((2-nbrightness)**(col++) + (nbrightness-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
}
}
}
return *this;
}
//! Draw a 2D textured triangle, with perspective correction.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const float opacity=1,
const float brightness=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float
nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
nbrightness = brightness<0?0:(brightness>2?2:brightness);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
float
ntx0 = tx0/z0, nty0 = ty0/z0,
ntx1 = tx1/z1, nty1 = ty1/z1,
ntx2 = tx2/z2, nty2 = ty2/z2,
nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
if (ny0>=dimy() || ny2<0) return *this;
float
ptxl = (ntx1 - ntx0)/(ny1 - ny0),
ptxr = (ntx2 - ntx0)/(ny2 - ny0),
ptxn = (ntx2 - ntx1)/(ny2 - ny1),
ptyl = (nty1 - nty0)/(ny1 - ny0),
ptyr = (nty2 - nty0)/(ny2 - ny0),
ptyn = (nty2 - nty1)/(ny2 - ny1),
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
_cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
int xleft = xleft0, xright = xright0;
float
zleft = zl, zright = zr,
txleft = txl, txright = txr,
tyleft = tyl, tyright = tyr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
const int dx = xright - xleft;
const float
pentez = (zright - zleft)/dx,
pentetx = (txright - txleft)/dx,
pentety = (tyright - tyleft)/dx;
if (xleft<0 && dx) {
zleft-=xleft*(zright - zleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)*col;
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else if (nbrightness<1) for (int x=xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(nbrightness**col);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
}
} else {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(nopacity**col + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
}
}
zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
}
return *this;
}
//! Draw a 2D textured triangle, with z-buffering and perspective correction.
template<typename tc>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const float opacity=1,
const float brightness=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,opacity,brightness);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float
nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0),
nbrightness = brightness<0?0:(brightness>2?2:brightness);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2;
float
ntx0 = tx0/z0, nty0 = ty0/z0,
ntx1 = tx1/z1, nty1 = ty1/z1,
ntx2 = tx2/z2, nty2 = ty2/z2,
nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2);
if (ny0>=dimy() || ny2<0) return *this;
float
ptxl = (ntx1 - ntx0)/(ny1 - ny0),
ptxr = (ntx2 - ntx0)/(ny2 - ny0),
ptxn = (ntx2 - ntx1)/(ny2 - ny1),
ptyl = (nty1 - nty0)/(ny1 - ny0),
ptyr = (nty2 - nty0)/(ny2 - ny0),
ptyn = (nty2 - nty1)/(ny2 - ny1),
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
_cimg_for_triangle1(*this,xleft0,xright0,y,nx0,ny0,nx1,ny1,nx2,ny2) {
if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
int xleft = xleft0, xright = xright0;
float
zleft = zl, zright = zr,
txleft = txl, txright = txr,
tyleft = tyl, tyright = tyr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright);
const int dx = xright - xleft;
const float
pentez = (zright - zleft)/dx,
pentetx = (txright - txleft)/dx,
pentety = (tyright - tyleft)/dx;
if (xleft<0 && dx) {
zleft-=xleft*(zright - zleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T *ptrd = ptr(xleft,y,0,0);
float *ptrz = zbuffer + xleft + y*width;
if (opacity>=1) {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)*col;
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(nbrightness**col);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
}
} else {
if (nbrightness==1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(nopacity**col + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else if (nbrightness<1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(nopacity*nbrightness**col + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
} else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
const T val = (T)((2-nbrightness)**col + (nbrightness-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
}
}
zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
}
return *this;
}
//! Draw a 2D Pseudo-Phong-shaded triangle.
/**
\param x0 = X-coordinate of the first corner in the instance image.
\param y0 = Y-coordinate of the first corner in the instance image.
\param x1 = X-coordinate of the second corner in the instance image.
\param y1 = Y-coordinate of the second corner in the instance image.
\param x2 = X-coordinate of the third corner in the instance image.
\param y2 = Y-coordinate of the third corner in the instance image.
\param color = array of dimv() values of type \c T, defining the global drawing color.
\param light = light image.
\param lx0 = X-coordinate of the first corner in the light image.
\param ly0 = Y-coordinate of the first corner in the light image.
\param lx1 = X-coordinate of the second corner in the light image.
\param ly1 = Y-coordinate of the second corner in the light image.
\param lx2 = X-coordinate of the third corner in the light image.
\param ly2 = Y-coordinate of the third corner in the light image.
\param opacity = opacity of the drawing.
\note Clipping is supported, but texture coordinates do not support clipping.
**/
template<typename tc, typename tl>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const tc *const color,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle : Specified color is (null).",
pixel_type());
if (!light)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,color,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
const int whz = width*height*depth, offx = dim*whz-1;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2);
if (ny0>=dimy() || ny2<0) return *this;
_cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
int
xleft = xleft0, xright = xright0,
lxleft = lxleft0, lxright = lxright0,
lyleft = lyleft0, lyright = lyright0;
if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright);
const int
dx = xright - xleft,
dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
rlx = dx?(lxright - lxleft)/dx:0,
rly = dx?(lyright - lyleft)/dx:0,
slx = lxright>lxleft?1:-1,
sly = lyright>lyleft?1:-1,
ndlx = dlx - (dx?dx*(dlx/dx):0),
ndly = dly - (dx?dx*(dly/dx):0);
int errlx = dx>>1, errly = errlx;
if (xleft<0 && dx) {
lxleft-=xleft*(lxright - lxleft)/dx;
lyleft-=xleft*(lyright - lyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
const tl l = light(lxleft,lyleft);
const tc *col = color;
cimg_forV(*this,k) {
*ptrd = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
ptrd+=whz;
}
ptrd-=offx;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
} else for (int x = xleft; x<=xright; ++x) {
const tl l = light(lxleft,lyleft);
const tc *col = color;
cimg_forV(*this,k) {
const T val = (T)(l<1?l**(col++):((2-l)**(col++)+(l-1)*maxval));
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz;
}
ptrd-=offx;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
}
}
return *this;
}
//! Draw a 2D Pseudo-Phong-shaded triangle.
template<typename tc, typename tl>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& color,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
return draw_triangle(x0,y0,x1,y1,x2,y2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
}
//! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering.
template<typename tc, typename tl>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const tc *const color,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified color is (null).",
pixel_type());
if (!light)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,
+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, offx = dim*whz;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
float nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,nlx0,nlx1,nly0,nly1,nz0,nz1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,nlx0,nlx2,nly0,nly2,nz0,nz2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,nlx1,nlx2,nly1,nly2,nz1,nz2);
if (ny0>=dimy() || ny2<0) return *this;
float
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1)));
_cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
if (y==ny1) { zl = nz1; pzl = pzn; }
int
xleft = xleft0, xright = xright0,
lxleft = lxleft0, lxright = lxright0,
lyleft = lyleft0, lyright = lyright0;
float zleft = zl, zright = zr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,lxleft,lxright,lyleft,lyright);
const int
dx = xright - xleft,
dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
rlx = dx?(lxright - lxleft)/dx:0,
rly = dx?(lyright - lyleft)/dx:0,
slx = lxright>lxleft?1:-1,
sly = lyright>lyleft?1:-1,
ndlx = dlx - (dx?dx*(dlx/dx):0),
ndly = dly - (dx?dx*(dly/dx):0);
const float pentez = (zright - zleft)/dx;
int errlx = dx>>1, errly = errlx;
if (xleft<0 && dx) {
zleft-=xleft*(zright - zleft)/dx;
lxleft-=xleft*(lxright - lxleft)/dx;
lyleft-=xleft*(lyright - lyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T *ptrd = ptr(xleft,y,0,0);
float *ptrz = zbuffer + xleft + y*width;
if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tl l = light(lxleft,lyleft);
const tc *col = color;
cimg_forV(*this,k) {
const tc cval = *(col++);
*ptrd = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
ptrd+=whz;
}
ptrd-=offx;
}
zleft+=pentez;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
} else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const tl l = light(lxleft,lyleft);
const tc *col = color;
cimg_forV(*this,k) {
const tc cval = *(col++);
const T val = (T)(l<1?l*cval:(2-l)*cval+(l-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz;
}
ptrd-=offx;
}
zleft+=pentez;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
}
zr+=pzr; zl+=pzl;
}
return *this;
}
//! Draw a 2D Pseudo-Phong-shaded triangle, with z-buffering.
template<typename tc, typename tl>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& color,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
}
//! Draw a 2D Gouraud-shaded textured triangle.
/**
\param x0 = X-coordinate of the first corner in the instance image.
\param y0 = Y-coordinate of the first corner in the instance image.
\param x1 = X-coordinate of the second corner in the instance image.
\param y1 = Y-coordinate of the second corner in the instance image.
\param x2 = X-coordinate of the third corner in the instance image.
\param y2 = Y-coordinate of the third corner in the instance image.
\param texture = texture image used to fill the triangle.
\param tx0 = X-coordinate of the first corner in the texture image.
\param ty0 = Y-coordinate of the first corner in the texture image.
\param tx1 = X-coordinate of the second corner in the texture image.
\param ty1 = Y-coordinate of the second corner in the texture image.
\param tx2 = X-coordinate of the third corner in the texture image.
\param ty2 = Y-coordinate of the third corner in the texture image.
\param brightness0 = brightness value of the first corner.
\param brightness1 = brightness value of the second corner.
\param brightness2 = brightness value of the third corner.
\param opacity = opacity of the drawing.
\note Clipping is supported, but texture coordinates do not support clipping.
**/
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
if (is_empty()) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture))
return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,brightness0,brightness1,brightness2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nc0,nc1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nc0,nc2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nc1,nc2);
if (ny0>=dimy() || ny2<0) return *this;
_cimg_for_triangle4(*this,xleft0,cleft0,txleft0,tyleft0,xright0,cright0,txright0,tyright0,y,
nx0,ny0,nc0,ntx0,nty0,nx1,ny1,nc1,ntx1,nty1,nx2,ny2,nc2,ntx2,nty2) {
int
xleft = xleft0, xright = xright0,
cleft = cleft0, cright = cright0,
txleft = txleft0, txright = txright0,
tyleft = tyleft0, tyright = tyright0;
if (xright<xleft) cimg::swap(xleft,xright,cleft,cright,txleft,txright,tyleft,tyright);
const int
dx = xright - xleft,
dc = cright>cleft?cright - cleft:cleft - cright,
dtx = txright>txleft?txright - txleft:txleft - txright,
dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
rc = dx?(cright - cleft)/dx:0,
rtx = dx?(txright - txleft)/dx:0,
rty = dx?(tyright - tyleft)/dx:0,
sc = cright>cleft?1:-1,
stx = txright>txleft?1:-1,
sty = tyright>tyleft?1:-1,
ndc = dc - (dx?dx*(dc/dx):0),
ndtx = dtx - (dx?dx*(dtx/dx):0),
ndty = dty - (dx?dx*(dty/dx):0);
int errc = dx>>1, errtx = errc, errty = errc;
if (xleft<0 && dx) {
cleft-=xleft*(cright - cleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
} else for (int x = xleft; x<=xright; ++x) {
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
}
}
return *this;
}
//! Draw a 2D Gouraud-shaded textured triangle, with perspective correction.
template<typename tc>
CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
brightness0,brightness1,brightness2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
float
ntx0 = tx0/z0, nty0 = ty0/z0,
ntx1 = tx1/z1, nty1 = ty1/z1,
ntx2 = tx2/z2, nty2 = ty2/z2,
nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
if (ny0>=dimy() || ny2<0) return *this;
float
ptxl = (ntx1 - ntx0)/(ny1 - ny0),
ptxr = (ntx2 - ntx0)/(ny2 - ny0),
ptxn = (ntx2 - ntx1)/(ny2 - ny1),
ptyl = (nty1 - nty0)/(ny1 - ny0),
ptyr = (nty2 - nty0)/(ny2 - ny0),
ptyn = (nty2 - nty1)/(ny2 - ny1),
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
_cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
int
xleft = xleft0, xright = xright0,
cleft = cleft0, cright = cright0;
float
zleft = zl, zright = zr,
txleft = txl, txright = txr,
tyleft = tyl, tyright = tyr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
const int
dx = xright - xleft,
dc = cright>cleft?cright - cleft:cleft - cright,
rc = dx?(cright - cleft)/dx:0,
sc = cright>cleft?1:-1,
ndc = dc - (dx?dx*(dc/dx):0);
const float
pentez = (zright - zleft)/dx,
pentetx = (txright - txleft)/dx,
pentety = (tyright - tyleft)/dx;
int errc = dx>>1;
if (xleft<0 && dx) {
cleft-=xleft*(cright - cleft)/dx;
zleft-=xleft*(zright - zleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
} else for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
}
zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
}
return *this;
}
//! Draw a 2D Gouraud-shaded textured triangle, with z-buffering and perspective correction.
template<typename tc>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const float brightness0,
const float brightness1,
const float brightness2,
const float opacity=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,
brightness0,brightness1,brightness2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nc0 = (int)((brightness0<0?0:(brightness0>2?2:brightness0))*256),
nc1 = (int)((brightness1<0?0:(brightness1>2?2:brightness1))*256),
nc2 = (int)((brightness2<0?0:(brightness2>2?2:brightness2))*256);
float
ntx0 = tx0/z0, nty0 = ty0/z0,
ntx1 = tx1/z1, nty1 = ty1/z1,
ntx2 = tx2/z2, nty2 = ty2/z2,
nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nz0,nz1,nc0,nc1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nz0,nz2,nc0,nc2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nz1,nz2,nc1,nc2);
if (ny0>=dimy() || ny2<0) return *this;
float
ptxl = (ntx1 - ntx0)/(ny1 - ny0),
ptxr = (ntx2 - ntx0)/(ny2 - ny0),
ptxn = (ntx2 - ntx1)/(ny2 - ny1),
ptyl = (nty1 - nty0)/(ny1 - ny0),
ptyr = (nty2 - nty0)/(ny2 - ny0),
ptyn = (nty2 - nty1)/(ny2 - ny1),
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
_cimg_for_triangle2(*this,xleft0,cleft0,xright0,cright0,y,nx0,ny0,nc0,nx1,ny1,nc1,nx2,ny2,nc2) {
if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
int
xleft = xleft0, xright = xright0,
cleft = cleft0, cright = cright0;
float
zleft = zl, zright = zr,
txleft = txl, txright = txr,
tyleft = tyl, tyright = tyr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,cleft,cright);
const int
dx = xright - xleft,
dc = cright>cleft?cright - cleft:cleft - cright,
rc = dx?(cright - cleft)/dx:0,
sc = cright>cleft?1:-1,
ndc = dc - (dx?dx*(dc/dx):0);
const float
pentez = (zright - zleft)/dx,
pentetx = (txright - txleft)/dx,
pentety = (tyright - tyleft)/dx;
int errc = dx>>1;
if (xleft<0 && dx) {
cleft-=xleft*(cright - cleft)/dx;
zleft-=xleft*(zright - zleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y);
float *ptrz = zbuffer + xleft + y*width;
if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
} else for (int x = xleft; x<=xright; ++x, ++ptrd, ++ptrz) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
const T val = (T)(cleft<256?cleft**col/256:((512-cleft)**col+(cleft-256)*maxval)/256);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
cleft+=rc+((errc-=ndc)<0?errc+=dx,sc:0);
}
zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
}
return *this;
}
//! Draw a 2D Pseudo-Phong-shaded textured triangle.
/**
\param x0 = X-coordinate of the first corner in the instance image.
\param y0 = Y-coordinate of the first corner in the instance image.
\param x1 = X-coordinate of the second corner in the instance image.
\param y1 = Y-coordinate of the second corner in the instance image.
\param x2 = X-coordinate of the third corner in the instance image.
\param y2 = Y-coordinate of the third corner in the instance image.
\param texture = texture image used to fill the triangle.
\param tx0 = X-coordinate of the first corner in the texture image.
\param ty0 = Y-coordinate of the first corner in the texture image.
\param tx1 = X-coordinate of the second corner in the texture image.
\param ty1 = Y-coordinate of the second corner in the texture image.
\param tx2 = X-coordinate of the third corner in the texture image.
\param ty2 = Y-coordinate of the third corner in the texture image.
\param light = light image.
\param lx0 = X-coordinate of the first corner in the light image.
\param ly0 = Y-coordinate of the first corner in the light image.
\param lx1 = X-coordinate of the second corner in the light image.
\param ly1 = Y-coordinate of the second corner in the light image.
\param lx2 = X-coordinate of the third corner in the light image.
\param ly2 = Y-coordinate of the third corner in the light image.
\param opacity = opacity of the drawing.
\note Clipping is supported, but texture coordinates do not support clipping.
**/
template<typename tc, typename tl>
CImg<T>& draw_triangle(const int x0, const int y0,
const int x1, const int y1,
const int x2, const int y2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
if (is_empty()) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (!light)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
if (is_overlapped(texture)) return draw_triangle(x0,y0,x1,y1,x2,y2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
if (is_overlapped(light)) return draw_triangle(x0,y0,x1,y1,x2,y2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
ntx0 = tx0, nty0 = ty0, ntx1 = tx1, nty1 = ty1, ntx2 = tx2, nty2 = ty2,
nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2);
if (ny0>=dimy() || ny2<0) return *this;
_cimg_for_triangle5(*this,xleft0,lxleft0,lyleft0,txleft0,tyleft0,xright0,lxright0,lyright0,txright0,tyright0,y,
nx0,ny0,nlx0,nly0,ntx0,nty0,nx1,ny1,nlx1,nly1,ntx1,nty1,nx2,ny2,nlx2,nly2,ntx2,nty2) {
int
xleft = xleft0, xright = xright0,
lxleft = lxleft0, lxright = lxright0,
lyleft = lyleft0, lyright = lyright0,
txleft = txleft0, txright = txright0,
tyleft = tyleft0, tyright = tyright0;
if (xright<xleft) cimg::swap(xleft,xright,lxleft,lxright,lyleft,lyright,txleft,txright,tyleft,tyright);
const int
dx = xright - xleft,
dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
dtx = txright>txleft?txright - txleft:txleft - txright,
dty = tyright>tyleft?tyright - tyleft:tyleft - tyright,
rlx = dx?(lxright - lxleft)/dx:0,
rly = dx?(lyright - lyleft)/dx:0,
rtx = dx?(txright - txleft)/dx:0,
rty = dx?(tyright - tyleft)/dx:0,
slx = lxright>lxleft?1:-1,
sly = lyright>lyleft?1:-1,
stx = txright>txleft?1:-1,
sty = tyright>tyleft?1:-1,
ndlx = dlx - (dx?dx*(dlx/dx):0),
ndly = dly - (dx?dx*(dly/dx):0),
ndtx = dtx - (dx?dx*(dtx/dx):0),
ndty = dty - (dx?dx*(dty/dx):0);
int errlx = dx>>1, errly = errlx, errtx = errlx, errty = errlx;
if (xleft<0 && dx) {
lxleft-=xleft*(lxright - lxleft)/dx;
lyleft-=xleft*(lyright - lyleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
const tl l = light(lxleft,lyleft);
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
*ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
} else for (int x = xleft; x<=xright; ++x) {
const tl l = light(lxleft,lyleft);
const tc *col = texture.ptr(txleft,tyleft);
cimg_forV(*this,k) {
const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
txleft+=rtx+((errtx-=ndtx)<0?errtx+=dx,stx:0);
tyleft+=rty+((errty-=ndty)<0?errty+=dx,sty:0);
}
}
return *this;
}
//! Draw a 2D Pseudo-Phong-shaded textured triangle, with perspective correction.
template<typename tc, typename tl>
CImg<T>& draw_triangle(const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (!light)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
if (is_overlapped(texture)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
if (is_overlapped(light)) return draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz-1;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
float
ntx0 = tx0/z0, nty0 = ty0/z0,
ntx1 = tx1/z1, nty1 = ty1/z1,
ntx2 = tx2/z2, nty2 = ty2/z2,
nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
if (ny0>=dimy() || ny2<0) return *this;
float
ptxl = (ntx1 - ntx0)/(ny1 - ny0),
ptxr = (ntx2 - ntx0)/(ny2 - ny0),
ptxn = (ntx2 - ntx1)/(ny2 - ny1),
ptyl = (nty1 - nty0)/(ny1 - ny0),
ptyr = (nty2 - nty0)/(ny2 - ny0),
ptyn = (nty2 - nty1)/(ny2 - ny1),
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
_cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
int
xleft = xleft0, xright = xright0,
lxleft = lxleft0, lxright = lxright0,
lyleft = lyleft0, lyright = lyright0;
float
zleft = zl, zright = zr,
txleft = txl, txright = txr,
tyleft = tyl, tyright = tyr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
const int
dx = xright - xleft,
dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
rlx = dx?(lxright - lxleft)/dx:0,
rly = dx?(lyright - lyleft)/dx:0,
slx = lxright>lxleft?1:-1,
sly = lyright>lyleft?1:-1,
ndlx = dlx - (dx?dx*(dlx/dx):0),
ndly = dly - (dx?dx*(dly/dx):0);
const float
pentez = (zright - zleft)/dx,
pentetx = (txright - txleft)/dx,
pentety = (tyright - tyleft)/dx;
int errlx = dx>>1, errly = errlx;
if (xleft<0 && dx) {
zleft-=xleft*(zright - zleft)/dx;
lxleft-=xleft*(lxright - lxleft)/dx;
lyleft-=xleft*(lyright - lyleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y,0,0);
if (opacity>=1) for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tl l = light(lxleft,lyleft);
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
} else for (int x = xleft; x<=xright; ++x) {
const float invz = 1/zleft;
const tl l = light(lxleft,lyleft);
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx; zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
}
zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
}
return *this;
}
//! Draw a 2D Pseudo-Phong-shaded textured triangle, with z-buffering and perspective correction.
template<typename tc, typename tl>
CImg<T>& draw_triangle(float *const zbuffer,
const int x0, const int y0, const float z0,
const int x1, const int y1, const float z1,
const int x2, const int y2, const float z2,
const CImg<tc>& texture,
const int tx0, const int ty0,
const int tx1, const int ty1,
const int tx2, const int ty2,
const CImg<tl>& light,
const int lx0, const int ly0,
const int lx1, const int ly1,
const int lx2, const int ly2,
const float opacity=1) {
if (is_empty() || z0<=0 || z1<=0 || z2<=0) return *this;
if (!texture || texture.dim<dim)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified texture (%u,%u,%u,%u,%p) is not a valid argument.",
pixel_type(),texture.width,texture.height,texture.depth,texture.dim,texture.data);
if (!light)
throw CImgArgumentException("CImg<%s>::draw_triangle() : Specified light texture (%u,%u,%u,%u,%p) is empty.",
pixel_type(),light.width,light.height,light.depth,light.dim,light.data);
if (is_overlapped(texture)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
+texture,tx0,ty0,tx1,ty1,tx2,ty2,light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
if (is_overlapped(light)) return draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,
texture,tx0,ty0,tx1,ty1,tx2,ty2,+light,lx0,ly0,lx1,ly1,lx2,ly2,opacity);
static const T maxval = (T)cimg::min(cimg::type<T>::max(),cimg::type<tc>::max());
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const int whz = width*height*depth, twhz = texture.width*texture.height*texture.depth, offx = dim*whz;
int nx0 = x0, ny0 = y0, nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2,
nlx0 = lx0, nly0 = ly0, nlx1 = lx1, nly1 = ly1, nlx2 = lx2, nly2 = ly2;
float
ntx0 = tx0/z0, nty0 = ty0/z0,
ntx1 = tx1/z1, nty1 = ty1/z1,
ntx2 = tx2/z2, nty2 = ty2/z2,
nz0 = 1/z0, nz1 = 1/z1, nz2 = 1/z2;
if (ny0>ny1) cimg::swap(nx0,nx1,ny0,ny1,ntx0,ntx1,nty0,nty1,nlx0,nlx1,nly0,nly1,nz0,nz1);
if (ny0>ny2) cimg::swap(nx0,nx2,ny0,ny2,ntx0,ntx2,nty0,nty2,nlx0,nlx2,nly0,nly2,nz0,nz2);
if (ny1>ny2) cimg::swap(nx1,nx2,ny1,ny2,ntx1,ntx2,nty1,nty2,nlx1,nlx2,nly1,nly2,nz1,nz2);
if (ny0>=dimy() || ny2<0) return *this;
float
ptxl = (ntx1 - ntx0)/(ny1 - ny0),
ptxr = (ntx2 - ntx0)/(ny2 - ny0),
ptxn = (ntx2 - ntx1)/(ny2 - ny1),
ptyl = (nty1 - nty0)/(ny1 - ny0),
ptyr = (nty2 - nty0)/(ny2 - ny0),
ptyn = (nty2 - nty1)/(ny2 - ny1),
pzl = (nz1 - nz0)/(ny1 - ny0),
pzr = (nz2 - nz0)/(ny2 - ny0),
pzn = (nz2 - nz1)/(ny2 - ny1),
zr = ny0>=0?nz0:(nz0 - ny0*(nz2 - nz0)/(ny2 - ny0)),
txr = ny0>=0?ntx0:(ntx0 - ny0*(ntx2 - ntx0)/(ny2 - ny0)),
tyr = ny0>=0?nty0:(nty0 - ny0*(nty2 - nty0)/(ny2 - ny0)),
zl = ny1>=0?(ny0>=0?nz0:(nz0 - ny0*(nz1 - nz0)/(ny1 - ny0))):(pzl=pzn,(nz1 - ny1*(nz2 - nz1)/(ny2 - ny1))),
txl = ny1>=0?(ny0>=0?ntx0:(ntx0 - ny0*(ntx1 - ntx0)/(ny1 - ny0))):(ptxl=ptxn,(ntx1 - ny1*(ntx2 - ntx1)/(ny2 - ny1))),
tyl = ny1>=0?(ny0>=0?nty0:(nty0 - ny0*(nty1 - nty0)/(ny1 - ny0))):(ptyl=ptyn,(nty1 - ny1*(nty2 - nty1)/(ny2 - ny1)));
_cimg_for_triangle3(*this,xleft0,lxleft0,lyleft0,xright0,lxright0,lyright0,y,
nx0,ny0,nlx0,nly0,nx1,ny1,nlx1,nly1,nx2,ny2,nlx2,nly2) {
if (y==ny1) { zl = nz1; txl = ntx1; tyl = nty1; pzl = pzn; ptxl = ptxn; ptyl = ptyn; }
int
xleft = xleft0, xright = xright0,
lxleft = lxleft0, lxright = lxright0,
lyleft = lyleft0, lyright = lyright0;
float
zleft = zl, zright = zr,
txleft = txl, txright = txr,
tyleft = tyl, tyright = tyr;
if (xright<xleft) cimg::swap(xleft,xright,zleft,zright,txleft,txright,tyleft,tyright,lxleft,lxright,lyleft,lyright);
const int
dx = xright - xleft,
dlx = lxright>lxleft?lxright - lxleft:lxleft - lxright,
dly = lyright>lyleft?lyright - lyleft:lyleft - lyright,
rlx = dx?(lxright - lxleft)/dx:0,
rly = dx?(lyright - lyleft)/dx:0,
slx = lxright>lxleft?1:-1,
sly = lyright>lyleft?1:-1,
ndlx = dlx - (dx?dx*(dlx/dx):0),
ndly = dly - (dx?dx*(dly/dx):0);
const float
pentez = (zright - zleft)/dx,
pentetx = (txright - txleft)/dx,
pentety = (tyright - tyleft)/dx;
int errlx = dx>>1, errly = errlx;
if (xleft<0 && dx) {
zleft-=xleft*(zright - zleft)/dx;
lxleft-=xleft*(lxright - lxleft)/dx;
lyleft-=xleft*(lyright - lyleft)/dx;
txleft-=xleft*(txright - txleft)/dx;
tyleft-=xleft*(tyright - tyleft)/dx;
}
if (xleft<0) xleft = 0;
if (xright>=dimx()-1) xright = dimx()-1;
T* ptrd = ptr(xleft,y);
float *ptrz = zbuffer + xleft + y*width;
if (opacity>=1) for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tl l = light(lxleft,lyleft);
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
*ptrd = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
} else for (int x = xleft; x<=xright; ++x, ++ptrz, ++ptrd) {
if (zleft>*ptrz) {
*ptrz = zleft;
const float invz = 1/zleft;
const tl l = light(lxleft,lyleft);
const tc *col = texture.ptr((int)(txleft*invz),(int)(tyleft*invz));
cimg_forV(*this,k) {
const T val = (T)(l<1?l**col:(2-l)**col+(l-1)*maxval);
*ptrd = (T)(nopacity*val + *ptrd*copacity);
ptrd+=whz; col+=twhz;
}
ptrd-=offx;
}
zleft+=pentez; txleft+=pentetx; tyleft+=pentety;
lxleft+=rlx+((errlx-=ndlx)<0?errlx+=dx,slx:0);
lyleft+=rly+((errly-=ndly)<0?errly+=dx,sly:0);
}
zr+=pzr; txr+=ptxr; tyr+=ptyr; zl+=pzl; txl+=ptxl; tyl+=ptyl;
}
return *this;
}
// Draw a 2D ellipse (inner routine).
template<typename tc>
CImg<T>& _draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
const tc *const color, const float opacity,
const unsigned int pattern) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_ellipse : Specified color is (null).",
pixel_type());
_draw_scanline(color,opacity);
const float
nr1 = cimg::abs(r1), nr2 = cimg::abs(r2),
norm = (float)cimg_std::sqrt(ru*ru+rv*rv),
u = norm>0?ru/norm:1,
v = norm>0?rv/norm:0,
rmax = cimg::max(nr1,nr2),
l1 = (float)cimg_std::pow(rmax/(nr1>0?nr1:1e-6),2),
l2 = (float)cimg_std::pow(rmax/(nr2>0?nr2:1e-6),2),
a = l1*u*u + l2*v*v,
b = u*v*(l1-l2),
c = l1*v*v + l2*u*u;
const int
yb = (int)cimg_std::sqrt(a*rmax*rmax/(a*c-b*b)),
tymin = y0 - yb - 1,
tymax = y0 + yb + 1,
ymin = tymin<0?0:tymin,
ymax = tymax>=dimy()?height-1:tymax;
int oxmin = 0, oxmax = 0;
bool first_line = true;
for (int y = ymin; y<=ymax; ++y) {
const float
Y = y-y0 + (y<y0?0.5f:-0.5f),
delta = b*b*Y*Y-a*(c*Y*Y-rmax*rmax),
sdelta = delta>0?(float)cimg_std::sqrt(delta)/a:0.0f,
bY = b*Y/a,
fxmin = x0-0.5f-bY-sdelta,
fxmax = x0+0.5f-bY+sdelta;
const int xmin = (int)fxmin, xmax = (int)fxmax;
if (!pattern) _draw_scanline(xmin,xmax,y,color,opacity);
else {
if (first_line) {
if (y0-yb>=0) _draw_scanline(xmin,xmax,y,color,opacity);
else draw_point(xmin,y,color,opacity).draw_point(xmax,y,color,opacity);
first_line = false;
} else {
if (xmin<oxmin) _draw_scanline(xmin,oxmin-1,y,color,opacity);
else _draw_scanline(oxmin+(oxmin==xmin?0:1),xmin,y,color,opacity);
if (xmax<oxmax) _draw_scanline(xmax,oxmax-1,y,color,opacity);
else _draw_scanline(oxmax+(oxmax==xmax?0:1),xmax,y,color,opacity);
if (y==tymax) _draw_scanline(xmin+1,xmax-1,y,color,opacity);
}
}
oxmin = xmin; oxmax = xmax;
}
return *this;
}
//! Draw a filled ellipse.
/**
\param x0 = X-coordinate of the ellipse center.
\param y0 = Y-coordinate of the ellipse center.
\param r1 = First radius of the ellipse.
\param r2 = Second radius of the ellipse.
\param ru = X-coordinate of the orientation vector related to the first radius.
\param rv = Y-coordinate of the orientation vector related to the first radius.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
const tc *const color, const float opacity=1) {
return _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,0U);
}
//! Draw a filled ellipse.
template<typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
const CImg<tc>& color, const float opacity=1) {
return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity);
}
//! Draw a filled ellipse.
/**
\param x0 = X-coordinate of the ellipse center.
\param y0 = Y-coordinate of the ellipse center.
\param tensor = Diffusion tensor describing the ellipse.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename t, typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
const tc *const color, const float opacity=1) {
CImgList<t> eig = tensor.get_symmetric_eigen();
const CImg<t> &val = eig[0], &vec = eig[1];
return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity);
}
//! Draw a filled ellipse.
template<typename t, typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
const CImg<tc>& color, const float opacity=1) {
return draw_ellipse(x0,y0,tensor,color.data,opacity);
}
//! Draw an outlined ellipse.
/**
\param x0 = X-coordinate of the ellipse center.
\param y0 = Y-coordinate of the ellipse center.
\param r1 = First radius of the ellipse.
\param r2 = Second radius of the ellipse.
\param ru = X-coordinate of the orientation vector related to the first radius.
\param rv = Y-coordinate of the orientation vector related to the first radius.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
const tc *const color, const float opacity,
const unsigned int pattern) {
if (pattern) _draw_ellipse(x0,y0,r1,r2,ru,rv,color,opacity,pattern);
return *this;
}
//! Draw an outlined ellipse.
template<typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const float r1, const float r2, const float ru, const float rv,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_ellipse(x0,y0,r1,r2,ru,rv,color.data,opacity,pattern);
}
//! Draw an outlined ellipse.
/**
\param x0 = X-coordinate of the ellipse center.
\param y0 = Y-coordinate of the ellipse center.
\param tensor = Diffusion tensor describing the ellipse.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param pattern = If zero, the ellipse is filled, else pattern is an integer whose bits describe the outline pattern.
\param opacity = opacity of the drawing.
**/
template<typename t, typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
const tc *const color, const float opacity,
const unsigned int pattern) {
CImgList<t> eig = tensor.get_symmetric_eigen();
const CImg<t> &val = eig[0], &vec = eig[1];
return draw_ellipse(x0,y0,val(0),val(1),vec(0,0),vec(0,1),color,opacity,pattern);
}
//! Draw an outlined ellipse.
template<typename t, typename tc>
CImg<T>& draw_ellipse(const int x0, const int y0, const CImg<t> &tensor,
const CImg<tc>& color, const float opacity,
const unsigned int pattern) {
return draw_ellipse(x0,y0,tensor,color.data,opacity,pattern);
}
//! Draw a filled circle.
/**
\param x0 X-coordinate of the circle center.
\param y0 Y-coordinate of the circle center.
\param radius Circle radius.
\param color Array of dimv() values of type \c T, defining the drawing color.
\param opacity Drawing opacity.
\note
- Circle version of the Bresenham's algorithm is used.
**/
template<typename tc>
CImg<T>& draw_circle(const int x0, const int y0, int radius,
const tc *const color, const float opacity=1) {
if (!is_empty()) {
if (!color)
throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",
pixel_type());
_draw_scanline(color,opacity);
if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
if (y0>=0 && y0<dimy()) _draw_scanline(x0-radius,x0+radius,y0,color,opacity);
for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
if (f>=0) {
const int x1 = x0-x, x2 = x0+x, y1 = y0-y, y2 = y0+y;
if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
f+=(ddFy+=2); --y;
}
const bool no_diag = y!=(x++);
++(f+=(ddFx+=2));
const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x;
if (no_diag) {
if (y1>=0 && y1<dimy()) _draw_scanline(x1,x2,y1,color,opacity);
if (y2>=0 && y2<dimy()) _draw_scanline(x1,x2,y2,color,opacity);
}
}
}
return *this;
}
//! Draw a filled circle.
template<typename tc>
CImg<T>& draw_circle(const int x0, const int y0, int radius,
const CImg<tc>& color, const float opacity=1) {
return draw_circle(x0,y0,radius,color.data,opacity);
}
//! Draw an outlined circle.
/**
\param x0 X-coordinate of the circle center.
\param y0 Y-coordinate of the circle center.
\param radius Circle radius.
\param color Array of dimv() values of type \c T, defining the drawing color.
\param opacity Drawing opacity.
**/
template<typename tc>
CImg<T>& draw_circle(const int x0, const int y0, int radius,
const tc *const color, const float opacity,
const unsigned int) {
if (!is_empty()) {
if (!color)
throw CImgArgumentException("CImg<%s>::draw_circle : Specified color is (null).",
pixel_type());
if (radius<0 || x0-radius>=dimx() || y0+radius<0 || y0-radius>=dimy()) return *this;
if (!radius) return draw_point(x0,y0,color,opacity);
draw_point(x0-radius,y0,color,opacity).draw_point(x0+radius,y0,color,opacity).
draw_point(x0,y0-radius,color,opacity).draw_point(x0,y0+radius,color,opacity);
if (radius==1) return *this;
for (int f=1-radius, ddFx=0, ddFy=-(radius<<1), x=0, y=radius; x<y; ) {
if (f>=0) { f+=(ddFy+=2); --y; }
++x; ++(f+=(ddFx+=2));
if (x!=y+1) {
const int x1 = x0-y, x2 = x0+y, y1 = y0-x, y2 = y0+x, x3 = x0-x, x4 = x0+x, y3 = y0-y, y4 = y0+y;
draw_point(x1,y1,color,opacity).draw_point(x1,y2,color,opacity).
draw_point(x2,y1,color,opacity).draw_point(x2,y2,color,opacity);
if (x!=y)
draw_point(x3,y3,color,opacity).draw_point(x4,y4,color,opacity).
draw_point(x4,y3,color,opacity).draw_point(x3,y4,color,opacity);
}
}
}
return *this;
}
//! Draw an outlined circle.
template<typename tc>
CImg<T>& draw_circle(const int x0, const int y0, int radius, const CImg<tc>& color,
const float opacity,
const unsigned int pattern) {
return draw_circle(x0,y0,radius,color.data,opacity,pattern);
}
// Draw a text (internal).
template<typename tc1, typename tc2, typename t>
CImg<T>& _draw_text(const int x0, const int y0, const char *const text,
const tc1 *const foreground_color, const tc2 *const background_color,
const float opacity, const CImgList<t>& font) {
if (!text) return *this;
if (!font)
throw CImgArgumentException("CImg<%s>::draw_text() : Specified font (%u,%p) is empty.",
pixel_type(),font.size,font.data);
const int text_length = cimg::strlen(text);
if (is_empty()) {
// If needed, pre-compute necessary size of the image
int x = 0, y = 0, w = 0;
unsigned char c = 0;
for (int i = 0; i<text_length; ++i) {
c = text[i];
switch (c) {
case '\n' : y+=font[' '].height; if (x>w) w = x; x = 0; break;
case '\t' : x+=4*font[' '].width; break;
default : if (c<font.size) x+=font[c].width;
}
}
if (x!=0 || c=='\n') {
if (x>w) w=x;
y+=font[' '].height;
}
assign(x0+w,y0+y,1,font[' '].dim,0);
if (background_color) cimg_forV(*this,k) get_shared_channel(k).fill((T)background_color[k]);
}
int x = x0, y = y0;
CImg<T> letter;
for (int i = 0; i<text_length; ++i) {
const unsigned char c = text[i];
switch (c) {
case '\n' : y+=font[' '].height; x = x0; break;
case '\t' : x+=4*font[' '].width; break;
default : if (c<font.size) {
letter = font[c];
const CImg<T>& mask = (c+256)<(int)font.size?font[c+256]:font[c];
if (foreground_color) for (unsigned int p = 0; p<letter.width*letter.height; ++p)
if (mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)(letter(p,0,0,k)*foreground_color[k]);
if (background_color) for (unsigned int p = 0; p<letter.width*letter.height; ++p)
if (!mask(p)) cimg_forV(*this,k) letter(p,0,0,k) = (T)background_color[k];
if (!background_color && font.size>=512) draw_image(x,y,letter,mask,opacity,(T)1);
else draw_image(x,y,letter,opacity);
x+=letter.width;
}
}
}
return *this;
}
//! Draw a text.
/**
\param x0 X-coordinate of the text in the instance image.
\param y0 Y-coordinate of the text in the instance image.
\param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
\param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
\param font Font used for drawing text.
\param opacity Drawing opacity.
\param format 'printf'-style format string, followed by arguments.
\note Clipping is supported.
**/
template<typename tc1, typename tc2, typename t>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const tc1 *const foreground_color, const tc2 *const background_color,
const float opacity, const CImgList<t>& font, ...) {
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
}
//! Draw a text.
template<typename tc1, typename tc2, typename t>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const CImg<tc1>& foreground_color, const CImg<tc2>& background_color,
const float opacity, const CImgList<t>& font, ...) {
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
}
//! Draw a text.
template<typename tc, typename t>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const tc *const foreground_color, const int background_color,
const float opacity, const CImgList<t>& font, ...) {
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,foreground_color,(tc*)background_color,opacity,font);
}
//! Draw a text.
template<typename tc, typename t>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const int foreground_color, const tc *const background_color,
const float opacity, const CImgList<t>& font, ...) {
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font);
cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font);
}
//! Draw a text.
/**
\param x0 X-coordinate of the text in the instance image.
\param y0 Y-coordinate of the text in the instance image.
\param foreground_color Array of dimv() values of type \c T, defining the foreground color (0 means 'transparent').
\param background_color Array of dimv() values of type \c T, defining the background color (0 means 'transparent').
\param font_size Size of the font (nearest match).
\param opacity Drawing opacity.
\param format 'printf'-style format string, followed by arguments.
\note Clipping is supported.
**/
template<typename tc1, typename tc2>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const tc1 *const foreground_color, const tc2 *const background_color,
const float opacity=1, const unsigned int font_size=11, ...) {
static CImgList<T> font;
static unsigned int fsize = 0;
if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,foreground_color,background_color,opacity,font);
}
//! Draw a text.
template<typename tc1, typename tc2>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const CImg<tc1>& foreground_color, const CImg<tc2>& background_color,
const float opacity=1, const unsigned int font_size=11, ...) {
static CImgList<T> font;
static unsigned int fsize = 0;
if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,foreground_color.data,background_color.data,opacity,font);
}
//! Draw a text.
template<typename tc>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const tc *const foreground_color, const int background_color=0,
const float opacity=1, const unsigned int font_size=11, ...) {
static CImgList<T> font;
static unsigned int fsize = 0;
if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,foreground_color,(const tc*)background_color,opacity,font);
}
//! Draw a text.
template<typename tc>
CImg<T>& draw_text(const int x0, const int y0, const char *const text,
const int foreground_color, const tc *const background_color,
const float opacity=1, const unsigned int font_size=11, ...) {
static CImgList<T> font;
static unsigned int fsize = 0;
if (fsize!=font_size) { font = CImgList<T>::font(font_size); fsize = font_size; }
char tmp[2048] = { 0 }; cimg_std::va_list ap; va_start(ap,font_size); cimg_std::vsprintf(tmp,text,ap); va_end(ap);
return _draw_text(x0,y0,tmp,(tc*)foreground_color,background_color,opacity,font);
}
//! Draw a vector field in the instance image, using a colormap.
/**
\param flow Image of 2d vectors used as input data.
\param color Image of dimv()-D vectors corresponding to the color of each arrow.
\param sampling Length (in pixels) between each arrow.
\param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
\param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
\param opacity Opacity of the drawing.
\param pattern Used pattern to draw lines.
\note Clipping is supported.
**/
template<typename t1, typename t2>
CImg<T>& draw_quiver(const CImg<t1>& flow,
const t2 *const color, const float opacity=1,
const unsigned int sampling=25, const float factor=-20,
const int quiver_type=0, const unsigned int pattern=~0U) {
return draw_quiver(flow,CImg<t2>(color,dim,1,1,1,true),opacity,sampling,factor,quiver_type,pattern);
}
//! Draw a vector field in the instance image, using a colormap.
/**
\param flow Image of 2d vectors used as input data.
\param color Image of dimv()-D vectors corresponding to the color of each arrow.
\param sampling Length (in pixels) between each arrow.
\param factor Length factor of each arrow (if <0, computed as a percentage of the maximum length).
\param quiver_type Type of plot. Can be 0 (arrows) or 1 (segments).
\param opacity Opacity of the drawing.
\param pattern Used pattern to draw lines.
\note Clipping is supported.
**/
template<typename t1, typename t2>
CImg<T>& draw_quiver(const CImg<t1>& flow,
const CImg<t2>& color, const float opacity=1,
const unsigned int sampling=25, const float factor=-20,
const int quiver_type=0, const unsigned int pattern=~0U) {
if (!is_empty()) {
if (!flow || flow.dim!=2)
throw CImgArgumentException("CImg<%s>::draw_quiver() : Specified flow (%u,%u,%u,%u,%p) has wrong dimensions.",
pixel_type(),flow.width,flow.height,flow.depth,flow.dim,flow.data);
if (sampling<=0)
throw CImgArgumentException("CImg<%s>::draw_quiver() : Incorrect sampling value = %g",
pixel_type(),sampling);
const bool colorfield = (color.width==flow.width && color.height==flow.height && color.depth==1 && color.dim==dim);
if (is_overlapped(flow)) return draw_quiver(+flow,color,opacity,sampling,factor,quiver_type,pattern);
float vmax,fact;
if (factor<=0) {
float m, M = (float)flow.get_pointwise_norm(2).maxmin(m);
vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
fact = -factor;
} else { fact = factor; vmax = 1; }
for (unsigned int y=sampling/2; y<height; y+=sampling)
for (unsigned int x=sampling/2; x<width; x+=sampling) {
const unsigned int X = x*flow.width/width, Y = y*flow.height/height;
float u = (float)flow(X,Y,0,0)*fact/vmax, v = (float)flow(X,Y,0,1)*fact/vmax;
if (!quiver_type) {
const int xx = x+(int)u, yy = y+(int)v;
if (colorfield) draw_arrow(x,y,xx,yy,color.get_vector_at(X,Y).data,opacity,45,sampling/5.0f,pattern);
else draw_arrow(x,y,xx,yy,color,opacity,45,sampling/5.0f,pattern);
} else {
if (colorfield) draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color.get_vector_at(X,Y),opacity,pattern);
else draw_line((int)(x-0.5*u),(int)(y-0.5*v),(int)(x+0.5*u),(int)(y+0.5*v),color,opacity,pattern);
}
}
}
return *this;
}
//! Draw a 1D graph on the instance image.
/**
\param data Image containing the graph values I = f(x).
\param color Array of dimv() values of type \c T, defining the drawing color.
\param gtype Define the type of the plot :
- 0 = Plot using points clouds.
- 1 = Plot using linear interpolation (segments).
- 2 = Plot with bars.
- 3 = Plot using cubic interpolation (3-polynomials).
- 4 = Plot using cross clouds.
\param ymin Lower bound of the y-range.
\param ymax Upper bound of the y-range.
\param opacity Drawing opacity.
\param pattern Drawing pattern.
\note
- if \c ymin==ymax==0, the y-range is computed automatically from the input sample.
**/
template<typename t, typename tc>
CImg<T>& draw_graph(const CImg<t>& data,
const tc *const color, const float opacity=1,
const unsigned int plot_type=1, const unsigned int vertex_type=1,
const double ymin=0, const double ymax=0,
const unsigned int pattern=~0U) {
if (is_empty() || height<=1) return *this;;
const unsigned long siz = data.size();
if (!color)
throw CImgArgumentException("CImg<%s>::draw_graph() : Specified color is (null)",
pixel_type());
tc *color1 = 0, *color2 = 0;
if (plot_type==3) {
color1 = new tc[dim]; color2 = new tc[dim];
cimg_forV(*this,k) { color1[k] = (tc)(color[k]*0.6f); color2[k] = (tc)(color[k]*0.3f); }
}
double m = ymin, M = ymax;
if (ymin==ymax) m = (double)data.maxmin(M);
if (m==M) { --m; ++M; }
const float ca = (float)(M-m)/(height-1);
bool init_hatch = true;
// Draw graph edges
switch (plot_type%4) {
case 1 : { // Segments
int oX = 0, oY = (int)((data[0]-m)/ca);
for (unsigned long off = 1; off<siz; ++off) {
const int
X = (int)(off*width/siz),
Y = (int)((data[off]-m)/ca);
draw_line(oX,oY,X,Y,color,opacity,pattern,init_hatch);
oX = X; oY = Y;
init_hatch = false;
}
} break;
case 2 : { // Spline
const CImg<t> ndata = data.get_shared_points(0,siz-1);
int oY = (int)((data[0]-m)/ca);
cimg_forX(*this,x) {
const int Y = (int)((ndata._cubic_atX((float)x*ndata.width/width)-m)/ca);
if (x>0) draw_line(x,oY,x+1,Y,color,opacity,pattern,init_hatch);
init_hatch = false;
oY = Y;
}
} break;
case 3 : { // Bars
const int Y0 = (int)(-m/ca);
int oX = 0;
cimg_foroff(data,off) {
const int
X = (off+1)*width/siz-1,
Y = (int)((data[off]-m)/ca);
draw_rectangle(oX,Y0,X,Y,color1,opacity).
draw_line(oX,Y,oX,Y0,color2,opacity).
draw_line(oX,Y0,X,Y0,Y<=Y0?color2:color,opacity).
draw_line(X,Y,X,Y0,color,opacity).
draw_line(oX,Y,X,Y,Y<=Y0?color:color2,opacity);
oX = X+1;
}
} break;
default : break; // No edges
}
// Draw graph points
switch (vertex_type%8) {
case 1 : { // Point
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_point(X,Y,color,opacity);
}
} break;
case 2 : { // Standard Cross
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_line(X-3,Y,X+3,Y,color,opacity).draw_line(X,Y-3,X,Y+3,color,opacity);
}
} break;
case 3 : { // Rotated Cross
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_line(X-3,Y-3,X+3,Y+3,color,opacity).draw_line(X-3,Y+3,X+3,Y-3,color,opacity);
}
} break;
case 4 : { // Filled Circle
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_circle(X,Y,3,color,opacity);
}
} break;
case 5 : { // Outlined circle
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_circle(X,Y,3,color,opacity,0U);
}
} break;
case 6 : { // Square
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_rectangle(X-3,Y-3,X+3,Y+3,color,opacity,~0U);
}
} break;
case 7 : { // Diamond
cimg_foroff(data,off) {
const int X = off*width/siz, Y = (int)((data[off]-m)/ca);
draw_line(X,Y-4,X+4,Y,color,opacity).
draw_line(X+4,Y,X,Y+4,color,opacity).
draw_line(X,Y+4,X-4,Y,color,opacity).
draw_line(X-4,Y,X,Y-4,color,opacity);
}
} break;
default : break; // No vertices
}
if (color1) delete[] color1; if (color2) delete[] color2;
return *this;
}
//! Draw a 1D graph on the instance image.
template<typename t, typename tc>
CImg<T>& draw_graph(const CImg<t>& data,
const CImg<tc>& color, const float opacity=1,
const unsigned int plot_type=1, const unsigned int vertex_type=1,
const double ymin=0, const double ymax=0,
const unsigned int pattern=~0U) {
return draw_graph(data,color.data,opacity,plot_type,vertex_type,ymin,ymax,pattern);
}
//! Draw a labeled horizontal axis on the instance image.
/**
\param xvalues Lower bound of the x-range.
\param y Y-coordinate of the horizontal axis in the instance image.
\param color Array of dimv() values of type \c T, defining the drawing color.
\param opacity Drawing opacity.
\param pattern Drawing pattern.
\param opacity_out Drawing opacity of 'outside' axes.
\note if \c precision==0, precision of the labels is automatically computed.
**/
template<typename t, typename tc>
CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U) {
if (!is_empty()) {
int siz = (int)xvalues.size()-1;
if (siz<=0) draw_line(0,y,width-1,y,color,opacity,pattern);
else {
if (xvalues[0]<xvalues[siz]) draw_arrow(0,y,width-1,y,color,opacity,30,5,pattern);
else draw_arrow(width-1,y,0,y,color,opacity,30,5,pattern);
const int yt = (y+14)<dimy()?(y+3):(y-14);
char txt[32];
cimg_foroff(xvalues,x) {
cimg_std::sprintf(txt,"%g",(double)xvalues(x));
const int xi = (int)(x*(width-1)/siz), xt = xi-(int)cimg::strlen(txt)*3;
draw_point(xi,y-1,color,opacity).draw_point(xi,y+1,color,opacity).
draw_text(xt<0?0:xt,yt,txt,color,(tc*)0,opacity,11);
}
}
}
return *this;
}
//! Draw a labeled horizontal axis on the instance image.
template<typename t, typename tc>
CImg<T>& draw_axis(const CImg<t>& xvalues, const int y,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U) {
return draw_axis(xvalues,y,color.data,opacity,pattern);
}
//! Draw a labeled vertical axis on the instance image.
template<typename t, typename tc>
CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
const tc *const color, const float opacity=1,
const unsigned int pattern=~0U) {
if (!is_empty()) {
int siz = (int)yvalues.size()-1;
if (siz<=0) draw_line(x,0,x,height-1,color,opacity,pattern);
else {
if (yvalues[0]<yvalues[siz]) draw_arrow(x,0,x,height-1,color,opacity,30,5,pattern);
else draw_arrow(x,height-1,x,0,color,opacity,30,5,pattern);
char txt[32];
cimg_foroff(yvalues,y) {
cimg_std::sprintf(txt,"%g",(double)yvalues(y));
const int
yi = (int)(y*(height-1)/siz),
tmp = yi-5,
nyi = tmp<0?0:(tmp>=dimy()-11?dimy()-11:tmp),
xt = x-(int)cimg::strlen(txt)*7;
draw_point(x-1,yi,color,opacity).draw_point(x+1,yi,color,opacity);
if (xt>0) draw_text(xt,nyi,txt,color,(tc*)0,opacity,11);
else draw_text(x+3,nyi,txt,color,(tc*)0,opacity,11);
}
}
}
return *this;
}
//! Draw a labeled vertical axis on the instance image.
template<typename t, typename tc>
CImg<T>& draw_axis(const int x, const CImg<t>& yvalues,
const CImg<tc>& color, const float opacity=1,
const unsigned int pattern=~0U) {
return draw_axis(x,yvalues,color.data,opacity,pattern);
}
//! Draw a labeled horizontal+vertical axis on the instance image.
template<typename tx, typename ty, typename tc>
CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
const tc *const color, const float opacity=1,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
if (!is_empty()) {
const CImg<tx> nxvalues(xvalues.data,xvalues.size(),1,1,1,true);
const int sizx = (int)xvalues.size()-1, wm1 = (int)(width)-1;
if (sizx>0) {
float ox = (float)nxvalues[0];
for (unsigned int x = 1; x<width; ++x) {
const float nx = (float)nxvalues._linear_atX((float)x*sizx/wm1);
if (nx*ox<=0) { draw_axis(nx==0?x:x-1,yvalues,color,opacity,patterny); break; }
ox = nx;
}
}
const CImg<ty> nyvalues(yvalues.data,yvalues.size(),1,1,1,true);
const int sizy = (int)yvalues.size()-1, hm1 = (int)(height)-1;
if (sizy>0) {
float oy = (float)nyvalues[0];
for (unsigned int y = 1; y<height; ++y) {
const float ny = (float)nyvalues._linear_atX((float)y*sizy/hm1);
if (ny*oy<=0) { draw_axis(xvalues,ny==0?y:y-1,color,opacity,patternx); break; }
oy = ny;
}
}
}
return *this;
}
//! Draw a labeled horizontal+vertical axis on the instance image.
template<typename tx, typename ty, typename tc>
CImg<T>& draw_axis(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
const CImg<tc>& color, const float opacity=1,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
return draw_axis(xvalues,yvalues,color.data,opacity,patternx,patterny);
}
//! Draw a labeled horizontal+vertical axis on the instance image.
template<typename tc>
CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
const tc *const color, const float opacity=1,
const int subdivisionx=-60, const int subdivisiony=-60,
const float precisionx=0, const float precisiony=0,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
if (!is_empty()) {
const float
dx = cimg::abs(x1-x0), dy = cimg::abs(y1-y0),
px = (precisionx==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0):precisionx,
py = (precisiony==0)?(float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0):precisiony;
draw_axis(CImg<floatT>::sequence(subdivisionx>0?subdivisionx:1-dimx()/subdivisionx,x0,x1).round(px),
CImg<floatT>::sequence(subdivisiony>0?subdivisiony:1-dimy()/subdivisiony,y0,y1).round(py),
color,opacity,patternx,patterny);
}
return *this;
}
//! Draw a labeled horizontal+vertical axis on the instance image.
template<typename tc>
CImg<T>& draw_axis(const float x0, const float x1, const float y0, const float y1,
const CImg<tc>& color, const float opacity=1,
const int subdivisionx=-60, const int subdivisiony=-60,
const float precisionx=0, const float precisiony=0,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
return draw_axis(x0,x1,y0,y1,color.data,opacity,subdivisionx,subdivisiony,precisionx,precisiony,patternx,patterny);
}
//! Draw grid.
template<typename tx, typename ty, typename tc>
CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
const tc *const color, const float opacity=1,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
if (!is_empty()) {
if (xvalues) cimg_foroff(xvalues,x) {
const int xi = (int)xvalues[x];
if (xi>=0 && xi<dimx()) draw_line(xi,0,xi,height-1,color,opacity,patternx);
}
if (yvalues) cimg_foroff(yvalues,y) {
const int yi = (int)yvalues[y];
if (yi>=0 && yi<dimy()) draw_line(0,yi,width-1,yi,color,opacity,patterny);
}
}
return *this;
}
//! Draw grid.
template<typename tx, typename ty, typename tc>
CImg<T>& draw_grid(const CImg<tx>& xvalues, const CImg<ty>& yvalues,
const CImg<tc>& color, const float opacity=1,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
return draw_grid(xvalues,yvalues,color.data,opacity,patternx,patterny);
}
//! Draw grid.
template<typename tc>
CImg<T>& draw_grid(const float deltax, const float deltay,
const float offsetx, const float offsety,
const bool invertx, const bool inverty,
const tc *const color, const float opacity=1,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
CImg<uintT> seqx, seqy;
if (deltax!=0) {
const float dx = deltax>0?deltax:width*-deltax/100;
const unsigned int nx = (unsigned int)(width/dx);
seqx = CImg<uintT>::sequence(1+nx,0,(unsigned int)(dx*nx));
if (offsetx) cimg_foroff(seqx,x) seqx(x) = (unsigned int)cimg::mod(seqx(x)+offsetx,(float)width);
if (invertx) cimg_foroff(seqx,x) seqx(x) = width-1-seqx(x);
}
if (deltay!=0) {
const float dy = deltay>0?deltay:height*-deltay/100;
const unsigned int ny = (unsigned int)(height/dy);
seqy = CImg<uintT>::sequence(1+ny,0,(unsigned int)(dy*ny));
if (offsety) cimg_foroff(seqy,y) seqy(y) = (unsigned int)cimg::mod(seqy(y)+offsety,(float)height);
if (inverty) cimg_foroff(seqy,y) seqy(y) = height-1-seqy(y);
}
return draw_grid(seqx,seqy,color,opacity,patternx,patterny);
}
//! Draw grid.
template<typename tc>
CImg<T>& draw_grid(const float deltax, const float deltay,
const float offsetx, const float offsety,
const bool invertx, const bool inverty,
const CImg<tc>& color, const float opacity=1,
const unsigned int patternx=~0U, const unsigned int patterny=~0U) {
return draw_grid(deltax,deltay,offsetx,offsety,invertx,inverty,color.data,opacity,patternx,patterny);
}
//! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
/**
\param x X-coordinate of the starting point of the region to fill.
\param y Y-coordinate of the starting point of the region to fill.
\param z Z-coordinate of the starting point of the region to fill.
\param color An array of dimv() values of type \c T, defining the drawing color.
\param region Image that will contain the mask of the filled region mask, as an output.
\param sigma Tolerance concerning neighborhood values.
\param opacity Opacity of the drawing.
\param high_connexity Tells if 8-connexity must be used (only for 2D images).
\return \p region is initialized with the binary mask of the filled region.
**/
template<typename tc, typename t>
CImg<T>& draw_fill(const int x, const int y, const int z,
const tc *const color, const float opacity,
CImg<t>& region, const float sigma=0,
const bool high_connexity=false) {
#define _cimg_draw_fill_test(x,y,z,res) if (region(x,y,z)) res = false; else { \
res = true; \
const T *reference_col = reference_color.ptr() + dim, *ptrs = ptr(x,y,z) + siz; \
for (unsigned int i = dim; res && i; --i) { ptrs-=whz; res = (cimg::abs(*ptrs - *(--reference_col))<=sigma); } \
region(x,y,z) = (t)(res?1:noregion); \
}
#define _cimg_draw_fill_set(x,y,z) { \
const tc *col = color; \
T *ptrd = ptr(x,y,z); \
if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)*(col++); ptrd+=whz; } \
else cimg_forV(*this,k) { *ptrd = (T)(*(col++)*nopacity + *ptrd*copacity); ptrd+=whz; } \
}
#define _cimg_draw_fill_insert(x,y,z) { \
if (posr1>=remaining.height) remaining.resize(3,remaining.height<<1,1,1,0); \
unsigned int *ptrr = remaining.ptr(0,posr1); \
*(ptrr++) = x; *(ptrr++) = y; *(ptrr++) = z; ++posr1; \
}
#define _cimg_draw_fill_test_neighbor(x,y,z,cond) if (cond) { \
const unsigned int tx = x, ty = y, tz = z; \
_cimg_draw_fill_test(tx,ty,tz,res); if (res) _cimg_draw_fill_insert(tx,ty,tz); \
}
if (!color)
throw CImgArgumentException("CImg<%s>::draw_fill() : Specified color is (null).",
pixel_type());
region.assign(width,height,depth,1,(t)0);
if (x>=0 && x<dimx() && y>=0 && y<dimy() && z>=0 && z<dimz()) {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const unsigned int whz = width*height*depth, siz = dim*whz, W1 = width-1, H1 = height-1, D1 = depth-1;
const bool threed = depth>1;
const CImg<T> reference_color = get_vector_at(x,y,z);
CImg<uintT> remaining(3,512,1,1,0);
remaining(0,0) = x; remaining(1,0) = y; remaining(2,0) = z;
unsigned int posr0 = 0, posr1 = 1;
region(x,y,z) = (t)1;
const t noregion = ((t)1==(t)2)?(t)0:(t)(-1);
if (threed) do { // 3D version of the filling algorithm
const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++), zc = *(pcurr++);
if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
bool cont, res;
unsigned int nxc = xc;
do { // X-backward
_cimg_draw_fill_set(nxc,yc,zc);
_cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
_cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
_cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
_cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
} while (cont);
nxc = xc;
do { // X-forward
if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,zc,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(nxc,yc,zc);
_cimg_draw_fill_test_neighbor(nxc,yc-1,zc,yc!=0);
_cimg_draw_fill_test_neighbor(nxc,yc+1,zc,yc<H1);
_cimg_draw_fill_test_neighbor(nxc,yc,zc-1,zc!=0);
_cimg_draw_fill_test_neighbor(nxc,yc,zc+1,zc<D1);
}
} while (cont);
unsigned int nyc = yc;
do { // Y-backward
if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(xc,nyc,zc);
_cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
_cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
_cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
_cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
}
} while (cont);
nyc = yc;
do { // Y-forward
if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,zc,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(xc,nyc,zc);
_cimg_draw_fill_test_neighbor(xc-1,nyc,zc,xc!=0);
_cimg_draw_fill_test_neighbor(xc+1,nyc,zc,xc<W1);
_cimg_draw_fill_test_neighbor(xc,nyc,zc-1,zc!=0);
_cimg_draw_fill_test_neighbor(xc,nyc,zc+1,zc<D1);
}
} while (cont);
unsigned int nzc = zc;
do { // Z-backward
if (nzc) { --nzc; _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(xc,yc,nzc);
_cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
_cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
_cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
_cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
}
} while (cont);
nzc = zc;
do { // Z-forward
if ((++nzc)<=D1) { _cimg_draw_fill_test(xc,yc,nzc,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(xc,nyc,zc);
_cimg_draw_fill_test_neighbor(xc-1,yc,nzc,xc!=0);
_cimg_draw_fill_test_neighbor(xc+1,yc,nzc,xc<W1);
_cimg_draw_fill_test_neighbor(xc,yc-1,nzc,yc!=0);
_cimg_draw_fill_test_neighbor(xc,yc+1,nzc,yc<H1);
}
} while (cont);
} while (posr1>posr0);
else do { // 2D version of the filling algorithm
const unsigned int *pcurr = remaining.ptr(0,posr0++), xc = *(pcurr++), yc = *(pcurr++);
if (posr0>=512) { remaining.translate(0,posr0); posr1-=posr0; posr0 = 0; }
bool cont, res;
unsigned int nxc = xc;
do { // X-backward
_cimg_draw_fill_set(nxc,yc,0);
_cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
_cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
if (high_connexity) {
_cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
_cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
_cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
_cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
}
if (nxc) { --nxc; _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
} while (cont);
nxc = xc;
do { // X-forward
if ((++nxc)<=W1) { _cimg_draw_fill_test(nxc,yc,0,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(nxc,yc,0);
_cimg_draw_fill_test_neighbor(nxc,yc-1,0,yc!=0);
_cimg_draw_fill_test_neighbor(nxc,yc+1,0,yc<H1);
if (high_connexity) {
_cimg_draw_fill_test_neighbor(nxc-1,yc-1,0,(nxc!=0 && yc!=0));
_cimg_draw_fill_test_neighbor(nxc+1,yc-1,0,(nxc<W1 && yc!=0));
_cimg_draw_fill_test_neighbor(nxc-1,yc+1,0,(nxc!=0 && yc<H1));
_cimg_draw_fill_test_neighbor(nxc+1,yc+1,0,(nxc<W1 && yc<H1));
}
}
} while (cont);
unsigned int nyc = yc;
do { // Y-backward
if (nyc) { --nyc; _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(xc,nyc,0);
_cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
_cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
if (high_connexity) {
_cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
_cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
_cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
_cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
}
}
} while (cont);
nyc = yc;
do { // Y-forward
if ((++nyc)<=H1) { _cimg_draw_fill_test(xc,nyc,0,cont); } else cont = false;
if (cont) {
_cimg_draw_fill_set(xc,nyc,0);
_cimg_draw_fill_test_neighbor(xc-1,nyc,0,xc!=0);
_cimg_draw_fill_test_neighbor(xc+1,nyc,0,xc<W1);
if (high_connexity) {
_cimg_draw_fill_test_neighbor(xc-1,nyc-1,0,(xc!=0 && nyc!=0));
_cimg_draw_fill_test_neighbor(xc+1,nyc-1,0,(xc<W1 && nyc!=0));
_cimg_draw_fill_test_neighbor(xc-1,nyc+1,0,(xc!=0 && nyc<H1));
_cimg_draw_fill_test_neighbor(xc+1,nyc+1,0,(xc<W1 && nyc<H1));
}
}
} while (cont);
} while (posr1>posr0);
if (noregion) cimg_for(region,ptr,t) if (*ptr==noregion) *ptr = (t)0;
}
return *this;
}
//! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
template<typename tc, typename t>
CImg<T>& draw_fill(const int x, const int y, const int z,
const CImg<tc>& color, const float opacity,
CImg<t>& region, const float sigma=0, const bool high_connexity=false) {
return draw_fill(x,y,z,color.data,opacity,region,sigma,high_connexity);
}
//! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
/**
\param x = X-coordinate of the starting point of the region to fill.
\param y = Y-coordinate of the starting point of the region to fill.
\param z = Z-coordinate of the starting point of the region to fill.
\param color = an array of dimv() values of type \c T, defining the drawing color.
\param sigma = tolerance concerning neighborhood values.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_fill(const int x, const int y, const int z,
const tc *const color, const float opacity=1,
const float sigma=0, const bool high_connexity=false) {
CImg<boolT> tmp;
return draw_fill(x,y,z,color,opacity,tmp,sigma,high_connexity);
}
//! Draw a 3D filled region starting from a point (\c x,\c y,\ z) in the instance image.
template<typename tc>
CImg<T>& draw_fill(const int x, const int y, const int z,
const CImg<tc>& color, const float opacity=1,
const float sigma=0, const bool high_connexity=false) {
return draw_fill(x,y,z,color.data,opacity,sigma,high_connexity);
}
//! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
/**
\param x = X-coordinate of the starting point of the region to fill.
\param y = Y-coordinate of the starting point of the region to fill.
\param color = an array of dimv() values of type \c T, defining the drawing color.
\param sigma = tolerance concerning neighborhood values.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_fill(const int x, const int y,
const tc *const color, const float opacity=1,
const float sigma=0, const bool high_connexity=false) {
CImg<boolT> tmp;
return draw_fill(x,y,0,color,opacity,tmp,sigma,high_connexity);
}
//! Draw a 2D filled region starting from a point (\c x,\c y) in the instance image.
template<typename tc>
CImg<T>& draw_fill(const int x, const int y,
const CImg<tc>& color, const float opacity=1,
const float sigma=0, const bool high_connexity=false) {
return draw_fill(x,y,color.data,opacity,sigma,high_connexity);
}
//! Draw a plasma random texture.
/**
\param x0 = X-coordinate of the upper-left corner of the plasma.
\param y0 = Y-coordinate of the upper-left corner of the plasma.
\param x1 = X-coordinate of the lower-right corner of the plasma.
\param y1 = Y-coordinate of the lower-right corner of the plasma.
\param alpha = Alpha-parameter of the plasma.
\param beta = Beta-parameter of the plasma.
\param opacity = opacity of the drawing.
**/
CImg<T>& draw_plasma(const int x0, const int y0, const int x1, const int y1,
const float alpha=1, const float beta=1,
const float opacity=1) {
if (!is_empty()) {
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
int nx0 = x0, nx1 = x1, ny0 = y0, ny1 = y1;
if (nx1<nx0) cimg::swap(nx0,nx1);
if (ny1<ny0) cimg::swap(ny0,ny1);
if (nx0<0) nx0 = 0;
if (nx1>=dimx()) nx1 = width-1;
if (ny0<0) ny0 = 0;
if (ny1>=dimy()) ny1 = height-1;
const int xc = (nx0+nx1)/2, yc = (ny0+ny1)/2, dx = (xc-nx0), dy = (yc-ny0);
const Tfloat dc = (Tfloat)(cimg_std::sqrt((float)(dx*dx+dy*dy))*alpha + beta);
Tfloat val = 0;
cimg_forV(*this,k) {
if (opacity>=1) {
const Tfloat
val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)),
val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k));
(*this)(xc,ny0,0,k) = (T)((val0+val1)/2);
(*this)(xc,ny1,0,k) = (T)((val2+val3)/2);
(*this)(nx0,yc,0,k) = (T)((val0+val2)/2);
(*this)(nx1,yc,0,k) = (T)((val1+val3)/2);
do {
val = (Tfloat)(0.25f*((Tfloat)((*this)(nx0,ny0,0,k)) +
(Tfloat)((*this)(nx1,ny0,0,k)) +
(Tfloat)((*this)(nx1,ny1,0,k)) +
(Tfloat)((*this)(nx0,ny1,0,k))) +
dc*cimg::grand());
} while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
(*this)(xc,yc,0,k) = (T)val;
} else {
const Tfloat
val0 = (Tfloat)((*this)(nx0,ny0,0,k)), val1 = (Tfloat)((*this)(nx1,ny0,0,k)),
val2 = (Tfloat)((*this)(nx0,ny1,0,k)), val3 = (Tfloat)((*this)(nx1,ny1,0,k));
(*this)(xc,ny0,0,k) = (T)(((val0+val1)*nopacity + copacity*(*this)(xc,ny0,0,k))/2);
(*this)(xc,ny1,0,k) = (T)(((val2+val3)*nopacity + copacity*(*this)(xc,ny1,0,k))/2);
(*this)(nx0,yc,0,k) = (T)(((val0+val2)*nopacity + copacity*(*this)(nx0,yc,0,k))/2);
(*this)(nx1,yc,0,k) = (T)(((val1+val3)*nopacity + copacity*(*this)(nx1,yc,0,k))/2);
do {
val = (Tfloat)(0.25f*(((Tfloat)((*this)(nx0,ny0,0,k)) +
(Tfloat)((*this)(nx1,ny0,0,k)) +
(Tfloat)((*this)(nx1,ny1,0,k)) +
(Tfloat)((*this)(nx0,ny1,0,k))) +
dc*cimg::grand())*nopacity + copacity*(*this)(xc,yc,0,k));
} while (val<(Tfloat)cimg::type<T>::min() || val>(Tfloat)cimg::type<T>::max());
(*this)(xc,yc,0,k) = (T)val;
}
}
if (xc!=nx0 || yc!=ny0) {
draw_plasma(nx0,ny0,xc,yc,alpha,beta,opacity);
draw_plasma(xc,ny0,nx1,yc,alpha,beta,opacity);
draw_plasma(nx0,yc,xc,ny1,alpha,beta,opacity);
draw_plasma(xc,yc,nx1,ny1,alpha,beta,opacity);
}
}
return *this;
}
//! Draw a plasma random texture.
/**
\param alpha = Alpha-parameter of the plasma.
\param beta = Beta-parameter of the plasma.
\param opacity = opacity of the drawing.
**/
CImg<T>& draw_plasma(const float alpha=1, const float beta=1,
const float opacity=1) {
return draw_plasma(0,0,width-1,height-1,alpha,beta,opacity);
}
//! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
template<typename tc>
CImg<T>& draw_mandelbrot(const int x0, const int y0, const int x1, const int y1,
const CImg<tc>& color_palette, const float opacity=1,
const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
const unsigned int itermax=255,
const bool normalized_iteration=false,
const bool julia_set=false,
const double paramr=0, const double parami=0) {
if (is_empty()) return *this;
CImg<tc> palette;
if (color_palette) palette.assign(color_palette.data,color_palette.size()/color_palette.dim,1,1,color_palette.dim,true);
if (palette && palette.dim!=dim)
throw CImgArgumentException("CImg<%s>::draw_mandelbrot() : Specified color palette (%u,%u,%u,%u,%p) is not \n"
"compatible with instance image (%u,%u,%u,%u,%p).",
pixel_type(),color_palette.width,color_palette.height,color_palette.depth,color_palette.dim,
color_palette.data,width,height,depth,dim,data);
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0), ln2 = (float)cimg_std::log(2.0);
unsigned int iter = 0;
cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
const double x = z0r + p*(z1r-z0r)/width, y = z0i + q*(z1i-z0i)/height;
double zr, zi, cr, ci;
if (julia_set) { zr = x; zi = y; cr = paramr; ci = parami; }
else { zr = paramr; zi = parami; cr = x; ci = y; }
for (iter=1; zr*zr + zi*zi<=4 && iter<=itermax; ++iter) {
const double temp = zr*zr - zi*zi + cr;
zi = 2*zr*zi + ci;
zr = temp;
}
if (iter>itermax) {
if (palette) {
if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette(0,k);
else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(0,k)*nopacity + (*this)(p,q,0,k)*copacity);
} else {
if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)0;
else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)((*this)(p,q,0,k)*copacity);
}
} else if (normalized_iteration) {
const float
normz = (float)cimg::abs(zr*zr+zi*zi),
niter = (float)(iter + 1 - cimg_std::log(cimg_std::log(normz))/ln2);
if (palette) {
if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._linear_atX(niter,k);
else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette._linear_atX(niter,k)*nopacity + (*this)(p,q,0,k)*copacity);
} else {
if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)niter;
else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(niter*nopacity + (*this)(p,q,0,k)*copacity);
}
} else {
if (palette) {
if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)palette._atX(iter,k);
else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(palette(iter,k)*nopacity + (*this)(p,q,0,k)*copacity);
} else {
if (opacity>=1) cimg_forV(*this,k) (*this)(p,q,0,k) = (T)iter;
else cimg_forV(*this,k) (*this)(p,q,0,k) = (T)(iter*nopacity + (*this)(p,q,0,k)*copacity);
}
}
}
return *this;
}
//! Draw a quadratic Mandelbrot or Julia fractal set, computed using the Escape Time Algorithm.
template<typename tc>
CImg<T>& draw_mandelbrot(const CImg<tc>& color_palette, const float opacity=1,
const double z0r=-2, const double z0i=-2, const double z1r=2, const double z1i=2,
const unsigned int itermax=255,
const bool normalized_iteration=false,
const bool julia_set=false,
const double paramr=0, const double parami=0) {
return draw_mandelbrot(0,0,width-1,height-1,color_palette,opacity,z0r,z0i,z1r,z1i,itermax,normalized_iteration,julia_set,paramr,parami);
}
//! Draw a 1D gaussian function in the instance image.
/**
\param xc = X-coordinate of the gaussian center.
\param sigma = Standard variation of the gaussian distribution.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_gaussian(const float xc, const float sigma,
const tc *const color, const float opacity=1) {
if (is_empty()) return *this;
if (!color)
throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",
pixel_type());
const float sigma2 = 2*sigma*sigma, nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const unsigned int whz = width*height*depth;
const tc *col = color;
cimg_forX(*this,x) {
const float dx = (x - xc), val = (float)cimg_std::exp(-dx*dx/sigma2);
T *ptrd = ptr(x,0,0,0);
if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
col-=dim;
}
return *this;
}
//! Draw a 1D gaussian function in the instance image.
template<typename tc>
CImg<T>& draw_gaussian(const float xc, const float sigma,
const CImg<tc>& color, const float opacity=1) {
return draw_gaussian(xc,sigma,color.data,opacity);
}
//! Draw an anisotropic 2D gaussian function.
/**
\param xc = X-coordinate of the gaussian center.
\param yc = Y-coordinate of the gaussian center.
\param tensor = 2x2 covariance matrix.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename t, typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
const tc *const color, const float opacity=1) {
if (is_empty()) return *this;
typedef typename cimg::superset<t,float>::type tfloat;
if (tensor.width!=2 || tensor.height!=2 || tensor.depth!=1 || tensor.dim!=1)
throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 2x2 matrix.",
pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
if (!color)
throw CImgArgumentException("CImg<%s>::draw_gaussian() : Specified color is (null)",
pixel_type());
const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
const tfloat a = invT2(0,0), b = 2*invT2(1,0), c = invT2(1,1);
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const unsigned int whz = width*height*depth;
const tc *col = color;
float dy = -yc;
cimg_forY(*this,y) {
float dx = -xc;
cimg_forX(*this,x) {
const float val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dy*dy);
T *ptrd = ptr(x,y,0,0);
if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
col-=dim;
++dx;
}
++dy;
}
return *this;
}
//! Draw an anisotropic 2D gaussian function.
template<typename t, typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const CImg<t>& tensor,
const CImg<tc>& color, const float opacity=1) {
return draw_gaussian(xc,yc,tensor,color.data,opacity);
}
//! Draw an anisotropic 2D gaussian function.
template<typename tc>
CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
const tc *const color, const float opacity=1) {
const double
a = r1*ru*ru + r2*rv*rv,
b = (r1-r2)*ru*rv,
c = r1*rv*rv + r2*ru*ru;
const CImg<Tfloat> tensor(2,2,1,1, a,b,b,c);
return draw_gaussian(xc,yc,tensor,color,opacity);
}
//! Draw an anisotropic 2D gaussian function.
template<typename tc>
CImg<T>& draw_gaussian(const int xc, const int yc, const float r1, const float r2, const float ru, const float rv,
const CImg<tc>& color, const float opacity=1) {
return draw_gaussian(xc,yc,r1,r2,ru,rv,color.data,opacity);
}
//! Draw an isotropic 2D gaussian function.
/**
\param xc = X-coordinate of the gaussian center.
\param yc = Y-coordinate of the gaussian center.
\param sigma = standard variation of the gaussian distribution.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
const tc *const color, const float opacity=1) {
return draw_gaussian(xc,yc,CImg<floatT>::diagonal(sigma,sigma),color,opacity);
}
//! Draw an isotropic 2D gaussian function.
template<typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const float sigma,
const CImg<tc>& color, const float opacity=1) {
return draw_gaussian(xc,yc,sigma,color.data,opacity);
}
//! Draw an anisotropic 3D gaussian function.
/**
\param xc = X-coordinate of the gaussian center.
\param yc = Y-coordinate of the gaussian center.
\param zc = Z-coordinate of the gaussian center.
\param tensor = 3x3 covariance matrix.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename t, typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
const tc *const color, const float opacity=1) {
if (is_empty()) return *this;
typedef typename cimg::superset<t,float>::type tfloat;
if (tensor.width!=3 || tensor.height!=3 || tensor.depth!=1 || tensor.dim!=1)
throw CImgArgumentException("CImg<%s>::draw_gaussian() : Tensor parameter (%u,%u,%u,%u,%p) is not a 3x3 matrix.",
pixel_type(),tensor.width,tensor.height,tensor.depth,tensor.dim,tensor.data);
const CImg<tfloat> invT = tensor.get_invert(), invT2 = (invT*invT)/(-2.0);
const tfloat a = invT(0,0), b = 2*invT(1,0), c = 2*invT(2,0), d = invT(1,1), e = 2*invT(2,1), f = invT(2,2);
const float nopacity = cimg::abs(opacity), copacity = 1 - cimg::max(opacity,0);
const unsigned int whz = width*height*depth;
const tc *col = color;
cimg_forXYZ(*this,x,y,z) {
const float
dx = (x - xc), dy = (y - yc), dz = (z - zc),
val = (float)cimg_std::exp(a*dx*dx + b*dx*dy + c*dx*dz + d*dy*dy + e*dy*dz + f*dz*dz);
T *ptrd = ptr(x,y,z,0);
if (opacity>=1) cimg_forV(*this,k) { *ptrd = (T)(val*(*col++)); ptrd+=whz; }
else cimg_forV(*this,k) { *ptrd = (T)(nopacity*val*(*col++) + *ptrd*copacity); ptrd+=whz; }
col-=dim;
}
return *this;
}
//! Draw an anisotropic 3D gaussian function.
template<typename t, typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const CImg<t>& tensor,
const CImg<tc>& color, const float opacity=1) {
return draw_gaussian(xc,yc,zc,tensor,color.data,opacity);
}
//! Draw an isotropic 3D gaussian function.
/**
\param xc = X-coordinate of the gaussian center.
\param yc = Y-coordinate of the gaussian center.
\param zc = Z-coordinate of the gaussian center.
\param sigma = standard variation of the gaussian distribution.
\param color = array of dimv() values of type \c T, defining the drawing color.
\param opacity = opacity of the drawing.
**/
template<typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
const tc *const color, const float opacity=1) {
return draw_gaussian(xc,yc,zc,CImg<floatT>::diagonal(sigma,sigma,sigma),color,opacity);
}
//! Draw an isotropic 3D gaussian function.
template<typename tc>
CImg<T>& draw_gaussian(const float xc, const float yc, const float zc, const float sigma,
const CImg<tc>& color, const float opacity=1) {
return draw_gaussian(xc,yc,zc,sigma,color.data,opacity);
}
// Draw a 3D object (internal)
template<typename tc, typename to>
void _draw_object3d_sprite(const int x, const int y,
const CImg<tc>& color, const CImg<to>& opacity, const CImg<T>& sprite) {
if (opacity.width==color.width && opacity.height==color.height)
draw_image(x,y,sprite,opacity.get_resize(sprite.width,sprite.height,1,sprite.dim,1));
else
draw_image(x,y,sprite,opacity(0));
}
template<typename tc>
void _draw_object3d_sprite(const int x, const int y,
const CImg<tc>& color, const float opacity, const CImg<T>& sprite) {
if (color) draw_image(x,y,sprite,opacity);
}
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& _draw_object3d(void *const pboard, float *const zbuffer,
const float X, const float Y, const float Z,
const tp& points, const unsigned int nb_points,
const CImgList<tf>& primitives,
const CImgList<tc>& colors,
const to& opacities, const unsigned int nb_opacities,
const unsigned int render_type,
const bool double_sided, const float focale,
const float lightx, const float lighty, const float lightz,
const float specular_light, const float specular_shine) {
if (is_empty()) return *this;
#ifndef cimg_use_board
if (pboard) return *this;
#endif
const float
nspec = 1-(specular_light<0?0:(specular_light>1?1:specular_light)),
nspec2 = 1+(specular_shine<0?0:specular_shine),
nsl1 = (nspec2-1)/cimg::sqr(nspec-1),
nsl2 = (1-2*nsl1*nspec),
nsl3 = nspec2-nsl1-nsl2;
// Create light texture for phong-like rendering
static CImg<floatT> light_texture;
if (render_type==5) {
if (colors.size>primitives.size) light_texture.assign(colors[primitives.size])/=255;
else {
static float olightx = 0, olighty = 0, olightz = 0, ospecular_shine = 0;
if (!light_texture || lightx!=olightx || lighty!=olighty || lightz!=olightz || specular_shine!=ospecular_shine) {
light_texture.assign(512,512);
const float white[] = { 1 },
dlx = lightx-X, dly = lighty-Y, dlz = lightz-Z,
nl = (float)cimg_std::sqrt(dlx*dlx+dly*dly+dlz*dlz),
nlx = light_texture.width/2*(1+dlx/nl),
nly = light_texture.height/2*(1+dly/nl);
light_texture.draw_gaussian(nlx,nly,light_texture.width/3.0f,white);
cimg_forXY(light_texture,x,y) {
const float factor = light_texture(x,y);
if (factor>nspec) light_texture(x,y) = cimg::min(2,nsl1*factor*factor+nsl2*factor+nsl3);
}
olightx = lightx; olighty = lighty; olightz = lightz; ospecular_shine = specular_shine;
}
}
}
// Compute 3D to 2D projection
CImg<floatT> projections(nb_points,2);
cimg_forX(projections,l) {
const float
x = (float)points(l,0),
y = (float)points(l,1),
z = (float)points(l,2);
const float projectedz = z + Z + focale;
projections(l,1) = Y + focale*y/projectedz;
projections(l,0) = X + focale*x/projectedz;
}
// Compute and sort visible primitives
CImg<uintT> visibles(primitives.size);
CImg<floatT> zrange(primitives.size);
unsigned int nb_visibles = 0;
const float zmin = -focale+1.5f;
{ cimglist_for(primitives,l) {
const CImg<tf>& primitive = primitives[l];
switch (primitive.size()) {
case 1 : { // Point
const unsigned int i0 = (unsigned int)primitive(0);
const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
if (z0>zmin && x0>=0 && x0<width && y0>=0 && y0<height) {
visibles(nb_visibles) = (unsigned int)l;
zrange(nb_visibles++) = z0;
}
} break;
case 5 : { // Sphere
const unsigned int
i0 = (unsigned int)primitive(0),
i1 = (unsigned int)primitive(1),
i2 = (unsigned int)primitive(2);
const float x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2));
int radius;
if (i2) radius = (int)(i2*focale/(z0+focale));
else {
const float x1 = projections(i1,0), y1 = projections(i1,1);
const int deltax = (int)(x1-x0), deltay = (int)(y1-y0);
radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay));
}
if (z0>zmin && x0+radius>=0 && x0-radius<width && y0+radius>=0 && y0-radius<height) {
visibles(nb_visibles) = (unsigned int)l;
zrange(nb_visibles++) = z0;
}
} break;
case 2 : // Line
case 6 : {
const unsigned int
i0 = (unsigned int)primitive(0),
i1 = (unsigned int)primitive(1);
const float
x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2));
float xm, xM, ym, yM;
if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
if (z0>zmin && z1>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
visibles(nb_visibles) = (unsigned int)l;
zrange(nb_visibles++) = 0.5f*(z0+z1);
}
} break;
case 3 : // Triangle
case 9 : {
const unsigned int
i0 = (unsigned int)primitive(0),
i1 = (unsigned int)primitive(1),
i2 = (unsigned int)primitive(2);
const float
x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2));
float xm, xM, ym, yM;
if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
if (x2<xm) xm = x2;
if (x2>xM) xM = x2;
if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
if (y2<ym) ym = y2;
if (y2>yM) yM = y2;
if (z0>zmin && z1>zmin && z2>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
const float d = (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0);
if (double_sided || d<0) {
visibles(nb_visibles) = (unsigned int)l;
zrange(nb_visibles++) = (z0+z1+z2)/3;
}
}
} break;
case 4 : // Rectangle
case 12 : {
const unsigned int
i0 = (unsigned int)primitive(0),
i1 = (unsigned int)primitive(1),
i2 = (unsigned int)primitive(2),
i3 = (unsigned int)primitive(3);
const float
x0 = projections(i0,0), y0 = projections(i0,1), z0 = (float)(Z+points(i0,2)),
x1 = projections(i1,0), y1 = projections(i1,1), z1 = (float)(Z+points(i1,2)),
x2 = projections(i2,0), y2 = projections(i2,1), z2 = (float)(Z+points(i2,2)),
x3 = projections(i3,0), y3 = projections(i3,1), z3 = (float)(Z+points(i3,2));
float xm, xM, ym, yM;
if (x0<x1) { xm = x0; xM = x1; } else { xm = x1; xM = x0; }
if (x2<xm) xm = x2;
if (x2>xM) xM = x2;
if (x3<xm) xm = x3;
if (x3>xM) xM = x3;
if (y0<y1) { ym = y0; yM = y1; } else { ym = y1; yM = y0; }
if (y2<ym) ym = y2;
if (y2>yM) yM = y2;
if (y3<ym) ym = y3;
if (y3>yM) yM = y3;
if (z0>zmin && z1>zmin && z2>zmin && z3>zmin && xM>=0 && xm<width && yM>=0 && ym<height) {
const float d = (x1 - x0)*(y2 - y0) - (x2 - x0)*(y1 - y0);
if (double_sided || d<0) {
visibles(nb_visibles) = (unsigned int)l;
zrange(nb_visibles++) = (z0 + z1 + z2 + z3)/4;
}
}
} break;
default :
throw CImgArgumentException("CImg<%s>::draw_object3d() : Primitive %u is invalid (size = %u, can be 1,2,3,4,5,6,9 or 12)",
pixel_type(),l,primitive.size());
}}
}
if (nb_visibles<=0) return *this;
CImg<uintT> permutations;
CImg<floatT>(zrange.data,nb_visibles,1,1,1,true).sort(permutations,false);
// Compute light properties
CImg<floatT> lightprops;
switch (render_type) {
case 3 : { // Flat Shading
lightprops.assign(nb_visibles);
cimg_forX(lightprops,l) {
const CImg<tf>& primitive = primitives(visibles(permutations(l)));
const unsigned int psize = primitive.size();
if (psize==3 || psize==4 || psize==9 || psize==12) {
const unsigned int
i0 = (unsigned int)primitive(0),
i1 = (unsigned int)primitive(1),
i2 = (unsigned int)primitive(2);
const float
x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
nx = dy1*dz2 - dz1*dy2,
ny = dz1*dx2 - dx1*dz2,
nz = dx1*dy2 - dy1*dx2,
norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
lx = X + (x0 + x1 + x2)/3 - lightx,
ly = Y + (y0 + y1 + y2)/3 - lighty,
lz = Z + (z0 + z1 + z2)/3 - lightz,
nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
factor = cimg::max(cimg::abs(-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
lightprops[l] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
} else lightprops[l] = 1;
}
} break;
case 4 : // Gouraud Shading
case 5 : { // Phong-Shading
CImg<floatT> points_normals(nb_points,3,1,1,0);
for (unsigned int l=0; l<nb_visibles; ++l) {
const CImg<tf>& primitive = primitives[visibles(l)];
const unsigned int psize = primitive.size();
const bool
triangle_flag = (psize==3) || (psize==9),
rectangle_flag = (psize==4) || (psize==12);
if (triangle_flag || rectangle_flag) {
const unsigned int
i0 = (unsigned int)primitive(0),
i1 = (unsigned int)primitive(1),
i2 = (unsigned int)primitive(2),
i3 = rectangle_flag?(unsigned int)primitive(3):0;
const float
x0 = (float)points(i0,0), y0 = (float)points(i0,1), z0 = (float)points(i0,2),
x1 = (float)points(i1,0), y1 = (float)points(i1,1), z1 = (float)points(i1,2),
x2 = (float)points(i2,0), y2 = (float)points(i2,1), z2 = (float)points(i2,2),
dx1 = x1 - x0, dy1 = y1 - y0, dz1 = z1 - z0,
dx2 = x2 - x0, dy2 = y2 - y0, dz2 = z2 - z0,
nnx = dy1*dz2 - dz1*dy2,
nny = dz1*dx2 - dx1*dz2,
nnz = dx1*dy2 - dy1*dx2,
norm = 1e-5f + (float)cimg_std::sqrt(nnx*nnx + nny*nny + nnz*nnz),
nx = nnx/norm,
ny = nny/norm,
nz = nnz/norm;
points_normals(i0,0)+=nx; points_normals(i0,1)+=ny; points_normals(i0,2)+=nz;
points_normals(i1,0)+=nx; points_normals(i1,1)+=ny; points_normals(i1,2)+=nz;
points_normals(i2,0)+=nx; points_normals(i2,1)+=ny; points_normals(i2,2)+=nz;
if (rectangle_flag) { points_normals(i3,0)+=nx; points_normals(i3,1)+=ny; points_normals(i3,2)+=nz; }
}
}
if (double_sided) cimg_forX(points_normals,p) if (points_normals(p,2)>0) {
points_normals(p,0) = -points_normals(p,0);
points_normals(p,1) = -points_normals(p,1);
points_normals(p,2) = -points_normals(p,2);
}
if (render_type==4) {
lightprops.assign(nb_points);
cimg_forX(lightprops,ll) {
const float
nx = points_normals(ll,0),
ny = points_normals(ll,1),
nz = points_normals(ll,2),
norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
lx = (float)(X + points(ll,0) - lightx),
ly = (float)(Y + points(ll,1) - lighty),
lz = (float)(Z + points(ll,2) - lightz),
nl = (float)cimg_std::sqrt(1e-5f + lx*lx + ly*ly + lz*lz),
factor = cimg::max((-lx*nx-ly*ny-lz*nz)/(norm*nl),0);
lightprops[ll] = factor<=nspec?factor:(nsl1*factor*factor + nsl2*factor + nsl3);
}
} else {
const unsigned int
lw2 = light_texture.width/2 - 1,
lh2 = light_texture.height/2 - 1;
lightprops.assign(nb_points,2);
cimg_forX(lightprops,ll) {
const float
nx = points_normals(ll,0),
ny = points_normals(ll,1),
nz = points_normals(ll,2),
norm = (float)cimg_std::sqrt(1e-5f + nx*nx + ny*ny + nz*nz),
nnx = nx/norm,
nny = ny/norm;
lightprops(ll,0) = lw2*(1 + nnx);
lightprops(ll,1) = lh2*(1 + nny);
}
}
} break;
}
// Draw visible primitives
const CImg<tc> default_color(1,dim,1,1,(tc)200);
{ for (unsigned int l = 0; l<nb_visibles; ++l) {
const unsigned int n_primitive = visibles(permutations(l));
const CImg<tf>& primitive = primitives[n_primitive];
const CImg<tc>& color = n_primitive<colors.size?colors[n_primitive]:default_color;
const float opac = n_primitive<nb_opacities?opacities(n_primitive,0):1.0f;
#ifdef cimg_use_board
BoardLib::Board &board = *(BoardLib::Board*)pboard;
#endif
switch (primitive.size()) {
case 1 : { // Colored point or sprite
const unsigned int n0 = (unsigned int)primitive[0];
const int x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
if (color.size()==dim) {
draw_point(x0,y0,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.fillCircle((float)x0,dimy()-(float)y0,0);
}
#endif
} else {
const float z = Z + points(n0,2);
const int
factor = (int)(focale*100/(z+focale)),
sw = color.width*factor/200,
sh = color.height*factor/200;
if (x0+sw>=0 && x0-sw<dimx() && y0+sh>=0 && y0-sh<dimy()) {
const CImg<T> sprite = color.get_resize(-factor,-factor,1,-100,render_type<=3?1:3);
_draw_object3d_sprite(x0-sw,y0-sh,color,opacities[n_primitive%nb_opacities],sprite);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128);
board.setFillColor(BoardLib::Color::none);
board.drawRectangle((float)x0-sw,dimy()-(float)y0+sh,sw,sh);
}
#endif
}
}
} break;
case 2 : { // Colored line
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
const float
z0 = points(n0,2) + Z + focale,
z1 = points(n1,2) + Z + focale;
if (render_type) {
if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac);
else draw_line(x0,y0,x1,y1,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.drawLine((float)x0,dimy()-(float)y0,x1,dimy()-(float)y1);
}
#endif
} else {
draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.drawCircle((float)x0,dimy()-(float)y0,0);
board.drawCircle((float)x1,dimy()-(float)y1,0);
}
#endif
}
} break;
case 5 : { // Colored sphere
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1],
n2 = (unsigned int)primitive[2];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1);
int radius;
if (n2) radius = (int)(n2*focale/(Z+points(n0,2)+focale));
else {
const int
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
deltax = x1-x0, deltay = y1-y0;
radius = (int)cimg_std::sqrt((float)(deltax*deltax + deltay*deltay));
}
switch (render_type) {
case 0 :
draw_point(x0,y0,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.fillCircle((float)x0,dimy()-(float)y0,0);
}
#endif
break;
case 1 :
draw_circle(x0,y0,radius,color,opac,~0U);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.setFillColor(BoardLib::Color::none);
board.drawCircle((float)x0,dimy()-(float)y0,(float)radius);
}
#endif
break;
default :
draw_circle(x0,y0,radius,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.fillCircle((float)x0,dimy()-(float)y0,(float)radius);
}
#endif
break;
}
} break;
case 6 : { // Textured line
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1],
tx0 = (unsigned int)primitive[2],
ty0 = (unsigned int)primitive[3],
tx1 = (unsigned int)primitive[4],
ty1 = (unsigned int)primitive[5];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1);
const float
z0 = points(n0,2) + Z + focale,
z1 = points(n1,2) + Z + focale;
if (render_type) {
if (zbuffer) draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac);
else draw_line(x0,y0,x1,y1,color,tx0,ty0,tx1,ty1,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
}
#endif
} else {
draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.drawCircle((float)x0,dimy()-(float)y0,0);
board.drawCircle((float)x1,dimy()-(float)y1,0);
}
#endif
}
} break;
case 3 : { // Colored triangle
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1],
n2 = (unsigned int)primitive[2];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
const float
z0 = points(n0,2) + Z + focale,
z1 = points(n1,2) + Z + focale,
z2 = points(n2,2) + Z + focale;
switch (render_type) {
case 0 :
draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).draw_point(x2,y2,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.drawCircle((float)x0,dimy()-(float)y0,0);
board.drawCircle((float)x1,dimy()-(float)y1,0);
board.drawCircle((float)x2,dimy()-(float)y2,0);
}
#endif
break;
case 1 :
if (zbuffer)
draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,opac).
draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac);
else
draw_line(x0,y0,x1,y1,color,opac).draw_line(x0,y0,x2,y2,color,opac).
draw_line(x1,y1,x2,y2,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2);
board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
}
#endif
break;
case 2 :
if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac);
else draw_triangle(x0,y0,x1,y1,x2,y2,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
}
#endif
break;
case 3 :
if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l));
else _draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l));
#ifdef cimg_use_board
if (pboard) {
const float lp = cimg::min(lightprops(l),1);
board.setPenColorRGBi((unsigned char)(color[0]*lp),
(unsigned char)(color[1]*lp),
(unsigned char)(color[2]*lp),
(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
}
#endif
break;
case 4 :
if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
else draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprops(n0),lightprops(n1),lightprops(n2),opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi((unsigned char)(color[0]),
(unsigned char)(color[1]),
(unsigned char)(color[2]),
(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0),
(float)x1,dimy()-(float)y1,lightprops(n1),
(float)x2,dimy()-(float)y2,lightprops(n2));
}
#endif
break;
case 5 : {
const unsigned int
lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1);
if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
else draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac);
#ifdef cimg_use_board
if (pboard) {
const float
l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))),
l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))),
l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1))));
board.setPenColorRGBi((unsigned char)(color[0]),
(unsigned char)(color[1]),
(unsigned char)(color[2]),
(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
(float)x1,dimy()-(float)y1,l1,
(float)x2,dimy()-(float)y2,l2);
}
#endif
} break;
}
} break;
case 4 : { // Colored rectangle
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1],
n2 = (unsigned int)primitive[2],
n3 = (unsigned int)primitive[3];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
const float
z0 = points(n0,2) + Z + focale,
z1 = points(n1,2) + Z + focale,
z2 = points(n2,2) + Z + focale,
z3 = points(n3,2) + Z + focale;
switch (render_type) {
case 0 :
draw_point(x0,y0,color,opac).draw_point(x1,y1,color,opac).
draw_point(x2,y2,color,opac).draw_point(x3,y3,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.drawCircle((float)x0,dimy()-(float)y0,0);
board.drawCircle((float)x1,dimy()-(float)y1,0);
board.drawCircle((float)x2,dimy()-(float)y2,0);
board.drawCircle((float)x3,dimy()-(float)y3,0);
}
#endif
break;
case 1 :
if (zbuffer)
draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,opac).draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,opac).
draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,opac).draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,opac);
else
draw_line(x0,y0,x1,y1,color,opac).draw_line(x1,y1,x2,y2,color,opac).
draw_line(x2,y2,x3,y3,color,opac).draw_line(x3,y3,x0,y0,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0);
}
#endif
break;
case 2 :
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,opac).draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,opac);
else
draw_triangle(x0,y0,x1,y1,x2,y2,color,opac).draw_triangle(x0,y0,x2,y2,x3,y3,color,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(color[0],color[1],color[2],(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
}
#endif
break;
case 3 :
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color.data,opac,lightprops(l)).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color.data,opac,lightprops(l));
else
_draw_triangle(x0,y0,x1,y1,x2,y2,color.data,opac,lightprops(l)).
_draw_triangle(x0,y0,x2,y2,x3,y3,color.data,opac,lightprops(l));
#ifdef cimg_use_board
if (pboard) {
const float lp = cimg::min(lightprops(l),1);
board.setPenColorRGBi((unsigned char)(color[0]*lp),
(unsigned char)(color[1]*lp),
(unsigned char)(color[2]*lp),(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
}
#endif
break;
case 4 : {
const float
lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,lightprop0,lightprop1,lightprop2,opac).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,lightprop0,lightprop2,lightprop3,opac);
else
draw_triangle(x0,y0,x1,y1,x2,y2,color,lightprop0,lightprop1,lightprop2,opac).
draw_triangle(x0,y0,x2,y2,x3,y3,color,lightprop0,lightprop2,lightprop3,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi((unsigned char)(color[0]),
(unsigned char)(color[1]),
(unsigned char)(color[2]),
(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
(float)x1,dimy()-(float)y1,lightprop1,
(float)x2,dimy()-(float)y2,lightprop2);
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
(float)x2,dimy()-(float)y2,lightprop2,
(float)x3,dimy()-(float)y3,lightprop3);
}
#endif
} break;
case 5 : {
const unsigned int
lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
else
draw_triangle(x0,y0,x1,y1,x2,y2,color,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
draw_triangle(x0,y0,x2,y2,x3,y3,color,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
#ifdef cimg_use_board
if (pboard) {
const float
l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))),
l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))),
l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))),
l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3)));
board.setPenColorRGBi((unsigned char)(color[0]),
(unsigned char)(color[1]),
(unsigned char)(color[2]),
(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
(float)x1,dimy()-(float)y1,l1,
(float)x2,dimy()-(float)y2,l2);
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
(float)x2,dimy()-(float)y2,l2,
(float)x3,dimy()-(float)y3,l3);
}
#endif
} break;
}
} break;
case 9 : { // Textured triangle
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1],
n2 = (unsigned int)primitive[2],
tx0 = (unsigned int)primitive[3],
ty0 = (unsigned int)primitive[4],
tx1 = (unsigned int)primitive[5],
ty1 = (unsigned int)primitive[6],
tx2 = (unsigned int)primitive[7],
ty2 = (unsigned int)primitive[8];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
x2 = (int)projections(n2,0), y2 = (int)projections(n2,1);
const float
z0 = points(n0,2) + Z + focale,
z1 = points(n1,2) + Z + focale,
z2 = points(n2,2) + Z + focale;
switch (render_type) {
case 0 :
draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.drawCircle((float)x0,dimy()-(float)y0,0);
board.drawCircle((float)x1,dimy()-(float)y1,0);
board.drawCircle((float)x2,dimy()-(float)y2,0);
}
#endif
break;
case 1 :
if (zbuffer)
draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
draw_line(zbuffer,x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
else
draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
draw_line(x0,y0,z0,x2,y2,z2,color,tx0,ty0,tx2,ty2,opac).
draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
board.drawLine((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2);
board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
}
#endif
break;
case 2 :
if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
}
#endif
break;
case 3 :
if (zbuffer) draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
else draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l));
#ifdef cimg_use_board
if (pboard) {
const float lp = cimg::min(lightprops(l),1);
board.setPenColorRGBi((unsigned char)(128*lp),
(unsigned char)(128*lp),
(unsigned char)(128*lp),
(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
}
#endif
break;
case 4 :
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
else
draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprops(n0),lightprops(n1),lightprops(n2),opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprops(n0),
(float)x1,dimy()-(float)y1,lightprops(n1),
(float)x2,dimy()-(float)y2,lightprops(n2));
}
#endif
break;
case 5 :
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
(unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1),
(unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1),
(unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1),
opac);
else
draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,
(unsigned int)lightprops(n0,0), (unsigned int)lightprops(n0,1),
(unsigned int)lightprops(n1,0), (unsigned int)lightprops(n1,1),
(unsigned int)lightprops(n2,0), (unsigned int)lightprops(n2,1),
opac);
#ifdef cimg_use_board
if (pboard) {
const float
l0 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n0,0))), (int)(light_texture.dimy()/2*(1+lightprops(n0,1)))),
l1 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n1,0))), (int)(light_texture.dimy()/2*(1+lightprops(n1,1)))),
l2 = light_texture((int)(light_texture.dimx()/2*(1+lightprops(n2,0))), (int)(light_texture.dimy()/2*(1+lightprops(n2,1))));
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,(float)x1,dimy()-(float)y1,l1,(float)x2,dimy()-(float)y2,l2);
}
#endif
break;
}
} break;
case 12 : { // Textured rectangle
const unsigned int
n0 = (unsigned int)primitive[0],
n1 = (unsigned int)primitive[1],
n2 = (unsigned int)primitive[2],
n3 = (unsigned int)primitive[3],
tx0 = (unsigned int)primitive[4],
ty0 = (unsigned int)primitive[5],
tx1 = (unsigned int)primitive[6],
ty1 = (unsigned int)primitive[7],
tx2 = (unsigned int)primitive[8],
ty2 = (unsigned int)primitive[9],
tx3 = (unsigned int)primitive[10],
ty3 = (unsigned int)primitive[11];
const int
x0 = (int)projections(n0,0), y0 = (int)projections(n0,1),
x1 = (int)projections(n1,0), y1 = (int)projections(n1,1),
x2 = (int)projections(n2,0), y2 = (int)projections(n2,1),
x3 = (int)projections(n3,0), y3 = (int)projections(n3,1);
const float
z0 = points(n0,2) + Z + focale,
z1 = points(n1,2) + Z + focale,
z2 = points(n2,2) + Z + focale,
z3 = points(n3,2) + Z + focale;
switch (render_type) {
case 0 :
draw_point(x0,y0,color.get_vector_at(tx0,ty0),opac).
draw_point(x1,y1,color.get_vector_at(tx1,ty1),opac).
draw_point(x2,y2,color.get_vector_at(tx2,ty2),opac).
draw_point(x3,y3,color.get_vector_at(tx3,ty3),opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.drawCircle((float)x0,dimy()-(float)y0,0);
board.drawCircle((float)x1,dimy()-(float)y1,0);
board.drawCircle((float)x2,dimy()-(float)y2,0);
board.drawCircle((float)x3,dimy()-(float)y3,0);
}
#endif
break;
case 1 :
if (zbuffer)
draw_line(zbuffer,x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
draw_line(zbuffer,x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
draw_line(zbuffer,x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
draw_line(zbuffer,x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
else
draw_line(x0,y0,z0,x1,y1,z1,color,tx0,ty0,tx1,ty1,opac).
draw_line(x1,y1,z1,x2,y2,z2,color,tx1,ty1,tx2,ty2,opac).
draw_line(x2,y2,z2,x3,y3,z3,color,tx2,ty2,tx3,ty3,opac).
draw_line(x3,y3,z3,x0,y0,z0,color,tx3,ty3,tx0,ty0,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.drawLine((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1);
board.drawLine((float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
board.drawLine((float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
board.drawLine((float)x3,dimy()-(float)y3,(float)x0,dimy()-(float)y0);
}
#endif
break;
case 2 :
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
else
draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac).
draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
}
#endif
break;
case 3 :
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
else
draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,opac,lightprops(l)).
draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,opac,lightprops(l));
#ifdef cimg_use_board
if (pboard) {
const float lp = cimg::min(lightprops(l),1);
board.setPenColorRGBi((unsigned char)(128*lp),
(unsigned char)(128*lp),
(unsigned char)(128*lp),
(unsigned char)(opac*255));
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x1,dimy()-(float)y1,(float)x2,dimy()-(float)y2);
board.fillTriangle((float)x0,dimy()-(float)y0,(float)x2,dimy()-(float)y2,(float)x3,dimy()-(float)y3);
}
#endif
break;
case 4 : {
const float
lightprop0 = lightprops(n0), lightprop1 = lightprops(n1),
lightprop2 = lightprops(n2), lightprop3 = lightprops(n3);
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
else
draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,lightprop0,lightprop1,lightprop2,opac).
draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,lightprop0,lightprop2,lightprop3,opac);
#ifdef cimg_use_board
if (pboard) {
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
(float)x1,dimy()-(float)y1,lightprop1,
(float)x2,dimy()-(float)y2,lightprop2);
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,lightprop0,
(float)x2,dimy()-(float)y2,lightprop2,
(float)x3,dimy()-(float)y3,lightprop3);
}
#endif
} break;
case 5 : {
const unsigned int
lx0 = (unsigned int)lightprops(n0,0), ly0 = (unsigned int)lightprops(n0,1),
lx1 = (unsigned int)lightprops(n1,0), ly1 = (unsigned int)lightprops(n1,1),
lx2 = (unsigned int)lightprops(n2,0), ly2 = (unsigned int)lightprops(n2,1),
lx3 = (unsigned int)lightprops(n3,0), ly3 = (unsigned int)lightprops(n3,1);
if (zbuffer)
draw_triangle(zbuffer,x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
draw_triangle(zbuffer,x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
else
draw_triangle(x0,y0,z0,x1,y1,z1,x2,y2,z2,color,tx0,ty0,tx1,ty1,tx2,ty2,light_texture,lx0,ly0,lx1,ly1,lx2,ly2,opac).
draw_triangle(x0,y0,z0,x2,y2,z2,x3,y3,z3,color,tx0,ty0,tx2,ty2,tx3,ty3,light_texture,lx0,ly0,lx2,ly2,lx3,ly3,opac);
#ifdef cimg_use_board
if (pboard) {
const float
l0 = light_texture((int)(light_texture.dimx()/2*(1+lx0)), (int)(light_texture.dimy()/2*(1+ly0))),
l1 = light_texture((int)(light_texture.dimx()/2*(1+lx1)), (int)(light_texture.dimy()/2*(1+ly1))),
l2 = light_texture((int)(light_texture.dimx()/2*(1+lx2)), (int)(light_texture.dimy()/2*(1+ly2))),
l3 = light_texture((int)(light_texture.dimx()/2*(1+lx3)), (int)(light_texture.dimy()/2*(1+ly3)));
board.setPenColorRGBi(128,128,128,(unsigned char)(opac*255));
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
(float)x1,dimy()-(float)y1,l1,
(float)x2,dimy()-(float)y2,l2);
board.fillGouraudTriangle((float)x0,dimy()-(float)y0,l0,
(float)x2,dimy()-(float)y2,l2,
(float)x3,dimy()-(float)y3,l3);
}
#endif
} break;
}
} break;
}
}
}
return *this;
}
//! Draw a 3D object.
/**
\param X = X-coordinate of the 3d object position
\param Y = Y-coordinate of the 3d object position
\param Z = Z-coordinate of the 3d object position
\param points = Image N*3 describing 3D point coordinates
\param primitives = List of P primitives
\param colors = List of P color (or textures)
\param opacities = Image of P opacities
\param render_type = Render type (0=Points, 1=Lines, 2=Faces (no light), 3=Faces (flat), 4=Faces(Gouraud)
\param double_sided = Tell if object faces have two sides or are oriented.
\param focale = length of the focale
\param lightx = X-coordinate of the light
\param lighty = Y-coordinate of the light
\param lightz = Z-coordinate of the light
\param specular_shine = Shininess of the object
**/
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
const CImg<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImgList<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
primitives,colors,opacities,opacities.size,
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#ifdef cimg_use_board
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(BoardLib::Board& board,
const float x0, const float y0, const float z0,
const CImg<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImgList<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
primitives,colors,opacities,opacities.size,
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#endif
//! Draw a 3D object.
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
const CImgList<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImgList<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size,
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#ifdef cimg_use_board
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(BoardLib::Board& board,
const float x0, const float y0, const float z0,
const CImgList<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImgList<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size,
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#endif
//! Draw a 3D object.
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
const CImg<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImg<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d(0,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width,
primitives,colors,opacities,opacities.size(),
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#ifdef cimg_use_board
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(BoardLib::Board& board,
const float x0, const float y0, const float z0,
const CImg<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImg<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points.height<3?points:points.get_resize(-100,3,1,1,0),points.width
,primitives,colors,opacities,opacities.size(),
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#endif
//! Draw a 3D object.
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
const CImgList<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImg<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d(0,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(),
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#ifdef cimg_use_board
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(BoardLib::Board& board,
const float x0, const float y0, const float z0,
const CImgList<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const CImg<to>& opacities,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
if (!points) return *this;
return _draw_object3d((void*)&board,zbuffer,x0,y0,z0,points,points.size,primitives,colors,opacities,opacities.size(),
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine);
}
#endif
//! Draw a 3D object.
template<typename tp, typename tf, typename tc>
CImg<T>& draw_object3d(const float x0, const float y0, const float z0,
const tp& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
static const CImg<floatT> opacities;
return draw_object3d(x0,y0,z0,points,primitives,colors,opacities,
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
}
#ifdef cimg_use_board
template<typename tp, typename tf, typename tc, typename to>
CImg<T>& draw_object3d(BoardLib::Board& board,
const float x0, const float y0, const float z0,
const tp& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors,
const unsigned int render_type=4,
const bool double_sided=false, const float focale=500,
const float lightx=0, const float lighty=0, const float lightz=-5000,
const float specular_light=0.2f, const float specular_shine=0.1f,
float *const zbuffer=0) {
static const CImg<floatT> opacities;
return draw_object3d(x0,y0,z0,points,primitives,colors,opacities,
render_type,double_sided,focale,lightx,lighty,lightz,specular_light,specular_shine,zbuffer);
}
#endif
//@}
//----------------------------
//
//! \name Image Filtering
//@{
//----------------------------
//! Compute the correlation of the instance image by a mask.
/**
The correlation of the instance image \p *this by the mask \p mask is defined to be :
res(x,y,z) = sum_{i,j,k} (*this)(x+i,y+j,z+k)*mask(i,j,k)
\param mask = the correlation kernel.
\param cond = the border condition type (0=zero, 1=dirichlet)
\param weighted_correl = enable local normalization.
**/
template<typename t>
CImg<T>& correlate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_correl=false) {
return get_correlate(mask,cond,weighted_correl).transfer_to(*this);
}
template<typename t>
CImg<typename cimg::superset2<T,t,float>::type> get_correlate(const CImg<t>& mask, const unsigned int cond=1,
const bool weighted_correl=false) const {
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
if (is_empty()) return *this;
if (!mask || mask.dim!=1)
throw CImgArgumentException("CImg<%s>::correlate() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
CImg<Ttfloat> dest(width,height,depth,dim);
if (cond && mask.width==mask.height && ((mask.depth==1 && mask.width<=5) || (mask.depth==mask.width && mask.width<=3))) {
// A special optimization is done for 2x2, 3x3, 4x4, 5x5, 2x2x2 and 3x3x3 mask (with cond=1)
switch (mask.depth) {
case 3 : {
T I[27] = { 0 };
cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] +
I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] +
I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] +
I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3x3(*this,x,y,z,v,I) {
const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] +
I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] +
I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
I[18]*I[18] + I[19]*I[19] + I[20]*I[20] +
I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
I[24]*I[24] + I[25]*I[25] + I[26]*I[26]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
case 2 : {
T I[8] = { 0 };
cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[0]*mask[0] + I[1]*mask[1] +
I[2]*mask[2] + I[3]*mask[3] +
I[4]*mask[4] + I[5]*mask[5] +
I[6]*mask[6] + I[7]*mask[7]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2x2(*this,x,y,z,v,I) {
const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
I[2]*I[2] + I[3]*I[3] +
I[4]*I[4] + I[5]*I[5] +
I[6]*I[6] + I[7]*I[7]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
default :
case 1 :
switch (mask.width) {
case 6 : {
T I[36] = { 0 };
cimg_forZV(*this,z,v) cimg_for6x6(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] + I[ 5]*mask[ 5] +
I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] +
I[18]*mask[18] + I[19]*mask[19] + I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] +
I[24]*mask[24] + I[25]*mask[25] + I[26]*mask[26] + I[27]*mask[27] + I[28]*mask[28] + I[29]*mask[29] +
I[30]*mask[30] + I[31]*mask[31] + I[32]*mask[32] + I[33]*mask[33] + I[34]*mask[34] + I[35]*mask[35]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] + I[ 5]*I[ 5] +
I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15] + I[16]*I[16] + I[17]*I[17] +
I[18]*I[18] + I[19]*I[19] + I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] +
I[24]*I[24] + I[25]*I[25] + I[26]*I[26] + I[27]*I[27] + I[28]*I[28] + I[29]*I[29] +
I[30]*I[30] + I[31]*I[31] + I[32]*I[32] + I[33]*I[33] + I[34]*I[34] + I[35]*I[35]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
case 5 : {
T I[25] = { 0 };
cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] + I[ 4]*mask[ 4] +
I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] + I[ 8]*mask[ 8] + I[ 9]*mask[ 9] +
I[10]*mask[10] + I[11]*mask[11] + I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] +
I[15]*mask[15] + I[16]*mask[16] + I[17]*mask[17] + I[18]*mask[18] + I[19]*mask[19] +
I[20]*mask[20] + I[21]*mask[21] + I[22]*mask[22] + I[23]*mask[23] + I[24]*mask[24]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for5x5(*this,x,y,z,v,I) {
const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] + I[ 4]*I[ 4] +
I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] + I[ 8]*I[ 8] + I[ 9]*I[ 9] +
I[10]*I[10] + I[11]*I[11] + I[12]*I[12] + I[13]*I[13] + I[14]*I[14] +
I[15]*I[15] + I[16]*I[16] + I[17]*I[17] + I[18]*I[18] + I[19]*I[19] +
I[20]*I[20] + I[21]*I[21] + I[22]*I[22] + I[23]*I[23] + I[24]*I[24]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
case 4 : {
T I[16] = { 0 };
cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[ 0]*mask[ 0] + I[ 1]*mask[ 1] + I[ 2]*mask[ 2] + I[ 3]*mask[ 3] +
I[ 4]*mask[ 4] + I[ 5]*mask[ 5] + I[ 6]*mask[ 6] + I[ 7]*mask[ 7] +
I[ 8]*mask[ 8] + I[ 9]*mask[ 9] + I[10]*mask[10] + I[11]*mask[11] +
I[12]*mask[12] + I[13]*mask[13] + I[14]*mask[14] + I[15]*mask[15]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for4x4(*this,x,y,z,v,I) {
const double weight = (double)(I[ 0]*I[ 0] + I[ 1]*I[ 1] + I[ 2]*I[ 2] + I[ 3]*I[ 3] +
I[ 4]*I[ 4] + I[ 5]*I[ 5] + I[ 6]*I[ 6] + I[ 7]*I[ 7] +
I[ 8]*I[ 8] + I[ 9]*I[ 9] + I[10]*I[10] + I[11]*I[11] +
I[12]*I[12] + I[13]*I[13] + I[14]*I[14] + I[15]*I[15]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
case 3 : {
T I[9] = { 0 };
cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[0]*mask[0] + I[1]*mask[1] + I[2]*mask[2] +
I[3]*mask[3] + I[4]*mask[4] + I[5]*mask[5] +
I[6]*mask[6] + I[7]*mask[7] + I[8]*mask[8]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for3x3(*this,x,y,z,v,I) {
const double weight = (double)(I[0]*I[0] + I[1]*I[1] + I[2]*I[2] +
I[3]*I[3] + I[4]*I[4] + I[5]*I[5] +
I[6]*I[6] + I[7]*I[7] + I[8]*I[8]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
case 2 : {
T I[4] = { 0 };
cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) dest(x,y,z,v) = (Ttfloat)
(I[0]*mask[0] + I[1]*mask[1] +
I[2]*mask[2] + I[3]*mask[3]);
if (weighted_correl) cimg_forZV(*this,z,v) cimg_for2x2(*this,x,y,z,v,I) {
const double weight = (double)(I[0]*I[0] + I[1]*I[1] +
I[2]*I[2] + I[3]*I[3]);
if (weight>0) dest(x,y,z,v)/=(Ttfloat)cimg_std::sqrt(weight);
}
} break;
case 1 : (dest.assign(*this))*=mask(0); break;
}
}
} else { // Generic version for other masks
const int
mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
cimg_forV(*this,v)
if (!weighted_correl) { // Classical correlation
for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
Ttfloat val = 0;
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
val+=(*this)(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
dest(x,y,z,v) = (Ttfloat)val;
}
if (cond)
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Ttfloat val = 0;
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
val+=_atXYZ(x+xm,y+ym,z+zm,v)*mask(mx1+xm,my1+ym,mz1+zm);
dest(x,y,z,v) = (Ttfloat)val;
}
else
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Ttfloat val = 0;
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm)
val+=atXYZ(x+xm,y+ym,z+zm,v,0)*mask(mx1+xm,my1+ym,mz1+zm);
dest(x,y,z,v) = (Ttfloat)val;
}
} else { // Weighted correlation
for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
Ttfloat val = 0, weight = 0;
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const Ttfloat cval = (Ttfloat)(*this)(x+xm,y+ym,z+zm,v);
val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
weight+=cval*cval;
}
dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
}
if (cond)
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Ttfloat val = 0, weight = 0;
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const Ttfloat cval = (Ttfloat)_atXYZ(x+xm,y+ym,z+zm,v);
val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
weight+=cval*cval;
}
dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
}
else
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Ttfloat val = 0, weight = 0;
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const Ttfloat cval = (Ttfloat)atXYZ(x+xm,y+ym,z+zm,v,0);
val+=cval*mask(mx1+xm,my1+ym,mz1+zm);
weight+=cval*cval;
}
dest(x,y,z,v) = (weight>(Ttfloat)0)?(Ttfloat)(val/cimg_std::sqrt((double)weight)):(Ttfloat)0;
}
}
}
return dest;
}
//! Compute the convolution of the image by a mask.
/**
The result \p res of the convolution of an image \p img by a mask \p mask is defined to be :
res(x,y,z) = sum_{i,j,k} img(x-i,y-j,z-k)*mask(i,j,k)
\param mask = the correlation kernel.
\param cond = the border condition type (0=zero, 1=dirichlet)
\param weighted_convol = enable local normalization.
**/
template<typename t>
CImg<T>& convolve(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_convol=false) {
return get_convolve(mask,cond,weighted_convol).transfer_to(*this);
}
template<typename t>
CImg<typename cimg::superset2<T,t,float>::type> get_convolve(const CImg<t>& mask, const unsigned int cond=1,
const bool weighted_convol=false) const {
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
if (is_empty()) return *this;
if (!mask || mask.dim!=1)
throw CImgArgumentException("CImg<%s>::convolve() : Specified mask (%u,%u,%u,%u,%p) is not scalar.",
pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
return get_correlate(CImg<t>(mask.ptr(),mask.size(),1,1,1,true).get_mirror('x').resize(mask,-1),cond,weighted_convol);
}
//! Return the erosion of the image by a structuring element.
template<typename t>
CImg<T>& erode(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_erosion=false) {
return get_erode(mask,cond,weighted_erosion).transfer_to(*this);
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_erode(const CImg<t>& mask, const unsigned int cond=1,
const bool weighted_erosion=false) const {
typedef typename cimg::superset<T,t>::type Tt;
if (is_empty()) return *this;
if (!mask || mask.dim!=1)
throw CImgArgumentException("CImg<%s>::erode() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
CImg<Tt> dest(width,height,depth,dim);
const int
mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
cimg_forV(*this,v)
if (!weighted_erosion) { // Classical erosion
for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
Tt min_val = cimg::type<Tt>::max();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v);
if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
}
dest(x,y,z,v) = min_val;
}
if (cond)
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt min_val = cimg::type<Tt>::max();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v);
if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
}
dest(x,y,z,v) = min_val;
}
else
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt min_val = cimg::type<Tt>::max();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0);
if (mask(mx1+xm,my1+ym,mz1+zm) && cval<min_val) min_val = cval;
}
dest(x,y,z,v) = min_val;
}
} else { // Weighted erosion
for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
Tt min_val = cimg::type<Tt>::max();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const t mval = mask(mx1+xm,my1+ym,mz1+zm);
const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) + mval);
if (mval && cval<min_val) min_val = cval;
}
dest(x,y,z,v) = min_val;
}
if (cond)
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt min_val = cimg::type<Tt>::max();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const t mval = mask(mx1+xm,my1+ym,mz1+zm);
const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) + mval);
if (mval && cval<min_val) min_val = cval;
}
dest(x,y,z,v) = min_val;
}
else
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt min_val = cimg::type<Tt>::max();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const t mval = mask(mx1+xm,my1+ym,mz1+zm);
const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) + mval);
if (mval && cval<min_val) min_val = cval;
}
dest(x,y,z,v) = min_val;
}
}
return dest;
}
//! Erode the image by a square structuring element of size n.
CImg<T>& erode(const unsigned int n, const unsigned int cond=1) {
if (n<2) return *this;
return get_erode(n,cond).transfer_to(*this);
}
CImg<T> get_erode(const unsigned int n, const unsigned int cond=1) const {
static CImg<T> mask;
if (n<2) return *this;
if (mask.width!=n) mask.assign(n,n,1,1,1);
const CImg<T> res = get_erode(mask,cond,false);
if (n>20) mask.assign();
return res;
}
//! Dilate the image by a structuring element.
template<typename t>
CImg<T>& dilate(const CImg<t>& mask, const unsigned int cond=1, const bool weighted_dilatation=false) {
return get_dilate(mask,cond,weighted_dilatation).transfer_to(*this);
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_dilate(const CImg<t>& mask, const unsigned int cond=1,
const bool weighted_dilatation=false) const {
typedef typename cimg::superset<T,t>::type Tt;
if (is_empty()) return *this;
if (!mask || mask.dim!=1)
throw CImgArgumentException("CImg<%s>::dilate() : Specified mask (%u,%u,%u,%u,%p) is not a scalar image.",
pixel_type(),mask.width,mask.height,mask.depth,mask.dim,mask.data);
CImg<Tt> dest(width,height,depth,dim);
const int
mx2 = mask.dimx()/2, my2 = mask.dimy()/2, mz2 = mask.dimz()/2,
mx1 = mx2 - 1 + (mask.dimx()%2), my1 = my2 - 1 + (mask.dimy()%2), mz1 = mz2 - 1 + (mask.dimz()%2),
mxe = dimx() - mx2, mye = dimy() - my2, mze = dimz() - mz2;
cimg_forV(*this,v)
if (!weighted_dilatation) { // Classical dilatation
for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
Tt max_val = cimg::type<Tt>::min();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const Tt cval = (Tt)(*this)(x+xm,y+ym,z+zm,v);
if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
}
dest(x,y,z,v) = max_val;
}
if (cond)
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt max_val = cimg::type<Tt>::min();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const T cval = (Tt)_atXYZ(x+xm,y+ym,z+zm,v);
if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
}
dest(x,y,z,v) = max_val;
}
else
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt max_val = cimg::type<Tt>::min();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const T cval = (Tt)atXYZ(x+xm,y+ym,z+zm,v,0);
if (mask(mx1+xm,my1+ym,mz1+zm) && cval>max_val) max_val = cval;
}
dest(x,y,z,v) = max_val;
}
} else { // Weighted dilatation
for (int z = mz1; z<mze; ++z) for (int y = my1; y<mye; ++y) for (int x = mx1; x<mxe; ++x) {
Tt max_val = cimg::type<Tt>::min();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const t mval = mask(mx1+xm,my1+ym,mz1+zm);
const Tt cval = (Tt)((*this)(x+xm,y+ym,z+zm,v) - mval);
if (mval && cval>max_val) max_val = cval;
}
dest(x,y,z,v) = max_val;
}
if (cond)
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt max_val = cimg::type<Tt>::min();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const t mval = mask(mx1+xm,my1+ym,mz1+zm);
const Tt cval = (Tt)(_atXYZ(x+xm,y+ym,z+zm,v) - mval);
if (mval && cval>max_val) max_val = cval;
}
dest(x,y,z,v) = max_val;
}
else
cimg_forYZV(*this,y,z,v)
for (int x = 0; x<dimx(); (y<my1 || y>=mye || z<mz1 || z>=mze)?++x:((x<mx1-1 || x>=mxe)?++x:(x=mxe))) {
Tt max_val = cimg::type<Tt>::min();
for (int zm = -mz1; zm<=mz2; ++zm) for (int ym = -my1; ym<=my2; ++ym) for (int xm = -mx1; xm<=mx2; ++xm) {
const t mval = mask(mx1+xm,my1+ym,mz1+zm);
const Tt cval = (Tt)(atXYZ(x+xm,y+ym,z+zm,v,0) - mval);
if (mval && cval>max_val) max_val = cval;
}
dest(x,y,z,v) = max_val;
}
}
return dest;
}
//! Dilate the image by a square structuring element of size n.
CImg<T>& dilate(const unsigned int n, const unsigned int cond=1) {
if (n<2) return *this;
return get_dilate(n,cond).transfer_to(*this);
}
CImg<T> get_dilate(const unsigned int n, const unsigned int cond=1) const {
static CImg<T> mask;
if (n<2) return *this;
if (mask.width!=n) mask.assign(n,n,1,1,1);
const CImg<T> res = get_dilate(mask,cond,false);
if (n>20) mask.assign();
return res;
}
//! Add noise to the image.
/**
\param sigma = power of the noise. if sigma<0, it corresponds to the percentage of the maximum image value.
\param ntype = noise type. can be 0=gaussian, 1=uniform or 2=Salt and Pepper, 3=Poisson, 4=Rician.
\return A noisy version of the instance image.
**/
CImg<T>& noise(const double sigma, const unsigned int noise_type=0) {
if (!is_empty()) {
double nsigma = sigma, max = (double)cimg::type<T>::max(), min = (double)cimg::type<T>::min();
Tfloat m = 0, M = 0;
if (nsigma==0 && noise_type!=3) return *this;
if (nsigma<0 || noise_type==2) m = (Tfloat)minmax(M);
if (nsigma<0) nsigma = -nsigma*(M-m)/100.0;
switch (noise_type) {
case 0 : { // Gaussian noise
cimg_for(*this,ptr,T) {
double val = *ptr + nsigma*cimg::grand();
if (val>max) val = max;
if (val<min) val = min;
*ptr = (T)val;
}
} break;
case 1 : { // Uniform noise
cimg_for(*this,ptr,T) {
double val = *ptr + nsigma*cimg::crand();
if (val>max) val = max;
if (val<min) val = min;
*ptr = (T)val;
}
} break;
case 2 : { // Salt & Pepper noise
if (nsigma<0) nsigma = -nsigma;
if (M==m) { m = 0; M = (float)(cimg::type<T>::is_float()?1:cimg::type<T>::max()); }
cimg_for(*this,ptr,T) if (cimg::rand()*100<nsigma) *ptr = (T)(cimg::rand()<0.5?M:m);
} break;
case 3 : { // Poisson Noise
cimg_for(*this,ptr,T) *ptr = (T)cimg::prand(*ptr);
} break;
case 4 : { // Rice noise
const double sqrt2 = (double)cimg_std::sqrt(2.0);
cimg_for(*this,ptr,T) {
const double
val0 = (double)*ptr/sqrt2,
re = val0 + nsigma*cimg::grand(),
im = val0 + nsigma*cimg::grand();
double val = cimg_std::sqrt(re*re + im*im);
if (val>max) val = max;
if (val<min) val = min;
*ptr = (T)val;
}
} break;
default :
throw CImgArgumentException("CImg<%s>::noise() : Invalid noise type %d "
"(should be {0=Gaussian, 1=Uniform, 2=Salt&Pepper, 3=Poisson}).",pixel_type(),noise_type);
}
}
return *this;
}
CImg<T> get_noise(const double sigma, const unsigned int noise_type=0) const {
return (+*this).noise(sigma,noise_type);
}
//! Compute the result of the Deriche filter.
/**
The Canny-Deriche filter is a recursive algorithm allowing to compute blurred derivatives of
order 0,1 or 2 of an image.
**/
CImg<T>& deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) {
#define _cimg_deriche2_apply \
Tfloat *ptrY = Y.data, yb = 0, yp = 0; \
T xp = (T)0; \
if (cond) { xp = *ptrX; yb = yp = (Tfloat)(coefp*xp); } \
for (int m=0; m<N; ++m) { \
const T xc = *ptrX; ptrX+=off; \
const Tfloat yc = *(ptrY++) = (Tfloat)(a0*xc + a1*xp - b1*yp - b2*yb); \
xp = xc; yb = yp; yp = yc; \
} \
T xn = (T)0, xa = (T)0; \
Tfloat yn = 0, ya = 0; \
if (cond) { xn = xa = *(ptrX-off); yn = ya = (Tfloat)coefn*xn; } \
for (int n=N-1; n>=0; --n) { \
const T xc = *(ptrX-=off); \
const Tfloat yc = (Tfloat)(a2*xn + a3*xa - b1*yn - b2*ya); \
xa = xn; xn = xc; ya = yn; yn = yc; \
*ptrX = (T)(*(--ptrY)+yc); \
}
if (sigma<0)
throw CImgArgumentException("CImg<%s>::deriche() : Given filter variance (sigma = %g) is negative",
pixel_type(),sigma);
if (is_empty() || (sigma<0.1 && !order)) return *this;
const float
nsigma = sigma<0.1f?0.1f:sigma,
alpha = 1.695f/nsigma,
ema = (float)cimg_std::exp(-alpha),
ema2 = (float)cimg_std::exp(-2*alpha),
b1 = -2*ema,
b2 = ema2;
float a0 = 0, a1 = 0, a2 = 0, a3 = 0, coefp = 0, coefn = 0;
switch (order) {
case 0 : {
const float k = (1-ema)*(1-ema)/(1+2*alpha*ema-ema2);
a0 = k;
a1 = k*(alpha-1)*ema;
a2 = k*(alpha+1)*ema;
a3 = -k*ema2;
} break;
case 1 : {
const float k = (1-ema)*(1-ema)/ema;
a0 = k*ema;
a1 = a3 = 0;
a2 = -a0;
} break;
case 2 : {
const float
ea = (float)cimg_std::exp(-alpha),
k = -(ema2-1)/(2*alpha*ema),
kn = (-2*(-1+3*ea-3*ea*ea+ea*ea*ea)/(3*ea+1+3*ea*ea+ea*ea*ea));
a0 = kn;
a1 = -kn*(1+k*alpha)*ema;
a2 = kn*(1-k*alpha)*ema;
a3 = -kn*ema2;
} break;
default :
throw CImgArgumentException("CImg<%s>::deriche() : Given filter order (order = %u) must be 0,1 or 2",
pixel_type(),order);
}
coefp = (a0+a1)/(1+b1+b2);
coefn = (a2+a3)/(1+b1+b2);
switch (cimg::uncase(axis)) {
case 'x' : {
const int N = width, off = 1;
CImg<Tfloat> Y(N);
cimg_forYZV(*this,y,z,v) { T *ptrX = ptr(0,y,z,v); _cimg_deriche2_apply; }
} break;
case 'y' : {
const int N = height, off = width;
CImg<Tfloat> Y(N);
cimg_forXZV(*this,x,z,v) { T *ptrX = ptr(x,0,z,v); _cimg_deriche2_apply; }
} break;
case 'z' : {
const int N = depth, off = width*height;
CImg<Tfloat> Y(N);
cimg_forXYV(*this,x,y,v) { T *ptrX = ptr(x,y,0,v); _cimg_deriche2_apply; }
} break;
case 'v' : {
const int N = dim, off = width*height*depth;
CImg<Tfloat> Y(N);
cimg_forXYZ(*this,x,y,z) { T *ptrX = ptr(x,y,z,0); _cimg_deriche2_apply; }
} break;
}
return *this;
}
CImg<Tfloat> get_deriche(const float sigma, const int order=0, const char axis='x', const bool cond=true) const {
return CImg<Tfloat>(*this,false).deriche(sigma,order,axis,cond);
}
//! Return a blurred version of the image, using a Canny-Deriche filter.
/**
Blur the image with an anisotropic exponential filter (Deriche filter of order 0).
**/
CImg<T>& blur(const float sigmax, const float sigmay, const float sigmaz, const bool cond=true) {
if (!is_empty()) {
if (width>1 && sigmax>0) deriche(sigmax,0,'x',cond);
if (height>1 && sigmay>0) deriche(sigmay,0,'y',cond);
if (depth>1 && sigmaz>0) deriche(sigmaz,0,'z',cond);
}
return *this;
}
CImg<Tfloat> get_blur(const float sigmax, const float sigmay, const float sigmaz,
const bool cond=true) const {
return CImg<Tfloat>(*this,false).blur(sigmax,sigmay,sigmaz,cond);
}
//! Return a blurred version of the image, using a Canny-Deriche filter.
CImg<T>& blur(const float sigma, const bool cond=true) {
return blur(sigma,sigma,sigma,cond);
}
CImg<Tfloat> get_blur(const float sigma, const bool cond=true) const {
return CImg<Tfloat>(*this,false).blur(sigma,cond);
}
//! Blur the image anisotropically following a field of diffusion tensors.
/**
\param G = Field of square roots of diffusion tensors used to drive the smoothing.
\param amplitude = amplitude of the smoothing.
\param dl = spatial discretization.
\param da = angular discretization.
\param gauss_prec = precision of the gaussian function.
\param interpolation Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
\param fast_approx = Tell to use the fast approximation or not.
**/
template<typename t>
CImg<T>& blur_anisotropic(const CImg<t>& G, const float amplitude=60, const float dl=0.8f, const float da=30,
const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) {
#define _cimg_valign2d(i,j) \
{ Tfloat &u = W(i,j,0,0), &v = W(i,j,0,1); \
if (u*curru + v*currv<0) { u=-u; v=-v; }}
#define _cimg_valign3d(i,j,k) \
{ Tfloat &u = W(i,j,k,0), &v = W(i,j,k,1), &w = W(i,j,k,2); \
if (u*curru + v*currv + w*currw<0) { u=-u; v=-v; w=-w; }}
// Check arguments and init variables
if (!is_empty() && amplitude>0) {
if (!G || (G.dim!=3 && G.dim!=6) || G.width!=width || G.height!=height || G.depth!=depth)
throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Specified tensor field (%u,%u,%u,%u) is not valid.",
pixel_type(),G.width,G.height,G.depth,G.dim);
const float sqrt2amplitude = (float)cimg_std::sqrt(2*amplitude);
const bool threed = (G.dim>=6);
const int
dx1 = dimx()-1,
dy1 = dimy()-1,
dz1 = dimz()-1;
CImg<Tfloat>
dest(width,height,depth,dim,0),
W(width,height,depth,threed?4:3),
tmp(dim);
int N = 0;
if (threed)
// 3D version of the algorithm
for (float phi=(180%(int)da)/2.0f; phi<=180; phi+=da) {
const float
phir = (float)(phi*cimg::valuePI/180),
datmp = (float)(da/cimg_std::cos(phir)),
da2 = datmp<1?360.0f:datmp;
for (float theta=0; theta<360; (theta+=da2),++N) {
const float
thetar = (float)(theta*cimg::valuePI/180),
vx = (float)(cimg_std::cos(thetar)*cimg_std::cos(phir)),
vy = (float)(cimg_std::sin(thetar)*cimg_std::cos(phir)),
vz = (float)cimg_std::sin(phir);
const t
*pa = G.ptr(0,0,0,0),
*pb = G.ptr(0,0,0,1),
*pc = G.ptr(0,0,0,2),
*pd = G.ptr(0,0,0,3),
*pe = G.ptr(0,0,0,4),
*pf = G.ptr(0,0,0,5);
Tfloat
*pd0 = W.ptr(0,0,0,0),
*pd1 = W.ptr(0,0,0,1),
*pd2 = W.ptr(0,0,0,2),
*pd3 = W.ptr(0,0,0,3);
cimg_forXYZ(G,xg,yg,zg) {
const t
a = *(pa++), b = *(pb++), c = *(pc++),
d = *(pd++), e = *(pe++), f = *(pf++);
const float
u = (float)(a*vx + b*vy + c*vz),
v = (float)(b*vx + d*vy + e*vz),
w = (float)(c*vx + e*vy + f*vz),
n = (float)cimg_std::sqrt(1e-5+u*u+v*v+w*w),
dln = dl/n;
*(pd0++) = (Tfloat)(u*dln);
*(pd1++) = (Tfloat)(v*dln);
*(pd2++) = (Tfloat)(w*dln);
*(pd3++) = (Tfloat)n;
}
cimg_forXYZ(*this,x,y,z) {
tmp.fill(0);
const float
cu = (float)W(x,y,z,0),
cv = (float)W(x,y,z,1),
cw = (float)W(x,y,z,2),
n = (float)W(x,y,z,3),
fsigma = (float)(n*sqrt2amplitude),
length = gauss_prec*fsigma,
fsigma2 = 2*fsigma*fsigma;
float
S = 0,
pu = cu,
pv = cv,
pw = cw,
X = (float)x,
Y = (float)y,
Z = (float)z;
switch (interpolation_type) {
case 0 : {
// Nearest neighbor
for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
const int
cx = (int)(X+0.5f),
cy = (int)(Y+0.5f),
cz = (int)(Z+0.5f);
float
u = (float)W(cx,cy,cz,0),
v = (float)W(cx,cy,cz,1),
w = (float)W(cx,cy,cz,2);
if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,cz,k); ++S; }
else {
const float coef = (float)cimg_std::exp(-l*l/fsigma2);
cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,cz,k));
S+=coef;
}
X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
}
} break;
case 1 : {
// Linear interpolation
for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
const int
cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
const float
curru = (float)W(cx,cy,cz,0),
currv = (float)W(cx,cy,cz,1),
currw = (float)W(cx,cy,cz,2);
_cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz);
_cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz);
_cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz);
_cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz);
_cimg_valign3d(px,cy,cz); _cimg_valign3d(nx,cy,cz);
_cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz);
_cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz);
_cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz);
_cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz);
float
u = (float)(W._linear_atXYZ(X,Y,Z,0)),
v = (float)(W._linear_atXYZ(X,Y,Z,1)),
w = (float)(W._linear_atXYZ(X,Y,Z,2));
if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; }
else {
const float coef = (float)cimg_std::exp(-l*l/fsigma2);
cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k));
S+=coef;
}
X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
}
} break;
default : {
// 2nd order Runge Kutta
for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1 && Z>=0 && Z<=dz1; l+=dl) {
const int
cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1,
cz = (int)Z, pz = (cz-1<0)?0:cz-1, nz = (cz+1>dz1)?dz1:cz+1;
const float
curru = (float)W(cx,cy,cz,0),
currv = (float)W(cx,cy,cz,1),
currw = (float)W(cx,cy,cz,2);
_cimg_valign3d(px,py,pz); _cimg_valign3d(cx,py,pz); _cimg_valign3d(nx,py,pz);
_cimg_valign3d(px,cy,pz); _cimg_valign3d(cx,cy,pz); _cimg_valign3d(nx,cy,pz);
_cimg_valign3d(px,ny,pz); _cimg_valign3d(cx,ny,pz); _cimg_valign3d(nx,ny,pz);
_cimg_valign3d(px,py,cz); _cimg_valign3d(cx,py,cz); _cimg_valign3d(nx,py,cz);
_cimg_valign3d(px,cy,cz); _cimg_valign3d(nx,cy,cz);
_cimg_valign3d(px,ny,cz); _cimg_valign3d(cx,ny,cz); _cimg_valign3d(nx,ny,cz);
_cimg_valign3d(px,py,nz); _cimg_valign3d(cx,py,nz); _cimg_valign3d(nx,py,nz);
_cimg_valign3d(px,cy,nz); _cimg_valign3d(cx,cy,nz); _cimg_valign3d(nx,cy,nz);
_cimg_valign3d(px,ny,nz); _cimg_valign3d(cx,ny,nz); _cimg_valign3d(nx,ny,nz);
const float
u0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,0)),
v0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,1)),
w0 = (float)(0.5f*W._linear_atXYZ(X,Y,Z,2));
float
u = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,0)),
v = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,1)),
w = (float)(W._linear_atXYZ(X+u0,Y+v0,Z+w0,2));
if ((pu*u + pv*v + pw*w)<0) { u=-u; v=-v; w=-w; }
if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXYZ(X,Y,Z,k); ++S; }
else {
const float coef = (float)cimg_std::exp(-l*l/fsigma2);
cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXYZ(X,Y,Z,k));
S+=coef;
}
X+=(pu=u); Y+=(pv=v); Z+=(pw=w);
}
} break;
}
if (S>0) cimg_forV(dest,k) dest(x,y,z,k)+=tmp[k]/S;
else cimg_forV(dest,k) dest(x,y,z,k)+=(Tfloat)((*this)(x,y,z,k));
cimg_plugin_greycstoration_count;
}
}
} else
// 2D version of the algorithm
for (float theta=(360%(int)da)/2.0f; theta<360; (theta+=da),++N) {
const float
thetar = (float)(theta*cimg::valuePI/180),
vx = (float)(cimg_std::cos(thetar)),
vy = (float)(cimg_std::sin(thetar));
const t
*pa = G.ptr(0,0,0,0),
*pb = G.ptr(0,0,0,1),
*pc = G.ptr(0,0,0,2);
Tfloat
*pd0 = W.ptr(0,0,0,0),
*pd1 = W.ptr(0,0,0,1),
*pd2 = W.ptr(0,0,0,2);
cimg_forXY(G,xg,yg) {
const t a = *(pa++), b = *(pb++), c = *(pc++);
const float
u = (float)(a*vx + b*vy),
v = (float)(b*vx + c*vy),
n = (float)cimg_std::sqrt(1e-5+u*u+v*v),
dln = dl/n;
*(pd0++) = (Tfloat)(u*dln);
*(pd1++) = (Tfloat)(v*dln);
*(pd2++) = (Tfloat)n;
}
cimg_forXY(*this,x,y) {
tmp.fill(0);
const float
cu = (float)W(x,y,0,0),
cv = (float)W(x,y,0,1),
n = (float)W(x,y,0,2),
fsigma = (float)(n*sqrt2amplitude),
length = gauss_prec*fsigma,
fsigma2 = 2*fsigma*fsigma;
float
S = 0,
pu = cu,
pv = cv,
X = (float)x,
Y = (float)y;
switch (interpolation_type) {
case 0 : {
// Nearest-neighbor interpolation for 2D images
for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
const int
cx = (int)(X+0.5f),
cy = (int)(Y+0.5f);
float
u = (float)W(cx,cy,0,0),
v = (float)W(cx,cy,0,1);
if ((pu*u + pv*v)<0) { u=-u; v=-v; }
if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)(*this)(cx,cy,0,k); ++S; }
else {
const float coef = (float)cimg_std::exp(-l*l/fsigma2);
cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*(*this)(cx,cy,0,k));
S+=coef;
}
X+=(pu=u); Y+=(pv=v);
}
} break;
case 1 : {
// Linear interpolation for 2D images
for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
const int
cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
const float
curru = (float)W(cx,cy,0,0),
currv = (float)W(cx,cy,0,1);
_cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py);
_cimg_valign2d(px,cy); _cimg_valign2d(nx,cy);
_cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny);
float
u = (float)(W._linear_atXY(X,Y,0,0)),
v = (float)(W._linear_atXY(X,Y,0,1));
if ((pu*u + pv*v)<0) { u=-u; v=-v; }
if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; }
else {
const float coef = (float)cimg_std::exp(-l*l/fsigma2);
cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k));
S+=coef;
}
X+=(pu=u); Y+=(pv=v);
}
} break;
default : {
// 2nd-order Runge-kutta interpolation for 2D images
for (float l=0; l<length && X>=0 && X<=dx1 && Y>=0 && Y<=dy1; l+=dl) {
const int
cx = (int)X, px = (cx-1<0)?0:cx-1, nx = (cx+1>dx1)?dx1:cx+1,
cy = (int)Y, py = (cy-1<0)?0:cy-1, ny = (cy+1>dy1)?dy1:cy+1;
const float
curru = (float)W(cx,cy,0,0),
currv = (float)W(cx,cy,0,1);
_cimg_valign2d(px,py); _cimg_valign2d(cx,py); _cimg_valign2d(nx,py);
_cimg_valign2d(px,cy); _cimg_valign2d(nx,cy);
_cimg_valign2d(px,ny); _cimg_valign2d(cx,ny); _cimg_valign2d(nx,ny);
const float
u0 = (float)(0.5f*W._linear_atXY(X,Y,0,0)),
v0 = (float)(0.5f*W._linear_atXY(X,Y,0,1));
float
u = (float)(W._linear_atXY(X+u0,Y+v0,0,0)),
v = (float)(W._linear_atXY(X+u0,Y+v0,0,1));
if ((pu*u + pv*v)<0) { u=-u; v=-v; }
if (fast_approx) { cimg_forV(*this,k) tmp[k]+=(Tfloat)_linear_atXY(X,Y,0,k); ++S; }
else {
const float coef = (float)cimg_std::exp(-l*l/fsigma2);
cimg_forV(*this,k) tmp[k]+=(Tfloat)(coef*_linear_atXY(X,Y,0,k));
S+=coef;
}
X+=(pu=u); Y+=(pv=v);
}
}
}
if (S>0) cimg_forV(dest,k) dest(x,y,0,k)+=tmp[k]/S;
else cimg_forV(dest,k) dest(x,y,0,k)+=(Tfloat)((*this)(x,y,0,k));
cimg_plugin_greycstoration_count;
}
}
const Tfloat *ptrs = dest.data+dest.size();
const T m = cimg::type<T>::min(), M = cimg::type<T>::max();
cimg_for(*this,ptrd,T) { const Tfloat val = *(--ptrs)/N; *ptrd = val<m?m:(val>M?M:(T)val); }
}
return *this;
}
template<typename t>
CImg<T> get_blur_anisotropic(const CImg<t>& G, const float amplitude=60, const float dl=0.8f, const float da=30,
const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true) const {
return (+*this).blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
}
//! Blur an image in an anisotropic way.
/**
\param mask Binary mask.
\param amplitude Amplitude of the anisotropic blur.
\param sharpness Contour preservation.
\param anisotropy Smoothing anisotropy.
\param alpha Image pre-blurring (gaussian).
\param sigma Regularity of the tensor-valued geometry.
\param dl Spatial discretization.
\param da Angular discretization.
\param gauss_prec Precision of the gaussian function.
\param interpolation_type Used interpolation scheme (0 = nearest-neighbor, 1 = linear, 2 = Runge-Kutta)
\param fast_approx Tell to use the fast approximation or not
\param geom_factor Geometry factor.
**/
template<typename tm>
CImg<T>& blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true,
const float geom_factor=1) {
if (!is_empty() && amplitude>0) {
if (amplitude==0) return *this;
if (amplitude<0 || sharpness<0 || anisotropy<0 || anisotropy>1 || alpha<0 || sigma<0 || dl<0 || da<0 || gauss_prec<0)
throw CImgArgumentException("CImg<%s>::blur_anisotropic() : Given parameters are amplitude(%g), sharpness(%g), "
"anisotropy(%g), alpha(%g), sigma(%g), dl(%g), da(%g), gauss_prec(%g).\n"
"Admissible parameters are in the range : amplitude>0, sharpness>0, anisotropy in [0,1], "
"alpha>0, sigma>0, dl>0, da>0, gauss_prec>0.",
pixel_type(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec);
const bool threed = (depth>1), no_mask = mask.is_empty();
const float nsharpness = cimg::max(sharpness,1e-5f), power1 = 0.5f*nsharpness, power2 = power1/(1e-7f+1-anisotropy);
CImg<floatT> blurred = CImg<floatT>(*this,false).blur(alpha);
if (geom_factor>0) blurred*=geom_factor;
else blurred.normalize(0,-geom_factor);
if (threed) { // Field for 3D volumes
cimg_plugin_greycstoration_lock;
CImg<floatT> val(3), vec(3,3), G(blurred.get_structure_tensor());
if (sigma>0) G.blur(sigma);
cimg_forXYZ(*this,x,y,z) {
if (no_mask || mask(x,y,z)) {
G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
const float l1 = val[2], l2 = val[1], l3 = val[0],
ux = vec(0,0), uy = vec(0,1), uz = vec(0,2),
vx = vec(1,0), vy = vec(1,1), vz = vec(1,2),
wx = vec(2,0), wy = vec(2,1), wz = vec(2,2),
n1 = (float)cimg_std::pow(1+l1+l2+l3,-power1),
n2 = (float)cimg_std::pow(1+l1+l2+l3,-power2);
G(x,y,z,0) = n1*(ux*ux + vx*vx) + n2*wx*wx;
G(x,y,z,1) = n1*(ux*uy + vx*vy) + n2*wx*wy;
G(x,y,z,2) = n1*(ux*uz + vx*vz) + n2*wx*wz;
G(x,y,z,3) = n1*(uy*uy + vy*vy) + n2*wy*wy;
G(x,y,z,4) = n1*(uy*uz + vy*vz) + n2*wy*wz;
G(x,y,z,5) = n1*(uz*uz + vz*vz) + n2*wz*wz;
} else G(x,y,z,0) = G(x,y,z,1) = G(x,y,z,2) = G(x,y,z,3) = G(x,y,z,4) = G(x,y,z,5) = 0;
cimg_plugin_greycstoration_count;
}
cimg_plugin_greycstoration_unlock;
blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
} else { // Field for 2D images
cimg_plugin_greycstoration_lock;
CImg<floatT> val(2), vec(2,2), G(blurred.get_structure_tensor());
if (sigma>0) G.blur(sigma);
cimg_forXY(*this,x,y) {
if (no_mask || mask(x,y)) {
G.get_tensor_at(x,y).symmetric_eigen(val,vec);
const float l1 = val[1], l2 = val[0],
ux = vec(1,0), uy = vec(1,1),
vx = vec(0,0), vy = vec(0,1),
n1 = (float)cimg_std::pow(1+l1+l2,-power1),
n2 = (float)cimg_std::pow(1+l1+l2,-power2);
G(x,y,0,0) = n1*ux*ux + n2*vx*vx;
G(x,y,0,1) = n1*ux*uy + n2*vx*vy;
G(x,y,0,2) = n1*uy*uy + n2*vy*vy;
} else G(x,y,0,0) = G(x,y,0,1) = G(x,y,0,2) = 0;
cimg_plugin_greycstoration_count;
}
cimg_plugin_greycstoration_unlock;
blur_anisotropic(G,amplitude,dl,da,gauss_prec,interpolation_type,fast_approx);
}
}
return *this;
}
template<typename tm>
CImg<T> get_blur_anisotropic(const CImg<tm>& mask, const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
const bool fast_approx=true, const float geom_factor=1) const {
return (+*this).blur_anisotropic(mask,amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
}
//! Blur an image following in an anisotropic way.
CImg<T>& blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f, const float da=30,
const float gauss_prec=2, const unsigned int interpolation_type=0, const bool fast_approx=true,
const float geom_factor=1) {
return blur_anisotropic(CImg<T>(),amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
}
CImg<T> get_blur_anisotropic(const float amplitude, const float sharpness=0.7f, const float anisotropy=0.3f,
const float alpha=0.6f, const float sigma=1.1f, const float dl=0.8f,
const float da=30, const float gauss_prec=2, const unsigned int interpolation_type=0,
const bool fast_approx=true, const float geom_factor=1) const {
return (+*this).blur_anisotropic(amplitude,sharpness,anisotropy,alpha,sigma,dl,da,gauss_prec,interpolation_type,fast_approx,geom_factor);
}
//! Blur an image using the bilateral filter.
/**
\param sigmax Amount of blur along the X-axis.
\param sigmay Amount of blur along the Y-axis.
\param sigmaz Amount of blur along the Z-axis.
\param sigmar Amount of blur along the range axis.
\param bgridx Size of the bilateral grid along the X-axis.
\param bgridy Size of the bilateral grid along the Y-axis.
\param bgridz Size of the bilateral grid along the Z-axis.
\param bgridr Size of the bilateral grid along the range axis.
\param interpolation_type Use interpolation for image slicing.
\note This algorithm uses the optimisation technique proposed by S. Paris and F. Durand, in ECCV'2006
(extended for 3D volumetric images).
**/
CImg<T>& blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
const int bgridx, const int bgridy, const int bgridz, const int bgridr,
const bool interpolation_type=true) {
T m, M = maxmin(m);
const float range = (float)(1.0f+M-m);
const unsigned int
bx0 = bgridx>=0?bgridx:width*(-bgridx)/100,
by0 = bgridy>=0?bgridy:height*(-bgridy)/100,
bz0 = bgridz>=0?bgridz:depth*(-bgridz)/100,
br0 = bgridr>=0?bgridr:(int)(-range*bgridr/100),
bx = bx0>0?bx0:1,
by = by0>0?by0:1,
bz = bz0>0?bz0:1,
br = br0>0?br0:1;
const float
nsigmax = sigmax*bx/width,
nsigmay = sigmay*by/height,
nsigmaz = sigmaz*bz/depth,
nsigmar = sigmar*br/range;
if (nsigmax>0 || nsigmay>0 || nsigmaz>0 || nsigmar>0) {
const bool threed = depth>1;
if (threed) { // 3d version of the algorithm
CImg<floatT> bgrid(bx,by,bz,br), bgridw(bx,by,bz,br);
cimg_forV(*this,k) {
bgrid.fill(0); bgridw.fill(0);
cimg_forXYZ(*this,x,y,z) {
const T val = (*this)(x,y,z,k);
const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
bgrid(X,Y,Z,R) = (float)val;
bgridw(X,Y,Z,R) = 1;
}
bgrid.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
bgridw.blur(nsigmax,nsigmay,nsigmaz,true).deriche(nsigmar,0,'v',false);
if (interpolation_type) cimg_forXYZ(*this,x,y,z) {
const T val = (*this)(x,y,z,k);
const float X = (float)x*bx/width, Y = (float)y*by/height, Z = (float)z*bz/depth, R = (float)((val-m)*br/range),
bval0 = bgrid._linear_atXYZV(X,Y,Z,R), bval1 = bgridw._linear_atXYZV(X,Y,Z,R);
(*this)(x,y,z,k) = (T)(bval0/bval1);
} else cimg_forXYZ(*this,x,y,z) {
const T val = (*this)(x,y,z,k);
const int X = x*bx/width, Y = y*by/height, Z = z*bz/depth, R = (int)((val-m)*br/range);
const float bval0 = bgrid(X,Y,Z,R), bval1 = bgridw(X,Y,Z,R);
(*this)(x,y,z,k) = (T)(bval0/bval1);
}
}
} else { // 2d version of the algorithm
CImg<floatT> bgrid(bx,by,br,2);
cimg_forV(*this,k) {
bgrid.fill(0);
cimg_forXY(*this,x,y) {
const T val = (*this)(x,y,k);
const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
bgrid(X,Y,R,0) = (float)val;
bgrid(X,Y,R,1) = 1;
}
bgrid.blur(nsigmax,nsigmay,0,true).blur(0,0,nsigmar,false);
if (interpolation_type) cimg_forXY(*this,x,y) {
const T val = (*this)(x,y,k);
const float X = (float)x*bx/width, Y = (float)y*by/height, R = (float)((val-m)*br/range),
bval0 = bgrid._linear_atXYZ(X,Y,R,0), bval1 = bgrid._linear_atXYZ(X,Y,R,1);
(*this)(x,y,k) = (T)(bval0/bval1);
} else cimg_forXY(*this,x,y) {
const T val = (*this)(x,y,k);
const int X = x*bx/width, Y = y*by/height, R = (int)((val-m)*br/range);
const float bval0 = bgrid(X,Y,R,0), bval1 = bgrid(X,Y,R,1);
(*this)(x,y,k) = (T)(bval0/bval1);
}
}
}
}
return *this;
}
CImg<T> get_blur_bilateral(const float sigmax, const float sigmay, const float sigmaz, const float sigmar,
const int bgridx, const int bgridy, const int bgridz, const int bgridr,
const bool interpolation_type=true) const {
return (+*this).blur_bilateral(sigmax,sigmay,sigmaz,sigmar,bgridx,bgridy,bgridz,bgridr,interpolation_type);
}
//! Blur an image using the bilateral filter.
CImg<T>& blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
const bool interpolation_type=true) {
return blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type);
}
CImg<T> get_blur_bilateral(const float sigmas, const float sigmar, const int bgrids=-33, const int bgridr=32,
const bool interpolation_type=true) const {
return (+*this).blur_bilateral(sigmas,sigmas,sigmas,sigmar,bgrids,bgrids,bgrids,bgridr,interpolation_type);
}
//! Blur an image in its patch-based space.
CImg<T>& blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10,
const unsigned int lookup_size=4, const bool fast_approx=true) {
#define _cimg_blur_patch_fastfunc(x) ((x)>3?0:1)
#define _cimg_blur_patch_slowfunc(x) cimg_std::exp(-(x))
#define _cimg_blur_patch3d(N,func) { \
const unsigned int N3 = N*N*N; \
cimg_for##N##XYZ(*this,x,y,z) { \
cimg_plugin_greycstoration_count; \
cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,x,y,z,k,P.ptr(N3*k)); \
const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2; \
float sum_weights = 0; \
cimg_for_in##N##XYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) { \
cimg_forV(*this,k) cimg_get##N##x##N##x##N(*this,p,q,r,k,Q.ptr(N3*k)); \
float distance2 = 0; \
const T *pQ = Q.end(); \
cimg_for(P,pP,T) { const float dI = (float)*pP - (float)*(--pQ); distance2+=dI*dI; } \
distance2/=Pnorm; \
const float dx = (float)p - x, dy = (float)q - y, dz = (float)r - z, \
alldist = distance2 + (dx*dx + dy*dy + dz*dz)/sigma_s2, weight = (float)func(alldist); \
sum_weights+=weight; \
{ cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k); } \
} \
if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k)); \
}}
#define _cimg_blur_patch2d(N,func) { \
const unsigned int N2 = N*N; \
cimg_for##N##XY(*this,x,y) { \
cimg_plugin_greycstoration_count; \
cimg_forV(*this,k) cimg_get##N##x##N(*this,x,y,0,k,P.ptr(N2*k)); \
const int x0 = x-rsize1, y0 = y-rsize1, x1 = x+rsize2, y1 = y+rsize2; \
float sum_weights = 0; \
cimg_for_in##N##XY(*this,x0,y0,x1,y1,p,q) { \
cimg_forV(*this,k) cimg_get##N##x##N(*this,p,q,0,k,Q.ptr(N2*k)); \
float distance2 = 0; \
const T *pQ = Q.end(); \
cimg_for(P,pP,T) { const float dI = (float)*pP-(float)*(--pQ); distance2+=dI*dI; } \
distance2/=Pnorm; \
const float dx = (float)p-x, dy = (float)q-y, \
alldist = distance2 + (dx*dx+dy*dy)/sigma_s2, weight = (float)func(alldist); \
sum_weights+=weight; \
{ cimg_forV(*this,k) res(x,y,k)+=weight*(*this)(p,q,k); } \
} \
if (sum_weights>0) cimg_forV(*this,k) res(x,y,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,k) = (Tfloat)((*this)(x,y,k)); \
}}
CImg<Tfloat> res(width,height,depth,dim,0);
CImg<T> P(patch_size*patch_size*dim), Q(P);
const float sigma_s2 = sigma_s*sigma_s, sigma_p2 = sigma_p*sigma_p, Pnorm = P.size()*sigma_p2;
const int rsize2 = (int)lookup_size/2, rsize1 = rsize2-1+(lookup_size%2);
if (depth>1) switch (patch_size) { // 3D version
case 2 :
if (fast_approx) { _cimg_blur_patch3d(2,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch3d(2,_cimg_blur_patch_slowfunc); }
break;
case 3 :
if (fast_approx) { _cimg_blur_patch3d(3,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch3d(3,_cimg_blur_patch_slowfunc); }
break;
default : {
const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
cimg_forXYZ(*this,x,y,z) {
cimg_plugin_greycstoration_count;
P = get_crop(x - psize0,y - psize0,z - psize0,x + psize1,y + psize1,z + psize1,true);
const int x0 = x - rsize1, y0 = y - rsize1, z0 = z - rsize1, x1 = x + rsize2, y1 = y + rsize2, z1 = z + rsize2;
float sum_weights = 0;
cimg_for_inXYZ(*this,x0,y0,z0,x1,y1,z1,p,q,r) {
(Q = get_crop(p - psize0,q - psize0,r - psize0,p + psize1,q + psize1,r + psize1,true))-=P;
const float
dx = (float)x - p, dy = (float)y - q, dz = (float)z - r,
distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy + dz*dz)/sigma_s2),
weight = (float)cimg_std::exp(-distance2);
sum_weights+=weight;
cimg_forV(*this,k) res(x,y,z,k)+=weight*(*this)(p,q,r,k);
}
if (sum_weights>0) cimg_forV(*this,k) res(x,y,z,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,z,k) = (Tfloat)((*this)(x,y,z,k));
}
}
} else switch (patch_size) { // 2D version
case 2 :
if (fast_approx) { _cimg_blur_patch2d(2,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(2,_cimg_blur_patch_slowfunc); }
break;
case 3 :
if (fast_approx) { _cimg_blur_patch2d(3,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(3,_cimg_blur_patch_slowfunc); }
break;
case 4 :
if (fast_approx) { _cimg_blur_patch2d(4,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(4,_cimg_blur_patch_slowfunc); }
break;
case 5 :
if (fast_approx) { _cimg_blur_patch2d(5,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(5,_cimg_blur_patch_slowfunc); }
break;
case 6 :
if (fast_approx) { _cimg_blur_patch2d(6,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(6,_cimg_blur_patch_slowfunc); }
break;
case 7 :
if (fast_approx) { _cimg_blur_patch2d(7,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(7,_cimg_blur_patch_slowfunc); }
break;
case 8 :
if (fast_approx) { _cimg_blur_patch2d(8,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(8,_cimg_blur_patch_slowfunc); }
break;
case 9 :
if (fast_approx) { _cimg_blur_patch2d(9,_cimg_blur_patch_fastfunc); }
else { _cimg_blur_patch2d(9,_cimg_blur_patch_slowfunc); }
break;
default : {
const int psize1 = (int)patch_size/2, psize0 = psize1-1+(patch_size%2);
cimg_forXY(*this,x,y) {
cimg_plugin_greycstoration_count;
P = get_crop(x - psize0,y - psize0,x + psize1,y + psize1,true);
const int x0 = x - rsize1, y0 = y - rsize1, x1 = x + rsize2, y1 = y + rsize2;
float sum_weights = 0;
cimg_for_inXY(*this,x0,y0,x1,y1,p,q) {
(Q = get_crop(p - psize0,q - psize0,p + psize1,q + psize1,true))-=P;
const float
dx = (float)x - p, dy = (float)y - q,
distance2 = (float)(Q.pow(2).sum()/Pnorm + (dx*dx + dy*dy)/sigma_s2),
weight = (float)cimg_std::exp(-distance2);
sum_weights+=weight;
cimg_forV(*this,k) res(x,y,0,k)+=weight*(*this)(p,q,0,k);
}
if (sum_weights>0) cimg_forV(*this,k) res(x,y,0,k)/=sum_weights; else cimg_forV(*this,k) res(x,y,0,k) = (Tfloat)((*this)(x,y,0,k));
}
}
}
return res.transfer_to(*this);
}
CImg<T> get_blur_patch(const unsigned int patch_size, const float sigma_p, const float sigma_s=10,
const unsigned int lookup_size=4, const bool fast_approx=true) const {
return (+*this).blur_patch(patch_size,sigma_p,sigma_s,lookup_size,fast_approx);
}
//! Compute the Fast Fourier Transform of an image (along a specified axis).
CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
return CImgList<Tfloat>(*this).FFT(axis,invert);
}
//! Compute the Fast Fourier Transform on an image.
CImgList<Tfloat> get_FFT(const bool invert=false) const {
return CImgList<Tfloat>(*this).FFT(invert);
}
//! Apply a median filter.
CImg<T>& blur_median(const unsigned int n) {
return get_blur_median(n).transfer_to(*this);
}
CImg<T> get_blur_median(const unsigned int n) {
CImg<T> res(width,height,depth,dim);
if (!n || n==1) return *this;
const int hl=n/2, hr=hl-1+n%2;
if (res.depth!=1) { // 3D median filter
CImg<T> vois;
cimg_forXYZV(*this,x,y,z,k) {
const int
x0 = x - hl, y0 = y - hl, z0 = z-hl, x1 = x + hr, y1 = y + hr, z1 = z+hr,
nx0 = x0<0?0:x0, ny0 = y0<0?0:y0, nz0 = z0<0?0:z0,
nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1, nz1 = z1>=dimz()?dimz()-1:z1;
vois = get_crop(nx0,ny0,nz0,k,nx1,ny1,nz1,k);
res(x,y,z,k) = vois.median();
}
} else {
#define _cimg_median_sort(a,b) if ((a)>(b)) cimg::swap(a,b)
if (res.height!=1) switch (n) { // 2D median filter
case 3 : {
T I[9] = { 0 };
CImg_3x3(J,T);
cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
cimg_std::memcpy(J,I,9*sizeof(T));
_cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
_cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jpn, Jcn);
_cimg_median_sort(Jcp, Jnp); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jcn, Jnn);
_cimg_median_sort(Jpp, Jpc); _cimg_median_sort(Jnc, Jnn); _cimg_median_sort(Jcc, Jcn);
_cimg_median_sort(Jpc, Jpn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jnp, Jnc);
_cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcc, Jnp); _cimg_median_sort(Jpn, Jcc);
_cimg_median_sort(Jcc, Jnp);
res(x,y,0,k) = Jcc;
}
} break;
case 5 : {
T I[25] = { 0 };
CImg_5x5(J,T);
cimg_forV(*this,k) cimg_for5x5(*this,x,y,0,k,I) {
cimg_std::memcpy(J,I,25*sizeof(T));
_cimg_median_sort(Jbb, Jpb); _cimg_median_sort(Jnb, Jab); _cimg_median_sort(Jcb, Jab); _cimg_median_sort(Jcb, Jnb);
_cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbp, Jcp); _cimg_median_sort(Jbp, Jpp); _cimg_median_sort(Jap, Jbc);
_cimg_median_sort(Jnp, Jbc); _cimg_median_sort(Jnp, Jap); _cimg_median_sort(Jcc, Jnc); _cimg_median_sort(Jpc, Jnc);
_cimg_median_sort(Jpc, Jcc); _cimg_median_sort(Jbn, Jpn); _cimg_median_sort(Jac, Jpn); _cimg_median_sort(Jac, Jbn);
_cimg_median_sort(Jnn, Jan); _cimg_median_sort(Jcn, Jan); _cimg_median_sort(Jcn, Jnn); _cimg_median_sort(Jpa, Jca);
_cimg_median_sort(Jba, Jca); _cimg_median_sort(Jba, Jpa); _cimg_median_sort(Jna, Jaa); _cimg_median_sort(Jcb, Jbp);
_cimg_median_sort(Jnb, Jpp); _cimg_median_sort(Jbb, Jpp); _cimg_median_sort(Jbb, Jnb); _cimg_median_sort(Jab, Jcp);
_cimg_median_sort(Jpb, Jcp); _cimg_median_sort(Jpb, Jab); _cimg_median_sort(Jpc, Jac); _cimg_median_sort(Jnp, Jac);
_cimg_median_sort(Jnp, Jpc); _cimg_median_sort(Jcc, Jbn); _cimg_median_sort(Jap, Jbn); _cimg_median_sort(Jap, Jcc);
_cimg_median_sort(Jnc, Jpn); _cimg_median_sort(Jbc, Jpn); _cimg_median_sort(Jbc, Jnc); _cimg_median_sort(Jba, Jna);
_cimg_median_sort(Jcn, Jna); _cimg_median_sort(Jcn, Jba); _cimg_median_sort(Jpa, Jaa); _cimg_median_sort(Jnn, Jaa);
_cimg_median_sort(Jnn, Jpa); _cimg_median_sort(Jan, Jca); _cimg_median_sort(Jnp, Jcn); _cimg_median_sort(Jap, Jnn);
_cimg_median_sort(Jbb, Jnn); _cimg_median_sort(Jbb, Jap); _cimg_median_sort(Jbc, Jan); _cimg_median_sort(Jpb, Jan);
_cimg_median_sort(Jpb, Jbc); _cimg_median_sort(Jpc, Jba); _cimg_median_sort(Jcb, Jba); _cimg_median_sort(Jcb, Jpc);
_cimg_median_sort(Jcc, Jpa); _cimg_median_sort(Jnb, Jpa); _cimg_median_sort(Jnb, Jcc); _cimg_median_sort(Jnc, Jca);
_cimg_median_sort(Jab, Jca); _cimg_median_sort(Jab, Jnc); _cimg_median_sort(Jac, Jna); _cimg_median_sort(Jbp, Jna);
_cimg_median_sort(Jbp, Jac); _cimg_median_sort(Jbn, Jaa); _cimg_median_sort(Jpp, Jaa); _cimg_median_sort(Jpp, Jbn);
_cimg_median_sort(Jcp, Jpn); _cimg_median_sort(Jcp, Jan); _cimg_median_sort(Jnc, Jpa); _cimg_median_sort(Jbn, Jna);
_cimg_median_sort(Jcp, Jnc); _cimg_median_sort(Jcp, Jbn); _cimg_median_sort(Jpb, Jap); _cimg_median_sort(Jnb, Jpc);
_cimg_median_sort(Jbp, Jcn); _cimg_median_sort(Jpc, Jcn); _cimg_median_sort(Jap, Jcn); _cimg_median_sort(Jab, Jbc);
_cimg_median_sort(Jpp, Jcc); _cimg_median_sort(Jcp, Jac); _cimg_median_sort(Jab, Jpp); _cimg_median_sort(Jab, Jcp);
_cimg_median_sort(Jcc, Jac); _cimg_median_sort(Jbc, Jac); _cimg_median_sort(Jpp, Jcp); _cimg_median_sort(Jbc, Jcc);
_cimg_median_sort(Jpp, Jbc); _cimg_median_sort(Jpp, Jcn); _cimg_median_sort(Jcc, Jcn); _cimg_median_sort(Jcp, Jcn);
_cimg_median_sort(Jcp, Jbc); _cimg_median_sort(Jcc, Jnn); _cimg_median_sort(Jcp, Jcc); _cimg_median_sort(Jbc, Jnn);
_cimg_median_sort(Jcc, Jba); _cimg_median_sort(Jbc, Jba); _cimg_median_sort(Jbc, Jcc);
res(x,y,0,k) = Jcc;
}
} break;
default : {
CImg<T> vois;
cimg_forXYV(*this,x,y,k) {
const int
x0 = x - hl, y0 = y - hl, x1 = x + hr, y1 = y + hr,
nx0 = x0<0?0:x0, ny0 = y0<0?0:y0,
nx1 = x1>=dimx()?dimx()-1:x1, ny1 = y1>=dimy()?dimy()-1:y1;
vois = get_crop(nx0,ny0,0,k,nx1,ny1,0,k);
res(x,y,0,k) = vois.median();
}
}
} else switch (n) { // 1D median filter
case 2 : {
T I[4] = { 0 };
cimg_forV(*this,k) cimg_for2x2(*this,x,y,0,k,I) res(x,0,0,k) = (T)(0.5f*(I[0]+I[1]));
} break;
case 3 : {
T I[9] = { 0 };
cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
res(x,0,0,k) = I[3]<I[4]?
(I[4]<I[5]?I[4]:
(I[3]<I[5]?I[5]:I[3])):
(I[3]<I[5]?I[3]:
(I[4]<I[5]?I[5]:I[4]));
}
} break;
default : {
CImg<T> vois;
cimg_forXV(*this,x,k) {
const int
x0 = x - hl, x1 = x + hr,
nx0 = x0<0?0:x0, nx1 = x1>=dimx()?dimx()-1:x1;
vois = get_crop(nx0,0,0,k,nx1,0,0,k);
res(x,0,0,k) = vois.median();
}
}
}
}
return res;
}
//! Sharpen image using anisotropic shock filters or inverse diffusion.
CImg<T>& sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) {
if (is_empty()) return *this;
T valm, valM = maxmin(valm);
const bool threed = (depth>1);
const float nedge = 0.5f*edge;
CImg<Tfloat> val, vec, veloc(width,height,depth,dim);
if (threed) {
CImg_3x3x3(I,T);
if (sharpen_type) { // 3D Shock filter.
CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor());
if (sigma>0) G.blur(sigma);
cimg_forXYZ(G,x,y,z) {
G.get_tensor_at(x,y,z).symmetric_eigen(val,vec);
G(x,y,z,0) = vec(0,0);
G(x,y,z,1) = vec(0,1);
G(x,y,z,2) = vec(0,2);
G(x,y,z,3) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1]+val[2],-(Tfloat)nedge);
}
cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) {
const Tfloat
u = G(x,y,z,0),
v = G(x,y,z,1),
w = G(x,y,z,2),
amp = G(x,y,z,3),
ixx = (Tfloat)Incc + Ipcc - 2*Iccc,
ixy = 0.25f*((Tfloat)Innc + Ippc - Inpc - Ipnc),
ixz = 0.25f*((Tfloat)Incn + Ipcp - Incp - Ipcn),
iyy = (Tfloat)Icnc + Icpc - 2*Iccc,
iyz = 0.25f*((Tfloat)Icnn + Icpp - Icnp - Icpn),
izz = (Tfloat)Iccn + Iccp - 2*Iccc,
ixf = (Tfloat)Incc - Iccc,
ixb = (Tfloat)Iccc - Ipcc,
iyf = (Tfloat)Icnc - Iccc,
iyb = (Tfloat)Iccc - Icpc,
izf = (Tfloat)Iccn - Iccc,
izb = (Tfloat)Iccc - Iccp,
itt = u*u*ixx + v*v*iyy + w*w*izz + 2*u*v*ixy + 2*u*w*ixz + 2*v*w*iyz,
it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb) + w*cimg::minmod(izf,izb);
veloc(x,y,z,k) = -amp*cimg::sign(itt)*cimg::abs(it);
}
} else cimg_forV(*this,k) cimg_for3x3x3(*this,x,y,z,k,I) veloc(x,y,z,k) = -(Tfloat)Ipcc-Incc-Icpc-Icnc-Iccp-Iccn+6*Iccc; // 3D Inverse diffusion.
} else {
CImg_3x3(I,T);
if (sharpen_type) { // 2D Shock filter.
CImg<Tfloat> G = (alpha>0?get_blur(alpha).get_structure_tensor():get_structure_tensor());
if (sigma>0) G.blur(sigma);
cimg_forXY(G,x,y) {
G.get_tensor_at(x,y).symmetric_eigen(val,vec);
G(x,y,0) = vec(0,0);
G(x,y,1) = vec(0,1);
G(x,y,2) = 1 - (Tfloat)cimg_std::pow(1+val[0]+val[1],-(Tfloat)nedge);
}
cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) {
const Tfloat
u = G(x,y,0),
v = G(x,y,1),
amp = G(x,y,2),
ixx = (Tfloat)Inc + Ipc - 2*Icc,
ixy = 0.25f*((Tfloat)Inn + Ipp - Inp - Ipn),
iyy = (Tfloat)Icn + Icp - 2*Icc,
ixf = (Tfloat)Inc - Icc,
ixb = (Tfloat)Icc - Ipc,
iyf = (Tfloat)Icn - Icc,
iyb = (Tfloat)Icc - Icp,
itt = u*u*ixx + v*v*iyy + 2*u*v*ixy,
it = u*cimg::minmod(ixf,ixb) + v*cimg::minmod(iyf,iyb);
veloc(x,y,k) = -amp*cimg::sign(itt)*cimg::abs(it);
}
} else cimg_forV(*this,k) cimg_for3x3(*this,x,y,0,k,I) veloc(x,y,k) = -(Tfloat)Ipc-Inc-Icp-Icn+4*Icc; // 3D Inverse diffusion.
}
float m, M = (float)veloc.maxmin(m);
const float vmax = (float)cimg::max(cimg::abs(m),cimg::abs(M));
if (vmax!=0) { veloc*=amplitude/vmax; (*this)+=veloc; }
return cut(valm,valM);
}
CImg<T> get_sharpen(const float amplitude, const bool sharpen_type=false, const float edge=1, const float alpha=0, const float sigma=0) const {
return (+*this).sharpen(amplitude,sharpen_type,edge,alpha,sigma);
}
//! Compute the Haar multiscale wavelet transform (monodimensional version).
/**
\param axis Axis considered for the transform.
\param invert Set inverse of direct transform.
\param nb_scales Number of scales used for the transform.
**/
CImg<T>& haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) {
return get_haar(axis,invert,nb_scales).transfer_to(*this);
}
CImg<Tfloat> get_haar(const char axis, const bool invert=false, const unsigned int nb_scales=1) const {
if (is_empty() || !nb_scales) return *this;
CImg<Tfloat> res;
if (nb_scales==1) {
switch (cimg::uncase(axis)) { // Single scale transform
case 'x' : {
const unsigned int w = width/2;
if (w) {
if (w%2)
throw CImgInstanceException("CImg<%s>::haar() : Sub-image width = %u is not even at a particular scale (=%u).",
pixel_type(),w);
res.assign(width,height,depth,dim);
if (invert) cimg_forYZV(*this,y,z,v) { // Inverse transform along X
for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(xw,y,z,v);
res(x2++,y,z,v) = val0 - val1;
res(x2++,y,z,v) = val0 + val1;
}
} else cimg_forYZV(*this,y,z,v) { // Direct transform along X
for (unsigned int x=0, xw=w, x2=0; x<w; ++x, ++xw) {
const Tfloat val0 = (Tfloat)(*this)(x2++,y,z,v), val1 = (Tfloat)(*this)(x2++,y,z,v);
res(x,y,z,v) = (val0 + val1)/2;
res(xw,y,z,v) = (val1 - val0)/2;
}
}
} else return *this;
} break;
case 'y' : {
const unsigned int h = height/2;
if (h) {
if (h%2)
throw CImgInstanceException("CImg<%s>::haar() : Sub-image height = %u is not even at a particular scale.",
pixel_type(),h);
res.assign(width,height,depth,dim);
if (invert) cimg_forXZV(*this,x,z,v) { // Inverse transform along Y
for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) {
const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(x,yh,z,v);
res(x,y2++,z,v) = val0 - val1;
res(x,y2++,z,v) = val0 + val1;
}
} else cimg_forXZV(*this,x,z,v) {
for (unsigned int y=0, yh=h, y2=0; y<h; ++y, ++yh) { // Direct transform along Y
const Tfloat val0 = (Tfloat)(*this)(x,y2++,z,v), val1 = (Tfloat)(*this)(x,y2++,z,v);
res(x,y,z,v) = (val0 + val1)/2;
res(x,yh,z,v) = (val1 - val0)/2;
}
}
} else return *this;
} break;
case 'z' : {
const unsigned int d = depth/2;
if (d) {
if (d%2)
throw CImgInstanceException("CImg<%s>::haar() : Sub-image depth = %u is not even at a particular scale.",
pixel_type(),d);
res.assign(width,height,depth,dim);
if (invert) cimg_forXYV(*this,x,y,v) { // Inverse transform along Z
for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) {
const Tfloat val0 = (Tfloat)(*this)(x,y,z,v), val1 = (Tfloat)(*this)(x,y,zd,v);
res(x,y,z2++,v) = val0 - val1;
res(x,y,z2++,v) = val0 + val1;
}
} else cimg_forXYV(*this,x,y,v) {
for (unsigned int z=0, zd=d, z2=0; z<d; ++z, ++zd) { // Direct transform along Z
const Tfloat val0 = (Tfloat)(*this)(x,y,z2++,v), val1 = (Tfloat)(*this)(x,y,z2++,v);
res(x,y,z,v) = (val0 + val1)/2;
res(x,y,zd,v) = (val1 - val0)/2;
}
}
} else return *this;
} break;
default :
throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
pixel_type(),axis);
}
} else { // Multi-scale version
if (invert) {
res.assign(*this);
switch (cimg::uncase(axis)) {
case 'x' : {
unsigned int w = width;
for (unsigned int s=1; w && s<nb_scales; ++s) w/=2;
for (w=w?w:1; w<=width; w*=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',true,1));
} break;
case 'y' : {
unsigned int h = width;
for (unsigned int s=1; h && s<nb_scales; ++s) h/=2;
for (h=h?h:1; h<=height; h*=2) res.draw_image(res.get_crop(0,0,width-1,h-1).get_haar('y',true,1));
} break;
case 'z' : {
unsigned int d = depth;
for (unsigned int s=1; d && s<nb_scales; ++s) d/=2;
for (d=d?d:1; d<=depth; d*=2) res.draw_image(res.get_crop(0,0,0,width-1,height-1,d-1).get_haar('z',true,1));
} break;
default :
throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
pixel_type(),axis);
}
} else { // Direct transform
res = get_haar(axis,false,1);
switch (cimg::uncase(axis)) {
case 'x' : {
for (unsigned int s=1, w=width/2; w && s<nb_scales; ++s, w/=2) res.draw_image(res.get_crop(0,w-1).get_haar('x',false,1));
} break;
case 'y' : {
for (unsigned int s=1, h=height/2; h && s<nb_scales; ++s, h/=2) res.draw_image(res.get_crop(0,0,width-1,h-1).get_haar('y',false,1));
} break;
case 'z' : {
for (unsigned int s=1, d=depth/2; d && s<nb_scales; ++s, d/=2) res.draw_image(res.get_crop(0,0,0,width-1,height-1,d-1).get_haar('z',false,1));
} break;
default :
throw CImgArgumentException("CImg<%s>::haar() : Invalid axis '%c', must be 'x','y' or 'z'.",
pixel_type(),axis);
}
}
}
return res;
}
//! Compute the Haar multiscale wavelet transform.
/**
\param invert Set inverse of direct transform.
\param nb_scales Number of scales used for the transform.
**/
CImg<T>& haar(const bool invert=false, const unsigned int nb_scales=1) {
return get_haar(invert,nb_scales).transfer_to(*this);
}
CImg<Tfloat> get_haar(const bool invert=false, const unsigned int nb_scales=1) const {
CImg<Tfloat> res;
if (nb_scales==1) { // Single scale transform
if (width>1) get_haar('x',invert,1).transfer_to(res);
if (height>1) { if (res) res.get_haar('y',invert,1).transfer_to(res); else get_haar('y',invert,1).transfer_to(res); }
if (depth>1) { if (res) res.get_haar('z',invert,1).transfer_to(res); else get_haar('z',invert,1).transfer_to(res); }
if (res) return res;
} else { // Multi-scale transform
if (invert) { // Inverse transform
res.assign(*this);
if (width>1) {
if (height>1) {
if (depth>1) {
unsigned int w = width, h = height, d = depth; for (unsigned int s=1; w && h && d && s<nb_scales; ++s) { w/=2; h/=2; d/=2; }
for (w=w?w:1, h=h?h:1, d=d?d:1; w<=width && h<=height && d<=depth; w*=2, h*=2, d*=2)
res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).get_haar(true,1));
} else {
unsigned int w = width, h = height; for (unsigned int s=1; w && h && s<nb_scales; ++s) { w/=2; h/=2; }
for (w=w?w:1, h=h?h:1; w<=width && h<=height; w*=2, h*=2)
res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).get_haar(true,1));
}
} else {
if (depth>1) {
unsigned int w = width, d = depth; for (unsigned int s=1; w && d && s<nb_scales; ++s) { w/=2; d/=2; }
for (w=w?w:1, d=d?d:1; w<=width && d<=depth; w*=2, d*=2)
res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).get_haar(true,1));
} else {
unsigned int w = width; for (unsigned int s=1; w && s<nb_scales; ++s) w/=2;
for (w=w?w:1; w<=width; w*=2)
res.draw_image(res.get_crop(0,0,0,w-1,0,0).get_haar(true,1));
}
}
} else {
if (height>1) {
if (depth>1) {
unsigned int h = height, d = depth; for (unsigned int s=1; h && d && s<nb_scales; ++s) { h/=2; d/=2; }
for (h=h?h:1, d=d?d:1; h<=height && d<=depth; h*=2, d*=2)
res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).get_haar(true,1));
} else {
unsigned int h = height; for (unsigned int s=1; h && s<nb_scales; ++s) h/=2;
for (h=h?h:1; h<=height; h*=2)
res.draw_image(res.get_crop(0,0,0,0,h-1,0).get_haar(true,1));
}
} else {
if (depth>1) {
unsigned int d = depth; for (unsigned int s=1; d && s<nb_scales; ++s) d/=2;
for (d=d?d:1; d<=depth; d*=2)
res.draw_image(res.get_crop(0,0,0,0,0,d-1).get_haar(true,1));
} else return *this;
}
}
} else { // Direct transform
res = get_haar(false,1);
if (width>1) {
if (height>1) {
if (depth>1) for (unsigned int s=1, w=width/2, h=height/2, d=depth/2; w && h && d && s<nb_scales; ++s, w/=2, h/=2, d/=2)
res.draw_image(res.get_crop(0,0,0,w-1,h-1,d-1).haar(false,1));
else for (unsigned int s=1, w=width/2, h=height/2; w && h && s<nb_scales; ++s, w/=2, h/=2)
res.draw_image(res.get_crop(0,0,0,w-1,h-1,0).haar(false,1));
} else {
if (depth>1) for (unsigned int s=1, w=width/2, d=depth/2; w && d && s<nb_scales; ++s, w/=2, d/=2)
res.draw_image(res.get_crop(0,0,0,w-1,0,d-1).haar(false,1));
else for (unsigned int s=1, w=width/2; w && s<nb_scales; ++s, w/=2)
res.draw_image(res.get_crop(0,0,0,w-1,0,0).haar(false,1));
}
} else {
if (height>1) {
if (depth>1) for (unsigned int s=1, h=height/2, d=depth/2; h && d && s<nb_scales; ++s, h/=2, d/=2)
res.draw_image(res.get_crop(0,0,0,0,h-1,d-1).haar(false,1));
else for (unsigned int s=1, h=height/2; h && s<nb_scales; ++s, h/=2)
res.draw_image(res.get_crop(0,0,0,0,h-1,0).haar(false,1));
} else {
if (depth>1) for (unsigned int s=1, d=depth/2; d && s<nb_scales; ++s, d/=2)
res.draw_image(res.get_crop(0,0,0,0,0,d-1).haar(false,1));
else return *this;
}
}
}
return res;
}
return *this;
}
//! Estimate a displacement field between instance image and given target image.
CImg<T>& displacement_field(const CImg<T>& target, const float smooth=0.1f, const float precision=0.1f,
const unsigned int nb_scales=0, const unsigned int itermax=10000) {
return get_displacement_field(target,smooth,precision,nb_scales,itermax).transfer_to(*this);
}
CImg<Tfloat> get_displacement_field(const CImg<T>& target,
const float smoothness=0.1f, const float precision=0.1f,
const unsigned int nb_scales=0, const unsigned int itermax=10000) const {
if (is_empty() || !target) return *this;
if (!is_sameXYZV(target))
throw CImgArgumentException("CImg<%s>::displacement_field() : Instance image (%u,%u,%u,%u,%p) and target image (%u,%u,%u,%u,%p) "
"have different size.",
pixel_type(),width,height,depth,dim,data,
target.width,target.height,target.depth,target.dim,target.data);
if (smoothness<0)
throw CImgArgumentException("CImg<%s>::displacement_field() : Smoothness parameter %g is negative.",
pixel_type(),smoothness);
if (precision<0)
throw CImgArgumentException("CImg<%s>::displacement_field() : Precision parameter %g is negative.",
pixel_type(),precision);
const unsigned int nscales = nb_scales>0?nb_scales:(unsigned int)(2*cimg_std::log((double)(cimg::max(width,height,depth))));
Tfloat m1, M1 = (Tfloat)maxmin(m1), m2, M2 = (Tfloat)target.maxmin(m2);
const Tfloat factor = cimg::max(cimg::abs(m1),cimg::abs(M1),cimg::abs(m2),cimg::abs(M2));
CImg<Tfloat> U0;
const bool threed = (depth>1);
// Begin multi-scale motion estimation
for (int scale = (int)nscales-1; scale>=0; --scale) {
const float sfactor = (float)cimg_std::pow(1.5f,(float)scale), sprecision = (float)(precision/cimg_std::pow(2.25,1+scale));
const int
sw = (int)(width/sfactor), sh = (int)(height/sfactor), sd = (int)(depth/sfactor),
swidth = sw?sw:1, sheight = sh?sh:1, sdepth = sd?sd:1;
CImg<Tfloat>
I1 = get_resize(swidth,sheight,sdepth,-100,2),
I2 = target.get_resize(swidth,sheight,sdepth,-100,2);
I1/=factor; I2/=factor;
CImg<Tfloat> U;
if (U0) U = (U0*=1.5f).get_resize(I1.dimx(),I1.dimy(),I1.dimz(),-100,3);
else U.assign(I1.dimx(),I1.dimy(),I1.dimz(),threed?3:2,0);
// Begin single-scale motion estimation
CImg<Tfloat> veloc(U);
float dt = 2, Energy = cimg::type<float>::max();
const CImgList<Tfloat> dI = I2.get_gradient();
for (unsigned int iter=0; iter<itermax; iter++) {
veloc.fill(0);
float nEnergy = 0;
if (threed) {
cimg_for3XYZ(U,x,y,z) {
const float X = (float)(x + U(x,y,z,0)), Y = (float)(y + U(x,y,z,1)), Z = (float)(z + U(x,y,z,2));
cimg_forV(U,k) {
const Tfloat
Ux = 0.5f*(U(_n1x,y,z,k) - U(_p1x,y,z,k)),
Uy = 0.5f*(U(x,_n1y,z,k) - U(x,_p1y,z,k)),
Uz = 0.5f*(U(x,y,_n1z,k) - U(x,y,_p1z,k)),
Uxx = U(_n1x,y,z,k) + U(_p1x,y,z,k) - 2*U(x,y,z,k),
Uyy = U(x,_n1y,z,k) + U(x,_p1y,z,k) - 2*U(x,y,z,k),
Uzz = U(x,y,_n1z,k) + U(x,y,_n1z,k) - 2*U(x,y,z,k);
nEnergy += (float)(smoothness*(Ux*Ux + Uy*Uy + Uz*Uz));
Tfloat deltaIgrad = 0;
cimg_forV(I1,i) {
const Tfloat deltaIi = (float)(I2._linear_atXYZ(X,Y,Z,i) - I1(x,y,z,i));
nEnergy += (float)(deltaIi*deltaIi/2);
deltaIgrad+=-deltaIi*dI[k]._linear_atXYZ(X,Y,Z,i);
}
veloc(x,y,z,k) = deltaIgrad + smoothness*(Uxx + Uyy + Uzz);
}
}
} else {
cimg_for3XY(U,x,y) {
const float X = (float)(x + U(x,y,0)), Y = (float)(y + U(x,y,1));
cimg_forV(U,k) {
const Tfloat
Ux = 0.5f*(U(_n1x,y,k) - U(_p1x,y,k)),
Uy = 0.5f*(U(x,_n1y,k) - U(x,_p1y,k)),
Uxx = U(_n1x,y,k) + U(_p1x,y,k) - 2*U(x,y,k),
Uyy = U(x,_n1y,k) + U(x,_p1y,k) - 2*U(x,y,k);
nEnergy += (float)(smoothness*(Ux*Ux + Uy*Uy));
Tfloat deltaIgrad = 0;
cimg_forV(I1,i) {
const Tfloat deltaIi = (float)(I2._linear_atXY(X,Y,i) - I1(x,y,i));
nEnergy += (float)(deltaIi*deltaIi/2);
deltaIgrad+=-deltaIi*dI[k]._linear_atXY(X,Y,i);
}
veloc(x,y,k) = deltaIgrad + smoothness*(Uxx + Uyy);
}
}
}
const Tfloat vmax = cimg::max(cimg::abs(veloc.min()), cimg::abs(veloc.max()));
U+=(veloc*=dt/vmax);
if (cimg::abs(nEnergy-Energy)<sprecision) break;
if (nEnergy<Energy) dt*=0.5f;
Energy = nEnergy;
}
U.transfer_to(U0);
}
return U0;
}
//@}
//-----------------------------
//
//! \name Matrix and Vectors
//@{
//-----------------------------
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0) {
static CImg<T> r(1,1); r[0] = a0;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1) {
static CImg<T> r(1,2); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2) {
static CImg<T> r(1,3); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3) {
static CImg<T> r(1,4); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
static CImg<T> r(1,5); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4, const T& a5) {
static CImg<T> r(1,6); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6) {
static CImg<T> r(1,7); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7) {
static CImg<T> r(1,8); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8) {
static CImg<T> r(1,9); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9) {
static CImg<T> r(1,10); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10) {
static CImg<T> r(1,11); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10, const T& a11) {
static CImg<T> r(1,12); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10, const T& a11,
const T& a12) {
static CImg<T> r(1,13); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
*(ptr++) = a12;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10, const T& a11,
const T& a12, const T& a13) {
static CImg<T> r(1,14); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
*(ptr++) = a12; *(ptr++) = a13;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10, const T& a11,
const T& a12, const T& a13, const T& a14) {
static CImg<T> r(1,15); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
*(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
return r;
}
//! Return a vector with specified coefficients.
static CImg<T> vector(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10, const T& a11,
const T& a12, const T& a13, const T& a14, const T& a15) {
static CImg<T> r(1,16); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
*(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
return r;
}
//! Return a 1x1 square matrix with specified coefficients.
static CImg<T> matrix(const T& a0) {
return vector(a0);
}
//! Return a 2x2 square matrix with specified coefficients.
static CImg<T> matrix(const T& a0, const T& a1,
const T& a2, const T& a3) {
static CImg<T> r(2,2); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1;
*(ptr++) = a2; *(ptr++) = a3;
return r;
}
//! Return a 3x3 square matrix with specified coefficients.
static CImg<T> matrix(const T& a0, const T& a1, const T& a2,
const T& a3, const T& a4, const T& a5,
const T& a6, const T& a7, const T& a8) {
static CImg<T> r(3,3); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2;
*(ptr++) = a3; *(ptr++) = a4; *(ptr++) = a5;
*(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8;
return r;
}
//! Return a 4x4 square matrix with specified coefficients.
static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3,
const T& a4, const T& a5, const T& a6, const T& a7,
const T& a8, const T& a9, const T& a10, const T& a11,
const T& a12, const T& a13, const T& a14, const T& a15) {
static CImg<T> r(4,4); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3;
*(ptr++) = a4; *(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7;
*(ptr++) = a8; *(ptr++) = a9; *(ptr++) = a10; *(ptr++) = a11;
*(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14; *(ptr++) = a15;
return r;
}
//! Return a 5x5 square matrix with specified coefficients.
static CImg<T> matrix(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4,
const T& a5, const T& a6, const T& a7, const T& a8, const T& a9,
const T& a10, const T& a11, const T& a12, const T& a13, const T& a14,
const T& a15, const T& a16, const T& a17, const T& a18, const T& a19,
const T& a20, const T& a21, const T& a22, const T& a23, const T& a24) {
static CImg<T> r(5,5); T *ptr = r.data;
*(ptr++) = a0; *(ptr++) = a1; *(ptr++) = a2; *(ptr++) = a3; *(ptr++) = a4;
*(ptr++) = a5; *(ptr++) = a6; *(ptr++) = a7; *(ptr++) = a8; *(ptr++) = a9;
*(ptr++) = a10; *(ptr++) = a11; *(ptr++) = a12; *(ptr++) = a13; *(ptr++) = a14;
*(ptr++) = a15; *(ptr++) = a16; *(ptr++) = a17; *(ptr++) = a18; *(ptr++) = a19;
*(ptr++) = a20; *(ptr++) = a21; *(ptr++) = a22; *(ptr++) = a23; *(ptr++) = a24;
return r;
}
//! Return a 1x1 symmetric matrix with specified coefficients.
static CImg<T> tensor(const T& a1) {
return matrix(a1);
}
//! Return a 2x2 symmetric matrix tensor with specified coefficients.
static CImg<T> tensor(const T& a1, const T& a2, const T& a3) {
return matrix(a1,a2,a2,a3);
}
//! Return a 3x3 symmetric matrix with specified coefficients.
static CImg<T> tensor(const T& a1, const T& a2, const T& a3, const T& a4, const T& a5, const T& a6) {
return matrix(a1,a2,a3,a2,a4,a5,a3,a5,a6);
}
//! Return a 1x1 diagonal matrix with specified coefficients.
static CImg<T> diagonal(const T& a0) {
return matrix(a0);
}
//! Return a 2x2 diagonal matrix with specified coefficients.
static CImg<T> diagonal(const T& a0, const T& a1) {
return matrix(a0,0,0,a1);
}
//! Return a 3x3 diagonal matrix with specified coefficients.
static CImg<T> diagonal(const T& a0, const T& a1, const T& a2) {
return matrix(a0,0,0,0,a1,0,0,0,a2);
}
//! Return a 4x4 diagonal matrix with specified coefficients.
static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3) {
return matrix(a0,0,0,0,0,a1,0,0,0,0,a2,0,0,0,0,a3);
}
//! Return a 5x5 diagonal matrix with specified coefficients.
static CImg<T> diagonal(const T& a0, const T& a1, const T& a2, const T& a3, const T& a4) {
return matrix(a0,0,0,0,0,0,a1,0,0,0,0,0,a2,0,0,0,0,0,a3,0,0,0,0,0,a4);
}
//! Return a NxN identity matrix.
static CImg<T> identity_matrix(const unsigned int N) {
CImg<T> res(N,N,1,1,0);
cimg_forX(res,x) res(x,x) = 1;
return res;
}
//! Return a N-numbered sequence vector from \p a0 to \p a1.
static CImg<T> sequence(const unsigned int N, const T a0, const T a1) {
if (N) return CImg<T>(1,N).sequence(a0,a1);
return CImg<T>();
}
//! Return a 3x3 rotation matrix along the (x,y,z)-axis with an angle w.
static CImg<T> rotation_matrix(const float x, const float y, const float z, const float w, const bool quaternion_data=false) {
float X,Y,Z,W;
if (!quaternion_data) {
const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z),
nx = norm>0?x/norm:0,
ny = norm>0?y/norm:0,
nz = norm>0?z/norm:1,
nw = norm>0?w:0,
sina = (float)cimg_std::sin(nw/2),
cosa = (float)cimg_std::cos(nw/2);
X = nx*sina;
Y = ny*sina;
Z = nz*sina;
W = cosa;
} else {
const float norm = (float)cimg_std::sqrt(x*x + y*y + z*z + w*w);
if (norm>0) { X = x/norm; Y = y/norm; Z = z/norm; W = w/norm; }
else { X = Y = Z = 0; W = 1; }
}
const float xx = X*X, xy = X*Y, xz = X*Z, xw = X*W, yy = Y*Y, yz = Y*Z, yw = Y*W, zz = Z*Z, zw = Z*W;
return CImg<T>::matrix((T)(1-2*(yy+zz)), (T)(2*(xy+zw)), (T)(2*(xz-yw)),
(T)(2*(xy-zw)), (T)(1-2*(xx+zz)), (T)(2*(yz+xw)),
(T)(2*(xz+yw)), (T)(2*(yz-xw)), (T)(1-2*(xx+yy)));
}
//! Return a new image corresponding to the vector located at (\p x,\p y,\p z) of the current vector-valued image.
CImg<T> get_vector_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
static CImg<T> dest;
if (dest.height!=dim) dest.assign(1,dim);
const unsigned int whz = width*height*depth;
const T *ptrs = ptr(x,y,z);
T *ptrd = dest.data;
cimg_forV(*this,k) { *(ptrd++) = *ptrs; ptrs+=whz; }
return dest;
}
//! Set the image \p vec as the \a vector \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
template<typename t>
CImg<T>& set_vector_at(const CImg<t>& vec, const unsigned int x, const unsigned int y=0, const unsigned int z=0) {
if (x<width && y<height && z<depth) {
const unsigned int whz = width*height*depth;
const t *ptrs = vec.data;
T *ptrd = ptr(x,y,z);
for (unsigned int k=cimg::min((unsigned int)vec.size(),dim); k; --k) { *ptrd = (T)*(ptrs++); ptrd+=whz; }
}
return *this;
}
//! Return a new image corresponding to the \a square \a matrix located at (\p x,\p y,\p z) of the current vector-valued image.
CImg<T> get_matrix_at(const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) const {
const int n = (int)cimg_std::sqrt((double)dim);
CImg<T> dest(n,n);
cimg_forV(*this,k) dest[k]=(*this)(x,y,z,k);
return dest;
}
//! Set the image \p vec as the \a square \a matrix-valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
template<typename t>
CImg<T>& set_matrix_at(const CImg<t>& mat, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
return set_vector_at(mat,x,y,z);
}
//! Return a new image corresponding to the \a diffusion \a tensor located at (\p x,\p y,\p z) of the current vector-valued image.
CImg<T> get_tensor_at(const unsigned int x, const unsigned int y=0, const unsigned int z=0) const {
if (dim==6) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2),
(*this)(x,y,z,3),(*this)(x,y,z,4),(*this)(x,y,z,5));
if (dim==3) return tensor((*this)(x,y,z,0),(*this)(x,y,z,1),(*this)(x,y,z,2));
return tensor((*this)(x,y,z,0));
}
//! Set the image \p vec as the \a tensor \a valued pixel located at (\p x,\p y,\p z) of the current vector-valued image.
template<typename t>
CImg<T>& set_tensor_at(const CImg<t>& ten, const unsigned int x=0, const unsigned int y=0, const unsigned int z=0) {
if (ten.height==2) {
(*this)(x,y,z,0) = (T)ten[0];
(*this)(x,y,z,1) = (T)ten[1];
(*this)(x,y,z,2) = (T)ten[3];
}
else {
(*this)(x,y,z,0) = (T)ten[0];
(*this)(x,y,z,1) = (T)ten[1];
(*this)(x,y,z,2) = (T)ten[2];
(*this)(x,y,z,3) = (T)ten[4];
(*this)(x,y,z,4) = (T)ten[5];
(*this)(x,y,z,5) = (T)ten[8];
}
return *this;
}
//! Unroll all images values into a one-column vector.
CImg<T>& vector() {
return unroll('y');
}
CImg<T> get_vector() const {
return get_unroll('y');
}
//! Realign pixel values of the instance image as a square matrix
CImg<T>& matrix() {
const unsigned int siz = size();
switch (siz) {
case 1 : break;
case 4 : width = height = 2; break;
case 9 : width = height = 3; break;
case 16 : width = height = 4; break;
case 25 : width = height = 5; break;
case 36 : width = height = 6; break;
case 49 : width = height = 7; break;
case 64 : width = height = 8; break;
case 81 : width = height = 9; break;
case 100 : width = height = 10; break;
default : {
unsigned int i = 11, i2 = i*i;
while (i2<siz) { i2+=2*i+1; ++i; }
if (i2==siz) width = height = i;
else throw CImgInstanceException("CImg<%s>::matrix() : Image size = %u is not a square number",
pixel_type(),siz);
}
}
return *this;
}
CImg<T> get_matrix() const {
return (+*this).matrix();
}
//! Realign pixel values of the instance image as a symmetric tensor.
CImg<T>& tensor() {
return get_tensor().transfer_to(*this);
}
CImg<T> get_tensor() const {
CImg<T> res;
const unsigned int siz = size();
switch (siz) {
case 1 : break;
case 3 :
res.assign(2,2);
res(0,0) = (*this)(0);
res(1,0) = res(0,1) = (*this)(1);
res(1,1) = (*this)(2);
break;
case 6 :
res.assign(3,3);
res(0,0) = (*this)(0);
res(1,0) = res(0,1) = (*this)(1);
res(2,0) = res(0,2) = (*this)(2);
res(1,1) = (*this)(3);
res(2,1) = res(1,2) = (*this)(4);
res(2,2) = (*this)(5);
break;
default :
throw CImgInstanceException("CImg<%s>::tensor() : Wrong vector dimension = %u in instance image.",
pixel_type(), dim);
}
return res;
}
//! Unroll all images values into specified axis.
CImg<T>& unroll(const char axis) {
const unsigned int siz = size();
if (siz) switch (axis) {
case 'x' : width = siz; height=depth=dim=1; break;
case 'y' : height = siz; width=depth=dim=1; break;
case 'z' : depth = siz; width=height=dim=1; break;
case 'v' : dim = siz; width=height=depth=1; break;
default :
throw CImgArgumentException("CImg<%s>::unroll() : Given axis is '%c' which is not 'x','y','z' or 'v'",
pixel_type(),axis);
}
return *this;
}
CImg<T> get_unroll(const char axis) const {
return (+*this).unroll(axis);
}
//! Get a diagonal matrix, whose diagonal coefficients are the coefficients of the input image.
CImg<T>& diagonal() {
return get_diagonal().transfer_to(*this);
}
CImg<T> get_diagonal() const {
if (is_empty()) return *this;
CImg<T> res(size(),size(),1,1,0);
cimg_foroff(*this,off) res(off,off) = (*this)(off);
return res;
}
//! Get an identity matrix having same dimension than instance image.
CImg<T>& identity_matrix() {
return identity_matrix(cimg::max(width,height)).transfer_to(*this);
}
CImg<T> get_identity_matrix() const {
return identity_matrix(cimg::max(width,height));
}
//! Return a N-numbered sequence vector from \p a0 to \p a1.
CImg<T>& sequence(const T a0, const T a1) {
if (is_empty()) return *this;
const unsigned int siz = size() - 1;
T* ptr = data;
if (siz) {
const Tfloat delta = (Tfloat)a1 - a0;
cimg_foroff(*this,l) *(ptr++) = (T)(a0 + delta*l/siz);
} else *ptr = a0;
return *this;
}
CImg<T> get_sequence(const T a0, const T a1) const {
return (+*this).sequence(a0,a1);
}
//! Transpose the current matrix.
CImg<T>& transpose() {
if (width==1) { width=height; height=1; return *this; }
if (height==1) { height=width; width=1; return *this; }
if (width==height) {
cimg_forYZV(*this,y,z,v) for (int x=y; x<dimx(); ++x) cimg::swap((*this)(x,y,z,v),(*this)(y,x,z,v));
return *this;
}
return get_transpose().transfer_to(*this);
}
CImg<T> get_transpose() const {
return get_permute_axes("yxzv");
}
//! Invert the current matrix.
CImg<T>& invert(const bool use_LU=true) {
if (!is_empty()) {
if (width!=height || depth!=1 || dim!=1)
throw CImgInstanceException("CImg<%s>::invert() : Instance matrix (%u,%u,%u,%u,%p) is not square.",
pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_lapack
int INFO = (int)use_LU, N = width, LWORK = 4*N, *IPIV = new int[N];
Tfloat
*lapA = new Tfloat[N*N],
*WORK = new Tfloat[LWORK];
cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
cimg::getrf(N,lapA,IPIV,INFO);
if (INFO)
cimg::warn("CImg<%s>::invert() : LAPACK library function dgetrf_() returned error code %d.",
pixel_type(),INFO);
else {
cimg::getri(N,lapA,IPIV,WORK,LWORK,INFO);
if (INFO)
cimg::warn("CImg<%s>::invert() : LAPACK library function dgetri_() returned Error code %d",
pixel_type(),INFO);
}
if (!INFO) cimg_forXY(*this,k,l) (*this)(k,l) = (T)(lapA[k*N+l]); else fill(0);
delete[] IPIV; delete[] lapA; delete[] WORK;
#else
const double dete = width>3?-1.0:det();
if (dete!=0.0 && width==2) {
const double
a = data[0], c = data[1],
b = data[2], d = data[3];
data[0] = (T)(d/dete); data[1] = (T)(-c/dete);
data[2] = (T)(-b/dete); data[3] = (T)(a/dete);
} else if (dete!=0.0 && width==3) {
const double
a = data[0], d = data[1], g = data[2],
b = data[3], e = data[4], h = data[5],
c = data[6], f = data[7], i = data[8];
data[0] = (T)((i*e-f*h)/dete), data[1] = (T)((g*f-i*d)/dete), data[2] = (T)((d*h-g*e)/dete);
data[3] = (T)((h*c-i*b)/dete), data[4] = (T)((i*a-c*g)/dete), data[5] = (T)((g*b-a*h)/dete);
data[6] = (T)((b*f-e*c)/dete), data[7] = (T)((d*c-a*f)/dete), data[8] = (T)((a*e-d*b)/dete);
} else {
if (use_LU) { // LU-based inverse computation
CImg<Tfloat> A(*this), indx, col(1,width);
bool d;
A._LU(indx,d);
cimg_forX(*this,j) {
col.fill(0);
col(j) = 1;
col._solve(A,indx);
cimg_forX(*this,i) (*this)(j,i) = (T)col(i);
}
} else { // SVD-based inverse computation
CImg<Tfloat> U(width,width), S(1,width), V(width,width);
SVD(U,S,V,false);
U.transpose();
cimg_forY(S,k) if (S[k]!=0) S[k]=1/S[k];
S.diagonal();
*this = V*S*U;
}
}
#endif
}
return *this;
}
CImg<Tfloat> get_invert(const bool use_LU=true) const {
return CImg<Tfloat>(*this,false).invert(use_LU);
}
//! Compute the pseudo-inverse (Moore-Penrose) of the matrix.
CImg<T>& pseudoinvert() {
return get_pseudoinvert().transfer_to(*this);
}
CImg<Tfloat> get_pseudoinvert() const {
CImg<Tfloat> U, S, V;
SVD(U,S,V);
cimg_forX(V,x) {
const Tfloat s = S(x), invs = s!=0?1/s:(Tfloat)0;
cimg_forY(V,y) V(x,y)*=invs;
}
return V*U.transpose();
}
//! Compute the cross product between two 3d vectors.
template<typename t>
CImg<T>& cross(const CImg<t>& img) {
if (width!=1 || height<3 || img.width!=1 || img.height<3)
throw CImgInstanceException("CImg<%s>::cross() : Arguments (%u,%u,%u,%u,%p) and (%u,%u,%u,%u,%p) must be both 3d vectors.",
pixel_type(),width,height,depth,dim,data,img.width,img.height,img.depth,img.dim,img.data);
const T x = (*this)[0], y = (*this)[1], z = (*this)[2];
(*this)[0] = (T)(y*img[2]-z*img[1]);
(*this)[1] = (T)(z*img[0]-x*img[2]);
(*this)[2] = (T)(x*img[1]-y*img[0]);
return *this;
}
template<typename t>
CImg<typename cimg::superset<T,t>::type> get_cross(const CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImg<Tt>(*this).cross(img);
}
//! Solve a linear system AX=B where B=*this.
template<typename t>
CImg<T>& solve(const CImg<t>& A) {
if (width!=1 || depth!=1 || dim!=1 || height!=A.height || A.depth!=1 || A.dim!=1)
throw CImgArgumentException("CImg<%s>::solve() : Instance matrix size is (%u,%u,%u,%u) while "
"size of given matrix A is (%u,%u,%u,%u).",
pixel_type(),width,height,depth,dim,A.width,A.height,A.depth,A.dim);
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
if (A.width==A.height) {
#ifdef cimg_use_lapack
char TRANS='N';
int INFO, N = height, LWORK = 4*N, one = 1, *IPIV = new int[N];
Ttfloat
*lapA = new Ttfloat[N*N],
*lapB = new Ttfloat[N],
*WORK = new Ttfloat[LWORK];
cimg_forXY(A,k,l) lapA[k*N+l] = (Ttfloat)(A(k,l));
cimg_forY(*this,i) lapB[i] = (Ttfloat)((*this)(i));
cimg::getrf(N,lapA,IPIV,INFO);
if (INFO)
cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrf_() returned error code %d.",
pixel_type(),INFO);
if (!INFO) {
cimg::getrs(TRANS,N,lapA,IPIV,lapB,INFO);
if (INFO)
cimg::warn("CImg<%s>::solve() : LAPACK library function dgetrs_() returned Error code %d",
pixel_type(),INFO);
}
if (!INFO) cimg_forY(*this,i) (*this)(i) = (T)(lapB[i]); else fill(0);
delete[] IPIV; delete[] lapA; delete[] lapB; delete[] WORK;
#else
CImg<Ttfloat> lu(A);
CImg<Ttfloat> indx;
bool d;
lu._LU(indx,d);
_solve(lu,indx);
#endif
} else assign(A.get_pseudoinvert()*(*this));
return *this;
}
template<typename t>
CImg<typename cimg::superset2<T,t,float>::type> get_solve(const CImg<t>& A) const {
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
return CImg<Ttfloat>(*this,false).solve(A);
}
template<typename t, typename ti>
CImg<T>& _solve(const CImg<t>& A, const CImg<ti>& indx) {
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
const int N = size();
int ii = -1;
Ttfloat sum;
for (int i=0; i<N; ++i) {
const int ip = (int)indx[i];
Ttfloat sum = (*this)(ip);
(*this)(ip) = (*this)(i);
if (ii>=0) for (int j=ii; j<=i-1; ++j) sum-=A(j,i)*(*this)(j);
else if (sum!=0) ii=i;
(*this)(i) = (T)sum;
}
{ for (int i=N-1; i>=0; --i) {
sum = (*this)(i);
for (int j=i+1; j<N; ++j) sum-=A(j,i)*(*this)(j);
(*this)(i) = (T)(sum/A(i,i));
}}
return *this;
}
//! Solve a linear system AX=B where B=*this and A is a tridiagonal matrix A = [ b0,c0,0,...; a1,b1,c1,0,... ; ... ; ...,0,aN,bN ].
// (Use the Thomas Algorithm).
template<typename t>
CImg<T>& solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) {
const int siz = (int)size();
if ((int)a.size()!=siz || (int)b.size()!=siz || (int)c.size()!=siz)
throw CImgArgumentException("CImg<%s>::solve_tridiagonal() : arrays of triagonal coefficients have different size.",pixel_type);
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
CImg<Ttfloat> nc(siz);
const T *ptra = a.data, *ptrb = b.data, *ptrc = c.data;
T *ptrnc = nc.data, *ptrd = data;
const Ttfloat valb0 = (Ttfloat)*(ptrb++);
*ptrnc = *(ptrc++)/valb0;
Ttfloat vald = (Ttfloat)(*(ptrd++)/=valb0);
for (int i = 1; i<siz; ++i) {
const Ttfloat
vala = (Tfloat)*(ptra++),
id = 1/(*(ptrb++) - *(ptrnc++)*vala);
*ptrnc = *(ptrc++)*id;
vald = ((*ptrd-=vala*vald)*=id);
++ptrd;
}
vald = *(--ptrd);
for (int i = siz-2; i>=0; --i) vald = (*(--ptrd)-=*(--ptrnc)*vald);
return *this;
}
template<typename t>
CImg<typename cimg::superset2<T,t,float>::type> get_solve_tridiagonal(const CImg<t>& a, const CImg<t>& b, const CImg<t>& c) const {
typedef typename cimg::superset2<T,t,float>::type Ttfloat;
return CImg<Ttfloat>(*this,false).solve_tridiagonal(a,b,c);
}
//! Sort values of a vector and get permutations.
template<typename t>
CImg<T>& sort(CImg<t>& permutations, const bool increasing=true) {
if (is_empty()) permutations.assign();
else {
if (permutations.size()!=size()) permutations.assign(size());
cimg_foroff(permutations,off) permutations[off] = (t)off;
_quicksort(0,size()-1,permutations,increasing);
}
return *this;
}
template<typename t>
CImg<T> get_sort(CImg<t>& permutations, const bool increasing=true) const {
return (+*this).sort(permutations,increasing);
}
// Sort image values.
CImg<T>& sort(const bool increasing=true) {
CImg<T> foo;
return sort(foo,increasing);
}
CImg<T> get_sort(const bool increasing=true) const {
return (+*this).sort(increasing);
}
template<typename t>
CImg<T>& _quicksort(const int min, const int max, CImg<t>& permutations, const bool increasing) {
if (min<max) {
const int mid = (min+max)/2;
if (increasing) {
if ((*this)[min]>(*this)[mid]) {
cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
if ((*this)[mid]>(*this)[max]) {
cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
if ((*this)[min]>(*this)[mid]) {
cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
} else {
if ((*this)[min]<(*this)[mid]) {
cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
if ((*this)[mid]<(*this)[max]) {
cimg::swap((*this)[max],(*this)[mid]); cimg::swap(permutations[max],permutations[mid]); }
if ((*this)[min]<(*this)[mid]) {
cimg::swap((*this)[min],(*this)[mid]); cimg::swap(permutations[min],permutations[mid]); }
}
if (max-min>=3) {
const T pivot = (*this)[mid];
int i = min, j = max;
if (increasing) {
do {
while ((*this)[i]<pivot) ++i;
while ((*this)[j]>pivot) --j;
if (i<=j) {
cimg::swap((*this)[i],(*this)[j]);
cimg::swap(permutations[i++],permutations[j--]);
}
} while (i<=j);
} else {
do {
while ((*this)[i]>pivot) ++i;
while ((*this)[j]<pivot) --j;
if (i<=j) {
cimg::swap((*this)[i],(*this)[j]);
cimg::swap(permutations[i++],permutations[j--]);
}
} while (i<=j);
}
if (min<j) _quicksort(min,j,permutations,increasing);
if (i<max) _quicksort(i,max,permutations,increasing);
}
}
return *this;
}
//! Get a permutation of the pixels.
template<typename t>
CImg<T>& permute(const CImg<t>& permutation) {
return get_permute(permutation).transfer_to(*this);
}
template<typename t>
CImg<T> get_permute(const CImg<t>& permutation) const {
if (permutation.size()!=size())
throw CImgArgumentException("CImg<%s>::permute() : Instance image (%u,%u,%u,%u,%p) and permutation (%u,%u,%u,%u,%p)"
"have different sizes.",
pixel_type(),width,height,depth,dim,data,
permutation.width,permutation.height,permutation.depth,permutation.dim,permutation.data);
CImg<T> res(width,height,depth,dim);
const t *p = permutation.ptr(permutation.size());
cimg_for(res,ptr,T) *ptr = (*this)[*(--p)];
return res;
}
//! Compute the SVD of a general matrix.
template<typename t>
const CImg<T>& SVD(CImg<t>& U, CImg<t>& S, CImg<t>& V,
const bool sorting=true, const unsigned int max_iter=40, const float lambda=0) const {
if (is_empty()) { U.assign(); S.assign(); V.assign(); }
else {
U = *this;
if (lambda!=0) {
const unsigned int delta = cimg::min(U.width,U.height);
for (unsigned int i=0; i<delta; ++i) U(i,i) = (t)(U(i,i) + lambda);
}
if (S.size()<width) S.assign(1,width);
if (V.width<width || V.height<height) V.assign(width,width);
CImg<t> rv1(width);
t anorm = 0, c, f, g = 0, h, s, scale = 0;
int l = 0, nm = 0;
cimg_forX(U,i) {
l = i+1; rv1[i] = scale*g; g = s = scale = 0;
if (i<dimy()) {
for (int k=i; k<dimy(); ++k) scale+= cimg::abs(U(i,k));
if (scale) {
for (int k=i; k<dimy(); ++k) { U(i,k)/=scale; s+= U(i,k)*U(i,k); }
f = U(i,i); g = (t)((f>=0?-1:1)*cimg_std::sqrt(s)); h=f*g-s; U(i,i) = f-g;
for (int j=l; j<dimx(); ++j) {
s = 0; for (int k=i; k<dimy(); ++k) s+= U(i,k)*U(j,k);
f = s/h;
{ for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
}
{ for (int k=i; k<dimy(); ++k) U(i,k)*= scale; }
}
}
S[i]=scale*g;
g = s = scale = 0;
if (i<dimy() && i!=dimx()-1) {
for (int k=l; k<dimx(); ++k) scale += cimg::abs(U(k,i));
if (scale) {
for (int k=l; k<dimx(); ++k) { U(k,i)/= scale; s+= U(k,i)*U(k,i); }
f = U(l,i); g = (t)((f>=0?-1:1)*cimg_std::sqrt(s)); h = f*g-s; U(l,i) = f-g;
{ for (int k=l; k<dimx(); ++k) rv1[k]=U(k,i)/h; }
for (int j=l; j<dimy(); ++j) {
s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,j)*U(k,i);
{ for (int k=l; k<dimx(); ++k) U(k,j)+= s*rv1[k]; }
}
{ for (int k=l; k<dimx(); ++k) U(k,i)*= scale; }
}
}
anorm = (t)cimg::max((float)anorm,(float)(cimg::abs(S[i])+cimg::abs(rv1[i])));
}
{ for (int i=dimx()-1; i>=0; --i) {
if (i<dimx()-1) {
if (g) {
{ for (int j=l; j<dimx(); ++j) V(i,j) =(U(j,i)/U(l,i))/g; }
for (int j=l; j<dimx(); ++j) {
s = 0; for (int k=l; k<dimx(); ++k) s+= U(k,i)*V(j,k);
{ for (int k=l; k<dimx(); ++k) V(j,k)+= s*V(i,k); }
}
}
for (int j=l; j<dimx(); ++j) V(j,i) = V(i,j) = (t)0.0;
}
V(i,i) = (t)1.0; g = rv1[i]; l = i;
}
}
{ for (int i=cimg::min(dimx(),dimy())-1; i>=0; --i) {
l = i+1; g = S[i];
for (int j=l; j<dimx(); ++j) U(j,i) = 0;
if (g) {
g = 1/g;
for (int j=l; j<dimx(); ++j) {
s = 0; for (int k=l; k<dimy(); ++k) s+= U(i,k)*U(j,k);
f = (s/U(i,i))*g;
{ for (int k=i; k<dimy(); ++k) U(j,k)+= f*U(i,k); }
}
{ for (int j=i; j<dimy(); ++j) U(i,j)*= g; }
} else for (int j=i; j<dimy(); ++j) U(i,j) = 0;
++U(i,i);
}
}
for (int k=dimx()-1; k>=0; --k) {
for (unsigned int its=0; its<max_iter; ++its) {
bool flag = true;
for (l=k; l>=1; --l) {
nm = l-1;
if ((cimg::abs(rv1[l])+anorm)==anorm) { flag = false; break; }
if ((cimg::abs(S[nm])+anorm)==anorm) break;
}
if (flag) {
c = 0; s = 1;
for (int i=l; i<=k; ++i) {
f = s*rv1[i]; rv1[i] = c*rv1[i];
if ((cimg::abs(f)+anorm)==anorm) break;
g = S[i]; h = (t)cimg::_pythagore(f,g); S[i] = h; h = 1/h; c = g*h; s = -f*h;
cimg_forY(U,j) { const t y = U(nm,j), z = U(i,j); U(nm,j) = y*c+z*s; U(i,j) = z*c-y*s; }
}
}
const t z = S[k];
if (l==k) { if (z<0) { S[k] = -z; cimg_forX(U,j) V(k,j) = -V(k,j); } break; }
nm = k-1;
t x = S[l], y = S[nm];
g = rv1[nm]; h = rv1[k];
f = ((y-z)*(y+z)+(g-h)*(g+h))/(2*h*y);
g = (t)cimg::_pythagore(f,1.0);
f = ((x-z)*(x+z)+h*((y/(f+ (f>=0?g:-g)))-h))/x;
c = s = 1;
for (int j=l; j<=nm; ++j) {
const int i = j+1;
g = rv1[i]; h = s*g; g = c*g;
t y = S[i];
t z = (t)cimg::_pythagore(f,h);
rv1[j] = z; c = f/z; s = h/z;
f = x*c+g*s; g = g*c-x*s; h = y*s; y*=c;
cimg_forX(U,jj) { const t x = V(j,jj), z = V(i,jj); V(j,jj) = x*c+z*s; V(i,jj) = z*c-x*s; }
z = (t)cimg::_pythagore(f,h); S[j] = z;
if (z) { z = 1/z; c = f*z; s = h*z; }
f = c*g+s*y; x = c*y-s*g;
{ cimg_forY(U,jj) { const t y = U(j,jj); z = U(i,jj); U(j,jj) = y*c+z*s; U(i,jj) = z*c-y*s; }}
}
rv1[l] = 0; rv1[k]=f; S[k]=x;
}
}
if (sorting) {
CImg<intT> permutations(width);
CImg<t> tmp(width);
S.sort(permutations,false);
cimg_forY(U,k) {
cimg_forX(permutations,x) tmp(x) = U(permutations(x),k);
cimg_std::memcpy(U.ptr(0,k),tmp.data,sizeof(t)*width);
}
{ cimg_forY(V,k) {
cimg_forX(permutations,x) tmp(x) = V(permutations(x),k);
cimg_std::memcpy(V.ptr(0,k),tmp.data,sizeof(t)*width);
}}
}
}
return *this;
}
//! Compute the SVD of a general matrix.
template<typename t>
const CImg<T>& SVD(CImgList<t>& USV) const {
if (USV.size<3) USV.assign(3);
return SVD(USV[0],USV[1],USV[2]);
}
//! Compute the SVD of a general matrix.
CImgList<Tfloat> get_SVD(const bool sorting=true) const {
CImgList<Tfloat> res(3);
SVD(res[0],res[1],res[2],sorting);
return res;
}
- // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipies)
+ // INNER ROUTINE : Compute the LU decomposition of a permuted matrix (c.f. numerical recipes)
template<typename t>
CImg<T>& _LU(CImg<t>& indx, bool& d) {
const int N = dimx();
int imax = 0;
CImg<Tfloat> vv(N);
indx.assign(N);
d = true;
cimg_forX(*this,i) {
Tfloat vmax = 0;
cimg_forX(*this,j) {
const Tfloat tmp = cimg::abs((*this)(j,i));
if (tmp>vmax) vmax = tmp;
}
if (vmax==0) { indx.fill(0); return fill(0); }
vv[i] = 1/vmax;
}
cimg_forX(*this,j) {
for (int i=0; i<j; ++i) {
Tfloat sum=(*this)(j,i);
for (int k=0; k<i; ++k) sum-=(*this)(k,i)*(*this)(j,k);
(*this)(j,i) = (T)sum;
}
Tfloat vmax = 0;
{ for (int i=j; i<dimx(); ++i) {
Tfloat sum=(*this)(j,i);
for (int k=0; k<j; ++k) sum-=(*this)(k,i)*(*this)(j,k);
(*this)(j,i) = (T)sum;
const Tfloat tmp = vv[i]*cimg::abs(sum);
if (tmp>=vmax) { vmax=tmp; imax=i; }
}}
if (j!=imax) {
cimg_forX(*this,k) cimg::swap((*this)(k,imax),(*this)(k,j));
d =!d;
vv[imax] = vv[j];
}
indx[j] = (t)imax;
if ((*this)(j,j)==0) (*this)(j,j) = (T)1e-20;
if (j<N) {
const Tfloat tmp = 1/(Tfloat)(*this)(j,j);
for (int i=j+1; i<N; ++i) (*this)(j,i) = (T)((*this)(j,i)*tmp);
}
}
return *this;
}
//! Compute the eigenvalues and eigenvectors of a matrix.
template<typename t>
const CImg<T>& eigen(CImg<t>& val, CImg<t> &vec) const {
if (is_empty()) { val.assign(); vec.assign(); }
else {
if (width!=height || depth>1 || dim>1)
throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
if (val.size()<width) val.assign(1,width);
if (vec.size()<width*width) vec.assign(width,width);
switch (width) {
case 1 : { val[0]=(t)(*this)[0]; vec[0]=(t)1; } break;
case 2 : {
const double a = (*this)[0], b = (*this)[1], c = (*this)[2], d = (*this)[3], e = a+d;
double f = e*e-4*(a*d-b*c);
if (f<0)
cimg::warn("CImg<%s>::eigen() : Complex eigenvalues",
pixel_type());
f = cimg_std::sqrt(f);
const double l1 = 0.5*(e-f), l2 = 0.5*(e+f);
const double theta1 = cimg_std::atan2(l2-a,b), theta2 = cimg_std::atan2(l1-a,b);
val[0]=(t)l2;
val[1]=(t)l1;
vec(0,0) = (t)cimg_std::cos(theta1);
vec(0,1) = (t)cimg_std::sin(theta1);
vec(1,0) = (t)cimg_std::cos(theta2);
vec(1,1) = (t)cimg_std::sin(theta2);
} break;
default :
throw CImgInstanceException("CImg<%s>::eigen() : Eigenvalues computation of general matrices is limited"
"to 2x2 matrices (given is %ux%u)",
pixel_type(),width,height);
}
}
return *this;
}
//! Compute the eigenvalues and eigenvectors of a matrix.
CImgList<Tfloat> get_eigen() const {
CImgList<Tfloat> res(2);
eigen(res[0],res[1]);
return res;
}
//! Compute the eigenvalues and eigenvectors of a symmetric matrix.
template<typename t>
const CImg<T>& symmetric_eigen(CImg<t>& val, CImg<t>& vec) const {
if (is_empty()) { val.assign(); vec.assign(); }
else {
#ifdef cimg_use_lapack
char JOB = 'V', UPLO = 'U';
int N = width, LWORK = 4*N, INFO;
Tfloat
*lapA = new Tfloat[N*N],
*lapW = new Tfloat[N],
*WORK = new Tfloat[LWORK];
cimg_forXY(*this,k,l) lapA[k*N+l] = (Tfloat)((*this)(k,l));
cimg::syev(JOB,UPLO,N,lapA,lapW,WORK,LWORK,INFO);
if (INFO)
cimg::warn("CImg<%s>::symmetric_eigen() : LAPACK library function dsyev_() returned error code %d.",
pixel_type(),INFO);
val.assign(1,N);
vec.assign(N,N);
if (!INFO) {
cimg_forY(val,i) val(i) = (T)lapW[N-1-i];
cimg_forXY(vec,k,l) vec(k,l) = (T)(lapA[(N-1-k)*N+l]);
} else { val.fill(0); vec.fill(0); }
delete[] lapA; delete[] lapW; delete[] WORK;
#else
if (width!=height || depth>1 || dim>1)
throw CImgInstanceException("CImg<%s>::eigen() : Instance object (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
val.assign(1,width);
if (vec.data) vec.assign(width,width);
if (width<3) return eigen(val,vec);
CImg<t> V(width,width);
SVD(vec,val,V,false);
bool ambiguous = false;
float eig = 0;
cimg_forY(val,p) { // check for ambiguous cases.
if (val[p]>eig) eig = (float)val[p];
t scal = 0;
cimg_forY(vec,y) scal+=vec(p,y)*V(p,y);
if (cimg::abs(scal)<0.9f) ambiguous = true;
if (scal<0) val[p] = -val[p];
}
if (ambiguous) {
(eig*=2)++;
SVD(vec,val,V,false,40,eig);
val-=eig;
}
CImg<intT> permutations(width); // sort eigenvalues in decreasing order
CImg<t> tmp(width);
val.sort(permutations,false);
cimg_forY(vec,k) {
cimg_forX(permutations,x) tmp(x) = vec(permutations(x),k);
cimg_std::memcpy(vec.ptr(0,k),tmp.data,sizeof(t)*width);
}
#endif
}
return *this;
}
//! Compute the eigenvalues and eigenvectors of a symmetric matrix.
CImgList<Tfloat> get_symmetric_eigen() const {
CImgList<Tfloat> res(2);
symmetric_eigen(res[0],res[1]);
return res;
}
//@}
//-------------------
//
//! \name Display
//@{
//-------------------
//! Display an image into a CImgDisplay window.
const CImg<T>& display(CImgDisplay& disp) const {
disp.display(*this);
return *this;
}
//! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
const CImg<T>& display(CImgDisplay &disp, const bool display_info) const {
return _display(disp,0,display_info);
}
//! Display an image in a window with a title \p title, and wait a 'is_closed' or 'keyboard' event.\n
const CImg<T>& display(const char *const title=0, const bool display_info=true) const {
CImgDisplay disp;
return _display(disp,title,display_info);
}
const CImg<T>& _display(CImgDisplay &disp, const char *const title, const bool display_info) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::display() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
unsigned int oldw = 0, oldh = 0, XYZ[3], key = 0, mkey = 0;
int x0 = 0, y0 = 0, z0 = 0, x1 = dimx()-1, y1 = dimy()-1, z1 = dimz()-1;
float frametiming = 5;
char ntitle[256] = { 0 };
if (!disp) {
if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
}
cimg_std::strncpy(ntitle,disp.title,255);
if (display_info) print(ntitle);
CImg<T> zoom;
for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) {
if (reset_view) {
XYZ[0] = (x0 + x1)/2; XYZ[1] = (y0 + y1)/2; XYZ[2] = (z0 + z1)/2;
x0 = 0; y0 = 0; z0 = 0; x1 = width-1; y1 = height-1; z1 = depth-1;
oldw = disp.width; oldh = disp.height;
reset_view = false;
}
if (!x0 && !y0 && !z0 && x1==dimx()-1 && y1==dimy()-1 && z1==dimz()-1) zoom.assign();
else zoom = get_crop(x0,y0,z0,x1,y1,z1);
const unsigned int
dx = 1 + x1 - x0, dy = 1 + y1 - y0, dz = 1 + z1 - z0,
tw = dx + (dz>1?dz:0), th = dy + (dz>1?dz:0);
if (resize_disp) {
const unsigned int
ttw = tw*disp.width/oldw, tth = th*disp.height/oldh,
dM = cimg::max(ttw,tth), diM = cimg::max(disp.width,disp.height),
imgw = cimg::max(16U,ttw*diM/dM), imgh = cimg::max(16U,tth*diM/dM);
disp.normalscreen().resize(cimg_fitscreen(imgw,imgh,1),false);
resize_disp = false;
}
oldw = tw; oldh = th;
bool
go_up = false, go_down = false, go_left = false, go_right = false,
go_inc = false, go_dec = false, go_in = false, go_out = false,
go_in_center = false;
const CImg<T>& visu = zoom?zoom:*this;
const CImg<intT> selection = visu._get_select(disp,0,2,XYZ,0,x0,y0,z0);
if (disp.wheel) {
if (disp.is_keyCTRLLEFT) { if (!mkey || mkey==1) go_out = !(go_in = disp.wheel>0); go_in_center = false; mkey = 1; }
else if (disp.is_keySHIFTLEFT) { if (!mkey || mkey==2) go_right = !(go_left = disp.wheel>0); mkey = 2; }
else if (disp.is_keyALT || depth==1) { if (!mkey || mkey==3) go_down = !(go_up = disp.wheel>0); mkey = 3; }
else mkey = 0;
disp.wheel = 0;
} else mkey = 0;
const int
sx0 = selection(0), sy0 = selection(1), sz0 = selection(2),
sx1 = selection(3), sy1 = selection(4), sz1 = selection(5);
if (sx0>=0 && sy0>=0 && sz0>=0 && sx1>=0 && sy1>=0 && sz1>=0) {
x1 = x0 + sx1; y1 = y0 + sy1; z1 = z0 + sz1; x0+=sx0; y0+=sy0; z0+=sz0;
if (sx0==sx1 && sy0==sy1 && sz0==sz1) reset_view = true;
resize_disp = true;
} else switch (key = disp.key) {
case 0 : case cimg::keyCTRLLEFT : case cimg::keyPAD5 : case cimg::keySHIFTLEFT : case cimg::keyALT : disp.key = key = 0; break;
case cimg::keyP : if (visu.depth>1 && disp.is_keyCTRLLEFT) { // Special mode : play stack of frames
const unsigned int
w1 = visu.width*disp.width/(visu.width+(visu.depth>1?visu.depth:0)),
h1 = visu.height*disp.height/(visu.height+(visu.depth>1?visu.depth:0));
disp.resize(cimg_fitscreen(w1,h1,1),false).key = disp.wheel = key = 0;
for (unsigned int timer = 0; !key && !disp.is_closed && !disp.button; ) {
if (disp.is_resized) disp.resize();
if (!timer) {
visu.get_slice(XYZ[2]).display(disp.set_title("%s | z=%d",ntitle,XYZ[2]));
if (++XYZ[2]>=visu.depth) XYZ[2] = 0;
}
if (++timer>(unsigned int)frametiming) timer = 0;
if (disp.wheel) { frametiming-=disp.wheel/3.0f; disp.wheel = 0; }
switch (key = disp.key) {
case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
case cimg::keyPAGEUP : frametiming-=0.3f; key = 0; break;
case cimg::keyPAGEDOWN : frametiming+=0.3f; key = 0; break;
case cimg::keyD : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false);
disp.key = key = 0;
} break;
case cimg::keyC : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false);
disp.key = key = 0;
} break;
case cimg::keyR : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false);
disp.key = key = 0;
} break;
case cimg::keyF : if (disp.is_keyCTRLLEFT) {
disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen();
disp.key = key = 0;
} break;
}
frametiming = frametiming<1?1:(frametiming>39?39:frametiming);
disp.wait(20);
}
const unsigned int
w2 = (visu.width + (visu.depth>1?visu.depth:0))*disp.width/visu.width,
h2 = (visu.height + (visu.depth>1?visu.depth:0))*disp.height/visu.height;
disp.resize(cimg_fitscreen(w2,h2,1),false).set_title(ntitle);
key = disp.key = disp.button = disp.wheel = 0;
} break;
case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
case cimg::keyPADADD : go_in = true; go_in_center = true; key = 0; break;
case cimg::keyPADSUB : go_out = true; key = 0; break;
case cimg::keyARROWLEFT : case cimg::keyPAD4: go_left = true; key = 0; break;
case cimg::keyARROWRIGHT : case cimg::keyPAD6: go_right = true; key = 0; break;
case cimg::keyARROWUP : case cimg::keyPAD8: go_up = true; key = 0; break;
case cimg::keyARROWDOWN : case cimg::keyPAD2: go_down = true; key = 0; break;
case cimg::keyPAD7 : go_up = go_left = true; key = 0; break;
case cimg::keyPAD9 : go_up = go_right = true; key = 0; break;
case cimg::keyPAD1 : go_down = go_left = true; key = 0; break;
case cimg::keyPAD3 : go_down = go_right = true; key = 0; break;
case cimg::keyPAGEUP : go_inc = true; key = 0; break;
case cimg::keyPAGEDOWN : go_dec = true; key = 0; break;
}
if (go_in) {
const int
mx = go_in_center?disp.dimx()/2:disp.mouse_x,
my = go_in_center?disp.dimy()/2:disp.mouse_y,
mX = mx*(width+(depth>1?depth:0))/disp.width,
mY = my*(height+(depth>1?depth:0))/disp.height;
int X = XYZ[0], Y = XYZ[1], Z = XYZ[2];
if (mX<dimx() && mY<dimy()) { X = x0 + mX*(1+x1-x0)/width; Y = y0 + mY*(1+y1-y0)/height; Z = XYZ[2]; }
if (mX<dimx() && mY>=dimy()) { X = x0 + mX*(1+x1-x0)/width; Z = z0 + (mY-height)*(1+z1-z0)/depth; Y = XYZ[1]; }
if (mX>=dimx() && mY<dimy()) { Y = y0 + mY*(1+y1-y0)/height; Z = z0 + (mX-width)*(1+z1-z0)/depth; X = XYZ[0]; }
if (x1-x0>4) { x0 = X - 7*(X-x0)/8; x1 = X + 7*(x1-X)/8; }
if (y1-y0>4) { y0 = Y - 7*(Y-y0)/8; y1 = Y + 7*(y1-Y)/8; }
if (z1-z0>4) { z0 = Z - 7*(Z-z0)/8; z1 = Z + 7*(z1-Z)/8; }
}
if (go_out) {
const int
deltax = (x1-x0)/8, deltay = (y1-y0)/8, deltaz = (z1-z0)/8,
ndeltax = deltax?deltax:(width>1?1:0),
ndeltay = deltay?deltay:(height>1?1:0),
ndeltaz = deltaz?deltaz:(depth>1?1:0);
x0-=ndeltax; y0-=ndeltay; z0-=ndeltaz;
x1+=ndeltax; y1+=ndeltay; z1+=ndeltaz;
if (x0<0) { x1-=x0; x0 = 0; if (x1>=dimx()) x1 = dimx()-1; }
if (y0<0) { y1-=y0; y0 = 0; if (y1>=dimy()) y1 = dimy()-1; }
if (z0<0) { z1-=z0; z0 = 0; if (z1>=dimz()) z1 = dimz()-1; }
if (x1>=dimx()) { x0-=(x1-dimx()+1); x1 = dimx()-1; if (x0<0) x0 = 0; }
if (y1>=dimy()) { y0-=(y1-dimy()+1); y1 = dimy()-1; if (y0<0) y0 = 0; }
if (z1>=dimz()) { z0-=(z1-dimz()+1); z1 = dimz()-1; if (z0<0) z0 = 0; }
}
if (go_left) {
const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
else { x1-=x0; x0 = 0; }
}
if (go_right) {
const int delta = (x1-x0)/5, ndelta = delta?delta:(width>1?1:0);
if (x1+ndelta<dimx()) { x0+=ndelta; x1+=ndelta; }
else { x0+=(dimx()-1-x1); x1 = dimx()-1; }
}
if (go_up) {
const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
if (y0-ndelta>=0) { y0-=ndelta; y1-=ndelta; }
else { y1-=y0; y0 = 0; }
}
if (go_down) {
const int delta = (y1-y0)/5, ndelta = delta?delta:(height>1?1:0);
if (y1+ndelta<dimy()) { y0+=ndelta; y1+=ndelta; }
else { y0+=(dimy()-1-y1); y1 = dimy()-1; }
}
if (go_inc) {
const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
if (z0-ndelta>=0) { z0-=ndelta; z1-=ndelta; }
else { z1-=z0; z0 = 0; }
}
if (go_dec) {
const int delta = (z1-z0)/5, ndelta = delta?delta:(depth>1?1:0);
if (z1+ndelta<dimz()) { z0+=ndelta; z1+=ndelta; }
else { z0+=(depth-1-z1); z1 = depth-1; }
}
}
disp.key = key;
return *this;
}
//! Simple interface to select a shape from an image.
/**
\param selection Array of 6 values containing the selection result
\param coords_type Determine shape type to select (0=point, 1=vector, 2=rectangle, 3=circle)
\param disp Display window used to make the selection
\param XYZ Initial XYZ position (for volumetric images only)
\param color Color of the shape selector.
**/
CImg<T>& select(CImgDisplay &disp,
const int select_type=2, unsigned int *const XYZ=0,
const unsigned char *const color=0) {
return get_select(disp,select_type,XYZ,color).transfer_to(*this);
}
//! Simple interface to select a shape from an image.
CImg<T>& select(const char *const title,
const int select_type=2, unsigned int *const XYZ=0,
const unsigned char *const color=0) {
return get_select(title,select_type,XYZ,color).transfer_to(*this);
}
//! Simple interface to select a shape from an image.
CImg<intT> get_select(CImgDisplay &disp,
const int select_type=2, unsigned int *const XYZ=0,
const unsigned char *const color=0) const {
return _get_select(disp,0,select_type,XYZ,color,0,0,0);
}
//! Simple interface to select a shape from an image.
CImg<intT> get_select(const char *const title,
const int select_type=2, unsigned int *const XYZ=0,
const unsigned char *const color=0) const {
CImgDisplay disp;
return _get_select(disp,title,select_type,XYZ,color,0,0,0);
}
CImg<intT> _get_select(CImgDisplay &disp, const char *const title,
const int coords_type, unsigned int *const XYZ,
const unsigned char *const color,
const int origX, const int origY, const int origZ) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::select() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
if (!disp) {
char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); }
disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
}
const unsigned int
old_normalization = disp.normalization,
hatch = 0x55555555;
bool old_is_resized = disp.is_resized;
disp.normalization = 0;
disp.show().key = 0;
unsigned char foreground_color[] = { 255,255,105 }, background_color[] = { 0,0,0 };
if (color) cimg_std::memcpy(foreground_color,color,sizeof(unsigned char)*cimg::min(3,dimv()));
int area = 0, clicked_area = 0, phase = 0,
X0 = (int)((XYZ?XYZ[0]:width/2)%width), Y0 = (int)((XYZ?XYZ[1]:height/2)%height), Z0 = (int)((XYZ?XYZ[2]:depth/2)%depth),
X1 =-1, Y1 = -1, Z1 = -1,
X = -1, Y = -1, Z = -1,
oX = X, oY = Y, oZ = Z;
unsigned int old_button = 0, key = 0;
bool shape_selected = false, text_down = false;
CImg<ucharT> visu, visu0;
char text[1024] = { 0 };
while (!key && !disp.is_closed && !shape_selected) {
// Handle mouse motion and selection
oX = X; oY = Y; oZ = Z;
int mx = disp.mouse_x, my = disp.mouse_y;
const int mX = mx*(width+(depth>1?depth:0))/disp.width, mY = my*(height+(depth>1?depth:0))/disp.height;
area = 0;
if (mX<dimx() && mY<dimy()) { area = 1; X = mX; Y = mY; Z = phase?Z1:Z0; }
if (mX<dimx() && mY>=dimy()) { area = 2; X = mX; Z = mY-height; Y = phase?Y1:Y0; }
if (mX>=dimx() && mY<dimy()) { area = 3; Y = mY; Z = mX-width; X = phase?X1:X0; }
switch (key = disp.key) {
case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
case cimg::keyPAGEUP : if (disp.is_keyCTRLLEFT) { ++disp.wheel; key = 0; } break;
case cimg::keyPAGEDOWN : if (disp.is_keyCTRLLEFT) { --disp.wheel; key = 0; } break;
case cimg::keyD : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
disp.key = key = 0;
} break;
case cimg::keyC : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
disp.key = key = 0; visu0.assign();
} break;
case cimg::keyR : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true;
disp.key = key = 0; visu0.assign();
} break;
case cimg::keyF : if (disp.is_keyCTRLLEFT) {
disp.resize(disp.screen_dimx(),disp.screen_dimy(),false).toggle_fullscreen().is_resized = true;
disp.key = key = 0; visu0.assign();
} break;
case cimg::keyS : if (disp.is_keyCTRLLEFT) {
static unsigned int snap_number = 0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
if (visu0) {
visu.draw_text(2,2,"Saving snapshot...",foreground_color,background_color,0.8f,11).display(disp);
visu0.save(filename);
visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,0.8f,11,filename).display(disp);
}
disp.key = key = 0;
} break;
case cimg::keyO : if (disp.is_keyCTRLLEFT) {
static unsigned int snap_number = 0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.cimg",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
visu.draw_text(2,2,"Saving instance...",foreground_color,background_color,0.8f,11).display(disp);
save(filename);
visu.draw_text(2,2,"Instance '%s' saved.",foreground_color,background_color,0.8f,11,filename).display(disp);
disp.key = key = 0;
} break;
}
if (!area) mx = my = X = Y = Z = -1;
else {
if (disp.button&1 && phase<2) { X1 = X; Y1 = Y; Z1 = Z; }
if (!(disp.button&1) && phase>=2) {
switch (clicked_area) {
case 1 : Z1 = Z; break;
case 2 : Y1 = Y; break;
case 3 : X1 = X; break;
}
}
if (disp.button&2) { if (phase) { X1 = X; Y1 = Y; Z1 = Z; } else { X0 = X; Y0 = Y; Z0 = Z; } }
if (disp.button&4) { oX = X = X0; oY = Y = Y0; oZ = Z = Z0; phase = 0; visu.assign(); }
if (disp.wheel) {
if (depth>1 && !disp.is_keyCTRLLEFT && !disp.is_keySHIFTLEFT && !disp.is_keyALT) {
switch (area) {
case 1 : if (phase) Z = (Z1+=disp.wheel); else Z = (Z0+=disp.wheel); break;
case 2 : if (phase) Y = (Y1+=disp.wheel); else Y = (Y0+=disp.wheel); break;
case 3 : if (phase) X = (X1+=disp.wheel); else X = (X0+=disp.wheel); break;
}
disp.wheel = 0;
} else key = ~0U;
}
if ((disp.button&1)!=old_button) {
switch (phase++) {
case 0 : X0 = X1 = X; Y0 = Y1 = Y; Z0 = Z1 = Z; clicked_area = area; break;
case 1 : X1 = X; Y1 = Y; Z1 = Z; break;
}
old_button = disp.button&1;
}
if (depth>1 && (X!=oX || Y!=oY || Z!=oZ)) visu0.assign();
}
if (phase) {
if (!coords_type) shape_selected = phase?true:false;
else {
if (depth>1) shape_selected = (phase==3)?true:false;
else shape_selected = (phase==2)?true:false;
}
}
if (X0<0) X0 = 0; if (X0>=dimx()) X0 = dimx()-1; if (Y0<0) Y0 = 0; if (Y0>=dimy()) Y0 = dimy()-1;
if (Z0<0) Z0 = 0; if (Z0>=dimz()) Z0 = dimz()-1;
if (X1<1) X1 = 0; if (X1>=dimx()) X1 = dimx()-1; if (Y1<0) Y1 = 0; if (Y1>=dimy()) Y1 = dimy()-1;
if (Z1<0) Z1 = 0; if (Z1>=dimz()) Z1 = dimz()-1;
// Draw visualization image on the display
if (oX!=X || oY!=Y || oZ!=Z || !visu0) {
if (!visu0) {
CImg<Tuchar> tmp, tmp0;
if (depth!=1) {
tmp0 = (!phase)?get_projections2d(X0,Y0,Z0):get_projections2d(X1,Y1,Z1);
tmp = tmp0.get_channels(0,cimg::min(2U,dim-1));
} else tmp = get_channels(0,cimg::min(2U,dim-1));
switch (old_normalization) {
case 0 : visu0 = tmp; break;
case 3 :
if (cimg::type<T>::is_float()) visu0 = tmp.normalize(0,(T)255);
else {
const float m = (float)cimg::type<T>::min(), M = (float)cimg::type<T>::max();
visu0.assign(tmp.width,tmp.height,1,tmp.dim);
unsigned char *ptrd = visu0.end();
cimg_for(tmp,ptrs,Tuchar) *(--ptrd) = (unsigned char)((*ptrs-m)*255.0f/(M-m));
} break;
default : visu0 = tmp.normalize(0,255);
}
visu0.resize(disp);
}
visu = visu0;
if (!color) {
if (visu.mean()<200) {
foreground_color[0] = foreground_color[1] = foreground_color[2] = 255;
background_color[0] = background_color[1] = background_color[2] = 0;
} else {
foreground_color[0] = foreground_color[1] = foreground_color[2] = 0;
background_color[0] = background_color[1] = background_color[2] = 255;
}
}
const int d = (depth>1)?depth:0;
if (phase) switch (coords_type) {
case 1 : {
const int
x0 = (int)((X0+0.5f)*disp.width/(width+d)),
y0 = (int)((Y0+0.5f)*disp.height/(height+d)),
x1 = (int)((X1+0.5f)*disp.width/(width+d)),
y1 = (int)((Y1+0.5f)*disp.height/(height+d));
visu.draw_arrow(x0,y0,x1,y1,foreground_color,0.6f,30,5,hatch);
if (d) {
const int
zx0 = (int)((width+Z0+0.5f)*disp.width/(width+d)),
zx1 = (int)((width+Z1+0.5f)*disp.width/(width+d)),
zy0 = (int)((height+Z0+0.5f)*disp.height/(height+d)),
zy1 = (int)((height+Z1+0.5f)*disp.height/(height+d));
visu.draw_arrow(zx0,y0,zx1,y1,foreground_color,0.6f,30,5,hatch).
draw_arrow(x0,zy0,x1,zy1,foreground_color,0.6f,30,5,hatch);
}
} break;
case 2 : {
const int
x0 = (X0<X1?X0:X1)*disp.width/(width+d), y0 = (Y0<Y1?Y0:Y1)*disp.height/(height+d),
x1 = ((X0<X1?X1:X0)+1)*disp.width/(width+d)-1, y1 = ((Y0<Y1?Y1:Y0)+1)*disp.height/(height+d)-1;
visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.2f).draw_rectangle(x0,y0,x1,y1,foreground_color,0.6f,hatch);
if (d) {
const int
zx0 = (int)((width+(Z0<Z1?Z0:Z1))*disp.width/(width+d)),
zy0 = (int)((height+(Z0<Z1?Z0:Z1))*disp.height/(height+d)),
zx1 = (int)((width+(Z0<Z1?Z1:Z0)+1)*disp.width/(width+d))-1,
zy1 = (int)((height+(Z0<Z1?Z1:Z0)+1)*disp.height/(height+d))-1;
visu.draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.2f).draw_rectangle(zx0,y0,zx1,y1,foreground_color,0.6f,hatch);
visu.draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.2f).draw_rectangle(x0,zy0,x1,zy1,foreground_color,0.6f,hatch);
}
} break;
case 3 : {
const int
x0 = X0*disp.width/(width+d),
y0 = Y0*disp.height/(height+d),
x1 = X1*disp.width/(width+d)-1,
y1 = Y1*disp.height/(height+d)-1;
visu.draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1,0,foreground_color,0.2f).
draw_ellipse(x0,y0,(float)(x1-x0),(float)(y1-y0),1,0,foreground_color,0.6f,hatch);
if (d) {
const int
zx0 = (int)((width+Z0)*disp.width/(width+d)),
zy0 = (int)((height+Z0)*disp.height/(height+d)),
zx1 = (int)((width+Z1+1)*disp.width/(width+d))-1,
zy1 = (int)((height+Z1+1)*disp.height/(height+d))-1;
visu.draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1,0,foreground_color,0.2f).
draw_ellipse(zx0,y0,(float)(zx1-zx0),(float)(y1-y0),1,0,foreground_color,0.6f,hatch).
draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1,0,foreground_color,0.2f).
draw_ellipse(x0,zy0,(float)(x1-x0),(float)(zy1-zy0),1,0,foreground_color,0.6f,hatch);
}
} break;
} else {
const int
x0 = X*disp.width/(width+d),
y0 = Y*disp.height/(height+d),
x1 = (X+1)*disp.width/(width+d)-1,
y1 = (Y+1)*disp.height/(height+d)-1;
if (x1-x0>=4 && y1-y0>=4) visu.draw_rectangle(x0,y0,x1,y1,foreground_color,0.4f,~0U);
}
if (my<12) text_down = true;
if (my>=visu.dimy()-11) text_down = false;
if (!coords_type || !phase) {
if (X>=0 && Y>=0 && Z>=0 && X<dimx() && Y<dimy() && Z<dimz()) {
if (depth>1) cimg_std::sprintf(text,"Point (%d,%d,%d) = [ ",origX+X,origY+Y,origZ+Z);
else cimg_std::sprintf(text,"Point (%d,%d) = [ ",origX+X,origY+Y);
char *ctext = text + cimg::strlen(text), *const ltext = text + 512;
for (unsigned int k=0; k<dim && ctext<ltext; ++k) {
cimg_std::sprintf(ctext,cimg::type<T>::format(),cimg::type<T>::format((*this)(X,Y,Z,k)));
ctext = text + cimg::strlen(text);
*(ctext++) = ' '; *ctext = '\0';
}
cimg_std::sprintf(text + cimg::strlen(text),"]");
}
} else switch (coords_type) {
case 1 : {
const double dX = (double)(X0 - X1), dY = (double)(Y0 - Y1), dZ = (double)(Z0 - Z1), norm = cimg_std::sqrt(dX*dX+dY*dY+dZ*dZ);
if (depth>1) cimg_std::sprintf(text,"Vect (%d,%d,%d)-(%d,%d,%d), Norm = %g",
origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,norm);
else cimg_std::sprintf(text,"Vect (%d,%d)-(%d,%d), Norm = %g",
origX+X0,origY+Y0,origX+X1,origY+Y1,norm);
} break;
case 2 :
if (depth>1) cimg_std::sprintf(text,"Box (%d,%d,%d)-(%d,%d,%d), Size = (%d,%d,%d)",
origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origZ+(Z0<Z1?Z0:Z1),
origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),origZ+(Z0<Z1?Z1:Z0),
1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
else cimg_std::sprintf(text,"Box (%d,%d)-(%d,%d), Size = (%d,%d)",
origX+(X0<X1?X0:X1),origY+(Y0<Y1?Y0:Y1),origX+(X0<X1?X1:X0),origY+(Y0<Y1?Y1:Y0),
1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
break;
default :
if (depth>1) cimg_std::sprintf(text,"Ellipse (%d,%d,%d)-(%d,%d,%d), Radii = (%d,%d,%d)",
origX+X0,origY+Y0,origZ+Z0,origX+X1,origY+Y1,origZ+Z1,
1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1),1+cimg::abs(Z0-Z1));
else cimg_std::sprintf(text,"Ellipse (%d,%d)-(%d,%d), Radii = (%d,%d)",
origX+X0,origY+Y0,origX+X1,origY+Y1,1+cimg::abs(X0-X1),1+cimg::abs(Y0-Y1));
}
if (phase || (mx>=0 && my>=0)) visu.draw_text(0,text_down?visu.dimy()-11:0,text,foreground_color,background_color,0.7f,11);
disp.display(visu).wait(25);
} else if (!shape_selected) disp.wait();
if (disp.is_resized) { disp.resize(false); old_is_resized = true; disp.is_resized = false; visu0.assign(); }
}
// Return result
CImg<intT> res(1,6,1,1,-1);
if (XYZ) { XYZ[0] = (unsigned int)X0; XYZ[1] = (unsigned int)Y0; XYZ[2] = (unsigned int)Z0; }
if (shape_selected) {
if (coords_type==2) {
if (X0>X1) cimg::swap(X0,X1);
if (Y0>Y1) cimg::swap(Y0,Y1);
if (Z0>Z1) cimg::swap(Z0,Z1);
}
if (X1<0 || Y1<0 || Z1<0) X0 = Y0 = Z0 = X1 = Y1 = Z1 = -1;
switch (coords_type) {
case 1 :
case 2 : res[3] = X1; res[4] = Y1; res[5] = Z1;
default : res[0] = X0; res[1] = Y0; res[2] = Z0;
}
}
disp.button = 0;
disp.normalization = old_normalization;
disp.is_resized = old_is_resized;
if (key!=~0U) disp.key = key;
return res;
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf, typename tc, typename to>
const CImg<T>& display_object3d(CImgDisplay& disp,
const CImg<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const to& opacities,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return _display_object3d(disp,0,points,points.width,primitives,colors,opacities,centering,render_static,
render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf, typename tc, typename to>
const CImg<T>& display_object3d(const char *const title,
const CImg<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const to& opacities,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
CImgDisplay disp;
return _display_object3d(disp,title,points,points.width,primitives,colors,opacities,centering,render_static,
render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf, typename tc, typename to>
const CImg<T>& display_object3d(CImgDisplay& disp,
const CImgList<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const to& opacities,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return _display_object3d(disp,0,points,points.size,primitives,colors,opacities,centering,render_static,
render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf, typename tc, typename to>
const CImg<T>& display_object3d(const char *const title,
const CImgList<tp>& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors, const to& opacities,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
CImgDisplay disp;
return _display_object3d(disp,title,points,points.size,primitives,colors,opacities,centering,render_static,
render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf, typename tc>
const CImg<T>& display_object3d(CImgDisplay &disp,
const tp& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return display_object3d(disp,points,primitives,colors,CImg<floatT>(),centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf, typename tc>
const CImg<T>& display_object3d(const char *const title,
const tp& points, const CImgList<tf>& primitives,
const CImgList<tc>& colors,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return display_object3d(title,points,primitives,colors,CImg<floatT>(),centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf>
const CImg<T>& display_object3d(CImgDisplay &disp,
const tp& points, const CImgList<tf>& primitives,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return display_object3d(disp,points,primitives,CImgList<T>(),centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp, typename tf>
const CImg<T>& display_object3d(const char *const title,
const tp& points, const CImgList<tf>& primitives,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return display_object3d(title,points,primitives,CImgList<T>(),centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp>
const CImg<T>& display_object3d(CImgDisplay &disp,
const tp& points,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return display_object3d(disp,points,CImgList<uintT>(),centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
//! High-level interface for displaying a 3d object.
template<typename tp>
const CImg<T>& display_object3d(const char *const title,
const tp& points,
const bool centering=true,
const int render_static=4, const int render_motion=1,
const bool double_sided=false, const float focale=500,
const float specular_light=0.2f, const float specular_shine=0.1f,
const bool display_axes=true, float *const pose_matrix=0) const {
return display_object3d(title,points,CImgList<uintT>(),centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
T _display_object3d_at2(const int i, const int j) const {
return atXY(i,j,0,0,0);
}
template<typename tp, typename tf, typename tc, typename to>
const CImg<T>& _display_object3d(CImgDisplay& disp, const char *const title,
const tp& points, const unsigned int Npoints,
const CImgList<tf>& primitives,
const CImgList<tc>& colors, const to& opacities,
const bool centering,
const int render_static, const int render_motion,
const bool double_sided, const float focale,
const float specular_light, const float specular_shine,
const bool display_axes, float *const pose_matrix) const {
// Check input arguments
if (!points || !Npoints)
throw CImgArgumentException("CImg<%s>::display_object3d() : Given points are empty.",
pixel_type());
if (is_empty()) {
if (disp) return CImg<T>(disp.width,disp.height,1,colors[0].size(),0).
_display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
else return CImg<T>(cimg_fitscreen(640,480,1),1,colors[0].size(),0).
_display_object3d(disp,title,points,Npoints,primitives,colors,opacities,centering,
render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
if (!primitives) {
CImgList<tf> nprimitives(Npoints,1,1,1,1);
cimglist_for(nprimitives,l) nprimitives(l,0) = l;
return _display_object3d(disp,title,points,Npoints,nprimitives,colors,opacities,
centering,render_static,render_motion,double_sided,focale,specular_light,specular_shine,
display_axes,pose_matrix);
}
if (!disp) {
char ntitle[64] = { 0 }; if (!title) { cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); }
disp.assign(cimg_fitscreen(width,height,depth),title?title:ntitle,1);
}
CImgList<tc> _colors;
if (!colors) _colors.insert(primitives.size,CImg<tc>::vector(200,200,200));
const CImgList<tc> &ncolors = colors?colors:_colors;
// Init 3D objects and compute object statistics
CImg<floatT>
pose, rot_mat, zbuffer,
centered_points = centering?CImg<floatT>(Npoints,3):CImg<floatT>(),
rotated_points(Npoints,3),
bbox_points, rotated_bbox_points,
axes_points, rotated_axes_points,
bbox_opacities, axes_opacities;
CImgList<uintT> bbox_primitives, axes_primitives;
CImgList<T> bbox_colors, bbox_colors2, axes_colors;
float dx = 0, dy = 0, dz = 0, ratio = 1;
T minval = (T)0, maxval = (T)255;
if (disp.normalization && colors) {
minval = colors.minmax(maxval);
if (minval==maxval) { minval = (T)0; maxval = (T)255; }
}
const float meanval = (float)mean();
bool color_model = true;
if (cimg::abs(meanval-minval)>cimg::abs(meanval-maxval)) color_model = false;
const CImg<T>
background_color(1,1,1,dim,color_model?minval:maxval),
foreground_color(1,1,1,dim,color_model?maxval:minval);
float xm = cimg::type<float>::max(), xM = 0, ym = xm, yM = 0, zm = xm, zM = 0;
for (unsigned int i = 0; i<Npoints; ++i) {
const float
x = points._display_object3d_at2(i,0),
y = points._display_object3d_at2(i,1),
z = points._display_object3d_at2(i,2);
if (x<xm) xm = x;
if (x>xM) xM = x;
if (y<ym) ym = y;
if (y>yM) yM = y;
if (z<zm) zm = z;
if (z>zM) zM = z;
}
const float delta = cimg::max(xM-xm,yM-ym,zM-zm);
if (display_axes) {
rotated_axes_points = axes_points.assign(7,3,1,1,
0,20,0,0,22,-6,-6,
0,0,20,0,-6,22,-6,
0,0,0,20,0,0,22);
axes_opacities.assign(3,1,1,1,1);
axes_colors.assign(3,dim,1,1,1,foreground_color[0]);
axes_primitives.assign(3,1,2,1,1, 0,1, 0,2, 0,3);
}
// Begin user interaction loop
CImg<T> visu0(*this), visu;
bool init = true, clicked = false, redraw = true;
unsigned int key = 0;
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
disp.show().flush();
while (!disp.is_closed && !key) {
// Init object position and scale if necessary
if (init) {
ratio = delta>0?(2.0f*cimg::min(disp.width,disp.height)/(3.0f*delta)):0;
dx = 0.5f*(xM + xm); dy = 0.5f*(yM + ym); dz = 0.5f*(zM + zm);
if (centering) {
cimg_forX(centered_points,l) {
centered_points(l,0) = (float)((points(l,0) - dx)*ratio);
centered_points(l,1) = (float)((points(l,1) - dy)*ratio);
centered_points(l,2) = (float)((points(l,2) - dz)*ratio);
}
}
if (render_static<0 || render_motion<0) {
rotated_bbox_points = bbox_points.assign(8,3,1,1,
xm,xM,xM,xm,xm,xM,xM,xm,
ym,ym,yM,yM,ym,ym,yM,yM,
zm,zm,zm,zm,zM,zM,zM,zM);
bbox_primitives.assign(6,1,4,1,1, 0,3,2,1, 4,5,6,7, 1,2,6,5, 0,4,7,3, 0,1,5,4, 2,3,7,6);
bbox_colors.assign(6,dim,1,1,1,background_color[0]);
bbox_colors2.assign(6,dim,1,1,1,foreground_color[0]);
bbox_opacities.assign(bbox_colors.size,1,1,1,0.3f);
}
if (!pose) {
if (pose_matrix) pose = CImg<floatT>(pose_matrix,4,4,1,1,false);
else pose = CImg<floatT>::identity_matrix(4);
}
init = false;
redraw = true;
}
// Rotate and Draw 3D object
if (redraw) {
const float
r00 = pose(0,0), r10 = pose(1,0), r20 = pose(2,0), r30 = pose(3,0),
r01 = pose(0,1), r11 = pose(1,1), r21 = pose(2,1), r31 = pose(3,1),
r02 = pose(0,2), r12 = pose(1,2), r22 = pose(2,2), r32 = pose(3,2);
if ((clicked && render_motion>=0) || (!clicked && render_static>=0)) {
if (centering) cimg_forX(centered_points,l) {
const float x = centered_points(l,0), y = centered_points(l,1), z = centered_points(l,2);
rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
} else for (unsigned int l = 0; l<Npoints; ++l) {
const float
x = (float)points._display_object3d_at2(l,0),
y = (float)points._display_object3d_at2(l,1),
z = (float)points._display_object3d_at2(l,2);
rotated_points(l,0) = r00*x + r10*y + r20*z + r30;
rotated_points(l,1) = r01*x + r11*y + r21*z + r31;
rotated_points(l,2) = r02*x + r12*y + r22*z + r32;
}
} else {
if (!centering) cimg_forX(bbox_points,l) {
const float x = bbox_points(l,0), y = bbox_points(l,1), z = bbox_points(l,2);
rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
} else cimg_forX(bbox_points,l) {
const float x = (bbox_points(l,0)-dx)*ratio, y = (bbox_points(l,1)-dy)*ratio, z = (bbox_points(l,2)-dz)*ratio;
rotated_bbox_points(l,0) = r00*x + r10*y + r20*z + r30;
rotated_bbox_points(l,1) = r01*x + r11*y + r21*z + r31;
rotated_bbox_points(l,2) = r02*x + r12*y + r22*z + r32;
}
}
// Draw object
visu = visu0;
if ((clicked && render_motion<0) || (!clicked && render_static<0))
visu.draw_object3d(visu.width/2.0f,visu.height/2.0f,0,rotated_bbox_points,bbox_primitives,bbox_colors,bbox_opacities,2,false,focale).
draw_object3d(visu.width/2.0f,visu.height/2.0f,0,rotated_bbox_points,bbox_primitives,bbox_colors2,1,false,focale);
else visu.draw_object3d(visu.width/2.0f,visu.height/2.0f,0,
rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
(!clicked && render_static>0)?zbuffer.fill(0).ptr():0);
// Draw axes
if (display_axes) {
const float Xaxes = 25, Yaxes = visu.height - 35.0f;
cimg_forX(axes_points,l) {
const float x = axes_points(l,0), y = axes_points(l,1), z = axes_points(l,2);
rotated_axes_points(l,0) = r00*x + r10*y + r20*z;
rotated_axes_points(l,1) = r01*x + r11*y + r21*z;
rotated_axes_points(l,2) = r02*x + r12*y + r22*z;
}
axes_opacities(0,0) = (rotated_axes_points(1,2)>0)?0.5f:1.0f;
axes_opacities(1,0) = (rotated_axes_points(2,2)>0)?0.5f:1.0f;
axes_opacities(2,0) = (rotated_axes_points(3,2)>0)?0.5f:1.0f;
visu.draw_object3d(Xaxes,Yaxes,0,rotated_axes_points,axes_primitives,axes_colors,axes_opacities,1,false,focale).
draw_text((int)(Xaxes+rotated_axes_points(4,0)),
(int)(Yaxes+rotated_axes_points(4,1)),
"X",axes_colors[0].data,0,axes_opacities(0,0),11).
draw_text((int)(Xaxes+rotated_axes_points(5,0)),
(int)(Yaxes+rotated_axes_points(5,1)),
"Y",axes_colors[1].data,0,axes_opacities(1,0),11).
draw_text((int)(Xaxes+rotated_axes_points(6,0)),
(int)(Yaxes+rotated_axes_points(6,1)),
"Z",axes_colors[2].data,0,axes_opacities(2,0),11);
}
visu.display(disp);
if (!clicked || render_motion==render_static) redraw = false;
}
// Handle user interaction
disp.wait();
if ((disp.button || disp.wheel) && disp.mouse_x>=0 && disp.mouse_y>=0) {
redraw = true;
if (!clicked) { x0 = x1 = disp.mouse_x; y0 = y1 = disp.mouse_y; if (!disp.wheel) clicked = true; }
else { x1 = disp.mouse_x; y1 = disp.mouse_y; }
if (disp.button&1) {
const float
R = 0.45f*cimg::min(disp.width,disp.height),
R2 = R*R,
u0 = (float)(x0-disp.dimx()/2),
v0 = (float)(y0-disp.dimy()/2),
u1 = (float)(x1-disp.dimx()/2),
v1 = (float)(y1-disp.dimy()/2),
n0 = (float)cimg_std::sqrt(u0*u0+v0*v0),
n1 = (float)cimg_std::sqrt(u1*u1+v1*v1),
nu0 = n0>R?(u0*R/n0):u0,
nv0 = n0>R?(v0*R/n0):v0,
nw0 = (float)cimg_std::sqrt(cimg::max(0,R2-nu0*nu0-nv0*nv0)),
nu1 = n1>R?(u1*R/n1):u1,
nv1 = n1>R?(v1*R/n1):v1,
nw1 = (float)cimg_std::sqrt(cimg::max(0,R2-nu1*nu1-nv1*nv1)),
u = nv0*nw1-nw0*nv1,
v = nw0*nu1-nu0*nw1,
w = nv0*nu1-nu0*nv1,
n = (float)cimg_std::sqrt(u*u+v*v+w*w),
alpha = (float)cimg_std::asin(n/R2);
rot_mat = CImg<floatT>::rotation_matrix(u,v,w,alpha);
rot_mat *= pose.get_crop(0,0,2,2);
pose.draw_image(rot_mat);
x0=x1; y0=y1;
}
if (disp.button&2) { pose(3,2)+=(y1-y0); x0 = x1; y0 = y1; }
if (disp.wheel) { pose(3,2)-=focale*disp.wheel/10; disp.wheel = 0; }
if (disp.button&4) { pose(3,0)+=(x1-x0); pose(3,1)+=(y1-y0); x0 = x1; y0 = y1; }
if ((disp.button&1) && (disp.button&2)) { init = true; disp.button = 0; x0 = x1; y0 = y1; pose = CImg<floatT>::identity_matrix(4); }
} else if (clicked) { x0 = x1; y0 = y1; clicked = false; redraw = true; }
switch (key = disp.key) {
case 0 : case cimg::keyCTRLLEFT : disp.key = key = 0; break;
case cimg::keyD: if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
disp.key = key = 0;
} break;
case cimg::keyC : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
disp.key = key = 0;
} break;
case cimg::keyR : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(width,height,depth),false).is_resized = true;
disp.key = key = 0;
} break;
case cimg::keyF : if (disp.is_keyCTRLLEFT) {
disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true;
disp.key = key = 0;
} break;
case cimg::keyZ : if (disp.is_keyCTRLLEFT) { // Enable/Disable Z-buffer
if (zbuffer) zbuffer.assign();
else zbuffer.assign(disp.width,disp.height);
disp.key = key = 0; redraw = true;
} break;
case cimg::keyS : if (disp.is_keyCTRLLEFT) { // Save snapshot
static unsigned int snap_number = 0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
(+visu).draw_text(2,2,"Saving BMP snapshot...",foreground_color,background_color,1,11).display(disp);
visu.save(filename);
visu.draw_text(2,2,"Snapshot '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
disp.key = key = 0;
} break;
case cimg::keyO : if (disp.is_keyCTRLLEFT) { // Save object as an .OFF file
static unsigned int snap_number = 0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.off",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
visu.draw_text(2,2,"Saving object...",foreground_color,background_color,1,11).display(disp);
points.save_off(filename,primitives,ncolors);
visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
disp.key = key = 0;
} break;
#ifdef cimg_use_board
case cimg::keyP : if (disp.is_keyCTRLLEFT) { // Save object as a .EPS file
static unsigned int snap_number = 0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.eps",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
visu.draw_text(2,2,"Saving EPS snapshot...",foreground_color,background_color,1,11).display(disp);
BoardLib::Board board;
(+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0,
rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
zbuffer.fill(0).ptr());
board.saveEPS(filename);
visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
disp.key = key = 0;
} break;
case cimg::keyV : if (disp.is_keyCTRLLEFT) { // Save object as a .SVG file
static unsigned int snap_number = 0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.svg",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
visu.draw_text(2,2,"Saving SVG snapshot...",foreground_color,background_color,1,11).display(disp);
BoardLib::Board board;
(+visu).draw_object3d(board,visu.width/2.0f, visu.height/2.0f, 0,
rotated_points,primitives,ncolors,opacities,clicked?render_motion:render_static,
double_sided,focale,visu.dimx()/2.0f,visu.dimy()/2.0f,-5000,specular_light,specular_shine,
zbuffer.fill(0).ptr());
board.saveSVG(filename);
visu.draw_text(2,2,"Object '%s' saved.",foreground_color,background_color,1,11,filename).display(disp);
disp.key = key = 0;
} break;
#endif
}
if (disp.is_resized) { disp.resize(false); visu0 = get_resize(disp,1); if (zbuffer) zbuffer.assign(disp.width,disp.height); redraw = true; }
}
if (pose_matrix) cimg_std::memcpy(pose_matrix,pose.data,16*sizeof(float));
disp.button = 0;
disp.key = key;
return *this;
}
//! High-level interface for displaying a graph.
const CImg<T>& display_graph(CImgDisplay &disp,
const unsigned int plot_type=1, const unsigned int vertex_type=1,
const char *const labelx=0, const double xmin=0, const double xmax=0,
const char *const labely=0, const double ymin=0, const double ymax=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
const unsigned int siz = width*height*depth, onormalization = disp.normalization;
if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
disp.show().flush().normalization = 0;
double y0 = ymin, y1 = ymax, nxmin = xmin, nxmax = xmax;
if (nxmin==nxmax) { nxmin = 0; nxmax = siz - 1.0; }
int x0 = 0, x1 = size()/dimv()-1, key = 0;
for (bool reset_view = true, resize_disp = false; !key && !disp.is_closed; ) {
if (reset_view) { x0 = 0; x1 = size()/dimv()-1; y0 = ymin; y1 = ymax; reset_view = false; }
CImg<T> zoom(x1-x0+1,1,1,dimv());
cimg_forV(*this,k) zoom.get_shared_channel(k) = CImg<T>(ptr(x0,0,0,k),x1-x0+1,1,1,1,true);
if (y0==y1) y0 = zoom.minmax(y1);
if (y0==y1) { --y0; ++y1; }
const CImg<intT> selection = zoom.get_select_graph(disp,plot_type,vertex_type,
labelx,nxmin + x0*(nxmax-nxmin)/siz,nxmin + x1*(nxmax-nxmin)/siz,
labely,y0,y1);
const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y;
if (selection[0]>=0 && selection[2]>=0) {
x1 = x0 + selection[2];
x0 += selection[0];
if (x0==x1) reset_view = true;
if (selection[1]>=0 && selection[3]>=0) {
y0 = y1 - selection[3]*(y1-y0)/(disp.dimy()-32);
y1 -= selection[1]*(y1-y0)/(disp.dimy()-32);
}
} else {
bool go_in = false, go_out = false, go_left = false, go_right = false, go_up = false, go_down = false;
switch (key = disp.key) {
case cimg::keyHOME : case cimg::keyBACKSPACE : reset_view = resize_disp = true; key = 0; break;
case cimg::keyPADADD : go_in = true; key = 0; break;
case cimg::keyPADSUB : go_out = true; key = 0; break;
case cimg::keyARROWLEFT : case cimg::keyPAD4 : go_left = true; key = 0; break;
case cimg::keyARROWRIGHT : case cimg::keyPAD6 : go_right = true; key = 0; break;
case cimg::keyARROWUP : case cimg::keyPAD8 : go_up = true; key = 0; break;
case cimg::keyARROWDOWN : case cimg::keyPAD2 : go_down = true; key = 0; break;
case cimg::keyPAD7 : go_left = true; go_up = true; key = 0; break;
case cimg::keyPAD9 : go_right = true; go_up = true; key = 0; break;
case cimg::keyPAD1 : go_left = true; go_down = true; key = 0; break;
case cimg::keyPAD3 : go_right = true; go_down = true; key = 0; break;
}
if (disp.wheel) go_out = !(go_in = disp.wheel>0);
if (go_in) {
const int
xsiz = x1 - x0,
mx = (mouse_x-16)*xsiz/(disp.dimx()-32),
cx = x0 + (mx<0?0:(mx>=xsiz?xsiz:mx));
if (x1-x0>4) {
x0 = cx - 7*(cx-x0)/8; x1 = cx + 7*(x1-cx)/8;
if (disp.is_keyCTRLLEFT) {
const double
ysiz = y1 - y0,
my = (mouse_y-16)*ysiz/(disp.dimy()-32),
cy = y1 - (my<0?0:(my>=ysiz?ysiz:my));
y0 = cy - 7*(cy-y0)/8; y1 = cy + 7*(y1-cy)/8;
} else y0 = y1 = 0;
}
}
if (go_out) {
const int deltax = (x1-x0)/8, ndeltax = deltax?deltax:(siz>1?1:0);
x0-=ndeltax; x1+=ndeltax;
if (x0<0) { x1-=x0; x0 = 0; if (x1>=(int)siz) x1 = (int)siz-1; }
if (x1>=(int)siz) { x0-=(x1-siz+1); x1 = (int)siz-1; if (x0<0) x0 = 0; }
if (disp.is_keyCTRLLEFT) {
const double deltay = (y1-y0)/8, ndeltay = deltay?deltay:0.01;
y0-=ndeltay; y1+=ndeltay;
}
}
if (go_left) {
const int delta = (x1-x0)/5, ndelta = delta?delta:1;
if (x0-ndelta>=0) { x0-=ndelta; x1-=ndelta; }
else { x1-=x0; x0 = 0; }
go_left = false;
}
if (go_right) {
const int delta = (x1-x0)/5, ndelta = delta?delta:1;
if (x1+ndelta<(int)siz) { x0+=ndelta; x1+=ndelta; }
else { x0+=(siz-1-x1); x1 = siz-1; }
go_right = false;
}
if (go_up) {
const double delta = (y1-y0)/10, ndelta = delta?delta:1;
y0+=ndelta; y1+=ndelta;
go_up = false;
}
if (go_down) {
const double delta = (y1-y0)/10, ndelta = delta?delta:1;
y0-=ndelta; y1-=ndelta;
go_down = false;
}
}
}
disp.normalization = onormalization;
return *this;
}
//! High-level interface for displaying a graph.
const CImg<T>& display_graph(const char *const title=0,
const unsigned int plot_type=1, const unsigned int vertex_type=1,
const char *const labelx=0, const double xmin=0, const double xmax=0,
const char *const labely=0, const double ymin=0, const double ymax=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
char ntitle[64] = { 0 }; if (!title) cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type());
CImgDisplay disp(cimg_fitscreen(640,480,1),title?title:ntitle,0);
return display_graph(disp,plot_type,vertex_type,labelx,xmin,xmax,labely,ymin,ymax);
}
//! Select sub-graph in a graph.
CImg<intT> get_select_graph(CImgDisplay &disp,
const unsigned int plot_type=1, const unsigned int vertex_type=1,
const char *const labelx=0, const double xmin=0, const double xmax=0,
const char *const labely=0, const double ymin=0, const double ymax=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::display_graph() : Instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),width,height,depth,dim,data);
const unsigned int siz = width*height*depth, onormalization = disp.normalization;
if (!disp) { char ntitle[64] = { 0 }; cimg_std::sprintf(ntitle,"CImg<%s>",pixel_type()); disp.assign(640,480,ntitle,0); }
disp.show().key = disp.normalization = disp.button = disp.wheel = 0; // Must keep 'key' field unchanged.
double nymin = ymin, nymax = ymax, nxmin = xmin, nxmax = xmax;
if (nymin==nymax) nymin = (Tfloat)minmax(nymax);
if (nymin==nymax) { --nymin; ++nymax; }
if (nxmin==nxmax && nxmin==0) { nxmin = 0; nxmax = siz - 1.0; }
const unsigned char black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 220,220,220 };
const unsigned char gray2[] = { 110,110,110 }, ngray[] = { 35,35,35 };
static unsigned int odimv = 0;
static CImg<ucharT> palette;
if (odimv!=dim) {
odimv = dim;
palette = CImg<ucharT>(3,dim,1,1,120).noise(70,1);
if (dim==1) { palette[0] = palette[1] = 120; palette[2] = 200; }
else {
palette(0,0) = 220; palette(1,0) = 10; palette(2,0) = 10;
if (dim>1) { palette(0,1) = 10; palette(1,1) = 220; palette(2,1) = 10; }
if (dim>2) { palette(0,2) = 10; palette(1,2) = 10; palette(2,2) = 220; }
}
}
CImg<ucharT> visu0, visu, graph, text, axes;
const unsigned int whz = width*height*depth;
int x0 = -1, x1 = -1, y0 = -1, y1 = -1, omouse_x = -2, omouse_y = -2;
char message[1024] = { 0 };
unsigned int okey = 0, obutton = 0;
CImg_3x3(I,unsigned char);
for (bool selected = false; !selected && !disp.is_closed && !okey && !disp.wheel; ) {
const int mouse_x = disp.mouse_x, mouse_y = disp.mouse_y;
const unsigned int key = disp.key, button = disp.button;
// Generate graph representation.
if (!visu0) {
visu0.assign(disp.dimx(),disp.dimy(),1,3,220);
const int gdimx = disp.dimx() - 32, gdimy = disp.dimy() - 32;
if (gdimx>0 && gdimy>0) {
graph.assign(gdimx,gdimy,1,3,255);
graph.draw_grid(-10,-10,0,0,false,true,black,0.2f,0x33333333,0x33333333);
cimg_forV(*this,k) graph.draw_graph(get_shared_channel(k),&palette(0,k),(plot_type!=3 || dim==1)?1:0.6f,
plot_type,vertex_type,nymax,nymin);
axes.assign(gdimx,gdimy,1,1,0);
const float
dx = (float)cimg::abs(nxmax-nxmin), dy = (float)cimg::abs(nymax-nymin),
px = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dx)-2.0),
py = (float)cimg_std::pow(10.0,(int)cimg_std::log10(dy)-2.0);
const CImg<Tdouble>
seqx = CImg<Tdouble>::sequence(1 + gdimx/60,nxmin,nxmax).round(px),
seqy = CImg<Tdouble>::sequence(1 + gdimy/60,nymax,nymin).round(py);
axes.draw_axis(seqx,seqy,white);
if (nymin>0) axes.draw_axis(seqx,gdimy-1,gray);
if (nymax<0) axes.draw_axis(seqx,0,gray);
if (nxmin>0) axes.draw_axis(0,seqy,gray);
if (nxmax<0) axes.draw_axis(gdimx-1,seqy,gray);
cimg_for3x3(axes,x,y,0,0,I)
if (Icc) {
if (Icc==255) cimg_forV(graph,k) graph(x,y,k) = 0;
else cimg_forV(graph,k) graph(x,y,k) = (unsigned char)(2*graph(x,y,k)/3);
}
else if (Ipc || Inc || Icp || Icn || Ipp || Inn || Ipn || Inp) cimg_forV(graph,k) graph(x,y,k) = (graph(x,y,k)+255)/2;
visu0.draw_image(16,16,graph);
visu0.draw_line(15,15,16+gdimx,15,gray2).draw_line(16+gdimx,15,16+gdimx,16+gdimy,gray2).
draw_line(16+gdimx,16+gdimy,15,16+gdimy,white).draw_line(15,16+gdimy,15,15,white);
} else graph.assign();
text.assign().draw_text(0,0,labelx?labelx:"X-axis",white,ngray,1);
visu0.draw_image((visu0.dimx()-text.dimx())/2,visu0.dimy()-14,~text);
text.assign().draw_text(0,0,labely?labely:"Y-axis",white,ngray,1).rotate(-90);
visu0.draw_image(2,(visu0.dimy()-text.dimy())/2,~text);
visu.assign();
}
// Generate and display current view.
if (!visu) {
visu.assign(visu0);
if (graph && x0>=0 && x1>=0) {
const int
nx0 = x0<=x1?x0:x1,
nx1 = x0<=x1?x1:x0,
ny0 = y0<=y1?y0:y1,
ny1 = y0<=y1?y1:y0,
sx0 = 16 + nx0*(visu.dimx()-32)/whz,
sx1 = 15 + (nx1+1)*(visu.dimx()-32)/whz,
sy0 = 16 + ny0,
sy1 = 16 + ny1;
if (y0>=0 && y1>=0)
visu.draw_rectangle(sx0,sy0,sx1,sy1,gray,0.5f).draw_rectangle(sx0,sy0,sx1,sy1,black,0.5f,0xCCCCCCCCU);
else visu.draw_rectangle(sx0,0,sx1,visu.dimy()-17,gray,0.5f).
draw_line(sx0,16,sx0,visu.dimy()-17,black,0.5f,0xCCCCCCCCU).
draw_line(sx1,16,sx1,visu.dimy()-17,black,0.5f,0xCCCCCCCCU);
}
if (mouse_x>=16 && mouse_y>=16 && mouse_x<visu.dimx()-16 && mouse_y<visu.dimy()-16) {
if (graph) visu.draw_line(mouse_x,16,mouse_x,visu.dimy()-17,black,0.5f,0x55555555U);
const unsigned x = (mouse_x-16)*whz/(disp.dimx()-32);
const double cx = nxmin + x*(nxmax-nxmin)/whz;
if (dim>=7)
cimg_std::sprintf(message,"Value[%g] = ( %g %g %g ... %g %g %g )",cx,
(double)(*this)(x,0,0,0),(double)(*this)(x,0,0,1),(double)(*this)(x,0,0,2),
(double)(*this)(x,0,0,dim-4),(double)(*this)(x,0,0,dim-3),(double)(*this)(x,0,0,dim-1));
else {
cimg_std::sprintf(message,"Value[%g] = ( ",cx);
cimg_forV(*this,k) cimg_std::sprintf(message+cimg::strlen(message),"%g ",(double)(*this)(x,0,0,k));
cimg_std::sprintf(message+cimg::strlen(message),")");
}
if (x0>=0 && x1>=0) {
const int
nx0 = x0<=x1?x0:x1,
nx1 = x0<=x1?x1:x0,
ny0 = y0<=y1?y0:y1,
ny1 = y0<=y1?y1:y0;
const double
cx0 = nxmin + nx0*(nxmax-nxmin)/(visu.dimx()-32),
cx1 = nxmin + nx1*(nxmax-nxmin)/(visu.dimx()-32),
cy0 = nymax - ny0*(nymax-nymin)/(visu.dimy()-32),
cy1 = nymax - ny1*(nymax-nymin)/(visu.dimy()-32);
if (y0>=0 && y1>=0)
cimg_std::sprintf(message+cimg::strlen(message)," - Range ( %g, %g ) - ( %g, %g )",cx0,cy0,cx1,cy1);
else
cimg_std::sprintf(message+cimg::strlen(message)," - Range [ %g - %g ]",cx0,cx1);
}
text.assign().draw_text(0,0,message,white,ngray,1);
visu.draw_image((visu.dimx()-text.dimx())/2,2,~text);
}
visu.display(disp);
}
// Test keys.
switch (okey = key) {
case cimg::keyCTRLLEFT : okey = 0; break;
case cimg::keyD : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,false),
CImgDisplay::_fitscreen(3*disp.width/2,3*disp.height/2,1,128,-100,true),false).is_resized = true;
disp.key = okey = 0;
} break;
case cimg::keyC : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(2*disp.width/3,2*disp.height/3,1),false).is_resized = true;
disp.key = okey = 0;
} break;
case cimg::keyR : if (disp.is_keyCTRLLEFT) {
disp.normalscreen().resize(cimg_fitscreen(640,480,1),false).is_resized = true;
disp.key = okey = 0;
} break;
case cimg::keyF : if (disp.is_keyCTRLLEFT) {
disp.resize(disp.screen_dimx(),disp.screen_dimy()).toggle_fullscreen().is_resized = true;
disp.key = okey = 0;
} break;
case cimg::keyS : if (disp.is_keyCTRLLEFT) {
static unsigned int snap_number = 0;
if (visu || visu0) {
CImg<ucharT> &screen = visu?visu:visu0;
char filename[32] = { 0 };
cimg_std::FILE *file;
do {
cimg_std::sprintf(filename,"CImg_%.4u.bmp",snap_number++);
if ((file=cimg_std::fopen(filename,"r"))!=0) cimg_std::fclose(file);
} while (file);
(+screen).draw_text(2,2,"Saving BMP snapshot...",black,gray,1,11).display(disp);
screen.save(filename);
screen.draw_text(2,2,"Snapshot '%s' saved.",black,gray,1,11,filename).display(disp);
}
disp.key = okey = 0;
} break;
}
// Handle mouse motion and mouse buttons
if (obutton!=button || omouse_x!=mouse_x || omouse_y!=mouse_y) {
visu.assign();
if (disp.mouse_x>=0 && disp.mouse_y>=0) {
const int
mx = (mouse_x-16)*(int)whz/(disp.dimx()-32),
cx = mx<0?0:(mx>=(int)whz?whz-1:mx),
my = mouse_y-16,
cy = my<=0?0:(my>=(disp.dimy()-32)?(disp.dimy()-32):my);
if (button&1) { if (!obutton) { x0 = cx; y0 = -1; } else { x1 = cx; y1 = -1; }}
else if (button&2) { if (!obutton) { x0 = cx; y0 = cy; } else { x1 = cx; y1 = cy; }}
else if (obutton) { x1 = cx; y1 = y1>=0?cy:-1; selected = true; }
} else if (!button && obutton) selected = true;
obutton = button; omouse_x = mouse_x; omouse_y = mouse_y;
}
if (disp.is_resized) { disp.resize(false); visu0.assign(); }
if (visu && visu0) disp.wait();
}
disp.normalization = onormalization;
if (x1<x0) cimg::swap(x0,x1);
if (y1<y0) cimg::swap(y0,y1);
disp.key = okey;
return CImg<intT>(4,1,1,1,x0,y0,x1,y1);
}
//@}
//---------------------------
//
//! \name Image File Loading
//@{
//---------------------------
//! Load an image from a file.
/**
\param filename is the name of the image file to load.
\note The extension of \c filename defines the file format. If no filename
extension is provided, CImg<T>::get_load() will try to load a .cimg file.
**/
CImg<T>& load(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load() : Cannot load (null) filename.",
pixel_type());
const char *ext = cimg::split_filename(filename);
const unsigned int odebug = cimg::exception_mode();
cimg::exception_mode() = 0;
assign();
try {
#ifdef cimg_load_plugin
cimg_load_plugin(filename);
#endif
#ifdef cimg_load_plugin1
cimg_load_plugin1(filename);
#endif
#ifdef cimg_load_plugin2
cimg_load_plugin2(filename);
#endif
#ifdef cimg_load_plugin3
cimg_load_plugin3(filename);
#endif
#ifdef cimg_load_plugin4
cimg_load_plugin4(filename);
#endif
#ifdef cimg_load_plugin5
cimg_load_plugin5(filename);
#endif
#ifdef cimg_load_plugin6
cimg_load_plugin6(filename);
#endif
#ifdef cimg_load_plugin7
cimg_load_plugin7(filename);
#endif
#ifdef cimg_load_plugin8
cimg_load_plugin8(filename);
#endif
// ASCII formats
if (!cimg::strcasecmp(ext,"asc")) load_ascii(filename);
if (!cimg::strcasecmp(ext,"dlm") ||
!cimg::strcasecmp(ext,"txt")) load_dlm(filename);
// 2D binary formats
if (!cimg::strcasecmp(ext,"bmp")) load_bmp(filename);
if (!cimg::strcasecmp(ext,"jpg") ||
!cimg::strcasecmp(ext,"jpeg") ||
!cimg::strcasecmp(ext,"jpe") ||
!cimg::strcasecmp(ext,"jfif") ||
!cimg::strcasecmp(ext,"jif")) load_jpeg(filename);
if (!cimg::strcasecmp(ext,"png")) load_png(filename);
if (!cimg::strcasecmp(ext,"ppm") ||
!cimg::strcasecmp(ext,"pgm") ||
!cimg::strcasecmp(ext,"pnm")) load_pnm(filename);
if (!cimg::strcasecmp(ext,"tif") ||
!cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
if (!cimg::strcasecmp(ext,"cr2") ||
!cimg::strcasecmp(ext,"crw") ||
!cimg::strcasecmp(ext,"dcr") ||
!cimg::strcasecmp(ext,"mrw") ||
!cimg::strcasecmp(ext,"nef") ||
!cimg::strcasecmp(ext,"orf") ||
!cimg::strcasecmp(ext,"pix") ||
!cimg::strcasecmp(ext,"ptx") ||
!cimg::strcasecmp(ext,"raf") ||
!cimg::strcasecmp(ext,"srf")) load_dcraw_external(filename);
// 3D binary formats
if (!cimg::strcasecmp(ext,"dcm") ||
!cimg::strcasecmp(ext,"dicom")) load_medcon_external(filename);
if (!cimg::strcasecmp(ext,"hdr") ||
!cimg::strcasecmp(ext,"nii")) load_analyze(filename);
if (!cimg::strcasecmp(ext,"par") ||
!cimg::strcasecmp(ext,"rec")) load_parrec(filename);
if (!cimg::strcasecmp(ext,"inr")) load_inr(filename);
if (!cimg::strcasecmp(ext,"pan")) load_pandore(filename);
if (!cimg::strcasecmp(ext,"cimg") ||
!cimg::strcasecmp(ext,"cimgz") ||
*ext=='\0') return load_cimg(filename);
// Archive files
if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
// Image sequences
if (!cimg::strcasecmp(ext,"avi") ||
!cimg::strcasecmp(ext,"mov") ||
!cimg::strcasecmp(ext,"asf") ||
!cimg::strcasecmp(ext,"divx") ||
!cimg::strcasecmp(ext,"flv") ||
!cimg::strcasecmp(ext,"mpg") ||
!cimg::strcasecmp(ext,"m1v") ||
!cimg::strcasecmp(ext,"m2v") ||
!cimg::strcasecmp(ext,"m4v") ||
!cimg::strcasecmp(ext,"mjp") ||
!cimg::strcasecmp(ext,"mkv") ||
!cimg::strcasecmp(ext,"mpe") ||
!cimg::strcasecmp(ext,"movie") ||
!cimg::strcasecmp(ext,"ogm") ||
!cimg::strcasecmp(ext,"qt") ||
!cimg::strcasecmp(ext,"rm") ||
!cimg::strcasecmp(ext,"vob") ||
!cimg::strcasecmp(ext,"wmv") ||
!cimg::strcasecmp(ext,"xvid") ||
!cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type());
} catch (CImgException& e) {
if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) {
cimg::exception_mode() = odebug;
throw CImgIOException("CImg<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename);
} else try {
const char *const ftype = cimg::file_type(0,filename);
assign();
if (!cimg::strcmp(ftype,"pnm")) load_pnm(filename);
if (!cimg::strcmp(ftype,"bmp")) load_bmp(filename);
if (!cimg::strcmp(ftype,"jpeg")) load_jpeg(filename);
if (!cimg::strcmp(ftype,"pan")) load_pandore(filename);
if (!cimg::strcmp(ftype,"png")) load_png(filename);
if (!cimg::strcmp(ftype,"tiff")) load_tiff(filename);
if (is_empty()) throw CImgIOException("CImg<%s>::load()",pixel_type());
} catch (CImgException&) {
try {
load_other(filename);
} catch (CImgException&) {
assign();
}
}
}
cimg::exception_mode() = odebug;
if (is_empty())
throw CImgIOException("CImg<%s>::load() : File '%s', format not recognized.",pixel_type(),filename);
return *this;
}
static CImg<T> get_load(const char *const filename) {
return CImg<T>().load(filename);
}
//! Load an image from an ASCII file.
CImg<T>& load_ascii(const char *const filename) {
return _load_ascii(0,filename);
}
static CImg<T> get_load_ascii(const char *const filename) {
return CImg<T>().load_ascii(filename);
}
//! Load an image from an ASCII file.
CImg<T>& load_ascii(cimg_std::FILE *const file) {
return _load_ascii(file,0);
}
static CImg<T> get_load_ascii(cimg_std::FILE *const file) {
return CImg<T>().load_ascii(file);
}
CImg<T>& _load_ascii(cimg_std::FILE *const file, const char *const filename) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_ascii() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
char line[256] = { 0 };
int err = cimg_std::fscanf(nfile,"%*[^0-9]%255[^\n]",line);
unsigned int off, dx = 0, dy = 1, dz = 1, dv = 1;
cimg_std::sscanf(line,"%u%*c%u%*c%u%*c%u",&dx,&dy,&dz,&dv);
err = cimg_std::fscanf(nfile,"%*[^0-9.+-]");
if (!dx || !dy || !dz || !dv) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_ascii() : File '%s', invalid .ASC header, specified image dimensions are (%u,%u,%u,%u).",
pixel_type(),filename?filename:"(FILE*)",dx,dy,dz,dv);
}
assign(dx,dy,dz,dv);
const unsigned long siz = size();
double val;
T *ptr = data;
for (err = 1, off = 0; off<siz && err==1; ++off) {
err = cimg_std::fscanf(nfile,"%lf%*[^0-9.+-]",&val);
*(ptr++) = (T)val;
}
if (err!=1)
cimg::warn("CImg<%s>::load_ascii() : File '%s', only %u/%lu values read.",
pixel_type(),filename?filename:"(FILE*)",off-1,siz);
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a DLM file.
CImg<T>& load_dlm(const char *const filename) {
return _load_dlm(0,filename);
}
static CImg<T> get_load_dlm(const char *const filename) {
return CImg<T>().load_dlm(filename);
}
//! Load an image from a DLM file.
CImg<T>& load_dlm(cimg_std::FILE *const file) {
return _load_dlm(file,0);
}
static CImg<T> get_load_dlm(cimg_std::FILE *const file) {
return CImg<T>().load_dlm(file);
}
CImg<T>& _load_dlm(cimg_std::FILE *const file, const char *const filename) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_dlm() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
assign(256,256);
char c, delimiter[256] = { 0 }, tmp[256];
unsigned int cdx = 0, dx = 0, dy = 0;
int oerr = 0, err;
double val;
while ((err = cimg_std::fscanf(nfile,"%lf%255[^0-9.+-]",&val,delimiter))!=EOF) {
oerr = err;
if (err>0) (*this)(cdx++,dy) = (T)val;
if (cdx>=width) resize(width+256,1,1,1,0);
c = 0; if (!cimg_std::sscanf(delimiter,"%255[^\n]%c",tmp,&c) || c=='\n') {
dx = cimg::max(cdx,dx);
++dy;
if (dy>=height) resize(width,height+256,1,1,0);
cdx = 0;
}
}
if (cdx && oerr==1) { dx=cdx; ++dy; }
if (!dx || !dy) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_dlm() : File '%s', invalid DLM file, specified image dimensions are (%u,%u).",
pixel_type(),filename?filename:"(FILE*)",dx,dy);
}
resize(dx,dy,1,1,0);
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a BMP file.
CImg<T>& load_bmp(const char *const filename) {
return _load_bmp(0,filename);
}
static CImg<T> get_load_bmp(const char *const filename) {
return CImg<T>().load_bmp(filename);
}
//! Load an image from a BMP file.
CImg<T>& load_bmp(cimg_std::FILE *const file) {
return _load_bmp(file,0);
}
static CImg<T> get_load_bmp(cimg_std::FILE *const file) {
return CImg<T>().load_bmp(file);
}
CImg<T>& _load_bmp(cimg_std::FILE *const file, const char *const filename) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_bmp() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
unsigned char header[64];
cimg::fread(header,54,nfile);
if (header[0]!='B' || header[1]!='M') {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_bmp() : Invalid valid BMP file (filename '%s').",
pixel_type(),filename?filename:"(FILE*)");
}
assign();
// Read header and pixel buffer
int
file_size = header[0x02] + (header[0x03]<<8) + (header[0x04]<<16) + (header[0x05]<<24),
offset = header[0x0A] + (header[0x0B]<<8) + (header[0x0C]<<16) + (header[0x0D]<<24),
dx = header[0x12] + (header[0x13]<<8) + (header[0x14]<<16) + (header[0x15]<<24),
dy = header[0x16] + (header[0x17]<<8) + (header[0x18]<<16) + (header[0x19]<<24),
compression = header[0x1E] + (header[0x1F]<<8) + (header[0x20]<<16) + (header[0x21]<<24),
nb_colors = header[0x2E] + (header[0x2F]<<8) + (header[0x30]<<16) + (header[0x31]<<24),
bpp = header[0x1C] + (header[0x1D]<<8),
*palette = 0;
const int
dx_bytes = (bpp==1)?(dx/8+(dx%8?1:0)):((bpp==4)?(dx/2+(dx%2?1:0)):(dx*bpp/8)),
align = (4-dx_bytes%4)%4,
buf_size = cimg::min(cimg::abs(dy)*(dx_bytes+align),file_size-offset);
if (bpp<16) { if (!nb_colors) nb_colors=1<<bpp; } else nb_colors = 0;
if (nb_colors) { palette = new int[nb_colors]; cimg::fread(palette,nb_colors,nfile); }
const int xoffset = offset-54-4*nb_colors;
if (xoffset>0) cimg_std::fseek(nfile,xoffset,SEEK_CUR);
unsigned char *buffer = new unsigned char[buf_size], *ptrs = buffer;
cimg::fread(buffer,buf_size,nfile);
if (!file) cimg::fclose(nfile);
// Decompress buffer (if necessary)
if (compression) {
delete[] buffer;
if (file) {
throw CImgIOException("CImg<%s>::load_bmp() : Not able to read a compressed BMP file using a *FILE input",
pixel_type());
} else return load_other(filename);
}
// Read pixel data
assign(dx,cimg::abs(dy),1,3);
switch (bpp) {
case 1 : { // Monochrome
for (int y=height-1; y>=0; --y) {
unsigned char mask = 0x80, val = 0;
cimg_forX(*this,x) {
if (mask==0x80) val = *(ptrs++);
const unsigned char *col = (unsigned char*)(palette+(val&mask?1:0));
(*this)(x,y,2) = (T)*(col++);
(*this)(x,y,1) = (T)*(col++);
(*this)(x,y,0) = (T)*(col++);
mask = cimg::ror(mask);
} ptrs+=align; }
} break;
case 4 : { // 16 colors
for (int y=height-1; y>=0; --y) {
unsigned char mask = 0xF0, val = 0;
cimg_forX(*this,x) {
if (mask==0xF0) val = *(ptrs++);
const unsigned char color = (unsigned char)((mask<16)?(val&mask):((val&mask)>>4));
unsigned char *col = (unsigned char*)(palette+color);
(*this)(x,y,2) = (T)*(col++);
(*this)(x,y,1) = (T)*(col++);
(*this)(x,y,0) = (T)*(col++);
mask = cimg::ror(mask,4);
} ptrs+=align; }
} break;
case 8 : { // 256 colors
for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
const unsigned char *col = (unsigned char*)(palette+*(ptrs++));
(*this)(x,y,2) = (T)*(col++);
(*this)(x,y,1) = (T)*(col++);
(*this)(x,y,0) = (T)*(col++);
} ptrs+=align; }
} break;
case 16 : { // 16 bits colors
for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
const unsigned char c1 = *(ptrs++), c2 = *(ptrs++);
const unsigned short col = (unsigned short)(c1|(c2<<8));
(*this)(x,y,2) = (T)(col&0x1F);
(*this)(x,y,1) = (T)((col>>5)&0x1F);
(*this)(x,y,0) = (T)((col>>10)&0x1F);
} ptrs+=align; }
} break;
case 24 : { // 24 bits colors
for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
(*this)(x,y,2) = (T)*(ptrs++);
(*this)(x,y,1) = (T)*(ptrs++);
(*this)(x,y,0) = (T)*(ptrs++);
} ptrs+=align; }
} break;
case 32 : { // 32 bits colors
for (int y=height-1; y>=0; --y) { cimg_forX(*this,x) {
(*this)(x,y,2) = (T)*(ptrs++);
(*this)(x,y,1) = (T)*(ptrs++);
(*this)(x,y,0) = (T)*(ptrs++);
++ptrs;
} ptrs+=align; }
} break;
}
if (palette) delete[] palette;
delete[] buffer;
if (dy<0) mirror('y');
return *this;
}
//! Load an image from a JPEG file.
CImg<T>& load_jpeg(const char *const filename) {
return _load_jpeg(0,filename);
}
static CImg<T> get_load_jpeg(const char *const filename) {
return CImg<T>().load_jpeg(filename);
}
//! Load an image from a JPEG file.
CImg<T>& load_jpeg(cimg_std::FILE *const file) {
return _load_jpeg(file,0);
}
static CImg<T> get_load_jpeg(cimg_std::FILE *const file) {
return CImg<T>().load_jpeg(file);
}
CImg<T>& _load_jpeg(cimg_std::FILE *const file, const char *const filename) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_jpeg() : Cannot load (null) filename.",
pixel_type());
#ifndef cimg_use_jpeg
if (file)
throw CImgIOException("CImg<%s>::load_jpeg() : File '(FILE*)' cannot be read without using libjpeg.",
pixel_type());
else return load_other(filename);
#else
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo,nfile);
jpeg_read_header(&cinfo,TRUE);
jpeg_start_decompress(&cinfo);
if (cinfo.output_components!=1 && cinfo.output_components!=3 && cinfo.output_components!=4) {
cimg::warn("CImg<%s>::load_jpeg() : Don't know how to read image '%s' with libpeg, trying ImageMagick's convert",
pixel_type(),filename?filename:"(FILE*)");
if (!file) return load_other(filename);
else {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_jpeg() : Cannot read JPEG image '%s' using a *FILE input.",
pixel_type(),filename?filename:"(FILE*)");
}
}
const unsigned int row_stride = cinfo.output_width * cinfo.output_components;
unsigned char *buf = new unsigned char[cinfo.output_width*cinfo.output_height*cinfo.output_components], *buf2 = buf;
JSAMPROW row_pointer[1];
while (cinfo.output_scanline < cinfo.output_height) {
row_pointer[0] = &buf[cinfo.output_scanline*row_stride];
jpeg_read_scanlines(&cinfo,row_pointer,1);
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
if (!file) cimg::fclose(nfile);
assign(cinfo.output_width,cinfo.output_height,1,cinfo.output_components);
switch (dim) {
case 1 : {
T *ptr_g = data;
cimg_forXY(*this,x,y) *(ptr_g++) = (T)*(buf2++);
} break;
case 3 : {
T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
cimg_forXY(*this,x,y) {
*(ptr_r++) = (T)*(buf2++);
*(ptr_g++) = (T)*(buf2++);
*(ptr_b++) = (T)*(buf2++);
}
} break;
case 4 : {
T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1),
*ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
cimg_forXY(*this,x,y) {
*(ptr_r++) = (T)*(buf2++);
*(ptr_g++) = (T)*(buf2++);
*(ptr_b++) = (T)*(buf2++);
*(ptr_a++) = (T)*(buf2++);
}
} break;
}
delete[] buf;
return *this;
#endif
}
//! Load an image from a file, using Magick++ library.
// Added April/may 2006 by Christoph Hormann <chris_hormann@gmx.de>
// This is experimental code, not much tested, use with care.
CImg<T>& load_magick(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_magick() : Cannot load (null) filename.",
pixel_type());
#ifdef cimg_use_magick
Magick::Image image(filename);
const unsigned int W = image.size().width(), H = image.size().height();
switch (image.type()) {
case Magick::PaletteMatteType :
case Magick::TrueColorMatteType :
case Magick::ColorSeparationType : {
assign(W,H,1,4);
T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2), *adata = ptr(0,0,0,3);
Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
for (unsigned int off = W*H; off; --off) {
*(rdata++) = (T)(pixels->red);
*(gdata++) = (T)(pixels->green);
*(bdata++) = (T)(pixels->blue);
*(adata++) = (T)(pixels->opacity);
++pixels;
}
} break;
case Magick::PaletteType :
case Magick::TrueColorType : {
assign(W,H,1,3);
T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
for (unsigned int off = W*H; off; --off) {
*(rdata++) = (T)(pixels->red);
*(gdata++) = (T)(pixels->green);
*(bdata++) = (T)(pixels->blue);
++pixels;
}
} break;
case Magick::GrayscaleMatteType : {
assign(W,H,1,2);
T *data = ptr(0,0,0,0), *adata = ptr(0,0,0,1);
Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
for (unsigned int off = W*H; off; --off) {
*(data++) = (T)(pixels->red);
*(adata++) = (T)(pixels->opacity);
++pixels;
}
} break;
default : {
assign(W,H,1,1);
T *data = ptr(0,0,0,0);
Magick::PixelPacket *pixels = image.getPixels(0,0,W,H);
for (unsigned int off = W*H; off; --off) {
*(data++) = (T)(pixels->red);
++pixels;
}
}
}
#else
throw CImgIOException("CImg<%s>::load_magick() : File '%s', Magick++ library has not been linked.",
pixel_type(),filename);
#endif
return *this;
}
static CImg<T> get_load_magick(const char *const filename) {
return CImg<T>().load_magick(filename);
}
//! Load an image from a PNG file.
CImg<T>& load_png(const char *const filename) {
return _load_png(0,filename);
}
static CImg<T> get_load_png(const char *const filename) {
return CImg<T>().load_png(filename);
}
//! Load an image from a PNG file.
CImg<T>& load_png(cimg_std::FILE *const file) {
return _load_png(file,0);
}
static CImg<T> get_load_png(cimg_std::FILE *const file) {
return CImg<T>().load_png(file);
}
// (Note : Most of this function has been written by Eric Fausett)
CImg<T>& _load_png(cimg_std::FILE *const file, const char *const filename) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_png() : Cannot load (null) filename.",
pixel_type());
#ifndef cimg_use_png
if (file)
throw CImgIOException("CImg<%s>::load_png() : File '(FILE*)' cannot be read without using libpng.",
pixel_type());
else return load_other(filename);
#else
// Open file and check for PNG validity
const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"rb");
unsigned char pngCheck[8];
cimg::fread(pngCheck,8,(cimg_std::FILE*)nfile);
if (png_sig_cmp(pngCheck,0,8)) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_png() : File '%s' is not a valid PNG file.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
// Setup PNG structures for read
png_voidp user_error_ptr = 0;
png_error_ptr user_error_fn = 0, user_warning_fn = 0;
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,user_error_ptr,user_error_fn,user_warning_fn);
if (!png_ptr) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'png_ptr' data structure.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
if (!file) cimg::fclose(nfile);
png_destroy_read_struct(&png_ptr,(png_infopp)0,(png_infopp)0);
throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'info_ptr' data structure.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
png_infop end_info = png_create_info_struct(png_ptr);
if (!end_info) {
if (!file) cimg::fclose(nfile);
png_destroy_read_struct(&png_ptr,&info_ptr,(png_infopp)0);
throw CImgIOException("CImg<%s>::load_png() : File '%s', trouble initializing 'end_info' data structure.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
// Error handling callback for png file reading
if (setjmp(png_jmpbuf(png_ptr))) {
if (!file) cimg::fclose((cimg_std::FILE*)nfile);
png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
throw CImgIOException("CImg<%s>::load_png() : File '%s', unknown fatal error.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
png_init_io(png_ptr, nfile);
png_set_sig_bytes(png_ptr, 8);
// Get PNG Header Info up to data block
png_read_info(png_ptr,info_ptr);
png_uint_32 W, H;
int bit_depth, color_type, interlace_type;
png_get_IHDR(png_ptr,info_ptr,&W,&H,&bit_depth,&color_type,&interlace_type,int_p_NULL,int_p_NULL);
int new_bit_depth = bit_depth;
int new_color_type = color_type;
// Transforms to unify image data
if (new_color_type == PNG_COLOR_TYPE_PALETTE){
png_set_palette_to_rgb(png_ptr);
new_color_type -= PNG_COLOR_MASK_PALETTE;
new_bit_depth = 8;
}
if (new_color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8){
png_set_gray_1_2_4_to_8(png_ptr);
new_bit_depth = 8;
}
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png_ptr);
if (new_color_type == PNG_COLOR_TYPE_GRAY || new_color_type == PNG_COLOR_TYPE_GRAY_ALPHA){
png_set_gray_to_rgb(png_ptr);
new_color_type |= PNG_COLOR_MASK_COLOR;
}
if (new_color_type == PNG_COLOR_TYPE_RGB)
png_set_filler(png_ptr, 0xffffU, PNG_FILLER_AFTER);
png_read_update_info(png_ptr,info_ptr);
if (!(new_bit_depth==8 || new_bit_depth==16)) {
if (!file) cimg::fclose(nfile);
png_destroy_read_struct(&png_ptr, &end_info, (png_infopp)0);
throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong bit coding (bit_depth=%u)",
pixel_type(),nfilename?nfilename:"(FILE*)",new_bit_depth);
}
const int byte_depth = new_bit_depth>>3;
// Allocate Memory for Image Read
png_bytep *imgData = new png_bytep[H];
for (unsigned int row = 0; row<H; ++row) imgData[row] = new png_byte[byte_depth*4*W];
png_read_image(png_ptr,imgData);
png_read_end(png_ptr,end_info);
// Read pixel data
if (!(new_color_type==PNG_COLOR_TYPE_RGB || new_color_type==PNG_COLOR_TYPE_RGB_ALPHA)) {
if (!file) cimg::fclose(nfile);
png_destroy_read_struct(&png_ptr,&end_info,(png_infopp)0);
throw CImgIOException("CImg<%s>::load_png() : File '%s', wrong color coding (new_color_type=%u)",
pixel_type(),nfilename?nfilename:"(FILE*)",new_color_type);
}
const bool no_alpha_channel = (new_color_type==PNG_COLOR_TYPE_RGB);
assign(W,H,1,no_alpha_channel?3:4);
T *ptr1 = ptr(0,0,0,0), *ptr2 = ptr(0,0,0,1), *ptr3 = ptr(0,0,0,2), *ptr4 = ptr(0,0,0,3);
switch (new_bit_depth) {
case 8 : {
cimg_forY(*this,y){
const unsigned char *ptrs = (unsigned char*)imgData[y];
cimg_forX(*this,x){
*(ptr1++) = (T)*(ptrs++);
*(ptr2++) = (T)*(ptrs++);
*(ptr3++) = (T)*(ptrs++);
if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
}
}
} break;
case 16 : {
cimg_forY(*this,y){
const unsigned short *ptrs = (unsigned short*)(imgData[y]);
if (!cimg::endianness()) cimg::invert_endianness(ptrs,4*width);
cimg_forX(*this,x){
*(ptr1++) = (T)*(ptrs++);
*(ptr2++) = (T)*(ptrs++);
*(ptr3++) = (T)*(ptrs++);
if (no_alpha_channel) ++ptrs; else *(ptr4++) = (T)*(ptrs++);
}
}
} break;
}
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
// Deallocate Image Read Memory
cimg_forY(*this,n) delete[] imgData[n];
delete[] imgData;
if (!file) cimg::fclose(nfile);
return *this;
#endif
}
//! Load an image from a PNM file.
CImg<T>& load_pnm(const char *const filename) {
return _load_pnm(0,filename);
}
static CImg<T> get_load_pnm(const char *const filename) {
return CImg<T>().load_pnm(filename);
}
//! Load an image from a PNM file.
CImg<T>& load_pnm(cimg_std::FILE *const file) {
return _load_pnm(file,0);
}
static CImg<T> get_load_pnm(cimg_std::FILE *const file) {
return CImg<T>().load_pnm(file);
}
CImg<T>& _load_pnm(cimg_std::FILE *const file, const char *const filename) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_pnm() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
unsigned int ppm_type, W, H, colormax = 255;
char item[1024] = { 0 };
int err, rval, gval, bval;
const int cimg_iobuffer = 12*1024*1024;
while ((err=cimg_std::fscanf(nfile,"%1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
if (cimg_std::sscanf(item," P%u",&ppm_type)!=1) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PNM header 'P?' not found.",
pixel_type(),filename?filename:"(FILE*)");
}
while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
if ((err=cimg_std::sscanf(item," %u %u %u",&W,&H,&colormax))<2) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_pnm() : File '%s', WIDTH and HEIGHT fields are not defined in PNM header.",
pixel_type(),filename?filename:"(FILE*)");
}
if (err==2) {
while ((err=cimg_std::fscanf(nfile," %1023[^\n]",item))!=EOF && (item[0]=='#' || !err)) cimg_std::fgetc(nfile);
if (cimg_std::sscanf(item,"%u",&colormax)!=1)
cimg::warn("CImg<%s>::load_pnm() : File '%s', COLORMAX field is not defined in PNM header.",
pixel_type(),filename?filename:"(FILE*)");
}
cimg_std::fgetc(nfile);
assign();
switch (ppm_type) {
case 2 : { // Grey Ascii
assign(W,H,1,1);
T* rdata = data;
cimg_foroff(*this,off) { if (cimg_std::fscanf(nfile,"%d",&rval)>0) *(rdata++) = (T)rval; else break; }
} break;
case 3 : { // Color Ascii
assign(W,H,1,3);
T *rdata = ptr(0,0,0,0), *gdata = ptr(0,0,0,1), *bdata = ptr(0,0,0,2);
cimg_forXY(*this,x,y) {
if (cimg_std::fscanf(nfile,"%d %d %d",&rval,&gval,&bval)==3) { *(rdata++) = (T)rval; *(gdata++) = (T)gval; *(bdata++) = (T)bval; }
else break;
}
} break;
case 5 : { // Grey Binary
if (colormax<256) { // 8 bits
CImg<ucharT> raw;
assign(W,H,1,1);
T *ptrd = ptr(0,0,0,0);
for (int toread = (int)size(); toread>0; ) {
raw.assign(cimg::min(toread,cimg_iobuffer));
cimg::fread(raw.data,raw.width,nfile);
toread-=raw.width;
const unsigned char *ptrs = raw.data;
for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
}
} else { // 16 bits
CImg<ushortT> raw;
assign(W,H,1,1);
T *ptrd = ptr(0,0,0,0);
for (int toread = (int)size(); toread>0; ) {
raw.assign(cimg::min(toread,cimg_iobuffer/2));
cimg::fread(raw.data,raw.width,nfile);
if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width);
toread-=raw.width;
const unsigned short *ptrs = raw.data;
for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++);
}
}
} break;
case 6 : { // Color Binary
if (colormax<256) { // 8 bits
CImg<ucharT> raw;
assign(W,H,1,3);
T
*ptr_r = ptr(0,0,0,0),
*ptr_g = ptr(0,0,0,1),
*ptr_b = ptr(0,0,0,2);
for (int toread = (int)size(); toread>0; ) {
raw.assign(cimg::min(toread,cimg_iobuffer));
cimg::fread(raw.data,raw.width,nfile);
toread-=raw.width;
const unsigned char *ptrs = raw.data;
for (unsigned int off = raw.width/3; off; --off) {
*(ptr_r++) = (T)*(ptrs++);
*(ptr_g++) = (T)*(ptrs++);
*(ptr_b++) = (T)*(ptrs++);
}
}
} else { // 16 bits
CImg<ushortT> raw;
assign(W,H,1,3);
T
*ptr_r = ptr(0,0,0,0),
*ptr_g = ptr(0,0,0,1),
*ptr_b = ptr(0,0,0,2);
for (int toread = (int)size(); toread>0; ) {
raw.assign(cimg::min(toread,cimg_iobuffer/2));
cimg::fread(raw.data,raw.width,nfile);
if (!cimg::endianness()) cimg::invert_endianness(raw.data,raw.width);
toread-=raw.width;
const unsigned short *ptrs = raw.data;
for (unsigned int off = raw.width/3; off; --off) {
*(ptr_r++) = (T)*(ptrs++);
*(ptr_g++) = (T)*(ptrs++);
*(ptr_b++) = (T)*(ptrs++);
}
}
}
} break;
default :
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_pnm() : File '%s', PPM type 'P%d' not supported.",
pixel_type(),filename?filename:"(FILE*)",ppm_type);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a RGB file.
CImg<T>& load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
return _load_rgb(0,filename,dimw,dimh);
}
static CImg<T> get_load_rgb(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
return CImg<T>().load_rgb(filename,dimw,dimh);
}
//! Load an image from a RGB file.
CImg<T>& load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
return _load_rgb(file,0,dimw,dimh);
}
static CImg<T> get_load_rgb(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
return CImg<T>().load_rgb(file,dimw,dimh);
}
CImg<T>& _load_rgb(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_rgb() : Cannot load (null) filename.",
pixel_type());
if (!dimw || !dimh) return assign();
const int cimg_iobuffer = 12*1024*1024;
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
CImg<ucharT> raw;
assign(dimw,dimh,1,3);
T
*ptr_r = ptr(0,0,0,0),
*ptr_g = ptr(0,0,0,1),
*ptr_b = ptr(0,0,0,2);
for (int toread = (int)size(); toread>0; ) {
raw.assign(cimg::min(toread,cimg_iobuffer));
cimg::fread(raw.data,raw.width,nfile);
toread-=raw.width;
const unsigned char *ptrs = raw.data;
for (unsigned int off = raw.width/3; off; --off) {
*(ptr_r++) = (T)*(ptrs++);
*(ptr_g++) = (T)*(ptrs++);
*(ptr_b++) = (T)*(ptrs++);
}
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a RGBA file.
CImg<T>& load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
return _load_rgba(0,filename,dimw,dimh);
}
static CImg<T> get_load_rgba(const char *const filename, const unsigned int dimw, const unsigned int dimh=1) {
return CImg<T>().load_rgba(filename,dimw,dimh);
}
//! Load an image from a RGBA file.
CImg<T>& load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
return _load_rgba(file,0,dimw,dimh);
}
static CImg<T> get_load_rgba(cimg_std::FILE *const file, const unsigned int dimw, const unsigned int dimh=1) {
return CImg<T>().load_rgba(file,dimw,dimh);
}
CImg<T>& _load_rgba(cimg_std::FILE *const file, const char *const filename, const unsigned int dimw, const unsigned int dimh) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_rgba() : Cannot load (null) filename.",
pixel_type());
if (!dimw || !dimh) return assign();
const int cimg_iobuffer = 12*1024*1024;
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
CImg<ucharT> raw;
assign(dimw,dimh,1,4);
T
*ptr_r = ptr(0,0,0,0),
*ptr_g = ptr(0,0,0,1),
*ptr_b = ptr(0,0,0,2),
*ptr_a = ptr(0,0,0,3);
for (int toread = (int)size(); toread>0; ) {
raw.assign(cimg::min(toread,cimg_iobuffer));
cimg::fread(raw.data,raw.width,nfile);
toread-=raw.width;
const unsigned char *ptrs = raw.data;
for (unsigned int off = raw.width/4; off; --off) {
*(ptr_r++) = (T)*(ptrs++);
*(ptr_g++) = (T)*(ptrs++);
*(ptr_b++) = (T)*(ptrs++);
*(ptr_a++) = (T)*(ptrs++);
}
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a TIFF file.
CImg<T>& load_tiff(const char *const filename,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_tiff() : Cannot load (null) filename.",
pixel_type());
const unsigned int
nfirst_frame = first_frame<last_frame?first_frame:last_frame,
nstep_frame = step_frame?step_frame:1;
unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
#ifndef cimg_use_tiff
if (nfirst_frame || nlast_frame!=~0U || nstep_frame>1)
throw CImgArgumentException("CImg<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
"('cimg_use_tiff' must be defined).",
pixel_type(),filename);
return load_other(filename);
#else
TIFF *tif = TIFFOpen(filename,"r");
if (tif) {
unsigned int nb_images = 0;
do ++nb_images; while (TIFFReadDirectory(tif));
if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
cimg::warn("CImg<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
if (nfirst_frame>=nb_images) return assign();
if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
TIFFSetDirectory(tif,0);
CImg<T> frame;
for (unsigned int l = nfirst_frame; l<=nlast_frame; l+=nstep_frame) {
frame._load_tiff(tif,l);
if (l==nfirst_frame) assign(frame.width,frame.height,1+(nlast_frame-nfirst_frame)/nstep_frame,frame.dim);
if (frame.width>width || frame.height>height || frame.dim>dim)
resize(cimg::max(frame.width,width),cimg::max(frame.height,height),-100,cimg::max(frame.dim,dim),0);
draw_image(0,0,(l-nfirst_frame)/nstep_frame,frame);
}
TIFFClose(tif);
} else throw CImgException("CImg<%s>::load_tiff() : File '%s' cannot be opened.",
pixel_type(),filename);
return *this;
#endif
}
static CImg<T> get_load_tiff(const char *const filename,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1) {
return CImg<T>().load_tiff(filename,first_frame,last_frame,step_frame);
}
// (Original contribution by Jerome Boulanger).
#ifdef cimg_use_tiff
CImg<T>& _load_tiff(TIFF *tif, const unsigned int directory) {
if (!TIFFSetDirectory(tif,directory)) return assign();
uint16 samplesperpixel, bitspersample;
uint32 nx,ny;
const char *const filename = TIFFFileName(tif);
TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&nx);
TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&ny);
TIFFGetField(tif,TIFFTAG_SAMPLESPERPIXEL,&samplesperpixel);
if (samplesperpixel!=1 && samplesperpixel!=3 && samplesperpixel!=4) {
cimg::warn("CImg<%s>::load_tiff() : File '%s', unknow value for tag : TIFFTAG_SAMPLESPERPIXEL, will force it to 1.",
pixel_type(),filename);
samplesperpixel = 1;
}
TIFFGetFieldDefaulted(tif,TIFFTAG_BITSPERSAMPLE,&bitspersample);
assign(nx,ny,1,samplesperpixel);
if (bitspersample!=8 || !(samplesperpixel==3 || samplesperpixel==4)) {
uint16 photo, config;
TIFFGetField(tif,TIFFTAG_PLANARCONFIG,&config);
TIFFGetField(tif,TIFFTAG_PHOTOMETRIC,&photo);
if (TIFFIsTiled(tif)) {
uint32 tw, th;
TIFFGetField(tif,TIFFTAG_TILEWIDTH,&tw);
TIFFGetField(tif,TIFFTAG_TILELENGTH,&th);
if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
case 8 : {
unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
if (buf) {
for (unsigned int row = 0; row<ny; row+=th)
for (unsigned int col = 0; col<nx; col+=tw) {
if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a tile.",
pixel_type(),filename);
} else {
unsigned char *ptr = buf;
for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
(*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
}
}
_TIFFfree(buf);
}
} break;
case 16 : {
unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
if (buf) {
for (unsigned int row = 0; row<ny; row+=th)
for (unsigned int col = 0; col<nx; col+=tw) {
if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a tile.",
pixel_type(),filename);
} else {
unsigned short *ptr = buf;
for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
(*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
}
}
_TIFFfree(buf);
}
} break;
case 32 : {
float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
if (buf) {
for (unsigned int row = 0; row<ny; row+=th)
for (unsigned int col = 0; col<nx; col+=tw) {
if (TIFFReadTile(tif,buf,col,row,0,0)<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a tile.",
pixel_type(),filename);
} else {
float *ptr = buf;
for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
(*this)(cc,rr,vv) = (T)(float)(ptr[(rr-row)*th*samplesperpixel + (cc-col)*samplesperpixel + vv]);
}
}
_TIFFfree(buf);
}
} break;
} else switch (bitspersample) {
case 8 : {
unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFTileSize(tif));
if (buf) {
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
for (unsigned int row = 0; row<ny; row+=th)
for (unsigned int col = 0; col<nx; col+=tw) {
if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a tile.",
pixel_type(),filename);
} else {
unsigned char *ptr = buf;
for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
(*this)(cc,rr,vv) = (T)(float)*(ptr++);
}
}
_TIFFfree(buf);
}
} break;
case 16 : {
unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFTileSize(tif));
if (buf) {
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
for (unsigned int row = 0; row<ny; row+=th)
for (unsigned int col = 0; col<nx; col+=tw) {
if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a tile.",
pixel_type(),filename);
} else {
unsigned short *ptr = buf;
for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
(*this)(cc,rr,vv) = (T)(float)*(ptr++);
}
}
_TIFFfree(buf);
}
} break;
case 32 : {
float *buf = (float*)_TIFFmalloc(TIFFTileSize(tif));
if (buf) {
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
for (unsigned int row = 0; row<ny; row+=th)
for (unsigned int col = 0; col<nx; col+=tw) {
if (TIFFReadTile(tif,buf,col,row,0,vv)<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a tile.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a tile.",
pixel_type(),filename);
} else {
float *ptr = buf;
for (unsigned int rr = row; rr<cimg::min((unsigned int)(row+th),(unsigned int)ny); ++rr)
for (unsigned int cc = col; cc<cimg::min((unsigned int)(col+tw),(unsigned int)nx); ++cc)
(*this)(cc,rr,vv) = (T)(float)*(ptr++);
}
}
_TIFFfree(buf);
}
} break;
}
} else {
if (config==PLANARCONFIG_CONTIG) switch (bitspersample) {
case 8 : {
unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
if (buf) {
uint32 row, rowsperstrip = (uint32)-1;
TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
for (row = 0; row<ny; row+= rowsperstrip) {
uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif, row, 0);
if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a strip.",
pixel_type(),filename);
}
unsigned char *ptr = buf;
for (unsigned int rr = 0; rr<nrow; ++rr)
for (unsigned int cc = 0; cc<nx; ++cc)
for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
}
_TIFFfree(buf);
}
} break;
case 16 : {
unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
if (buf) {
uint32 row, rowsperstrip = (uint32)-1;
TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
for (row = 0; row<ny; row+= rowsperstrip) {
uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif, row, 0);
if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
_TIFFfree(buf); TIFFClose(tif);
throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
pixel_type(),filename);
}
unsigned short *ptr = buf;
for (unsigned int rr = 0; rr<nrow; ++rr)
for (unsigned int cc = 0; cc<nx; ++cc)
for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
}
_TIFFfree(buf);
}
} break;
case 32 : {
float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
if (buf) {
uint32 row, rowsperstrip = (uint32)-1;
TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
for (row = 0; row<ny; row+= rowsperstrip) {
uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif, row, 0);
if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
_TIFFfree(buf); TIFFClose(tif);
throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
pixel_type(),filename);
}
float *ptr = buf;
for (unsigned int rr = 0; rr<nrow; ++rr)
for (unsigned int cc = 0; cc<nx; ++cc)
for (unsigned int vv = 0; vv<samplesperpixel; ++vv) (*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
}
_TIFFfree(buf);
}
} break;
} else switch (bitspersample){
case 8 : {
unsigned char *buf = (unsigned char*)_TIFFmalloc(TIFFStripSize(tif));
if (buf) {
uint32 row, rowsperstrip = (uint32)-1;
TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
for (unsigned int vv=0; vv<samplesperpixel; ++vv)
for (row = 0; row<ny; row+= rowsperstrip) {
uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif, row, vv);
if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
_TIFFfree(buf); TIFFClose(tif);
- throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occure while reading a strip.",
+ throw CImgException("CImg<%s>::load_tiff() : File '%s', an error occurred while reading a strip.",
pixel_type(),filename);
}
unsigned char *ptr = buf;
for (unsigned int rr = 0;rr<nrow; ++rr)
for (unsigned int cc = 0; cc<nx; ++cc)
(*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
}
_TIFFfree(buf);
}
} break;
case 16 : {
unsigned short *buf = (unsigned short*)_TIFFmalloc(TIFFStripSize(tif));
if (buf) {
uint32 row, rowsperstrip = (uint32)-1;
TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
for (row = 0; row<ny; row+= rowsperstrip) {
uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif, row, vv);
if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
_TIFFfree(buf); TIFFClose(tif);
throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
pixel_type(),filename);
}
unsigned short *ptr = buf;
for (unsigned int rr = 0; rr<nrow; ++rr)
for (unsigned int cc = 0; cc<nx; ++cc)
(*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
}
_TIFFfree(buf);
}
} break;
case 32 : {
float *buf = (float*)_TIFFmalloc(TIFFStripSize(tif));
if (buf) {
uint32 row, rowsperstrip = (uint32)-1;
TIFFGetField(tif,TIFFTAG_ROWSPERSTRIP,&rowsperstrip);
for (unsigned int vv = 0; vv<samplesperpixel; ++vv)
for (row = 0; row<ny; row+= rowsperstrip) {
uint32 nrow = (row+rowsperstrip>ny?ny-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif, row, vv);
if ((TIFFReadEncodedStrip(tif,strip,buf,-1))<0) {
_TIFFfree(buf); TIFFClose(tif);
throw CImgException("CImg<%s>::load_tiff() : File '%s', error while reading a strip.",
pixel_type(),filename);
}
float *ptr = buf;
for (unsigned int rr = 0; rr<nrow; ++rr) for (unsigned int cc = 0; cc<nx; ++cc)
(*this)(cc,row+rr,vv) = (T)(float)*(ptr++);
}
_TIFFfree(buf);
}
} break;
}
}
} else {
uint32* raster = (uint32*)_TIFFmalloc(nx * ny * sizeof (uint32));
if (!raster) {
_TIFFfree(raster); TIFFClose(tif);
throw CImgException("CImg<%s>::load_tiff() : File '%s', not enough memory for buffer allocation.",
pixel_type(),filename);
}
TIFFReadRGBAImage(tif,nx,ny,raster,0);
switch (samplesperpixel) {
case 1 : {
cimg_forXY(*this,x,y) (*this)(x,y) = (T)(float)((raster[nx*(ny-1-y)+x]+ 128) / 257);
} break;
case 3 : {
cimg_forXY(*this,x,y) {
(*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
(*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
(*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
}
} break;
case 4 : {
cimg_forXY(*this,x,y) {
(*this)(x,y,0) = (T)(float)TIFFGetR(raster[nx*(ny-1-y)+x]);
(*this)(x,y,1) = (T)(float)TIFFGetG(raster[nx*(ny-1-y)+x]);
(*this)(x,y,2) = (T)(float)TIFFGetB(raster[nx*(ny-1-y)+x]);
(*this)(x,y,3) = (T)(float)TIFFGetA(raster[nx*(ny-1-y)+x]);
}
} break;
}
_TIFFfree(raster);
}
return *this;
}
#endif
//! Load an image from an ANALYZE7.5/NIFTI file.
CImg<T>& load_analyze(const char *const filename, float *const voxsize=0) {
return _load_analyze(0,filename,voxsize);
}
static CImg<T> get_load_analyze(const char *const filename, float *const voxsize=0) {
return CImg<T>().load_analyze(filename,voxsize);
}
//! Load an image from an ANALYZE7.5/NIFTI file.
CImg<T>& load_analyze(cimg_std::FILE *const file, float *const voxsize=0) {
return _load_analyze(file,0,voxsize);
}
static CImg<T> get_load_analyze(cimg_std::FILE *const file, float *const voxsize=0) {
return CImg<T>().load_analyze(file,voxsize);
}
CImg<T>& _load_analyze(cimg_std::FILE *const file, const char *const filename, float *const voxsize=0) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_analyze() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *nfile_header = 0, *nfile = 0;
if (!file) {
char body[1024];
const char *ext = cimg::split_filename(filename,body);
if (!cimg::strcasecmp(ext,"hdr")) { // File is an Analyze header file.
nfile_header = cimg::fopen(filename,"rb");
cimg_std::sprintf(body+cimg::strlen(body),".img");
nfile = cimg::fopen(body,"rb");
} else if (!cimg::strcasecmp(ext,"img")) { // File is an Analyze data file.
nfile = cimg::fopen(filename,"rb");
cimg_std::sprintf(body+cimg::strlen(body),".hdr");
nfile_header = cimg::fopen(body,"rb");
} else nfile_header = nfile = cimg::fopen(filename,"rb"); // File is a Niftii file.
} else nfile_header = nfile = file; // File is a Niftii file.
if (!nfile || !nfile_header)
throw CImgIOException("CImg<%s>::load_analyze() : File '%s', not recognized as an Analyze7.5 or NIFTI file.",
pixel_type(),filename?filename:"(FILE*)");
// Read header.
bool endian = false;
unsigned int header_size;
cimg::fread(&header_size,1,nfile_header);
if (!header_size)
throw CImgIOException("CImg<%s>::load_analyze() : File '%s', zero-sized header found.",
pixel_type(),filename?filename:"(FILE*)");
if (header_size>=4096) { endian = true; cimg::invert_endianness(header_size); }
unsigned char *header = new unsigned char[header_size];
cimg::fread(header+4,header_size-4,nfile_header);
if (!file && nfile_header!=nfile) cimg::fclose(nfile_header);
if (endian) {
cimg::invert_endianness((short*)(header+40),5);
cimg::invert_endianness((short*)(header+70),1);
cimg::invert_endianness((short*)(header+72),1);
cimg::invert_endianness((float*)(header+76),4);
cimg::invert_endianness((float*)(header+112),1);
}
unsigned short *dim = (unsigned short*)(header+40), dimx = 1, dimy = 1, dimz = 1, dimv = 1;
if (!dim[0])
cimg::warn("CImg<%s>::load_analyze() : File '%s', tells that image has zero dimensions.",
pixel_type(),filename?filename:"(FILE*)");
if (dim[0]>4)
cimg::warn("CImg<%s>::load_analyze() : File '%s', number of image dimension is %u, reading only the 4 first dimensions",
pixel_type(),filename?filename:"(FILE*)",dim[0]);
if (dim[0]>=1) dimx = dim[1];
if (dim[0]>=2) dimy = dim[2];
if (dim[0]>=3) dimz = dim[3];
if (dim[0]>=4) dimv = dim[4];
float scalefactor = *(float*)(header+112); if (scalefactor==0) scalefactor=1;
const unsigned short datatype = *(short*)(header+70);
if (voxsize) {
const float *vsize = (float*)(header+76);
voxsize[0] = vsize[1]; voxsize[1] = vsize[2]; voxsize[2] = vsize[3];
}
delete[] header;
// Read pixel data.
assign(dimx,dimy,dimz,dimv);
switch (datatype) {
case 2 : {
unsigned char *buffer = new unsigned char[dimx*dimy*dimz*dimv];
cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
delete[] buffer;
} break;
case 4 : {
short *buffer = new short[dimx*dimy*dimz*dimv];
cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
delete[] buffer;
} break;
case 8 : {
int *buffer = new int[dimx*dimy*dimz*dimv];
cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
delete[] buffer;
} break;
case 16 : {
float *buffer = new float[dimx*dimy*dimz*dimv];
cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
delete[] buffer;
} break;
case 64 : {
double *buffer = new double[dimx*dimy*dimz*dimv];
cimg::fread(buffer,dimx*dimy*dimz*dimv,nfile);
if (endian) cimg::invert_endianness(buffer,dimx*dimy*dimz*dimv);
cimg_foroff(*this,off) data[off] = (T)(buffer[off]*scalefactor);
delete[] buffer;
} break;
default :
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_analyze() : File '%s', cannot read images with 'datatype = %d'",
pixel_type(),filename?filename:"(FILE*)",datatype);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image (list) from a .cimg file.
CImg<T>& load_cimg(const char *const filename, const char axis='z', const char align='p') {
CImgList<T> list;
list.load_cimg(filename);
if (list.size==1) return list[0].transfer_to(*this);
return assign(list.get_append(axis,align));
}
static CImg<T> get_load_cimg(const char *const filename, const char axis='z', const char align='p') {
return CImg<T>().load_cimg(filename,axis,align);
}
//! Load an image (list) from a .cimg file.
CImg<T>& load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') {
CImgList<T> list;
list.load_cimg(file);
if (list.size==1) return list[0].transfer_to(*this);
return assign(list.get_append(axis,align));
}
static CImg<T> get_load_cimg(cimg_std::FILE *const file, const char axis='z', const char align='p') {
return CImg<T>().load_cimg(file,axis,align);
}
//! Load a sub-image (list) from a .cimg file.
CImg<T>& load_cimg(const char *const filename,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
const char axis='z', const char align='p') {
CImgList<T> list;
list.load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
if (list.size==1) return list[0].transfer_to(*this);
return assign(list.get_append(axis,align));
}
static CImg<T> get_load_cimg(const char *const filename,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
const char axis='z', const char align='p') {
return CImg<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
}
//! Load a sub-image (list) from a non-compressed .cimg file.
CImg<T>& load_cimg(cimg_std::FILE *const file,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
const char axis='z', const char align='p') {
CImgList<T> list;
list.load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
if (list.size==1) return list[0].transfer_to(*this);
return assign(list.get_append(axis,align));
}
static CImg<T> get_load_cimg(cimg_std::FILE *const file,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1,
const char axis='z', const char align='p') {
return CImg<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1,axis,align);
}
//! Load an image from an INRIMAGE-4 file.
CImg<T>& load_inr(const char *const filename, float *const voxsize=0) {
return _load_inr(0,filename,voxsize);
}
static CImg<T> get_load_inr(const char *const filename, float *const voxsize=0) {
return CImg<T>().load_inr(filename,voxsize);
}
//! Load an image from an INRIMAGE-4 file.
CImg<T>& load_inr(cimg_std::FILE *const file, float *const voxsize=0) {
return _load_inr(file,0,voxsize);
}
static CImg<T> get_load_inr(cimg_std::FILE *const file, float *voxsize=0) {
return CImg<T>().load_inr(file,voxsize);
}
// Load an image from an INRIMAGE-4 file (internal).
static void _load_inr_header(cimg_std::FILE *file, int out[8], float *const voxsize) {
char item[1024], tmp1[64], tmp2[64];
out[0] = cimg_std::fscanf(file,"%63s",item);
out[0] = out[1] = out[2] = out[3] = out[5] = 1; out[4] = out[6] = out[7] = -1;
if(cimg::strncasecmp(item,"#INRIMAGE-4#{",13)!=0)
throw CImgIOException("CImg<%s>::load_inr() : File does not appear to be a valid INR file.\n"
"(INRIMAGE-4 identifier not found)",
pixel_type());
while (cimg_std::fscanf(file," %63[^\n]%*c",item)!=EOF && cimg::strncmp(item,"##}",3)) {
cimg_std::sscanf(item," XDIM%*[^0-9]%d",out);
cimg_std::sscanf(item," YDIM%*[^0-9]%d",out+1);
cimg_std::sscanf(item," ZDIM%*[^0-9]%d",out+2);
cimg_std::sscanf(item," VDIM%*[^0-9]%d",out+3);
cimg_std::sscanf(item," PIXSIZE%*[^0-9]%d",out+6);
if (voxsize) {
cimg_std::sscanf(item," VX%*[^0-9.+-]%f",voxsize);
cimg_std::sscanf(item," VY%*[^0-9.+-]%f",voxsize+1);
cimg_std::sscanf(item," VZ%*[^0-9.+-]%f",voxsize+2);
}
if (cimg_std::sscanf(item," CPU%*[ =]%s",tmp1)) out[7]=cimg::strncasecmp(tmp1,"sun",3)?0:1;
switch (cimg_std::sscanf(item," TYPE%*[ =]%s %s",tmp1,tmp2)) {
case 0 : break;
case 2 : out[5] = cimg::strncasecmp(tmp1,"unsigned",8)?1:0; cimg_std::strcpy(tmp1,tmp2);
case 1 :
if (!cimg::strncasecmp(tmp1,"int",3) || !cimg::strncasecmp(tmp1,"fixed",5)) out[4] = 0;
if (!cimg::strncasecmp(tmp1,"float",5) || !cimg::strncasecmp(tmp1,"double",6)) out[4] = 1;
if (!cimg::strncasecmp(tmp1,"packed",6)) out[4] = 2;
if (out[4]>=0) break;
default :
throw CImgIOException("cimg::inr_header_read() : Invalid TYPE '%s'",tmp2);
}
}
if(out[0]<0 || out[1]<0 || out[2]<0 || out[3]<0)
throw CImgIOException("CImg<%s>::load_inr() : Bad dimensions in .inr file = ( %d , %d , %d , %d )",
pixel_type(),out[0],out[1],out[2],out[3]);
if(out[4]<0 || out[5]<0)
throw CImgIOException("CImg<%s>::load_inr() : TYPE is not fully defined",
pixel_type());
if(out[6]<0)
throw CImgIOException("CImg<%s>::load_inr() : PIXSIZE is not fully defined",
pixel_type());
if(out[7]<0)
throw CImgIOException("CImg<%s>::load_inr() : Big/Little Endian coding type is not defined",
pixel_type());
}
CImg<T>& _load_inr(cimg_std::FILE *const file, const char *const filename, float *const voxsize) {
#define _cimg_load_inr_case(Tf,sign,pixsize,Ts) \
if (!loaded && fopt[6]==pixsize && fopt[4]==Tf && fopt[5]==sign) { \
Ts *xval, *val = new Ts[fopt[0]*fopt[3]]; \
cimg_forYZ(*this,y,z) { \
cimg::fread(val,fopt[0]*fopt[3],nfile); \
if (fopt[7]!=endian) cimg::invert_endianness(val,fopt[0]*fopt[3]); \
xval = val; cimg_forX(*this,x) cimg_forV(*this,k) (*this)(x,y,z,k) = (T)*(xval++); \
} \
delete[] val; \
loaded = true; \
}
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_inr() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
int fopt[8], endian=cimg::endianness()?1:0;
bool loaded = false;
if (voxsize) voxsize[0]=voxsize[1]=voxsize[2]=1;
_load_inr_header(nfile,fopt,voxsize);
assign(fopt[0],fopt[1],fopt[2],fopt[3]);
_cimg_load_inr_case(0,0,8, unsigned char);
_cimg_load_inr_case(0,1,8, char);
_cimg_load_inr_case(0,0,16,unsigned short);
_cimg_load_inr_case(0,1,16,short);
_cimg_load_inr_case(0,0,32,unsigned int);
_cimg_load_inr_case(0,1,32,int);
_cimg_load_inr_case(1,0,32,float);
_cimg_load_inr_case(1,1,32,float);
_cimg_load_inr_case(1,0,64,double);
_cimg_load_inr_case(1,1,64,double);
if (!loaded) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_inr() : File '%s', cannot read images of the type specified in the file",
pixel_type(),filename?filename:"(FILE*)");
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a PANDORE file.
CImg<T>& load_pandore(const char *const filename) {
return _load_pandore(0,filename);
}
static CImg<T> get_load_pandore(const char *const filename) {
return CImg<T>().load_pandore(filename);
}
//! Load an image from a PANDORE file.
CImg<T>& load_pandore(cimg_std::FILE *const file) {
return _load_pandore(file,0);
}
static CImg<T> get_load_pandore(cimg_std::FILE *const file) {
return CImg<T>().load_pandore(file);
}
CImg<T>& _load_pandore(cimg_std::FILE *const file, const char *const filename) {
#define __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,ndim,stype) \
cimg::fread(dims,nbdim,nfile); \
if (endian) cimg::invert_endianness(dims,nbdim); \
assign(nwidth,nheight,ndepth,ndim); \
const unsigned int siz = size(); \
stype *buffer = new stype[siz]; \
cimg::fread(buffer,siz,nfile); \
if (endian) cimg::invert_endianness(buffer,siz); \
T *ptrd = data; \
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++); \
buffer-=siz; \
delete[] buffer
#define _cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1,stype2,stype3,ltype) { \
if (sizeof(stype1)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype1); } \
else if (sizeof(stype2)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype2); } \
else if (sizeof(stype3)==ltype) { __cimg_load_pandore_case(nbdim,nwidth,nheight,ndepth,dim,stype3); } \
else throw CImgIOException("CImg<%s>::load_pandore() : File '%s' cannot be read, datatype not supported on this architecture.", \
pixel_type(),filename?filename:"(FILE*)"); }
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_pandore() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
char header[32];
cimg::fread(header,12,nfile);
if (cimg::strncasecmp("PANDORE",header,7)) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_pandore() : File '%s' is not a valid PANDORE file, "
"(PANDORE identifier not found).",
pixel_type(),filename?filename:"(FILE*)");
}
unsigned int imageid, dims[8];
cimg::fread(&imageid,1,nfile);
const bool endian = (imageid>255);
if (endian) cimg::invert_endianness(imageid);
cimg::fread(header,20,nfile);
switch (imageid) {
case 2: _cimg_load_pandore_case(2,dims[1],1,1,1,uchar,uchar,uchar,1); break;
case 3: _cimg_load_pandore_case(2,dims[1],1,1,1,long,int,short,4); break;
case 4: _cimg_load_pandore_case(2,dims[1],1,1,1,double,float,float,4); break;
case 5: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,uchar,uchar,uchar,1); break;
case 6: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,long,int,short,4); break;
case 7: _cimg_load_pandore_case(3,dims[2],dims[1],1,1,double,float,float,4); break;
case 8: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,uchar,uchar,uchar,1); break;
case 9: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,long,int,short,4); break;
case 10: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],1,double,float,float,4); break;
case 11 : { // Region 1D
cimg::fread(dims,3,nfile);
if (endian) cimg::invert_endianness(dims,3);
assign(dims[1],1,1,1);
const unsigned siz = size();
if (dims[2]<256) {
unsigned char *buffer = new unsigned char[siz];
cimg::fread(buffer,siz,nfile);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
} else {
if (dims[2]<65536) {
unsigned short *buffer = new unsigned short[siz];
cimg::fread(buffer,siz,nfile);
if (endian) cimg::invert_endianness(buffer,siz);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
} else {
unsigned int *buffer = new unsigned int[siz];
cimg::fread(buffer,siz,nfile);
if (endian) cimg::invert_endianness(buffer,siz);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
}
}
}
break;
case 12 : { // Region 2D
cimg::fread(dims,4,nfile);
if (endian) cimg::invert_endianness(dims,4);
assign(dims[2],dims[1],1,1);
const unsigned int siz = size();
if (dims[3]<256) {
unsigned char *buffer = new unsigned char[siz];
cimg::fread(buffer,siz,nfile);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
} else {
if (dims[3]<65536) {
unsigned short *buffer = new unsigned short[siz];
cimg::fread(buffer,siz,nfile);
if (endian) cimg::invert_endianness(buffer,siz);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
} else {
unsigned long *buffer = new unsigned long[siz];
cimg::fread(buffer,siz,nfile);
if (endian) cimg::invert_endianness(buffer,siz);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
}
}
}
break;
case 13 : { // Region 3D
cimg::fread(dims,5,nfile);
if (endian) cimg::invert_endianness(dims,5);
assign(dims[3],dims[2],dims[1],1);
const unsigned int siz = size();
if (dims[4]<256) {
unsigned char *buffer = new unsigned char[siz];
cimg::fread(buffer,siz,nfile);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
} else {
if (dims[4]<65536) {
unsigned short *buffer = new unsigned short[siz];
cimg::fread(buffer,siz,nfile);
if (endian) cimg::invert_endianness(buffer,siz);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
} else {
unsigned int *buffer = new unsigned int[siz];
cimg::fread(buffer,siz,nfile);
if (endian) cimg::invert_endianness(buffer,siz);
T *ptrd = data;
cimg_foroff(*this,off) *(ptrd++) = (T)*(buffer++);
buffer-=siz;
delete[] buffer;
}
}
}
break;
case 16: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,uchar,uchar,uchar,1); break;
case 17: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,long,int,short,4); break;
case 18: _cimg_load_pandore_case(4,dims[2],dims[1],1,3,double,float,float,4); break;
case 19: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,uchar,uchar,uchar,1); break;
case 20: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,long,int,short,4); break;
case 21: _cimg_load_pandore_case(5,dims[3],dims[2],dims[1],3,double,float,float,4); break;
case 22: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],uchar,uchar,uchar,1); break;
case 23: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],long,int,short,4);
case 24: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],ulong,uint,ushort,4); break;
case 25: _cimg_load_pandore_case(2,dims[1],1,1,dims[0],double,float,float,4); break;
case 26: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],uchar,uchar,uchar,1); break;
case 27: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],long,int,short,4); break;
case 28: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],ulong,uint,ushort,4); break;
case 29: _cimg_load_pandore_case(3,dims[2],dims[1],1,dims[0],double,float,float,4); break;
case 30: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],uchar,uchar,uchar,1); break;
case 31: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],long,int,short,4); break;
case 32: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],ulong,uint,ushort,4); break;
case 33: _cimg_load_pandore_case(4,dims[3],dims[2],dims[1],dims[0],double,float,float,4); break;
case 34 : { // Points 1D
int ptbuf[4];
cimg::fread(ptbuf,1,nfile);
if (endian) cimg::invert_endianness(ptbuf,1);
assign(1); (*this)(0) = (T)ptbuf[0];
} break;
case 35 : { // Points 2D
int ptbuf[4];
cimg::fread(ptbuf,2,nfile);
if (endian) cimg::invert_endianness(ptbuf,2);
assign(2); (*this)(0) = (T)ptbuf[1]; (*this)(1) = (T)ptbuf[0];
} break;
case 36 : { // Points 3D
int ptbuf[4];
cimg::fread(ptbuf,3,nfile);
if (endian) cimg::invert_endianness(ptbuf,3);
assign(3); (*this)(0) = (T)ptbuf[2]; (*this)(1) = (T)ptbuf[1]; (*this)(2) = (T)ptbuf[0];
} break;
default :
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_pandore() : File '%s', cannot read images with ID_type = %u",
pixel_type(),filename?filename:"(FILE*)",imageid);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a PAR-REC (Philips) file.
CImg<T>& load_parrec(const char *const filename, const char axis='v', const char align='p') {
CImgList<T> list;
list.load_parrec(filename);
if (list.size==1) return list[0].transfer_to(*this);
return assign(list.get_append(axis,align));
}
static CImg<T> get_load_parrec(const char *const filename, const char axis='v', const char align='p') {
return CImg<T>().load_parrec(filename,axis,align);
}
//! Load an image from a .RAW file.
CImg<T>& load_raw(const char *const filename,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int sizez=1, const unsigned int sizev=1,
const bool multiplexed=false, const bool invert_endianness=false) {
return _load_raw(0,filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
}
static CImg<T> get_load_raw(const char *const filename,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int sizez=1, const unsigned int sizev=1,
const bool multiplexed=false, const bool invert_endianness=false) {
return CImg<T>().load_raw(filename,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
}
//! Load an image from a .RAW file.
CImg<T>& load_raw(cimg_std::FILE *const file,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int sizez=1, const unsigned int sizev=1,
const bool multiplexed=false, const bool invert_endianness=false) {
return _load_raw(file,0,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
}
static CImg<T> get_load_raw(cimg_std::FILE *const file,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int sizez=1, const unsigned int sizev=1,
const bool multiplexed=false, const bool invert_endianness=false) {
return CImg<T>().load_raw(file,sizex,sizey,sizez,sizev,multiplexed,invert_endianness);
}
CImg<T>& _load_raw(cimg_std::FILE *const file, const char *const filename,
const unsigned int sizex, const unsigned int sizey,
const unsigned int sizez, const unsigned int sizev,
const bool multiplexed, const bool invert_endianness) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_raw() : Cannot load (null) filename.",
pixel_type());
assign(sizex,sizey,sizez,sizev,0);
const unsigned int siz = size();
if (siz) {
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
if (!multiplexed) {
cimg::fread(data,siz,nfile);
if (invert_endianness) cimg::invert_endianness(data,siz);
}
else {
CImg<T> buf(1,1,1,sizev);
cimg_forXYZ(*this,x,y,z) {
cimg::fread(buf.data,sizev,nfile);
if (invert_endianness) cimg::invert_endianness(buf.data,sizev);
set_vector_at(buf,x,y,z); }
}
if (!file) cimg::fclose(nfile);
}
return *this;
}
//! Load a video sequence using FFMPEG av's libraries.
CImg<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
const char axis='z', const char align='p') {
return get_load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume,axis,align).transfer_to(*this);
}
static CImg<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false,
const char axis='z', const char align='p') {
return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format,resume).get_append(axis,align);
}
//! Load an image sequence from a YUV file.
CImg<T>& load_yuv(const char *const filename,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
return get_load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
}
static CImg<T> get_load_yuv(const char *const filename,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
}
//! Load an image sequence from a YUV file.
CImg<T>& load_yuv(cimg_std::FILE *const file,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
return get_load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb,axis,align).transfer_to(*this);
}
static CImg<T> get_load_yuv(cimg_std::FILE *const file,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true, const char axis='z', const char align='p') {
return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb).get_append(axis,align);
}
//! Load a 3D object from a .OFF file.
template<typename tf, typename tc>
CImg<T>& load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
return _load_off(0,filename,primitives,colors,invert_faces);
}
template<typename tf, typename tc>
static CImg<T> get_load_off(const char *const filename, CImgList<tf>& primitives, CImgList<tc>& colors,
const bool invert_faces=false) {
return CImg<T>().load_off(filename,primitives,colors,invert_faces);
}
//! Load a 3D object from a .OFF file.
template<typename tf, typename tc>
CImg<T>& load_off(cimg_std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces=false) {
return _load_off(file,0,primitives,colors,invert_faces);
}
template<typename tf, typename tc>
static CImg<T> get_load_off(cimg_std::FILE *const file, CImgList<tf>& primitives, CImgList<tc>& colors,
const bool invert_faces=false) {
return CImg<T>().load_off(file,primitives,colors,invert_faces);
}
template<typename tf, typename tc>
CImg<T>& _load_off(cimg_std::FILE *const file, const char *const filename,
CImgList<tf>& primitives, CImgList<tc>& colors, const bool invert_faces) {
if (!filename && !file)
throw CImgArgumentException("CImg<%s>::load_off() : Cannot load (null) filename.",
pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"r");
unsigned int nb_points = 0, nb_primitives = 0, nb_read = 0;
char line[256] = { 0 };
int err;
// Skip comments, and read magic string OFF
do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
if (cimg::strncasecmp(line,"OFF",3) && cimg::strncasecmp(line,"COFF",4)) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_off() : File '%s', keyword 'OFF' not found.",
pixel_type(),filename?filename:"(FILE*)");
}
do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
if ((err = cimg_std::sscanf(line,"%u%u%*[^\n] ",&nb_points,&nb_primitives))!=2) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_off() : File '%s', invalid vertices/primitives numbers.",
pixel_type(),filename?filename:"(FILE*)");
}
// Read points data
assign(nb_points,3);
float X = 0, Y = 0, Z = 0;
cimg_forX(*this,l) {
do { err = cimg_std::fscanf(nfile,"%255[^\n] ",line); } while (!err || (err==1 && line[0]=='#'));
if ((err = cimg_std::sscanf(line,"%f%f%f%*[^\n] ",&X,&Y,&Z))!=3) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::load_off() : File '%s', cannot read point %u/%u.\n",
pixel_type(),filename?filename:"(FILE*)",l+1,nb_points);
}
(*this)(l,0) = (T)X; (*this)(l,1) = (T)Y; (*this)(l,2) = (T)Z;
}
// Read primitive data
primitives.assign();
colors.assign();
bool stopflag = false;
while (!stopflag) {
float c0 = 0.7f, c1 = 0.7f, c2 = 0.7f;
unsigned int prim = 0, i0 = 0, i1 = 0, i2 = 0, i3 = 0, i4 = 0, i5 = 0, i6 = 0, i7 = 0;
line[0]='\0';
if ((err = cimg_std::fscanf(nfile,"%u",&prim))!=1) stopflag=true;
else {
++nb_read;
switch (prim) {
case 1 : {
if ((err = cimg_std::fscanf(nfile,"%u%255[^\n] ",&i0,line))<2) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
primitives.insert(CImg<tf>::vector(i0));
colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
}
} break;
case 2 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%255[^\n] ",&i0,&i1,line))<2) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
primitives.insert(CImg<tf>::vector(i0,i1));
colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
}
} break;
case 3 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%u%255[^\n] ",&i0,&i1,&i2,line))<3) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2));
else primitives.insert(CImg<tf>::vector(i0,i2,i1));
colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255)));
}
} break;
case 4 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,line))<4) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
if (invert_faces) primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
else primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
colors.insert(CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
}
} break;
case 5 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,line))<5) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
if (invert_faces) {
primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
primitives.insert(CImg<tf>::vector(i0,i3,i4));
}
else {
primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
primitives.insert(CImg<tf>::vector(i0,i4,i3));
}
colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
++nb_primitives;
}
} break;
case 6 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,line))<6) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
if (invert_faces) {
primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
}
else {
primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
}
colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
++nb_primitives;
}
} break;
case 7 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,line))<7) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
if (invert_faces) {
primitives.insert(CImg<tf>::vector(i0,i1,i3,i4));
primitives.insert(CImg<tf>::vector(i0,i4,i5,i6));
primitives.insert(CImg<tf>::vector(i1,i2,i3));
}
else {
primitives.insert(CImg<tf>::vector(i0,i4,i3,i1));
primitives.insert(CImg<tf>::vector(i0,i6,i5,i4));
primitives.insert(CImg<tf>::vector(i3,i2,i1));
}
colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
++(++nb_primitives);
}
} break;
case 8 : {
if ((err = cimg_std::fscanf(nfile,"%u%u%u%u%u%u%u%u%255[^\n] ",&i0,&i1,&i2,&i3,&i4,&i5,&i6,&i7,line))<7) {
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u.",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
} else {
err = cimg_std::sscanf(line,"%f%f%f",&c0,&c1,&c2);
if (invert_faces) {
primitives.insert(CImg<tf>::vector(i0,i1,i2,i3));
primitives.insert(CImg<tf>::vector(i0,i3,i4,i5));
primitives.insert(CImg<tf>::vector(i0,i5,i6,i7));
}
else {
primitives.insert(CImg<tf>::vector(i0,i3,i2,i1));
primitives.insert(CImg<tf>::vector(i0,i5,i4,i3));
primitives.insert(CImg<tf>::vector(i0,i7,i6,i5));
}
colors.insert(2,CImg<tc>::vector((tc)(c0*255),(tc)(c1*255),(tc)(c2*255),(tc)(c2*255)));
++(++nb_primitives);
}
} break;
default :
cimg::warn("CImg<%s>::load_off() : File '%s', invalid primitive %u/%u (%u vertices).",
pixel_type(),filename?filename:"(FILE*)",nb_read,nb_primitives,prim);
err = cimg_std::fscanf(nfile,"%*[^\n] ");
}
}
}
if (!file) cimg::fclose(nfile);
if (primitives.size!=nb_primitives)
cimg::warn("CImg<%s>::load_off() : File '%s', read only %u primitives instead of %u as claimed in the header.",
pixel_type(),filename?filename:"(FILE*)",primitives.size,nb_primitives);
return *this;
}
//! Load a video sequence using FFMPEG's external tool 'ffmpeg'.
CImg<T>& load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
return get_load_ffmpeg_external(filename,axis,align).transfer_to(*this);
}
static CImg<T> get_load_ffmpeg_external(const char *const filename, const char axis='z', const char align='p') {
return CImgList<T>().load_ffmpeg_external(filename).get_append(axis,align);
}
//! Load an image using GraphicsMagick's external tool 'gm'.
CImg<T>& load_graphicsmagick_external(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_graphicsmagick_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512];
cimg_std::FILE *file = 0;
#if cimg_OS==1
cimg_std::sprintf(command,"%s convert \"%s\" ppm:-",cimg::graphicsmagick_path(),filename);
file = popen(command,"r");
if (file) { load_pnm(file); pclose(file); return *this; }
#endif
do {
cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(command,"%s convert \"%s\" %s",cimg::graphicsmagick_path(),filename,filetmp);
cimg::system(command,cimg::graphicsmagick_path());
if (!(file = cimg_std::fopen(filetmp,"rb"))) {
cimg::fclose(cimg::fopen(filename,"r"));
throw CImgIOException("CImg<%s>::load_graphicsmagick_external() : Failed to open image '%s'.\n\n"
"Path of 'GraphicsMagick's gm' : \"%s\"\n"
"Path of temporary filename : \"%s\"",
pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
} else cimg::fclose(file);
load_pnm(filetmp);
cimg_std::remove(filetmp);
return *this;
}
static CImg<T> get_load_graphicsmagick_external(const char *const filename) {
return CImg<T>().load_graphicsmagick_external(filename);
}
//! Load a gzipped image file, using external tool 'gunzip'.
CImg<T>& load_gzip_external(const char *const filename) {
if (!filename)
throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512], body[512];
const char
*ext = cimg::split_filename(filename,body),
*ext2 = cimg::split_filename(body,0);
cimg_std::FILE *file = 0;
do {
if (!cimg::strcasecmp(ext,"gz")) {
if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext2);
else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
} else {
if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext);
else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
}
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
cimg::system(command);
if (!(file = cimg_std::fopen(filetmp,"rb"))) {
cimg::fclose(cimg::fopen(filename,"r"));
throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.",
pixel_type(),filename);
} else cimg::fclose(file);
load(filetmp);
cimg_std::remove(filetmp);
return *this;
}
static CImg<T> get_load_gzip_external(const char *const filename) {
return CImg<T>().load_gzip_external(filename);
}
//! Load an image using ImageMagick's external tool 'convert'.
CImg<T>& load_imagemagick_external(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_imagemagick_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512];
cimg_std::FILE *file = 0;
#if cimg_OS==1
cimg_std::sprintf(command,"%s \"%s\" ppm:-",cimg::imagemagick_path(),filename);
file = popen(command,"r");
if (file) { load_pnm(file); pclose(file); return *this; }
#endif
do {
cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(command,"%s \"%s\" %s",cimg::imagemagick_path(),filename,filetmp);
cimg::system(command,cimg::imagemagick_path());
if (!(file = cimg_std::fopen(filetmp,"rb"))) {
cimg::fclose(cimg::fopen(filename,"r"));
throw CImgIOException("CImg<%s>::load_imagemagick_external() : Failed to open image '%s'.\n\n"
"Path of 'ImageMagick's convert' : \"%s\"\n"
"Path of temporary filename : \"%s\"",
pixel_type(),filename,cimg::imagemagick_path(),filetmp);
} else cimg::fclose(file);
load_pnm(filetmp);
cimg_std::remove(filetmp);
return *this;
}
static CImg<T> get_load_imagemagick_external(const char *const filename) {
return CImg<T>().load_imagemagick_external(filename);
}
//! Load a DICOM image file, using XMedcon's external tool 'medcon'.
CImg<T>& load_medcon_external(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_medcon_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512], body[512];
cimg::fclose(cimg::fopen(filename,"r"));
cimg_std::FILE *file = 0;
do {
cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(command,"%s -w -c anlz -o %s -f %s",cimg::medcon_path(),filetmp,filename);
cimg::system(command);
cimg::split_filename(filetmp,body);
cimg_std::sprintf(command,"m000-%s.hdr",body);
file = cimg_std::fopen(command,"rb");
if (!file) {
throw CImgIOException("CImg<%s>::load_medcon_external() : Failed to open image '%s'.\n\n"
"Path of 'medcon' : \"%s\"\n"
"Path of temporary filename : \"%s\"",
pixel_type(),filename,cimg::medcon_path(),filetmp);
} else cimg::fclose(file);
load_analyze(command);
cimg_std::remove(command);
cimg_std::sprintf(command,"m000-%s.img",body);
cimg_std::remove(command);
return *this;
}
static CImg<T> get_load_medcon_external(const char *const filename) {
return CImg<T>().load_medcon_external(filename);
}
//! Load a RAW Color Camera image file, using external tool 'dcraw'.
CImg<T>& load_dcraw_external(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_dcraw_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512];
cimg_std::FILE *file = 0;
#if cimg_OS==1
cimg_std::sprintf(command,"%s -4 -c \"%s\"",cimg::dcraw_path(),filename);
file = popen(command,"r");
if (file) { load_pnm(file); pclose(file); return *this; }
#endif
do {
cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(command,"%s -4 -c \"%s\" > %s",cimg::dcraw_path(),filename,filetmp);
cimg::system(command,cimg::dcraw_path());
if (!(file = cimg_std::fopen(filetmp,"rb"))) {
cimg::fclose(cimg::fopen(filename,"r"));
throw CImgIOException("CImg<%s>::load_dcraw_external() : Failed to open image '%s'.\n\n"
"Path of 'dcraw' : \"%s\"\n"
"Path of temporary filename : \"%s\"",
pixel_type(),filename,cimg::dcraw_path(),filetmp);
} else cimg::fclose(file);
load_pnm(filetmp);
cimg_std::remove(filetmp);
return *this;
}
static CImg<T> get_load_dcraw_external(const char *const filename) {
return CImg<T>().load_dcraw_external(filename);
}
//! Load an image using ImageMagick's or GraphicsMagick's executables.
CImg<T>& load_other(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImg<%s>::load_other() : Cannot load (null) filename.",
pixel_type());
const unsigned int odebug = cimg::exception_mode();
cimg::exception_mode() = 0;
try { load_magick(filename); }
catch (CImgException&) {
try { load_imagemagick_external(filename); }
catch (CImgException&) {
try { load_graphicsmagick_external(filename); }
catch (CImgException&) {
assign();
}
}
}
cimg::exception_mode() = odebug;
if (is_empty())
throw CImgIOException("CImg<%s>::load_other() : File '%s' cannot be opened.",
pixel_type(),filename);
return *this;
}
static CImg<T> get_load_other(const char *const filename) {
return CImg<T>().load_other(filename);
}
//@}
//---------------------------
//
//! \name Image File Saving
//@{
//---------------------------
//! Save the image as a file.
/**
The used file format is defined by the file extension in the filename \p filename.
Parameter \p number can be used to add a 6-digit number to the filename before saving.
**/
const CImg<T>& save(const char *const filename, const int number=-1) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save() : Instance image (%u,%u,%u,%u,%p) cannot be saved as a (null) filename.",
pixel_type(),width,height,depth,dim,data);
const char *ext = cimg::split_filename(filename);
char nfilename[1024];
const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
#ifdef cimg_save_plugin
cimg_save_plugin(fn);
#endif
#ifdef cimg_save_plugin1
cimg_save_plugin1(fn);
#endif
#ifdef cimg_save_plugin2
cimg_save_plugin2(fn);
#endif
#ifdef cimg_save_plugin3
cimg_save_plugin3(fn);
#endif
#ifdef cimg_save_plugin4
cimg_save_plugin4(fn);
#endif
#ifdef cimg_save_plugin5
cimg_save_plugin5(fn);
#endif
#ifdef cimg_save_plugin6
cimg_save_plugin6(fn);
#endif
#ifdef cimg_save_plugin7
cimg_save_plugin7(fn);
#endif
#ifdef cimg_save_plugin8
cimg_save_plugin8(fn);
#endif
// ASCII formats
if (!cimg::strcasecmp(ext,"asc")) return save_ascii(fn);
if (!cimg::strcasecmp(ext,"dlm") ||
!cimg::strcasecmp(ext,"txt")) return save_dlm(fn);
if (!cimg::strcasecmp(ext,"cpp") ||
!cimg::strcasecmp(ext,"hpp") ||
!cimg::strcasecmp(ext,"h") ||
!cimg::strcasecmp(ext,"c")) return save_cpp(fn);
// 2D binary formats
if (!cimg::strcasecmp(ext,"bmp")) return save_bmp(fn);
if (!cimg::strcasecmp(ext,"jpg") ||
!cimg::strcasecmp(ext,"jpeg") ||
!cimg::strcasecmp(ext,"jpe") ||
!cimg::strcasecmp(ext,"jfif") ||
!cimg::strcasecmp(ext,"jif")) return save_jpeg(fn);
if (!cimg::strcasecmp(ext,"rgb")) return save_rgb(fn);
if (!cimg::strcasecmp(ext,"rgba")) return save_rgba(fn);
if (!cimg::strcasecmp(ext,"png")) return save_png(fn);
if (!cimg::strcasecmp(ext,"pgm") ||
!cimg::strcasecmp(ext,"ppm") ||
!cimg::strcasecmp(ext,"pnm")) return save_pnm(fn);
if (!cimg::strcasecmp(ext,"tif") ||
!cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
// 3D binary formats
if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
if (!cimg::strcasecmp(ext,"cimg") || ext[0]=='\0') return save_cimg(fn,false);
if (!cimg::strcasecmp(ext,"dcm")) return save_medcon_external(fn);
if (!cimg::strcasecmp(ext,"hdr") ||
!cimg::strcasecmp(ext,"nii")) return save_analyze(fn);
if (!cimg::strcasecmp(ext,"inr")) return save_inr(fn);
if (!cimg::strcasecmp(ext,"pan")) return save_pandore(fn);
if (!cimg::strcasecmp(ext,"raw")) return save_raw(fn);
// Archive files
if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
// Image sequences
if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
if (!cimg::strcasecmp(ext,"avi") ||
!cimg::strcasecmp(ext,"mov") ||
!cimg::strcasecmp(ext,"asf") ||
!cimg::strcasecmp(ext,"divx") ||
!cimg::strcasecmp(ext,"flv") ||
!cimg::strcasecmp(ext,"mpg") ||
!cimg::strcasecmp(ext,"m1v") ||
!cimg::strcasecmp(ext,"m2v") ||
!cimg::strcasecmp(ext,"m4v") ||
!cimg::strcasecmp(ext,"mjp") ||
!cimg::strcasecmp(ext,"mkv") ||
!cimg::strcasecmp(ext,"mpe") ||
!cimg::strcasecmp(ext,"movie") ||
!cimg::strcasecmp(ext,"ogm") ||
!cimg::strcasecmp(ext,"qt") ||
!cimg::strcasecmp(ext,"rm") ||
!cimg::strcasecmp(ext,"vob") ||
!cimg::strcasecmp(ext,"wmv") ||
!cimg::strcasecmp(ext,"xvid") ||
!cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
return save_other(fn);
}
// Save the image as an ASCII file (ASCII Raw + simple header) (internal).
const CImg<T>& _save_ascii(cimg_std::FILE *const file, const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_ascii() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_ascii() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
cimg_std::fprintf(nfile,"%u %u %u %u\n",width,height,depth,dim);
const T* ptrs = data;
cimg_forYZV(*this,y,z,v) {
cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g ",(double)*(ptrs++));
cimg_std::fputc('\n',nfile);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as an ASCII file (ASCII Raw + simple header).
const CImg<T>& save_ascii(const char *const filename) const {
return _save_ascii(0,filename);
}
//! Save the image as an ASCII file (ASCII Raw + simple header).
const CImg<T>& save_ascii(cimg_std::FILE *const file) const {
return _save_ascii(file,0);
}
// Save the image as a C or CPP source file (internal).
const CImg<T>& _save_cpp(cimg_std::FILE *const file, const char *const filename) const {
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_cpp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_cpp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
char varname[1024] = { 0 };
if (filename) cimg_std::sscanf(cimg::basename(filename),"%1023[a-zA-Z0-9_]",varname);
if (varname[0]=='\0') cimg_std::sprintf(varname,"unnamed");
cimg_std::fprintf(nfile,
"/* Define image '%s' of size %ux%ux%ux%u and type '%s' */\n"
"%s data_%s[] = { \n ",
varname,width,height,depth,dim,pixel_type(),pixel_type(),varname);
for (unsigned long off = 0, siz = size()-1; off<=siz; ++off) {
cimg_std::fprintf(nfile,cimg::type<T>::format(),cimg::type<T>::format((*this)[off]));
if (off==siz) cimg_std::fprintf(nfile," };\n");
else if (!((off+1)%16)) cimg_std::fprintf(nfile,",\n ");
else cimg_std::fprintf(nfile,", ");
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as a CPP source file.
const CImg<T>& save_cpp(const char *const filename) const {
return _save_cpp(0,filename);
}
//! Save the image as a CPP source file.
const CImg<T>& save_cpp(cimg_std::FILE *const file) const {
return _save_cpp(file,0);
}
// Save the image as a DLM file (internal).
const CImg<T>& _save_dlm(cimg_std::FILE *const file, const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_dlm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
if (depth>1)
cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Pixel values along Z will be unrolled.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (dim>1)
cimg::warn("CImg<%s>::save_dlm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. "
"Pixel values along V will be unrolled.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
const T* ptrs = data;
cimg_forYZV(*this,y,z,v) {
cimg_forX(*this,x) cimg_std::fprintf(nfile,"%g%s",(double)*(ptrs++),(x==dimx()-1)?"":",");
cimg_std::fputc('\n',nfile);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as a DLM file.
const CImg<T>& save_dlm(const char *const filename) const {
return _save_dlm(0,filename);
}
//! Save the image as a DLM file.
const CImg<T>& save_dlm(cimg_std::FILE *const file) const {
return _save_dlm(file,0);
}
// Save the image as a BMP file (internal).
const CImg<T>& _save_bmp(cimg_std::FILE *const file, const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_bmp() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
if (depth>1)
cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (dim>3)
cimg::warn("CImg<%s>::save_bmp() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
unsigned char header[54] = { 0 }, align_buf[4] = { 0 };
const unsigned int
align = (4 - (3*width)%4)%4,
buf_size = (3*width+align)*dimy(),
file_size = 54 + buf_size;
header[0] = 'B'; header[1] = 'M';
header[0x02] = file_size&0xFF;
header[0x03] = (file_size>>8)&0xFF;
header[0x04] = (file_size>>16)&0xFF;
header[0x05] = (file_size>>24)&0xFF;
header[0x0A] = 0x36;
header[0x0E] = 0x28;
header[0x12] = width&0xFF;
header[0x13] = (width>>8)&0xFF;
header[0x14] = (width>>16)&0xFF;
header[0x15] = (width>>24)&0xFF;
header[0x16] = height&0xFF;
header[0x17] = (height>>8)&0xFF;
header[0x18] = (height>>16)&0xFF;
header[0x19] = (height>>24)&0xFF;
header[0x1A] = 1;
header[0x1B] = 0;
header[0x1C] = 24;
header[0x1D] = 0;
header[0x22] = buf_size&0xFF;
header[0x23] = (buf_size>>8)&0xFF;
header[0x24] = (buf_size>>16)&0xFF;
header[0x25] = (buf_size>>24)&0xFF;
header[0x27] = 0x1;
header[0x2B] = 0x1;
cimg::fwrite(header,54,nfile);
const T
*pR = ptr(0,height-1,0,0),
*pG = (dim>=2)?ptr(0,height-1,0,1):0,
*pB = (dim>=3)?ptr(0,height-1,0,2):0;
switch (dim) {
case 1 : {
cimg_forY(*this,y) { cimg_forX(*this,x) {
const unsigned char val = (unsigned char)*(pR++);
cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile); cimg_std::fputc(val,nfile);
}
cimg::fwrite(align_buf,align,nfile);
pR-=2*width;
}} break;
case 2 : {
cimg_forY(*this,y) { cimg_forX(*this,x) {
cimg_std::fputc(0,nfile);
cimg_std::fputc((unsigned char)(*(pG++)),nfile);
cimg_std::fputc((unsigned char)(*(pR++)),nfile);
}
cimg::fwrite(align_buf,align,nfile);
pR-=2*width; pG-=2*width;
}} break;
default : {
cimg_forY(*this,y) { cimg_forX(*this,x) {
cimg_std::fputc((unsigned char)(*(pB++)),nfile);
cimg_std::fputc((unsigned char)(*(pG++)),nfile);
cimg_std::fputc((unsigned char)(*(pR++)),nfile);
}
cimg::fwrite(align_buf,align,nfile);
pR-=2*width; pG-=2*width; pB-=2*width;
}
}
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as a BMP file.
const CImg<T>& save_bmp(const char *const filename) const {
return _save_bmp(0,filename);
}
//! Save the image as a BMP file.
const CImg<T>& save_bmp(cimg_std::FILE *const file) const {
return _save_bmp(file,0);
}
// Save a file in JPEG format (internal).
const CImg<T>& _save_jpeg(cimg_std::FILE *const file, const char *const filename, const unsigned int quality) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_jpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_jpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
if (depth>1)
cimg::warn("CImg<%s>::save_jpeg() : File '%s, instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
#ifndef cimg_use_jpeg
if (!file) return save_other(filename,quality);
else throw CImgIOException("CImg<%s>::save_jpeg() : Cannot save a JPEG image in a *FILE output. Use libjpeg instead.",
pixel_type());
#else
// Fill pixel buffer
unsigned char *buf;
unsigned int dimbuf = 0;
J_COLOR_SPACE colortype = JCS_RGB;
switch (dim) {
case 1 : { // Greyscale images
unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=1)];
colortype = JCS_GRAYSCALE;
const T *ptr_g = data;
cimg_forXY(*this,x,y) *(buf2++) = (unsigned char)*(ptr_g++);
} break;
case 2 : { // RG images
unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1);
colortype = JCS_RGB;
cimg_forXY(*this,x,y) {
*(buf2++) = (unsigned char)*(ptr_r++);
*(buf2++) = (unsigned char)*(ptr_g++);
*(buf2++) = 0;
}
} break;
case 3 : { // RGB images
unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=3)];
const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2);
colortype = JCS_RGB;
cimg_forXY(*this,x,y) {
*(buf2++) = (unsigned char)*(ptr_r++);
*(buf2++) = (unsigned char)*(ptr_g++);
*(buf2++) = (unsigned char)*(ptr_b++);
}
} break;
default : { // CMYK images
unsigned char *buf2 = buf = new unsigned char[width*height*(dimbuf=4)];
const T *ptr_r = ptr(0,0,0,0), *ptr_g = ptr(0,0,0,1), *ptr_b = ptr(0,0,0,2), *ptr_a = ptr(0,0,0,3);
colortype = JCS_CMYK;
cimg_forXY(*this,x,y) {
*(buf2++) = (unsigned char)*(ptr_r++);
*(buf2++) = (unsigned char)*(ptr_g++);
*(buf2++) = (unsigned char)*(ptr_b++);
*(buf2++) = (unsigned char)*(ptr_a++);
}
}
}
// Call libjpeg functions
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
jpeg_stdio_dest(&cinfo,nfile);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = dimbuf;
cinfo.in_color_space = colortype;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo,quality<100?quality:100,TRUE);
jpeg_start_compress(&cinfo,TRUE);
const unsigned int row_stride = width*dimbuf;
JSAMPROW row_pointer[1];
while (cinfo.next_scanline < cinfo.image_height) {
row_pointer[0] = &buf[cinfo.next_scanline*row_stride];
jpeg_write_scanlines(&cinfo,row_pointer,1);
}
jpeg_finish_compress(&cinfo);
delete[] buf;
if (!file) cimg::fclose(nfile);
jpeg_destroy_compress(&cinfo);
return *this;
#endif
}
//! Save a file in JPEG format.
const CImg<T>& save_jpeg(const char *const filename, const unsigned int quality=100) const {
return _save_jpeg(0,filename,quality);
}
//! Save a file in JPEG format.
const CImg<T>& save_jpeg(cimg_std::FILE *const file, const unsigned int quality=100) const {
return _save_jpeg(file,0,quality);
}
//! Save the image using built-in ImageMagick++ library.
const CImg<T>& save_magick(const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_magick() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_magick() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_magick
Magick::Image image(Magick::Geometry(width,height),"black");
image.type(Magick::TrueColorType);
const T
*rdata = ptr(0,0,0,0),
*gdata = dim>1?ptr(0,0,0,1):0,
*bdata = dim>2?ptr(0,0,0,2):0;
Magick::PixelPacket *pixels = image.getPixels(0,0,width,height);
switch (dim) {
case 1 : // Scalar images
for (unsigned int off = width*height; off; --off) {
pixels->red = pixels->green = pixels->blue = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
++pixels;
}
break;
case 2 : // RG images
for (unsigned int off = width*height; off; --off) {
pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
pixels->blue = 0;
++pixels;
}
break;
default : // RGB images
for (unsigned int off = width*height; off; --off) {
pixels->red = Magick::Color::scaleDoubleToQuantum(*(rdata++)/255.0);
pixels->green = Magick::Color::scaleDoubleToQuantum(*(gdata++)/255.0);
pixels->blue = Magick::Color::scaleDoubleToQuantum(*(bdata++)/255.0);
++pixels;
}
}
image.syncPixels();
image.write(filename);
#else
throw CImgIOException("CImg<%s>::save_magick() : File '%s', Magick++ library has not been linked.",
pixel_type(),filename);
#endif
return *this;
}
// Save an image to a PNG file (internal).
// Most of this function has been written by Eric Fausett
const CImg<T>& _save_png(cimg_std::FILE *const file, const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_png() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
if (depth>1)
cimg::warn("CImg<%s>::save_png() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
#ifndef cimg_use_png
if (!file) return save_other(filename);
else throw CImgIOException("CImg<%s>::save_png() : Cannot save a PNG image in a *FILE output. You must use 'libpng' to do this instead.",
pixel_type());
#else
const char *volatile nfilename = filename; // two 'volatile' here to remove a g++ warning due to 'setjmp'.
cimg_std::FILE *volatile nfile = file?file:cimg::fopen(nfilename,"wb");
// Setup PNG structures for write
png_voidp user_error_ptr = 0;
png_error_ptr user_error_fn = 0, user_warning_fn = 0;
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,user_error_ptr, user_error_fn, user_warning_fn);
if(!png_ptr){
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'png_ptr' data structure.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr,(png_infopp)0);
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::save_png() : File '%s', error when initializing 'info_ptr' data structure.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
if (setjmp(png_jmpbuf(png_ptr))) {
png_destroy_write_struct(&png_ptr, &info_ptr);
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::save_png() : File '%s', unknown fatal error.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
png_init_io(png_ptr, nfile);
png_uint_32 width = dimx(), height = dimy();
float vmin, vmax = (float)maxmin(vmin);
const int bit_depth = (vmin<0 || vmax>=256)?16:8;
int color_type;
switch (dimv()) {
case 1 : color_type = PNG_COLOR_TYPE_GRAY; break;
case 2 : color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
case 3 : color_type = PNG_COLOR_TYPE_RGB; break;
default : color_type = PNG_COLOR_TYPE_RGB_ALPHA;
}
const int interlace_type = PNG_INTERLACE_NONE;
const int compression_type = PNG_COMPRESSION_TYPE_DEFAULT;
const int filter_method = PNG_FILTER_TYPE_DEFAULT;
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, color_type, interlace_type,compression_type, filter_method);
png_write_info(png_ptr, info_ptr);
const int byte_depth = bit_depth>>3;
const int numChan = dimv()>4?4:dimv();
const int pixel_bit_depth_flag = numChan * (bit_depth-1);
// Allocate Memory for Image Save and Fill pixel data
png_bytep *imgData = new png_byte*[height];
for (unsigned int row = 0; row<height; ++row) imgData[row] = new png_byte[byte_depth*numChan*width];
const T *pC0 = ptr(0,0,0,0);
switch (pixel_bit_depth_flag) {
case 7 : { // Gray 8-bit
cimg_forY(*this,y) {
unsigned char *ptrd = imgData[y];
cimg_forX(*this,x) *(ptrd++) = (unsigned char)*(pC0++);
}
} break;
case 14 : { // Gray w/ Alpha 8-bit
const T *pC1 = ptr(0,0,0,1);
cimg_forY(*this,y) {
unsigned char *ptrd = imgData[y];
cimg_forX(*this,x) {
*(ptrd++) = (unsigned char)*(pC0++);
*(ptrd++) = (unsigned char)*(pC1++);
}
}
} break;
case 21 : { // RGB 8-bit
const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2);
cimg_forY(*this,y) {
unsigned char *ptrd = imgData[y];
cimg_forX(*this,x) {
*(ptrd++) = (unsigned char)*(pC0++);
*(ptrd++) = (unsigned char)*(pC1++);
*(ptrd++) = (unsigned char)*(pC2++);
}
}
} break;
case 28 : { // RGB x/ Alpha 8-bit
const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2), *pC3 = ptr(0,0,0,3);
cimg_forY(*this,y){
unsigned char *ptrd = imgData[y];
cimg_forX(*this,x){
*(ptrd++) = (unsigned char)*(pC0++);
*(ptrd++) = (unsigned char)*(pC1++);
*(ptrd++) = (unsigned char)*(pC2++);
*(ptrd++) = (unsigned char)*(pC3++);
}
}
} break;
case 15 : { // Gray 16-bit
cimg_forY(*this,y){
unsigned short *ptrd = (unsigned short*)(imgData[y]);
cimg_forX(*this,x) *(ptrd++) = (unsigned short)*(pC0++);
if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],width);
}
} break;
case 30 : { // Gray w/ Alpha 16-bit
const T *pC1 = ptr(0,0,0,1);
cimg_forY(*this,y){
unsigned short *ptrd = (unsigned short*)(imgData[y]);
cimg_forX(*this,x) {
*(ptrd++) = (unsigned short)*(pC0++);
*(ptrd++) = (unsigned short)*(pC1++);
}
if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],2*width);
}
} break;
case 45 : { // RGB 16-bit
const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2);
cimg_forY(*this,y) {
unsigned short *ptrd = (unsigned short*)(imgData[y]);
cimg_forX(*this,x) {
*(ptrd++) = (unsigned short)*(pC0++);
*(ptrd++) = (unsigned short)*(pC1++);
*(ptrd++) = (unsigned short)*(pC2++);
}
if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],3*width);
}
} break;
case 60 : { // RGB w/ Alpha 16-bit
const T *pC1 = ptr(0,0,0,1), *pC2 = ptr(0,0,0,2), *pC3 = ptr(0,0,0,3);
cimg_forY(*this,y) {
unsigned short *ptrd = (unsigned short*)(imgData[y]);
cimg_forX(*this,x) {
*(ptrd++) = (unsigned short)*(pC0++);
*(ptrd++) = (unsigned short)*(pC1++);
*(ptrd++) = (unsigned short)*(pC2++);
*(ptrd++) = (unsigned short)*(pC3++);
}
if (!cimg::endianness()) cimg::invert_endianness((unsigned short*)imgData[y],4*width);
}
} break;
default :
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImg<%s>::save_png() : File '%s', unknown fatal error.",
pixel_type(),nfilename?nfilename:"(FILE*)");
}
png_write_image(png_ptr, imgData);
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
// Deallocate Image Write Memory
cimg_forY(*this,n) delete[] imgData[n];
delete[] imgData;
if (!file) cimg::fclose(nfile);
return *this;
#endif
}
//! Save a file in PNG format
const CImg<T>& save_png(const char *const filename) const {
return _save_png(0,filename);
}
//! Save a file in PNG format
const CImg<T>& save_png(cimg_std::FILE *const file) const {
return _save_png(file,0);
}
// Save the image as a PNM file (internal function).
const CImg<T>& _save_pnm(cimg_std::FILE *const file, const char *const filename) const {
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_pnm() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
double stmin, stmax = (double)maxmin(stmin);
if (depth>1)
cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is volumetric. Only the first slice will be saved.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (dim>3)
cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) is multispectral. Only the three first channels will be saved.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (stmin<0 || stmax>65535)
cimg::warn("CImg<%s>::save_pnm() : File '%s', instance image (%u,%u,%u,%u,%p) has pixel values in [%g,%g]. Probable type overflow.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data,stmin,stmax);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
const T
*ptrR = ptr(0,0,0,0),
*ptrG = (dim>=2)?ptr(0,0,0,1):0,
*ptrB = (dim>=3)?ptr(0,0,0,2):0;
const unsigned int buf_size = width*height*(dim==1?1:3);
cimg_std::fprintf(nfile,"P%c\n# CREATOR: CImg Library (original size = %ux%ux%ux%u)\n%u %u\n%u\n",
(dim==1?'5':'6'),width,height,depth,dim,width,height,stmax<256?255:(stmax<4096?4095:65535));
switch (dim) {
case 1 : { // Scalar image
if (stmax<256) { // Binary PGM 8 bits
unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
cimg_forXY(*this,x,y) *(xptrd++) = (unsigned char)*(ptrR++);
cimg::fwrite(ptrd,buf_size,nfile);
delete[] ptrd;
} else { // Binary PGM 16 bits
unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
cimg_forXY(*this,x,y) *(xptrd++) = (unsigned short)*(ptrR++);
if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
cimg::fwrite(ptrd,buf_size,nfile);
delete[] ptrd;
}
} break;
case 2 : { // RG image
if (stmax<256) { // Binary PPM 8 bits
unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
cimg_forXY(*this,x,y) {
*(xptrd++) = (unsigned char)*(ptrR++);
*(xptrd++) = (unsigned char)*(ptrG++);
*(xptrd++) = 0;
}
cimg::fwrite(ptrd,buf_size,nfile);
delete[] ptrd;
} else { // Binary PPM 16 bits
unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
cimg_forXY(*this,x,y) {
*(xptrd++) = (unsigned short)*(ptrR++);
*(xptrd++) = (unsigned short)*(ptrG++);
*(xptrd++) = 0;
}
if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
cimg::fwrite(ptrd,buf_size,nfile);
delete[] ptrd;
}
} break;
default : { // RGB image
if (stmax<256) { // Binary PPM 8 bits
unsigned char *ptrd = new unsigned char[buf_size], *xptrd = ptrd;
cimg_forXY(*this,x,y) {
*(xptrd++) = (unsigned char)*(ptrR++);
*(xptrd++) = (unsigned char)*(ptrG++);
*(xptrd++) = (unsigned char)*(ptrB++);
}
cimg::fwrite(ptrd,buf_size,nfile);
delete[] ptrd;
} else { // Binary PPM 16 bits
unsigned short *ptrd = new unsigned short[buf_size], *xptrd = ptrd;
cimg_forXY(*this,x,y) {
*(xptrd++) = (unsigned short)*(ptrR++);
*(xptrd++) = (unsigned short)*(ptrG++);
*(xptrd++) = (unsigned short)*(ptrB++);
}
if (!cimg::endianness()) cimg::invert_endianness(ptrd,buf_size);
cimg::fwrite(ptrd,buf_size,nfile);
delete[] ptrd;
}
}
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as a PNM file.
const CImg<T>& save_pnm(const char *const filename) const {
return _save_pnm(0,filename);
}
//! Save the image as a PNM file.
const CImg<T>& save_pnm(cimg_std::FILE *const file) const {
return _save_pnm(file,0);
}
// Save the image as a RGB file (internal).
const CImg<T>& _save_rgb(cimg_std::FILE *const file, const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_rgb() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
if (dim!=3)
cimg::warn("CImg<%s>::save_rgb() : File '%s', instance image (%u,%u,%u,%u,%p) has not exactly 3 channels.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
const unsigned int wh = width*height;
unsigned char *buffer = new unsigned char[3*wh], *nbuffer=buffer;
const T
*ptr1 = ptr(0,0,0,0),
*ptr2 = dim>1?ptr(0,0,0,1):0,
*ptr3 = dim>2?ptr(0,0,0,2):0;
switch (dim) {
case 1 : { // Scalar image
for (unsigned int k=0; k<wh; ++k) {
const unsigned char val = (unsigned char)*(ptr1++);
*(nbuffer++) = val;
*(nbuffer++) = val;
*(nbuffer++) = val;
}} break;
case 2 : { // RG image
for (unsigned int k=0; k<wh; ++k) {
*(nbuffer++) = (unsigned char)(*(ptr1++));
*(nbuffer++) = (unsigned char)(*(ptr2++));
*(nbuffer++) = 0;
}} break;
default : { // RGB image
for (unsigned int k=0; k<wh; ++k) {
*(nbuffer++) = (unsigned char)(*(ptr1++));
*(nbuffer++) = (unsigned char)(*(ptr2++));
*(nbuffer++) = (unsigned char)(*(ptr3++));
}
}
}
cimg::fwrite(buffer,3*wh,nfile);
if (!file) cimg::fclose(nfile);
delete[] buffer;
return *this;
}
//! Save the image as a RGB file.
const CImg<T>& save_rgb(const char *const filename) const {
return _save_rgb(0,filename);
}
//! Save the image as a RGB file.
const CImg<T>& save_rgb(cimg_std::FILE *const file) const {
return _save_rgb(file,0);
}
// Save the image as a RGBA file (internal).
const CImg<T>& _save_rgba(cimg_std::FILE *const file, const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_rgba() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_rgba() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
if (dim!=4)
cimg::warn("CImg<%s>::save_rgba() : File '%s, instance image (%u,%u,%u,%u,%p) has not exactly 4 channels.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
const unsigned int wh = width*height;
unsigned char *buffer = new unsigned char[4*wh], *nbuffer=buffer;
const T
*ptr1 = ptr(0,0,0,0),
*ptr2 = dim>1?ptr(0,0,0,1):0,
*ptr3 = dim>2?ptr(0,0,0,2):0,
*ptr4 = dim>3?ptr(0,0,0,3):0;
switch (dim) {
case 1 : { // Scalar images
for (unsigned int k=0; k<wh; ++k) {
const unsigned char val = (unsigned char)*(ptr1++);
*(nbuffer++) = val;
*(nbuffer++) = val;
*(nbuffer++) = val;
*(nbuffer++) = 255;
}} break;
case 2 : { // RG images
for (unsigned int k=0; k<wh; ++k) {
*(nbuffer++) = (unsigned char)(*(ptr1++));
*(nbuffer++) = (unsigned char)(*(ptr2++));
*(nbuffer++) = 0;
*(nbuffer++) = 255;
}} break;
case 3 : { // RGB images
for (unsigned int k=0; k<wh; ++k) {
*(nbuffer++) = (unsigned char)(*(ptr1++));
*(nbuffer++) = (unsigned char)(*(ptr2++));
*(nbuffer++) = (unsigned char)(*(ptr3++));
*(nbuffer++) = 255;
}} break;
default : { // RGBA images
for (unsigned int k=0; k<wh; ++k) {
*(nbuffer++) = (unsigned char)(*(ptr1++));
*(nbuffer++) = (unsigned char)(*(ptr2++));
*(nbuffer++) = (unsigned char)(*(ptr3++));
*(nbuffer++) = (unsigned char)(*(ptr4++));
}
}
}
cimg::fwrite(buffer,4*wh,nfile);
if (!file) cimg::fclose(nfile);
delete[] buffer;
return *this;
}
//! Save the image as a RGBA file.
const CImg<T>& save_rgba(const char *const filename) const {
return _save_rgba(0,filename);
}
//! Save the image as a RGBA file.
const CImg<T>& save_rgba(cimg_std::FILE *const file) const {
return _save_rgba(file,0);
}
// Save a plane into a tiff file
#ifdef cimg_use_tiff
const CImg<T>& _save_tiff(TIFF *tif, const unsigned int directory) const {
if (is_empty() || !tif) return *this;
const char *const filename = TIFFFileName(tif);
uint32 rowsperstrip = (uint32)-1;
uint16 spp = dim, bpp = sizeof(T)*8, photometric, compression = COMPRESSION_NONE;
if (spp==3 || spp==4) photometric = PHOTOMETRIC_RGB;
else photometric = PHOTOMETRIC_MINISBLACK;
TIFFSetDirectory(tif,directory);
TIFFSetField(tif,TIFFTAG_IMAGEWIDTH,width);
TIFFSetField(tif,TIFFTAG_IMAGELENGTH,height);
TIFFSetField(tif,TIFFTAG_ORIENTATION,ORIENTATION_TOPLEFT);
TIFFSetField(tif,TIFFTAG_SAMPLESPERPIXEL,spp);
if (cimg::type<T>::is_float()) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,3);
else if (cimg::type<T>::min()==0) TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,1);
else TIFFSetField(tif,TIFFTAG_SAMPLEFORMAT,2);
TIFFSetField(tif,TIFFTAG_BITSPERSAMPLE,bpp);
TIFFSetField(tif,TIFFTAG_PLANARCONFIG,PLANARCONFIG_CONTIG);
TIFFSetField(tif,TIFFTAG_PHOTOMETRIC,photometric);
TIFFSetField(tif,TIFFTAG_COMPRESSION,compression);
rowsperstrip = TIFFDefaultStripSize(tif,rowsperstrip);
TIFFSetField(tif,TIFFTAG_ROWSPERSTRIP,rowsperstrip);
TIFFSetField(tif,TIFFTAG_FILLORDER,FILLORDER_MSB2LSB);
TIFFSetField(tif,TIFFTAG_SOFTWARE,"CImg");
T *buf = (T*)_TIFFmalloc(TIFFStripSize(tif));
if (buf){
for (unsigned int row = 0; row<height; row+=rowsperstrip) {
uint32 nrow = (row+rowsperstrip>height?height-row:rowsperstrip);
tstrip_t strip = TIFFComputeStrip(tif,row,0);
tsize_t i = 0;
for (unsigned int rr = 0; rr<nrow; ++rr)
for (unsigned int cc = 0; cc<width; ++cc)
for (unsigned int vv = 0; vv<spp; ++vv)
buf[i++] = (*this)(cc,row+rr,vv);
if (TIFFWriteEncodedStrip(tif,strip,buf,i*sizeof(T))<0)
- throw CImgException("CImg<%s>::save_tiff() : File '%s', an error has occured while writing a strip.",
+ throw CImgException("CImg<%s>::save_tiff() : File '%s', an error has occurred while writing a strip.",
pixel_type(),filename?filename:"(FILE*)");
}
_TIFFfree(buf);
}
TIFFWriteDirectory(tif);
return (*this);
}
#endif
//! Save a file in TIFF format.
const CImg<T>& save_tiff(const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_tiff() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_tiff() : Specified filename is (null) for instance image (%u,%u,%u,%u,%p).",
pixel_type(),width,height,depth,dim,data);
#ifdef cimg_use_tiff
TIFF *tif = TIFFOpen(filename,"w");
if (tif) {
cimg_forZ(*this,z) get_slice(z)._save_tiff(tif,z);
TIFFClose(tif);
} else throw CImgException("CImg<%s>::save_tiff() : File '%s', error while opening file stream for writing.",
pixel_type(),filename);
#else
return save_other(filename);
#endif
return *this;
}
//! Save the image as an ANALYZE7.5 or NIFTI file.
const CImg<T>& save_analyze(const char *const filename, const float *const voxsize=0) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_analyze() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_analyze() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
cimg_std::FILE *file;
char header[348], hname[1024], iname[1024];
const char *ext = cimg::split_filename(filename);
short datatype=-1;
cimg_std::memset(header,0,348);
if (!ext[0]) { cimg_std::sprintf(hname,"%s.hdr",filename); cimg_std::sprintf(iname,"%s.img",filename); }
if (!cimg::strncasecmp(ext,"hdr",3)) {
cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(iname+cimg::strlen(iname)-3,"img");
}
if (!cimg::strncasecmp(ext,"img",3)) {
cimg_std::strcpy(hname,filename); cimg_std::strcpy(iname,filename); cimg_std::sprintf(hname+cimg::strlen(iname)-3,"hdr");
}
if (!cimg::strncasecmp(ext,"nii",3)) {
cimg_std::strcpy(hname,filename); iname[0] = 0;
}
((int*)(header))[0] = 348;
cimg_std::sprintf(header+4,"CImg");
cimg_std::sprintf(header+14," ");
((short*)(header+36))[0] = 4096;
((char*)(header+38))[0] = 114;
((short*)(header+40))[0] = 4;
((short*)(header+40))[1] = width;
((short*)(header+40))[2] = height;
((short*)(header+40))[3] = depth;
((short*)(header+40))[4] = dim;
if (!cimg::strcasecmp(pixel_type(),"bool")) datatype = 2;
if (!cimg::strcasecmp(pixel_type(),"unsigned char")) datatype = 2;
if (!cimg::strcasecmp(pixel_type(),"char")) datatype = 2;
if (!cimg::strcasecmp(pixel_type(),"unsigned short")) datatype = 4;
if (!cimg::strcasecmp(pixel_type(),"short")) datatype = 4;
if (!cimg::strcasecmp(pixel_type(),"unsigned int")) datatype = 8;
if (!cimg::strcasecmp(pixel_type(),"int")) datatype = 8;
if (!cimg::strcasecmp(pixel_type(),"unsigned long")) datatype = 8;
if (!cimg::strcasecmp(pixel_type(),"long")) datatype = 8;
if (!cimg::strcasecmp(pixel_type(),"float")) datatype = 16;
if (!cimg::strcasecmp(pixel_type(),"double")) datatype = 64;
if (datatype<0)
throw CImgIOException("CImg<%s>::save_analyze() : Cannot save image '%s' since pixel type (%s)"
"is not handled in Analyze7.5 specifications.\n",
pixel_type(),filename,pixel_type());
((short*)(header+70))[0] = datatype;
((short*)(header+72))[0] = sizeof(T);
((float*)(header+112))[0] = 1;
((float*)(header+76))[0] = 0;
if (voxsize) {
((float*)(header+76))[1] = voxsize[0];
((float*)(header+76))[2] = voxsize[1];
((float*)(header+76))[3] = voxsize[2];
} else ((float*)(header+76))[1] = ((float*)(header+76))[2] = ((float*)(header+76))[3] = 1;
file = cimg::fopen(hname,"wb");
cimg::fwrite(header,348,file);
if (iname[0]) { cimg::fclose(file); file = cimg::fopen(iname,"wb"); }
cimg::fwrite(data,size(),file);
cimg::fclose(file);
return *this;
}
//! Save the image as a .cimg file.
const CImg<T>& save_cimg(const char *const filename, const bool compress=false) const {
CImgList<T>(*this,true).save_cimg(filename,compress);
return *this;
}
// Save the image as a .cimg file.
const CImg<T>& save_cimg(cimg_std::FILE *const file, const bool compress=false) const {
CImgList<T>(*this,true).save_cimg(file,compress);
return *this;
}
//! Insert the image into an existing .cimg file, at specified coordinates.
const CImg<T>& save_cimg(const char *const filename,
const unsigned int n0,
const unsigned int x0, const unsigned int y0,
const unsigned int z0, const unsigned int v0) const {
CImgList<T>(*this,true).save_cimg(filename,n0,x0,y0,z0,v0);
return *this;
}
//! Insert the image into an existing .cimg file, at specified coordinates.
const CImg<T>& save_cimg(cimg_std::FILE *const file,
const unsigned int n0,
const unsigned int x0, const unsigned int y0,
const unsigned int z0, const unsigned int v0) const {
CImgList<T>(*this,true).save_cimg(file,n0,x0,y0,z0,v0);
return *this;
}
//! Save an empty .cimg file with specified dimensions.
static void save_empty_cimg(const char *const filename,
const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1) {
return CImgList<T>::save_empty_cimg(filename,1,dx,dy,dz,dv);
}
//! Save an empty .cimg file with specified dimensions.
static void save_empty_cimg(cimg_std::FILE *const file,
const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1) {
return CImgList<T>::save_empty_cimg(file,1,dx,dy,dz,dv);
}
// Save the image as an INRIMAGE-4 file (internal).
const CImg<T>& _save_inr(cimg_std::FILE *const file, const char *const filename, const float *const voxsize) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_inr() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_inr() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
int inrpixsize=-1;
const char *inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0";
if (!cimg::strcasecmp(pixel_type(),"unsigned char")) { inrtype = "unsigned fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
if (!cimg::strcasecmp(pixel_type(),"char")) { inrtype = "fixed\nPIXSIZE=8 bits\nSCALE=2**0"; inrpixsize = 1; }
if (!cimg::strcasecmp(pixel_type(),"unsigned short")) { inrtype = "unsigned fixed\nPIXSIZE=16 bits\nSCALE=2**0";inrpixsize = 2; }
if (!cimg::strcasecmp(pixel_type(),"short")) { inrtype = "fixed\nPIXSIZE=16 bits\nSCALE=2**0"; inrpixsize = 2; }
if (!cimg::strcasecmp(pixel_type(),"unsigned int")) { inrtype = "unsigned fixed\nPIXSIZE=32 bits\nSCALE=2**0";inrpixsize = 4; }
if (!cimg::strcasecmp(pixel_type(),"int")) { inrtype = "fixed\nPIXSIZE=32 bits\nSCALE=2**0"; inrpixsize = 4; }
if (!cimg::strcasecmp(pixel_type(),"float")) { inrtype = "float\nPIXSIZE=32 bits"; inrpixsize = 4; }
if (!cimg::strcasecmp(pixel_type(),"double")) { inrtype = "float\nPIXSIZE=64 bits"; inrpixsize = 8; }
if (inrpixsize<=0)
throw CImgIOException("CImg<%s>::save_inr() : Don't know how to save images of '%s'",
pixel_type(),pixel_type());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
char header[257];
int err = cimg_std::sprintf(header,"#INRIMAGE-4#{\nXDIM=%u\nYDIM=%u\nZDIM=%u\nVDIM=%u\n",width,height,depth,dim);
if (voxsize) err += cimg_std::sprintf(header+err,"VX=%g\nVY=%g\nVZ=%g\n",voxsize[0],voxsize[1],voxsize[2]);
err += cimg_std::sprintf(header+err,"TYPE=%s\nCPU=%s\n",inrtype,cimg::endianness()?"sun":"decm");
cimg_std::memset(header+err,'\n',252-err);
cimg_std::memcpy(header+252,"##}\n",4);
cimg::fwrite(header,256,nfile);
cimg_forXYZ(*this,x,y,z) cimg_forV(*this,k) cimg::fwrite(&((*this)(x,y,z,k)),1,nfile);
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as an INRIMAGE-4 file.
const CImg<T>& save_inr(const char *const filename, const float *const voxsize=0) const {
return _save_inr(0,filename,voxsize);
}
//! Save the image as an INRIMAGE-4 file.
const CImg<T>& save_inr(cimg_std::FILE *const file, const float *const voxsize=0) const {
return _save_inr(file,0,voxsize);
}
// Save the image as a PANDORE-5 file (internal).
unsigned int _save_pandore_header_length(unsigned int id, unsigned int *dims, const unsigned int colorspace) const {
unsigned int nbdims = 0;
if (id==2 || id==3 || id==4) { dims[0] = 1; dims[1] = width; nbdims = 2; }
if (id==5 || id==6 || id==7) { dims[0] = 1; dims[1] = height; dims[2] = width; nbdims=3; }
if (id==8 || id==9 || id==10) { dims[0] = dim; dims[1] = depth; dims[2] = height; dims[3] = width; nbdims = 4; }
if (id==16 || id==17 || id==18) { dims[0] = 3; dims[1] = height; dims[2] = width; dims[3] = colorspace; nbdims = 4; }
if (id==19 || id==20 || id==21) { dims[0] = 3; dims[1] = depth; dims[2] = height; dims[3] = width; dims[4] = colorspace; nbdims = 5; }
if (id==22 || id==23 || id==25) { dims[0] = dim; dims[1] = width; nbdims = 2; }
if (id==26 || id==27 || id==29) { dims[0] = dim; dims[1] = height; dims[2] = width; nbdims=3; }
if (id==30 || id==31 || id==33) { dims[0] = dim; dims[1] = depth; dims[2] = height; dims[3] = width; nbdims = 4; }
return nbdims;
}
const CImg<T>& _save_pandore(cimg_std::FILE *const file, const char *const filename, const unsigned int colorspace) const {
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
#define __cimg_save_pandore_case(dtype) \
dtype *buffer = new dtype[size()]; \
const T *ptrs = data; \
cimg_foroff(*this,off) *(buffer++) = (dtype)(*(ptrs++)); \
buffer-=size(); \
cimg::fwrite(buffer,size(),nfile); \
delete[] buffer
#define _cimg_save_pandore_case(sy,sz,sv,stype,id) \
if (!saved && (sy?(sy==height):true) && (sz?(sz==depth):true) && (sv?(sv==dim):true) && !cimg::strcmp(stype,pixel_type())) { \
unsigned int *iheader = (unsigned int*)(header+12); \
nbdims = _save_pandore_header_length((*iheader=id),dims,colorspace); \
cimg::fwrite(header,36,nfile); \
if (sizeof(ulong)==4) { ulong ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ulong)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
else if (sizeof(uint)==4) { uint ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (uint)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
else if (sizeof(ushort)==4) { ushort ndims[5]; for (int d = 0; d<5; ++d) ndims[d] = (ushort)dims[d]; cimg::fwrite(ndims,nbdims,nfile); } \
else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
"supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
depth,dim,data); \
if (id==2 || id==5 || id==8 || id==16 || id==19 || id==22 || id==26 || id==30) { \
__cimg_save_pandore_case(uchar); \
} else if (id==3 || id==6 || id==9 || id==17 || id==20 || id==23 || id==27 || id==31) { \
if (sizeof(ulong)==4) { __cimg_save_pandore_case(ulong); } \
else if (sizeof(uint)==4) { __cimg_save_pandore_case(uint); } \
else if (sizeof(ushort)==4) { __cimg_save_pandore_case(ushort); } \
else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
"supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
depth,dim,data); \
} else if (id==4 || id==7 || id==10 || id==18 || id==21 || id==25 || id==29 || id==33) { \
if (sizeof(double)==4) { __cimg_save_pandore_case(double); } \
else if (sizeof(float)==4) { __cimg_save_pandore_case(float); } \
else throw CImgIOException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p), output type is not" \
"supported on this architecture.",pixel_type(),filename?filename:"(FILE*)",width,height, \
depth,dim,data); \
} \
saved = true; \
}
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_pandore() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_pandore() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
unsigned char header[36] = { 'P','A','N','D','O','R','E','0','4',0,0,0,
0,0,0,0,'C','I','m','g',0,0,0,0,0,'N','o',' ','d','a','t','e',0,0,0,0 };
unsigned int nbdims, dims[5];
bool saved = false;
_cimg_save_pandore_case(1,1,1,"unsigned char",2);
_cimg_save_pandore_case(1,1,1,"char",3);
_cimg_save_pandore_case(1,1,1,"short",3);
_cimg_save_pandore_case(1,1,1,"unsigned short",3);
_cimg_save_pandore_case(1,1,1,"unsigned int",3);
_cimg_save_pandore_case(1,1,1,"int",3);
_cimg_save_pandore_case(1,1,1,"unsigned long",4);
_cimg_save_pandore_case(1,1,1,"long",3);
_cimg_save_pandore_case(1,1,1,"float",4);
_cimg_save_pandore_case(1,1,1,"double",4);
_cimg_save_pandore_case(0,1,1,"unsigned char",5);
_cimg_save_pandore_case(0,1,1,"char",6);
_cimg_save_pandore_case(0,1,1,"short",6);
_cimg_save_pandore_case(0,1,1,"unsigned short",6);
_cimg_save_pandore_case(0,1,1,"unsigned int",6);
_cimg_save_pandore_case(0,1,1,"int",6);
_cimg_save_pandore_case(0,1,1,"unsigned long",7);
_cimg_save_pandore_case(0,1,1,"long",6);
_cimg_save_pandore_case(0,1,1,"float",7);
_cimg_save_pandore_case(0,1,1,"double",7);
_cimg_save_pandore_case(0,0,1,"unsigned char",8);
_cimg_save_pandore_case(0,0,1,"char",9);
_cimg_save_pandore_case(0,0,1,"short",9);
_cimg_save_pandore_case(0,0,1,"unsigned short",9);
_cimg_save_pandore_case(0,0,1,"unsigned int",9);
_cimg_save_pandore_case(0,0,1,"int",9);
_cimg_save_pandore_case(0,0,1,"unsigned long",10);
_cimg_save_pandore_case(0,0,1,"long",9);
_cimg_save_pandore_case(0,0,1,"float",10);
_cimg_save_pandore_case(0,0,1,"double",10);
_cimg_save_pandore_case(0,1,3,"unsigned char",16);
_cimg_save_pandore_case(0,1,3,"char",17);
_cimg_save_pandore_case(0,1,3,"short",17);
_cimg_save_pandore_case(0,1,3,"unsigned short",17);
_cimg_save_pandore_case(0,1,3,"unsigned int",17);
_cimg_save_pandore_case(0,1,3,"int",17);
_cimg_save_pandore_case(0,1,3,"unsigned long",18);
_cimg_save_pandore_case(0,1,3,"long",17);
_cimg_save_pandore_case(0,1,3,"float",18);
_cimg_save_pandore_case(0,1,3,"double",18);
_cimg_save_pandore_case(0,0,3,"unsigned char",19);
_cimg_save_pandore_case(0,0,3,"char",20);
_cimg_save_pandore_case(0,0,3,"short",20);
_cimg_save_pandore_case(0,0,3,"unsigned short",20);
_cimg_save_pandore_case(0,0,3,"unsigned int",20);
_cimg_save_pandore_case(0,0,3,"int",20);
_cimg_save_pandore_case(0,0,3,"unsigned long",21);
_cimg_save_pandore_case(0,0,3,"long",20);
_cimg_save_pandore_case(0,0,3,"float",21);
_cimg_save_pandore_case(0,0,3,"double",21);
_cimg_save_pandore_case(1,1,0,"unsigned char",22);
_cimg_save_pandore_case(1,1,0,"char",23);
_cimg_save_pandore_case(1,1,0,"short",23);
_cimg_save_pandore_case(1,1,0,"unsigned short",23);
_cimg_save_pandore_case(1,1,0,"unsigned int",23);
_cimg_save_pandore_case(1,1,0,"int",23);
_cimg_save_pandore_case(1,1,0,"unsigned long",25);
_cimg_save_pandore_case(1,1,0,"long",23);
_cimg_save_pandore_case(1,1,0,"float",25);
_cimg_save_pandore_case(1,1,0,"double",25);
_cimg_save_pandore_case(0,1,0,"unsigned char",26);
_cimg_save_pandore_case(0,1,0,"char",27);
_cimg_save_pandore_case(0,1,0,"short",27);
_cimg_save_pandore_case(0,1,0,"unsigned short",27);
_cimg_save_pandore_case(0,1,0,"unsigned int",27);
_cimg_save_pandore_case(0,1,0,"int",27);
_cimg_save_pandore_case(0,1,0,"unsigned long",29);
_cimg_save_pandore_case(0,1,0,"long",27);
_cimg_save_pandore_case(0,1,0,"float",29);
_cimg_save_pandore_case(0,1,0,"double",29);
_cimg_save_pandore_case(0,0,0,"unsigned char",30);
_cimg_save_pandore_case(0,0,0,"char",31);
_cimg_save_pandore_case(0,0,0,"short",31);
_cimg_save_pandore_case(0,0,0,"unsigned short",31);
_cimg_save_pandore_case(0,0,0,"unsigned int",31);
_cimg_save_pandore_case(0,0,0,"int",31);
_cimg_save_pandore_case(0,0,0,"unsigned long",33);
_cimg_save_pandore_case(0,0,0,"long",31);
_cimg_save_pandore_case(0,0,0,"float",33);
_cimg_save_pandore_case(0,0,0,"double",33);
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as a PANDORE-5 file.
const CImg<T>& save_pandore(const char *const filename, const unsigned int colorspace=0) const {
return _save_pandore(0,filename,colorspace);
}
//! Save the image as a PANDORE-5 file.
const CImg<T>& save_pandore(cimg_std::FILE *const file, const unsigned int colorspace=0) const {
return _save_pandore(file,0,colorspace);
}
// Save the image as a RAW file (internal).
const CImg<T>& _save_raw(cimg_std::FILE *const file, const char *const filename, const bool multiplexed) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_raw() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_raw() : Instance image (%u,%u,%u,%u,%p), specified file is (null).",
pixel_type(),width,height,depth,dim,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
if (!multiplexed) cimg::fwrite(data,size(),nfile);
else {
CImg<T> buf(dim);
cimg_forXYZ(*this,x,y,z) {
cimg_forV(*this,k) buf[k] = (*this)(x,y,z,k);
cimg::fwrite(buf.data,dim,nfile);
}
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save the image as a RAW file.
const CImg<T>& save_raw(const char *const filename, const bool multiplexed=false) const {
return _save_raw(0,filename,multiplexed);
}
//! Save the image as a RAW file.
const CImg<T>& save_raw(cimg_std::FILE *const file, const bool multiplexed=false) const {
return _save_raw(file,0,multiplexed);
}
//! Save the image as a video sequence file, using FFMPEG library.
const CImg<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int fps=25) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_ffmpeg() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_ffmpeg() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
if (!fps)
throw CImgArgumentException("CImg<%s>::save_ffmpeg() : File '%s', specified framerate is 0.",
pixel_type(),filename);
#ifndef cimg_use_ffmpeg
return save_ffmpeg_external(filename,first_frame,last_frame);
#else
get_split('z').save_ffmpeg(filename,first_frame,last_frame,fps);
#endif
return *this;
}
//! Save the image as a YUV video sequence file.
const CImg<T>& save_yuv(const char *const filename, const bool rgb2yuv=true) const {
get_split('z').save_yuv(filename,rgb2yuv);
return *this;
}
//! Save the image as a YUV video sequence file.
const CImg<T>& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const {
get_split('z').save_yuv(file,rgb2yuv);
return *this;
}
// Save OFF files (internal).
template<typename tf, typename tc>
const CImg<T>& _save_off(cimg_std::FILE *const file, const char *const filename,
const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_off() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",width,height,depth,dim,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_off() : Specified filename is (null).",
pixel_type());
if (height<3) return get_resize(-100,3,1,1,0)._save_off(file,filename,primitives,colors,invert_faces);
CImgList<tc> _colors;
if (!colors) _colors.insert(primitives.size,CImg<tc>::vector(200,200,200));
const CImgList<tc>& ncolors = colors?colors:_colors;
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"w");
cimg_std::fprintf(nfile,"OFF\n%u %u %u\n",width,primitives.size,3*primitives.size);
cimg_forX(*this,i) cimg_std::fprintf(nfile,"%f %f %f\n",(float)((*this)(i,0)),(float)((*this)(i,1)),(float)((*this)(i,2)));
cimglist_for(primitives,l) {
const unsigned int prim = primitives[l].size();
const bool textured = (prim>4);
const CImg<tc>& color = ncolors[l];
const unsigned int s = textured?color.dimv():color.size();
const float
r = textured?(s>0?(float)(color.get_shared_channel(0).mean()/255.0f):1.0f):(s>0?(float)(color(0)/255.0f):1.0f),
g = textured?(s>1?(float)(color.get_shared_channel(1).mean()/255.0f):r) :(s>1?(float)(color(1)/255.0f):r),
b = textured?(s>2?(float)(color.get_shared_channel(2).mean()/255.0f):r) :(s>2?(float)(color(2)/255.0f):r);
switch (prim) {
case 1 :
cimg_std::fprintf(nfile,"1 %u %f %f %f\n",(unsigned int)primitives(l,0),r,g,b);
break;
case 2 : case 6 :
cimg_std::fprintf(nfile,"2 %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),r,g,b);
break;
case 3 : case 9 :
if (invert_faces)
cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),r,g,b);
else
cimg_std::fprintf(nfile,"3 %u %u %u %f %f %f\n",(unsigned int)primitives(l,0),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
break;
case 4 : case 12 :
if (invert_faces)
cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
(unsigned int)primitives(l,0),(unsigned int)primitives(l,1),(unsigned int)primitives(l,2),(unsigned int)primitives(l,3),r,g,b);
else
cimg_std::fprintf(nfile,"4 %u %u %u %u %f %f %f\n",
(unsigned int)primitives(l,0),(unsigned int)primitives(l,3),(unsigned int)primitives(l,2),(unsigned int)primitives(l,1),r,g,b);
break;
}
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save OFF files.
template<typename tf, typename tc>
const CImg<T>& save_off(const char *const filename,
const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
return _save_off(0,filename,primitives,colors,invert_faces);
}
//! Save OFF files.
template<typename tf, typename tc>
const CImg<T>& save_off(cimg_std::FILE *const file,
const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
return _save_off(file,0,primitives,colors,invert_faces);
}
//! Save the image as a video sequence file, using the external tool 'ffmpeg'.
const CImg<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const char *const codec="mpeg2video") const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_ffmpeg_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_ffmpeg_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
get_split('z').save_ffmpeg_external(filename,first_frame,last_frame,codec);
return *this;
}
//! Save the image using GraphicsMagick's gm.
/** Function that saves the image for other file formats that are not natively handled by CImg,
using the tool 'gm' from the GraphicsMagick package.\n
This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
the GraphicsMagick package in order to get
this function working properly (see http://www.graphicsmagick.org ).
**/
const CImg<T>& save_graphicsmagick_external(const char *const filename, const unsigned int quality=100) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_graphicsmagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_graphicsmagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
char command[1024],filetmp[512];
cimg_std::FILE *file;
do {
if (dim==1) cimg_std::sprintf(filetmp,"%s%s%s.pgm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
else cimg_std::sprintf(filetmp,"%s%s%s.ppm",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
save_pnm(filetmp);
cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::graphicsmagick_path(),quality,filetmp,filename);
cimg::system(command);
file = cimg_std::fopen(filename,"rb");
if (!file)
throw CImgIOException("CImg<%s>::save_graphicsmagick_external() : Failed to save image '%s'.\n\n"
"Path of 'gm' : \"%s\"\n"
"Path of temporary filename : \"%s\"\n",
pixel_type(),filename,cimg::graphicsmagick_path(),filetmp);
if (file) cimg::fclose(file);
cimg_std::remove(filetmp);
return *this;
}
//! Save an image as a gzipped file, using external tool 'gzip'.
const CImg<T>& save_gzip_external(const char *const filename) const {
if (!filename)
throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.",
pixel_type());
char command[1024], filetmp[512], body[512];
const char
*ext = cimg::split_filename(filename,body),
*ext2 = cimg::split_filename(body,0);
cimg_std::FILE *file;
do {
if (!cimg::strcasecmp(ext,"gz")) {
if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext2);
else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
} else {
if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext);
else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
}
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
save(filetmp);
cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
cimg::system(command);
file = cimg_std::fopen(filename,"rb");
if (!file)
throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.",
pixel_type(),filename);
else cimg::fclose(file);
cimg_std::remove(filetmp);
return *this;
}
//! Save the image using ImageMagick's convert.
/** Function that saves the image for other file formats that are not natively handled by CImg,
using the tool 'convert' from the ImageMagick package.\n
This is the case for all compressed image formats (GIF,PNG,JPG,TIF, ...). You need to install
the ImageMagick package in order to get
this function working properly (see http://www.imagemagick.org ).
**/
const CImg<T>& save_imagemagick_external(const char *const filename, const unsigned int quality=100) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_imagemagick_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_imagemagick_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
char command[1024], filetmp[512];
cimg_std::FILE *file;
do {
cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand(),dim==1?"pgm":"ppm");
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
save_pnm(filetmp);
cimg_std::sprintf(command,"%s -quality %u%% %s \"%s\"",cimg::imagemagick_path(),quality,filetmp,filename);
cimg::system(command);
file = cimg_std::fopen(filename,"rb");
if (!file)
throw CImgIOException("CImg<%s>::save_imagemagick_external() : Failed to save image '%s'.\n\n"
"Path of 'convert' : \"%s\"\n"
"Path of temporary filename : \"%s\"\n",
pixel_type(),filename,cimg::imagemagick_path(),filetmp);
if (file) cimg::fclose(file);
cimg_std::remove(filetmp);
return *this;
}
//! Save an image as a Dicom file (need '(X)Medcon' : http://xmedcon.sourceforge.net )
const CImg<T>& save_medcon_external(const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_medcon_external() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save_medcon_external() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type(),width,height,depth,dim,data);
char command[1024], filetmp[512], body[512];
cimg_std::FILE *file;
do {
cimg_std::sprintf(filetmp,"%s.hdr",cimg::filenamerand());
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
save_analyze(filetmp);
cimg_std::sprintf(command,"%s -w -c dicom -o %s -f %s",cimg::medcon_path(),filename,filetmp);
cimg::system(command);
cimg_std::remove(filetmp);
cimg::split_filename(filetmp,body);
cimg_std::sprintf(filetmp,"%s.img",body);
cimg_std::remove(filetmp);
cimg_std::sprintf(command,"m000-%s",filename);
file = cimg_std::fopen(command,"rb");
if (!file) {
cimg::fclose(cimg::fopen(filename,"r"));
throw CImgIOException("CImg<%s>::save_medcon_external() : Failed to save image '%s'.\n\n"
"Path of 'medcon' : \"%s\"\n"
"Path of temporary filename : \"%s\"",
pixel_type(),filename,cimg::medcon_path(),filetmp);
} else cimg::fclose(file);
cimg_std::rename(command,filename);
return *this;
}
// Try to save the image if other extension is provided.
const CImg<T>& save_other(const char *const filename, const unsigned int quality=100) const {
if (is_empty())
throw CImgInstanceException("CImg<%s>::save_other() : File '%s', instance image (%u,%u,%u,%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",width,height,depth,dim,data);
if (!filename)
throw CImgIOException("CImg<%s>::save_other() : Instance image (%u,%u,%u,%u,%p), specified filename is (null).",
pixel_type());
const unsigned int odebug = cimg::exception_mode();
bool is_saved = true;
cimg::exception_mode() = 0;
try { save_magick(filename); }
catch (CImgException&) {
try { save_imagemagick_external(filename,quality); }
catch (CImgException&) {
try { save_graphicsmagick_external(filename,quality); }
catch (CImgException&) {
is_saved = false;
}
}
}
cimg::exception_mode() = odebug;
if (!is_saved)
throw CImgIOException("CImg<%s>::save_other() : File '%s' cannot be saved.\n"
"Check you have either the ImageMagick or GraphicsMagick package installed.",
pixel_type(),filename);
return *this;
}
// Get a 40x38 color logo of a 'danger' item (internal).
static CImg<T> logo40x38() {
static bool first_time = true;
CImg<T> res(40,38,1,3);
if (first_time) {
const unsigned char *ptrs = cimg::logo40x38;
T *ptr1 = res.ptr(0,0,0,0), *ptr2 = res.ptr(0,0,0,1), *ptr3 = res.ptr(0,0,0,2);
for (unsigned int off = 0; off<res.width*res.height;) {
const unsigned char n = *(ptrs++), r = *(ptrs++), g = *(ptrs++), b = *(ptrs++);
for (unsigned int l=0; l<n; ++off, ++l) { *(ptr1++) = (T)r; *(ptr2++) = (T)g; *(ptr3++) = (T)b; }
}
first_time = false;
}
return res;
}
};
/*
#-----------------------------------------
#
#
#
# Definition of the CImgList<> structure
#
#
#
#------------------------------------------
*/
//! Class representing list of images CImg<T>.
template<typename T>
struct CImgList {
//! Size of the list (number of elements inside).
unsigned int size;
//! Allocation size of the list.
unsigned int allocsize;
//! Pointer to the first list element.
CImg<T> *data;
//! Define a CImgList<T>::iterator.
typedef CImg<T>* iterator;
//! Define a CImgList<T>::const_iterator.
typedef const CImg<T>* const_iterator;
//! Get value type.
typedef T value_type;
- // Define common T-dependant types.
+ // Define common T-dependent types.
typedef typename cimg::superset<T,bool>::type Tbool;
typedef typename cimg::superset<T,unsigned char>::type Tuchar;
typedef typename cimg::superset<T,char>::type Tchar;
typedef typename cimg::superset<T,unsigned short>::type Tushort;
typedef typename cimg::superset<T,short>::type Tshort;
typedef typename cimg::superset<T,unsigned int>::type Tuint;
typedef typename cimg::superset<T,int>::type Tint;
typedef typename cimg::superset<T,unsigned long>::type Tulong;
typedef typename cimg::superset<T,long>::type Tlong;
typedef typename cimg::superset<T,float>::type Tfloat;
typedef typename cimg::superset<T,double>::type Tdouble;
typedef typename cimg::last<T,bool>::type boolT;
typedef typename cimg::last<T,unsigned char>::type ucharT;
typedef typename cimg::last<T,char>::type charT;
typedef typename cimg::last<T,unsigned short>::type ushortT;
typedef typename cimg::last<T,short>::type shortT;
typedef typename cimg::last<T,unsigned int>::type uintT;
typedef typename cimg::last<T,int>::type intT;
typedef typename cimg::last<T,unsigned long>::type ulongT;
typedef typename cimg::last<T,long>::type longT;
typedef typename cimg::last<T,float>::type floatT;
typedef typename cimg::last<T,double>::type doubleT;
//@}
//---------------------------
//
//! \name Plugins
//@{
//---------------------------
#ifdef cimglist_plugin
#include cimglist_plugin
#endif
#ifdef cimglist_plugin1
#include cimglist_plugin1
#endif
#ifdef cimglist_plugin2
#include cimglist_plugin2
#endif
#ifdef cimglist_plugin3
#include cimglist_plugin3
#endif
#ifdef cimglist_plugin4
#include cimglist_plugin4
#endif
#ifdef cimglist_plugin5
#include cimglist_plugin5
#endif
#ifdef cimglist_plugin6
#include cimglist_plugin6
#endif
#ifdef cimglist_plugin7
#include cimglist_plugin7
#endif
#ifdef cimglist_plugin8
#include cimglist_plugin8
#endif
//@}
//------------------------------------------
//
//! \name Constructors - Destructor - Copy
//@{
//------------------------------------------
//! Destructor.
~CImgList() {
if (data) delete[] data;
}
//! Default constructor.
CImgList():
size(0),allocsize(0),data(0) {}
//! Construct an image list containing n empty images.
explicit CImgList(const unsigned int n):
size(n) {
data = new CImg<T>[allocsize = cimg::max(16UL,cimg::nearest_pow2(n))];
}
//! Default copy constructor.
template<typename t>
CImgList(const CImgList<t>& list):
size(0),allocsize(0),data(0) {
assign(list.size);
cimglist_for(*this,l) data[l].assign(list[l],false);
}
CImgList(const CImgList<T>& list):
size(0),allocsize(0),data(0) {
assign(list.size);
cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
}
//! Advanced copy constructor.
template<typename t>
CImgList(const CImgList<t>& list, const bool shared):
size(0),allocsize(0),data(0) {
assign(list.size);
if (shared)
throw CImgArgumentException("CImgList<%s>::CImgList() : Cannot construct a list instance with shared images from "
"a CImgList<%s> (different pixel types).",
pixel_type(),CImgList<t>::pixel_type());
cimglist_for(*this,l) data[l].assign(list[l],false);
}
CImgList(const CImgList<T>& list, const bool shared):
size(0),allocsize(0),data(0) {
assign(list.size);
cimglist_for(*this,l) data[l].assign(list[l],shared);
}
//! Construct an image list containing n images with specified size.
CImgList(const unsigned int n, const unsigned int width, const unsigned int height=1,
const unsigned int depth=1, const unsigned int dim=1):
size(0),allocsize(0),data(0) {
assign(n);
cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
}
//! Construct an image list containing n images with specified size, filled with specified value.
CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
const unsigned int depth, const unsigned int dim, const T val):
size(0),allocsize(0),data(0) {
assign(n);
cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
}
//! Construct an image list containing n images with specified size and specified pixel values (int version).
CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...):
size(0),allocsize(0),data(0) {
#define _CImgList_stdarg(t) { \
assign(n,width,height,depth,dim); \
const unsigned int siz = width*height*depth*dim, nsiz = siz*n; \
T *ptrd = data->data; \
va_list ap; \
va_start(ap,val1); \
for (unsigned int l=0, s=0, i=0; i<nsiz; ++i) { \
*(ptrd++) = (T)(i==0?val0:(i==1?val1:va_arg(ap,t))); \
if ((++s)==siz) { ptrd = data[++l].data; s=0; } \
} \
va_end(ap); \
}
_CImgList_stdarg(int);
}
//! Construct an image list containing n images with specified size and specified pixel values (double version).
CImgList(const unsigned int n, const unsigned int width, const unsigned int height,
const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...):
size(0),allocsize(0),data(0) {
_CImgList_stdarg(double);
}
//! Construct a list containing n copies of the image img.
template<typename t>
CImgList(const unsigned int n, const CImg<t>& img):
size(0),allocsize(0),data(0) {
assign(n);
cimglist_for(*this,l) data[l].assign(img,img.is_shared);
}
//! Construct a list containing n copies of the image img, forcing the shared state.
template<typename t>
CImgList(const unsigned int n, const CImg<t>& img, const bool shared):
size(0),allocsize(0),data(0) {
assign(n);
cimglist_for(*this,l) data[l].assign(img,shared);
}
//! Construct an image list from one image.
template<typename t>
explicit CImgList(const CImg<t>& img):
size(0),allocsize(0),data(0) {
assign(1);
data[0].assign(img,img.is_shared);
}
//! Construct an image list from one image, forcing the shared state.
template<typename t>
explicit CImgList(const CImg<t>& img, const bool shared):
size(0),allocsize(0),data(0) {
assign(1);
data[0].assign(img,shared);
}
//! Construct an image list from two images.
template<typename t1, typename t2>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2):
size(0),allocsize(0),data(0) {
assign(2);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared);
}
//! Construct an image list from two images, forcing the shared state.
template<typename t1, typename t2>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared):
size(0),allocsize(0),data(0) {
assign(2);
data[0].assign(img1,shared); data[1].assign(img2,shared);
}
//! Construct an image list from three images.
template<typename t1, typename t2, typename t3>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3):
size(0),allocsize(0),data(0) {
assign(3);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared);
}
//! Construct an image list from three images, forcing the shared state.
template<typename t1, typename t2, typename t3>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared):
size(0),allocsize(0),data(0) {
assign(3);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
}
//! Construct an image list from four images.
template<typename t1, typename t2, typename t3, typename t4>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4):
size(0),allocsize(0),data(0) {
assign(4);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
}
//! Construct an image list from four images, forcing the shared state.
template<typename t1, typename t2, typename t3, typename t4>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4, const bool shared):
size(0),allocsize(0),data(0) {
assign(4);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
}
//! Construct an image list from five images.
template<typename t1, typename t2, typename t3, typename t4, typename t5>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5):
size(0),allocsize(0),data(0) {
assign(5);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
data[4].assign(img5,img5.is_shared);
}
//! Construct an image list from five images, forcing the shared state.
template<typename t1, typename t2, typename t3, typename t4, typename t5>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const bool shared):
size(0),allocsize(0),data(0) {
assign(5);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared);
}
//! Construct an image list from six images.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6):
size(0),allocsize(0),data(0) {
assign(6);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared);
}
//! Construct an image list from six images, forcing the shared state.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const bool shared):
size(0),allocsize(0),data(0) {
assign(6);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared); data[5].assign(img6,shared);
}
//! Construct an image list from seven images.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7):
size(0),allocsize(0),data(0) {
assign(7);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared);
}
//! Construct an image list from seven images, forcing the shared state.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared):
size(0),allocsize(0),data(0) {
assign(7);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
}
//! Construct an image list from eight images.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8):
size(0),allocsize(0),data(0) {
assign(8);
data[0].assign(img1,img1.is_shared); data[1].assign(img2,img2.is_shared); data[2].assign(img3,img3.is_shared); data[3].assign(img4,img4.is_shared);
data[4].assign(img5,img5.is_shared); data[5].assign(img6,img6.is_shared); data[6].assign(img7,img7.is_shared); data[7].assign(img8,img8.is_shared);
}
//! Construct an image list from eight images, forcing the shared state.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
CImgList(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared):
size(0),allocsize(0),data(0) {
assign(8);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
}
//! Construct an image list from a filename.
explicit CImgList(const char *const filename):
size(0),allocsize(0),data(0) {
assign(filename);
}
//! In-place version of the default constructor and default destructor.
CImgList<T>& assign() {
if (data) delete[] data;
size = allocsize = 0;
data = 0;
return *this;
}
//! Equivalent to assign() (STL-compliant name).
CImgList<T>& clear() {
return assign();
}
//! In-place version of the corresponding constructor.
CImgList<T>& assign(const unsigned int n) {
if (n) {
if (allocsize<n || allocsize>(n<<2)) {
if (data) delete[] data;
data = new CImg<T>[allocsize=cimg::max(16UL,cimg::nearest_pow2(n))];
}
size = n;
} else assign();
return *this;
}
//! In-place version of the corresponding constructor.
CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height=1,
const unsigned int depth=1, const unsigned int dim=1) {
assign(n);
cimglist_for(*this,l) data[l].assign(width,height,depth,dim);
return *this;
}
//! In-place version of the corresponding constructor.
CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
const unsigned int depth, const unsigned int dim, const T val) {
assign(n);
cimglist_for(*this,l) data[l].assign(width,height,depth,dim,val);
return *this;
}
//! In-place version of the corresponding constructor.
CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
const unsigned int depth, const unsigned int dim, const int val0, const int val1, ...) {
_CImgList_stdarg(int);
return *this;
}
//! In-place version of the corresponding constructor.
CImgList<T>& assign(const unsigned int n, const unsigned int width, const unsigned int height,
const unsigned int depth, const unsigned int dim, const double val0, const double val1, ...) {
_CImgList_stdarg(double);
return *this;
}
//! In-place version of the copy constructor.
template<typename t>
CImgList<T>& assign(const CImgList<t>& list) {
assign(list.size);
cimglist_for(*this,l) data[l].assign(list[l],list[l].is_shared);
return *this;
}
//! In-place version of the copy constructor.
template<typename t>
CImgList<T>& assign(const CImgList<t>& list, const bool shared) {
assign(list.size);
cimglist_for(*this,l) data[l].assign(list[l],shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t>
CImgList<T>& assign(const unsigned int n, const CImg<t>& img, const bool shared=false) {
assign(n);
cimglist_for(*this,l) data[l].assign(img,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t>
CImgList<T>& assign(const CImg<t>& img, const bool shared=false) {
assign(1);
data[0].assign(img,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const bool shared=false) {
assign(2);
data[0].assign(img1,shared); data[1].assign(img2,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2, typename t3>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const bool shared=false) {
assign(3);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2, typename t3, typename t4>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const bool shared=false) {
assign(4);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2, typename t3, typename t4, typename t5>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const bool shared=false) {
assign(5);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const bool shared=false) {
assign(6);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared); data[5].assign(img6,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const bool shared=false) {
assign(7);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared);
return *this;
}
//! In-place version of the corresponding constructor.
template<typename t1, typename t2, typename t3, typename t4, typename t5, typename t6, typename t7, typename t8>
CImgList<T>& assign(const CImg<t1>& img1, const CImg<t2>& img2, const CImg<t3>& img3, const CImg<t4>& img4,
const CImg<t5>& img5, const CImg<t6>& img6, const CImg<t7>& img7, const CImg<t8>& img8, const bool shared=false) {
assign(8);
data[0].assign(img1,shared); data[1].assign(img2,shared); data[2].assign(img3,shared); data[3].assign(img4,shared);
data[4].assign(img5,shared); data[5].assign(img6,shared); data[6].assign(img7,shared); data[7].assign(img8,shared);
return *this;
}
//! In-place version of the corresponding constructor.
CImgList<T>& assign(const char *const filename) {
return load(filename);
}
//! Transfer the content of the instance image list into another one.
template<typename t>
CImgList<T>& transfer_to(CImgList<t>& list) {
list.assign(*this);
assign();
return list;
}
CImgList<T>& transfer_to(CImgList<T>& list) {
list.assign();
return swap(list);
}
//! Swap all fields of two CImgList instances (use with care !)
CImgList<T>& swap(CImgList<T>& list) {
cimg::swap(size,list.size);
cimg::swap(allocsize,list.allocsize);
cimg::swap(data,list.data);
return list;
}
//! Return a string describing the type of the image pixels in the list (template parameter \p T).
static const char* pixel_type() {
return cimg::type<T>::string();
}
//! Return \p true if list is empty.
bool is_empty() const {
return (!data || !size);
}
//! Return \p true if list is not empty.
operator bool() const {
return !is_empty();
}
//! Return \p true if list if of specified size.
bool is_sameN(const unsigned int n) const {
return (size==n);
}
//! Return \p true if list if of specified size.
template<typename t>
bool is_sameN(const CImgList<t>& list) const {
return (size==list.size);
}
// Define useful dimension check functions.
// (not documented because they are macro-generated).
#define _cimglist_def_is_same1(axis) \
bool is_same##axis(const unsigned int val) const { \
bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis(val); return res; \
} \
bool is_sameN##axis(const unsigned int n, const unsigned int val) const { \
return is_sameN(n) && is_same##axis(val); \
} \
#define _cimglist_def_is_same2(axis1,axis2) \
bool is_same##axis1##axis2(const unsigned int val1, const unsigned int val2) const { \
bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis1##axis2(val1,val2); return res; \
} \
bool is_sameN##axis1##axis2(const unsigned int n, const unsigned int val1, const unsigned int val2) const { \
return is_sameN(n) && is_same##axis1##axis2(val1,val2); \
} \
#define _cimglist_def_is_same3(axis1,axis2,axis3) \
bool is_same##axis1##axis2##axis3(const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis1##axis2##axis3(val1,val2,val3); return res; \
} \
bool is_sameN##axis1##axis2##axis3(const unsigned int n, const unsigned int val1, const unsigned int val2, const unsigned int val3) const { \
return is_sameN(n) && is_same##axis1##axis2##axis3(val1,val2,val3); \
} \
#define _cimglist_def_is_same(axis) \
template<typename t> bool is_same##axis(const CImg<t>& img) const { \
bool res = true; for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_same##axis(img); return res; \
} \
template<typename t> bool is_same##axis(const CImgList<t>& list) const { \
const unsigned int lmin = cimg::min(size,list.size); \
bool res = true; for (unsigned int l = 0; l<lmin && res; ++l) res = data[l].is_same##axis(list[l]); return res; \
} \
template<typename t> bool is_sameN##axis(const unsigned int n, const CImg<t>& img) const { \
return (is_sameN(n) && is_same##axis(img)); \
} \
template<typename t> bool is_sameN##axis(const CImgList<t>& list) const { \
return (is_sameN(list) && is_same##axis(list)); \
}
_cimglist_def_is_same(XY)
_cimglist_def_is_same(XZ)
_cimglist_def_is_same(XV)
_cimglist_def_is_same(YZ)
_cimglist_def_is_same(YV)
_cimglist_def_is_same(XYZ)
_cimglist_def_is_same(XYV)
_cimglist_def_is_same(YZV)
_cimglist_def_is_same(XYZV)
_cimglist_def_is_same1(X)
_cimglist_def_is_same1(Y)
_cimglist_def_is_same1(Z)
_cimglist_def_is_same1(V)
_cimglist_def_is_same2(X,Y)
_cimglist_def_is_same2(X,Z)
_cimglist_def_is_same2(X,V)
_cimglist_def_is_same2(Y,Z)
_cimglist_def_is_same2(Y,V)
_cimglist_def_is_same2(Z,V)
_cimglist_def_is_same3(X,Y,Z)
_cimglist_def_is_same3(X,Y,V)
_cimglist_def_is_same3(X,Z,V)
_cimglist_def_is_same3(Y,Z,V)
bool is_sameXYZV(const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
bool res = true;
for (unsigned int l = 0; l<size && res; ++l) res = data[l].is_sameXYZV(dx,dy,dz,dv);
return res;
}
bool is_sameNXYZV(const unsigned int n, const unsigned int dx, const unsigned int dy, const unsigned int dz, const unsigned int dv) const {
return is_sameN(n) && is_sameXYZV(dx,dy,dz,dv);
}
//! Return \c true if the list contains the pixel (n,x,y,z,v).
bool containsNXYZV(const int n, const int x=0, const int y=0, const int z=0, const int v=0) const {
if (is_empty()) return false;
return n>=0 && n<(int)size && x>=0 && x<data[n].dimx() && y>=0 && y<data[n].dimy() && z>=0 && z<data[n].dimz() && v>=0 && v<data[n].dimv();
}
//! Return \c true if the list contains the image (n).
bool containsN(const int n) const {
if (is_empty()) return false;
return n>=0 && n<(int)size;
}
//! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z,v).
template<typename t>
bool contains(const T& pixel, t& n, t& x, t&y, t& z, t& v) const {
if (is_empty()) return false;
cimglist_for(*this,l) if (data[l].contains(pixel,x,y,z,v)) { n = (t)l; return true; }
return false;
}
//! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y,z).
template<typename t>
bool contains(const T& pixel, t& n, t& x, t&y, t& z) const {
t v;
return contains(pixel,n,x,y,z,v);
}
//! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x,y).
template<typename t>
bool contains(const T& pixel, t& n, t& x, t&y) const {
t z,v;
return contains(pixel,n,x,y,z,v);
}
//! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n,x).
template<typename t>
bool contains(const T& pixel, t& n, t& x) const {
t y,z,v;
return contains(pixel,n,x,y,z,v);
}
//! Return \c true if one of the image list contains the specified referenced value. If true, set coordinates (n).
template<typename t>
bool contains(const T& pixel, t& n) const {
t x,y,z,v;
return contains(pixel,n,x,y,z,v);
}
//! Return \c true if one of the image list contains the specified referenced value.
bool contains(const T& pixel) const {
unsigned int n,x,y,z,v;
return contains(pixel,n,x,y,z,v);
}
//! Return \c true if the list contains the image 'img'. If true, returns the position (n) of the image in the list.
template<typename t>
bool contains(const CImg<T>& img, t& n) const {
if (is_empty()) return false;
const CImg<T> *const ptr = &img;
cimglist_for(*this,i) if (data+i==ptr) { n = (t)i; return true; }
return false;
}
//! Return \c true if the list contains the image img.
bool contains(const CImg<T>& img) const {
unsigned int n;
return contains(img,n);
}
//@}
//------------------------------
//
- //! \name Arithmetics Operators
+ //! \name Arithmetic Operators
//@{
//------------------------------
//! Assignment operator
template<typename t>
CImgList<T>& operator=(const CImgList<t>& list) {
return assign(list);
}
CImgList<T>& operator=(const CImgList<T>& list) {
return assign(list);
}
//! Assignment operator.
template<typename t>
CImgList<T>& operator=(const CImg<t>& img) {
cimglist_for(*this,l) data[l] = img;
return *this;
}
//! Assignment operator.
CImgList<T>& operator=(const T val) {
cimglist_for(*this,l) data[l].fill(val);
return *this;
}
//! Operator+.
CImgList<T> operator+() const {
return CImgList<T>(*this);
}
//! Operator+=.
#ifdef cimg_use_visualcpp6
CImgList<T>& operator+=(const T val)
#else
template<typename t>
CImgList<T>& operator+=(const t val)
#endif
{
cimglist_for(*this,l) (*this)[l]+=val;
return *this;
}
//! Operator+=.
template<typename t>
CImgList<T>& operator+=(const CImgList<t>& list) {
const unsigned int sizemax = cimg::min(size,list.size);
for (unsigned int l=0; l<sizemax; ++l) (*this)[l]+=list[l];
return *this;
}
//! Operator++ (prefix).
CImgList<T>& operator++() {
cimglist_for(*this,l) ++(*this)[l];
return *this;
}
//! Operator++ (postfix).
CImgList<T> operator++(int) {
CImgList<T> copy(*this);
++*this;
return copy;
}
//! Operator-.
CImgList<T> operator-() const {
CImgList<T> res(size);
cimglist_for(res,l) res[l].assign(-data[l]);
return res;
}
//! Operator-=.
#ifdef cimg_use_visualcpp6
CImgList<T>& operator-=(const T val)
#else
template<typename t>
CImgList<T>& operator-=(const t val)
#endif
{
cimglist_for(*this,l) (*this)[l]-=val;
return *this;
}
//! Operator-=.
template<typename t>
CImgList<T>& operator-=(const CImgList<t>& list) {
const unsigned int sizemax = min(size,list.size);
for (unsigned int l=0; l<sizemax; ++l) (*this)[l]-=list[l];
return *this;
}
//! Operator-- (prefix).
CImgList<T>& operator--() {
cimglist_for(*this,l) --(*this)[l];
return *this;
}
//! Operator-- (postfix).
CImgList<T> operator--(int) {
CImgList<T> copy(*this);
--*this;
return copy;
}
//! Operator*=.
#ifdef cimg_use_visualcpp6
CImgList<T>& operator*=(const double val)
#else
template<typename t>
CImgList<T>& operator*=(const t val)
#endif
{
cimglist_for(*this,l) (*this)[l]*=val;
return *this;
}
//! Operator*=.
template<typename t>
CImgList<T>& operator*=(const CImgList<t>& list) {
const unsigned int N = cimg::min(size,list.size);
for (unsigned int l=0; l<N; ++l) (*this)[l]*=list[l];
return this;
}
//! Operator/=.
#ifdef cimg_use_visualcpp6
CImgList<T>& operator/=(const double val)
#else
template<typename t>
CImgList<T>& operator/=(const t val)
#endif
{
cimglist_for(*this,l) (*this)[l]/=val;
return *this;
}
//! Operator/=.
template<typename t>
CImgList<T>& operator/=(const CImgList<t>& list) {
const unsigned int N = cimg::min(size,list.size);
for (unsigned int l=0; l<N; ++l) (*this)[l]/=list[l];
return this;
}
//! Return a reference to the maximum pixel value of the instance list.
const T& max() const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.",
pixel_type());
const T *ptrmax = data->data;
T max_value = *ptrmax;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
}
return *ptrmax;
}
//! Return a reference to the maximum pixel value of the instance list.
T& max() {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::max() : Instance image list is empty.",
pixel_type());
T *ptrmax = data->data;
T max_value = *ptrmax;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) if ((*ptr)>max_value) max_value = *(ptrmax=ptr);
}
return *ptrmax;
}
//! Return a reference to the minimum pixel value of the instance list.
const T& min() const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.",
pixel_type());
const T *ptrmin = data->data;
T min_value = *ptrmin;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
}
return *ptrmin;
}
//! Return a reference to the minimum pixel value of the instance list.
T& min() {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::min() : Instance image list is empty.",
pixel_type());
T *ptrmin = data->data;
T min_value = *ptrmin;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) if ((*ptr)<min_value) min_value = *(ptrmin=ptr);
}
return *ptrmin;
}
//! Return a reference to the minimum pixel value of the instance list.
template<typename t>
const T& minmax(t& max_val) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.",
pixel_type());
const T *ptrmin = data->data;
T min_value = *ptrmin, max_value = min_value;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) {
const T val = *ptr;
if (val<min_value) { min_value = val; ptrmin = ptr; }
if (val>max_value) max_value = val;
}
}
max_val = (t)max_value;
return *ptrmin;
}
//! Return a reference to the minimum pixel value of the instance list.
template<typename t>
T& minmax(t& max_val) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::minmax() : Instance image list is empty.",
pixel_type());
T *ptrmin = data->data;
T min_value = *ptrmin, max_value = min_value;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) {
const T val = *ptr;
if (val<min_value) { min_value = val; ptrmin = ptr; }
if (val>max_value) max_value = val;
}
}
max_val = (t)max_value;
return *ptrmin;
}
//! Return a reference to the minimum pixel value of the instance list.
template<typename t>
const T& maxmin(t& min_val) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.",
pixel_type());
const T *ptrmax = data->data;
T min_value = *ptrmax, max_value = min_value;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) {
const T val = *ptr;
if (val>max_value) { max_value = val; ptrmax = ptr; }
if (val<min_value) min_value = val;
}
}
min_val = (t)min_value;
return *ptrmax;
}
//! Return a reference to the minimum pixel value of the instance list.
template<typename t>
T& maxmin(t& min_val) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::maxmin() : Instance image list is empty.",
pixel_type());
T *ptrmax = data->data;
T min_value = *ptrmax, max_value = min_value;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) {
const T val = *ptr;
if (val>max_value) { max_value = val; ptrmax = ptr; }
if (val<min_value) min_value = val;
}
}
min_val = (t)min_value;
return *ptrmax;
}
//! Return the mean pixel value of the instance list.
double mean() const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::mean() : Instance image list is empty.",
pixel_type());
double val = 0;
unsigned int siz = 0;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) val+=(double)*ptr;
siz+=img.size();
}
return val/siz;
}
//! Return the variance of the instance list.
double variance() {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::variance() : Instance image list is empty.",
pixel_type());
double res = 0;
unsigned int siz = 0;
double S = 0, S2 = 0;
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_for(img,ptr,T) { const double val = (double)*ptr; S+=val; S2+=val*val; }
siz+=img.size();
}
res = (S2 - S*S/siz)/siz;
return res;
}
//! Compute a list of statistics vectors (min,max,mean,variance,xmin,ymin,zmin,vmin,xmax,ymax,zmax,vmax).
CImgList<T>& stats(const unsigned int variance_method=1) {
if (is_empty()) return *this;
cimglist_for(*this,l) data[l].stats(variance_method);
return *this;
}
CImgList<Tfloat> get_stats(const unsigned int variance_method=1) const {
CImgList<Tfloat> res(size);
cimglist_for(*this,l) res[l] = data[l].get_stats(variance_method);
return res;
}
//@}
//-------------------------
//
//! \name List Manipulation
//@{
//-------------------------
//! Return a reference to the i-th element of the image list.
CImg<T>& operator[](const unsigned int pos) {
#if cimg_debug>=3
if (pos>=size) {
cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",
pixel_type(),pos,size);
return *data;
}
#endif
return data[pos];
}
const CImg<T>& operator[](const unsigned int pos) const {
#if cimg_debug>=3
if (pos>=size) {
cimg::warn("CImgList<%s>::operator[] : bad list position %u, in a list of %u images",
pixel_type(),pos,size);
return *data;
}
#endif
return data[pos];
}
//! Equivalent to CImgList<T>::operator[]
CImg<T>& operator()(const unsigned int pos) {
return (*this)[pos];
}
const CImg<T>& operator()(const unsigned int pos) const {
return (*this)[pos];
}
//! Return a reference to (x,y,z,v) pixel of the pos-th image of the list
T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
const unsigned int z=0, const unsigned int v=0) {
return (*this)[pos](x,y,z,v);
}
const T& operator()(const unsigned int pos, const unsigned int x, const unsigned int y=0,
const unsigned int z=0, const unsigned int v=0) const {
return (*this)[pos](x,y,z,v);
}
// This function is only here for template tricks.
T _display_object3d_at2(const int i, const int j) const {
return atNXY(i,0,j,0,0,0);
}
//! Read an image in specified position.
CImg<T>& at(const int pos) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::at() : Instance list is empty.",
pixel_type());
return data[pos<0?0:pos>=(int)size?(int)size-1:pos];
}
//! Read a pixel value with Dirichlet boundary conditions.
T& atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZV(x,y,z,v,out_val);
}
T atNXYZV(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZV(x,y,z,v,out_val);
}
//! Read a pixel value with Neumann boundary conditions.
T& atNXYZV(const int pos, const int x, const int y, const int z, const int v) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.",
pixel_type());
return _atNXYZV(pos,x,y,z,v);
}
T atNXYZV(const int pos, const int x, const int y, const int z, const int v) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNXYZV() : Instance list is empty.",
pixel_type());
return _atNXYZV(pos,x,y,z,v);
}
T& _atNXYZV(const int pos, const int x, const int y, const int z, const int v) {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v);
}
T _atNXYZV(const int pos, const int x, const int y, const int z, const int v) const {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZV(x,y,z,v);
}
//! Read a pixel value with Dirichlet boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
T& atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXYZ(x,y,z,v,out_val);
}
T atNXYZ(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
return (pos<0 || pos>=(int)size)?out_val:data[pos].atXYZ(x,y,z,v,out_val);
}
//! Read a pixel value with Neumann boundary conditions for the four first coordinates (\c pos, \c x,\c y,\c z).
T& atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.",
pixel_type());
return _atNXYZ(pos,x,y,z,v);
}
T atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNXYZ() : Instance list is empty.",
pixel_type());
return _atNXYZ(pos,x,y,z,v);
}
T& _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v);
}
T _atNXYZ(const int pos, const int x, const int y, const int z, const int v=0) const {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXYZ(x,y,z,v);
}
//! Read a pixel value with Dirichlet boundary conditions for the three first coordinates (\c pos, \c x,\c y).
T& atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atXY(x,y,z,v,out_val);
}
T atNXY(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
return (pos<0 || pos>=(int)size)?out_val:data[pos].atXY(x,y,z,v,out_val);
}
//! Read a pixel value with Neumann boundary conditions for the three first coordinates (\c pos, \c x,\c y).
T& atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.",
pixel_type());
return _atNXY(pos,x,y,z,v);
}
T atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNXY() : Instance list is empty.",
pixel_type());
return _atNXY(pos,x,y,z,v);
}
T& _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v);
}
T _atNXY(const int pos, const int x, const int y, const int z=0, const int v=0) const {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atXY(x,y,z,v);
}
//! Read a pixel value with Dirichlet boundary conditions for the two first coordinates (\c pos,\c x).
T& atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):data[pos].atX(x,y,z,v,out_val);
}
T atNX(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
return (pos<0 || pos>=(int)size)?out_val:data[pos].atX(x,y,z,v,out_val);
}
//! Read a pixel value with Neumann boundary conditions for the two first coordinates (\c pos, \c x).
T& atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.",
pixel_type());
return _atNX(pos,x,y,z,v);
}
T atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atNX() : Instance list is empty.",
pixel_type());
return _atNX(pos,x,y,z,v);
}
T& _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v);
}
T _atNX(const int pos, const int x, const int y=0, const int z=0, const int v=0) const {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)].atX(x,y,z,v);
}
//! Read a pixel value with Dirichlet boundary conditions for the first coordinates (\c pos).
T& atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) {
return (pos<0 || pos>=(int)size)?(cimg::temporary(out_val)=out_val):(*this)(pos,x,y,z,v);
}
T atN(const int pos, const int x, const int y, const int z, const int v, const T out_val) const {
return (pos<0 || pos>=(int)size)?out_val:(*this)(pos,x,y,z,v);
}
//! Read a pixel value with Neumann boundary conditions for the first coordinates (\c pos).
T& atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.",
pixel_type());
return _atN(pos,x,y,z,v);
}
T atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::atN() : Instance list is empty.",
pixel_type());
return _atN(pos,x,y,z,v);
}
T& _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v);
}
T _atN(const int pos, const int x=0, const int y=0, const int z=0, const int v=0) const {
return data[pos<0?0:(pos>=(int)size?(int)size-1:pos)](x,y,z,v);
}
//! Returns a reference to the last element.
CImg<T>& back() {
return (*this)(size-1);
}
const CImg<T>& back() const {
return (*this)(size-1);
}
//! Returns a reference to the first element.
CImg<T>& front() {
return *data;
}
const CImg<T>& front() const {
return *data;
}
//! Returns an iterator to the beginning of the vector.
iterator begin() {
return data;
}
const_iterator begin() const {
return data;
}
//! Return a reference to the first image.
const CImg<T>& first() const {
return *data;
}
CImg<T>& first() {
return *data;
}
//! Returns an iterator just past the last element.
iterator end() {
return data + size;
}
const_iterator end() const {
return data + size;
}
//! Return a reference to the last image.
const CImg<T>& last() const {
return data[size - 1];
}
CImg<T>& last() {
return data[size - 1];
}
//! Insert a copy of the image \p img into the current image list, at position \p pos.
template<typename t>
CImgList<T>& insert(const CImg<t>& img, const unsigned int pos, const bool shared) {
const unsigned int npos = pos==~0U?size:pos;
if (npos>size)
throw CImgArgumentException("CImgList<%s>::insert() : Cannot insert at position %u into a list with %u elements",
pixel_type(),npos,size);
if (shared)
throw CImgArgumentException("CImgList<%s>::insert(): Cannot insert a shared image CImg<%s> into a CImgList<%s>",
pixel_type(),img.pixel_type(),pixel_type());
CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
if (!size || !data) {
data = new_data;
*data = img;
} else {
if (new_data) {
if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
cimg_std::memset(data,0,sizeof(CImg<T>)*(size-1));
delete[] data;
data = new_data;
}
else if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
data[npos] = img;
}
return *this;
}
CImgList<T>& insert(const CImg<T>& img, const unsigned int pos, const bool shared) {
const unsigned int npos = pos==~0U?size:pos;
if (npos>size)
throw CImgArgumentException("CImgList<%s>::insert() : Can't insert at position %u into a list with %u elements",
pixel_type(),npos,size);
if (&img>=data && &img<data+size) return insert(+img,pos,shared);
CImg<T> *new_data = (++size>allocsize)?new CImg<T>[allocsize?(allocsize<<=1):(allocsize=16)]:0;
if (!size || !data) {
data = new_data;
if (shared && img) {
data->width = img.width; data->height = img.height; data->depth = img.depth; data->dim = img.dim;
data->is_shared = true; data->data = img.data;
} else *data = img;
}
else {
if (new_data) {
if (npos) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos);
if (npos!=size-1) cimg_std::memcpy(new_data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
if (shared && img) {
new_data[npos].width = img.width; new_data[npos].height = img.height; new_data[npos].depth = img.depth;
new_data[npos].dim = img.dim; new_data[npos].is_shared = true; new_data[npos].data = img.data;
} else {
new_data[npos].width = new_data[npos].height = new_data[npos].depth = new_data[npos].dim = 0; new_data[npos].data = 0;
new_data[npos] = img;
}
cimg_std::memset(data,0,sizeof(CImg<T>)*(size-1));
delete[] data;
data = new_data;
} else {
if (npos!=size-1) cimg_std::memmove(data+npos+1,data+npos,sizeof(CImg<T>)*(size-1-npos));
if (shared && img) {
data[npos].width = img.width; data[npos].height = img.height; data[npos].depth = img.depth; data[npos].dim = img.dim;
data[npos].is_shared = true; data[npos].data = img.data;
} else {
data[npos].width = data[npos].height = data[npos].depth = data[npos].dim = 0; data[npos].data = 0;
data[npos] = img;
}
}
}
return *this;
}
// The two functions below are necessary due to Visual C++ 6.0 function overloading bugs, when
// default parameters are used in function signatures.
template<typename t>
CImgList<T>& insert(const CImg<t>& img, const unsigned int pos) {
return insert(img,pos,false);
}
//! Insert a copy of the image \p img into the current image list, at position \p pos.
template<typename t>
CImgList<T>& insert(const CImg<t>& img) {
return insert(img,~0U,false);
}
template<typename t>
CImgList<T> get_insert(const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
return (+*this).insert(img,pos,shared);
}
//! Insert n empty images img into the current image list, at position \p pos.
CImgList<T>& insert(const unsigned int n, const unsigned int pos=~0U) {
CImg<T> foo;
if (!n) return *this;
const unsigned int npos = pos==~0U?size:pos;
for (unsigned int i=0; i<n; ++i) insert(foo,npos+i);
return *this;
}
CImgList<T> get_insert(const unsigned int n, const unsigned int pos=~0U) const {
return (+*this).insert(n,pos);
}
//! Insert n copies of the image \p img into the current image list, at position \p pos.
template<typename t>
CImgList<T>& insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) {
if (!n) return *this;
const unsigned int npos = pos==~0U?size:pos;
insert(img,npos,shared);
for (unsigned int i=1; i<n; ++i) insert(data[npos],npos+i,shared);
return *this;
}
template<typename t>
CImgList<T> get_insert(const unsigned int n, const CImg<t>& img, const unsigned int pos=~0U, const bool shared=false) const {
return (+*this).insert(n,img,pos,shared);
}
//! Insert a copy of the image list \p list into the current image list, starting from position \p pos.
template<typename t>
CImgList<T>& insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
const unsigned int npos = pos==~0U?size:pos;
if ((void*)this!=(void*)&list) cimglist_for(list,l) insert(list[l],npos+l,shared);
else insert(CImgList<T>(list),npos,shared);
return *this;
}
template<typename t>
CImgList<T> get_insert(const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
return (+*this).insert(list,pos,shared);
}
//! Insert n copies of the list \p list at position \p pos of the current list.
template<typename t>
CImgList<T>& insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) {
if (!n) return *this;
const unsigned int npos = pos==~0U?size:pos;
for (unsigned int i=0; i<n; ++i) insert(list,npos,shared);
return *this;
}
template<typename t>
CImgList<T> get_insert(const unsigned int n, const CImgList<t>& list, const unsigned int pos=~0U, const bool shared=false) const {
return (+*this).insert(n,list,pos,shared);
}
//! Insert a copy of the image \p img at the end of the current image list.
template<typename t>
CImgList<T>& operator<<(const CImg<t>& img) {
return insert(img);
}
//! Insert a copy of the image list \p list at the end of the current image list.
template<typename t>
CImgList<T>& operator<<(const CImgList<t>& list) {
return insert(list);
}
//! Return a copy of the current image list, where the image \p img has been inserted at the end.
template<typename t>
CImgList<T>& operator>>(CImg<t>& img) const {
typedef typename cimg::superset<T,t>::type Tt;
return CImgList<Tt>(*this).insert(img);
}
//! Insert a copy of the current image list at the beginning of the image list \p list.
template<typename t>
CImgList<T>& operator>>(CImgList<t>& list) const {
return list.insert(*this,0);
}
//! Remove the images at positions \p pos1 to \p pos2 from the image list.
CImgList<T>& remove(const unsigned int pos1, const unsigned int pos2) {
const unsigned int
npos1 = pos1<pos2?pos1:pos2,
tpos2 = pos1<pos2?pos2:pos1,
npos2 = tpos2<size?tpos2:size-1;
if (npos1>=size)
cimg::warn("CImgList<%s>::remove() : Cannot remove images from a list (%p,%u), at positions %u->%u.",
pixel_type(),data,size,npos1,tpos2);
else {
if (tpos2>=size)
cimg::warn("CImgList<%s>::remove() : Cannot remove all images from a list (%p,%u), at positions %u->%u.",
pixel_type(),data,size,npos1,tpos2);
for (unsigned int k = npos1; k<=npos2; ++k) data[k].assign();
const unsigned int nb = 1 + npos2 - npos1;
if (!(size-=nb)) return assign();
if (size>(allocsize>>2) || allocsize<=8) { // Removing items without reallocation.
if (npos1!=size) cimg_std::memmove(data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
cimg_std::memset(data+size,0,sizeof(CImg<T>)*nb);
} else { // Removing items with reallocation.
allocsize>>=2;
while (allocsize>8 && size<(allocsize>>1)) allocsize>>=1;
CImg<T> *new_data = new CImg<T>[allocsize];
if (npos1) cimg_std::memcpy(new_data,data,sizeof(CImg<T>)*npos1);
if (npos1!=size) cimg_std::memcpy(new_data+npos1,data+npos2+1,sizeof(CImg<T>)*(size-npos1));
if (size!=allocsize) cimg_std::memset(new_data+size,0,sizeof(allocsize-size));
cimg_std::memset(data,0,sizeof(CImg<T>)*(size+nb));
delete[] data;
data = new_data;
}
}
return *this;
}
CImgList<T> get_remove(const unsigned int pos1, const unsigned int pos2) const {
return (+*this).remove(pos1,pos2);
}
//! Remove the image at position \p pos from the image list.
CImgList<T>& remove(const unsigned int pos) {
return remove(pos,pos);
}
CImgList<T> get_remove(const unsigned int pos) const {
return (+*this).remove(pos);
}
//! Remove the last image from the image list.
CImgList<T>& remove() {
if (size) return remove(size-1);
else cimg::warn("CImgList<%s>::remove() : List is empty",
pixel_type());
return *this;
}
CImgList<T> get_remove() const {
return (+*this).remove();
}
//! Reverse list order.
CImgList<T>& reverse() {
for (unsigned int l=0; l<size/2; ++l) (*this)[l].swap((*this)[size-1-l]);
return *this;
}
CImgList<T> get_reverse() const {
return (+*this).reverse();
}
//! Get a sub-list.
CImgList<T>& crop(const unsigned int i0, const unsigned int i1, const bool shared=false) {
return get_crop(i0,i1,shared).transfer_to(*this);
}
CImgList<T> get_crop(const unsigned int i0, const unsigned int i1, const bool shared=false) const {
if (i0>i1 || i1>=size)
throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
pixel_type(),i0,i1,size,data);
CImgList<T> res(i1-i0+1);
cimglist_for(res,l) res[l].assign((*this)[i0+l],shared);
return res;
}
//! Get sub-images of a sublist.
CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
const int x0, const int y0, const int z0, const int v0,
const int x1, const int y1, const int z1, const int v1) {
return get_crop(i0,i1,x0,y0,z0,v0,x1,y1,z1,v1).transfer_to(*this);
}
CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
const int x0, const int y0, const int z0, const int v0,
const int x1, const int y1, const int z1, const int v1) const {
if (i0>i1 || i1>=size)
throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
pixel_type(),i0,i1,size,data);
CImgList<T> res(i1-i0+1);
cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,v0,x1,y1,z1,v1);
return res;
}
//! Get sub-images of a sublist.
CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1) {
return get_crop(i0,i1,x0,y0,z0,x1,y1,z1).transfer_to(*this);
}
CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
const int x0, const int y0, const int z0,
const int x1, const int y1, const int z1) const {
if (i0>i1 || i1>=size)
throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
pixel_type(),i0,i1,size,data);
CImgList<T> res(i1-i0+1);
cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,z0,x1,y1,z1);
return res;
}
//! Get sub-images of a sublist.
CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
const int x0, const int y0,
const int x1, const int y1) {
return get_crop(i0,i1,x0,y0,x1,y1).transfer_to(*this);
}
CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
const int x0, const int y0,
const int x1, const int y1) const {
if (i0>i1 || i1>=size)
throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
pixel_type(),i0,i1,size,data);
CImgList<T> res(i1-i0+1);
cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,y0,x1,y1);
return res;
}
//! Get sub-images of a sublist.
CImgList<T>& crop(const unsigned int i0, const unsigned int i1,
const int x0, const int x1) {
return get_crop(i0,i1,x0,x1).transfer_to(*this);
}
CImgList<T> get_crop(const unsigned int i0, const unsigned int i1,
const int x0, const int x1) const {
if (i0>i1 || i1>=size)
throw CImgArgumentException("CImgList<%s>::crop() : Cannot crop a sub-list (%u->%u) from a list (%u,%p)",
pixel_type(),i0,i1,size,data);
CImgList<T> res(i1-i0+1);
cimglist_for(res,l) res[l] = (*this)[i0+l].get_crop(x0,x1);
return res;
}
//! Display an image list into a CImgDisplay.
const CImgList<T>& operator>>(CImgDisplay& disp) const {
return display(disp);
}
//! Insert image \p img at the end of the list.
template<typename t>
CImgList<T>& push_back(const CImg<t>& img) {
return insert(img);
}
//! Insert image \p img at the front of the list.
template<typename t>
CImgList<T>& push_front(const CImg<t>& img) {
return insert(img,0);
}
//! Insert list \p list at the end of the current list.
template<typename t>
CImgList<T>& push_back(const CImgList<t>& list) {
return insert(list);
}
//! Insert list \p list at the front of the current list.
template<typename t>
CImgList<T>& push_front(const CImgList<t>& list) {
return insert(list,0);
}
//! Remove last element of the list.
CImgList<T>& pop_back() {
return remove(size-1);
}
//! Remove first element of the list.
CImgList<T>& pop_front() {
return remove(0);
}
//! Remove the element pointed by iterator \p iter.
CImgList<T>& erase(const iterator iter) {
return remove(iter-data);
}
//@}
//----------------------------
//
//! \name Fourier Transforms
//@{
//----------------------------
//! Compute the Fast Fourier Transform (along the specified axis).
CImgList<T>& FFT(const char axis, const bool invert=false) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",
pixel_type(),size,data);
if (!data[0])
throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) is empty",
pixel_type(),data[0].width,data[0].height,data[0].depth,data[0].dim,data[0].data);
if (size>2)
cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",
pixel_type(),size,data);
if (size==1) insert(CImg<T>(data[0].width,data[0].height,data[0].depth,data[0].dim,0));
CImg<T> &Ir = data[0], &Ii = data[1];
if (Ir.width!=Ii.width || Ir.height!=Ii.height || Ir.depth!=Ii.depth || Ir.dim!=Ii.dim)
throw CImgInstanceException("CImgList<%s>::FFT() : Real part (%u,%u,%u,%u,%p) and imaginary part (%u,%u,%u,%u,%p)"
"have different dimensions",
pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);
#ifdef cimg_use_fftw3
fftw_complex *data_in;
fftw_plan data_plan;
switch (cimg::uncase(axis)) {
case 'x' : {
data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex)*Ir.width);
data_plan = fftw_plan_dft_1d(Ir.width,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
cimg_forYZV(Ir,y,z,k) {
T *ptrr = Ir.ptr(0,y,z,k), *ptri = Ii.ptr(0,y,z,k);
double *ptrd = (double*)data_in;
cimg_forX(Ir,x) { *(ptrd++) = (double)*(ptrr++); *(ptrd++) = (double)*(ptri++); }
fftw_execute(data_plan);
const unsigned int fact = Ir.width;
if (invert) { cimg_forX(Ir,x) { *(--ptri) = (T)(*(--ptrd)/fact); *(--ptrr) = (T)(*(--ptrd)/fact); }}
else { cimg_forX(Ir,x) { *(--ptri) = (T)*(--ptrd); *(--ptrr) = (T)*(--ptrd); }}
}
} break;
case 'y' : {
data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.height);
data_plan = fftw_plan_dft_1d(Ir.height,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
const unsigned int off = Ir.width;
cimg_forXZV(Ir,x,z,k) {
T *ptrr = Ir.ptr(x,0,z,k), *ptri = Ii.ptr(x,0,z,k);
double *ptrd = (double*)data_in;
cimg_forY(Ir,y) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
fftw_execute(data_plan);
const unsigned int fact = Ir.height;
if (invert) { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
else { cimg_forY(Ir,y) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
}
} break;
case 'z' : {
data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.depth);
data_plan = fftw_plan_dft_1d(Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
const unsigned int off = Ir.width*Ir.height;
cimg_forXYV(Ir,x,y,k) {
T *ptrr = Ir.ptr(x,y,0,k), *ptri = Ii.ptr(x,y,0,k);
double *ptrd = (double*)data_in;
cimg_forZ(Ir,z) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
fftw_execute(data_plan);
const unsigned int fact = Ir.depth;
if (invert) { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
else { cimg_forZ(Ir,z) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
}
} break;
case 'v' : {
data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.dim);
data_plan = fftw_plan_dft_1d(Ir.dim,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
const unsigned int off = Ir.width*Ir.height*Ir.depth;
cimg_forXYZ(Ir,x,y,z) {
T *ptrr = Ir.ptr(x,y,z,0), *ptri = Ii.ptr(x,y,z,0);
double *ptrd = (double*)data_in;
cimg_forV(Ir,k) { *(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri; ptrr+=off; ptri+=off; }
fftw_execute(data_plan);
const unsigned int fact = Ir.dim;
if (invert) { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)(*(--ptrd)/fact); *ptrr = (T)(*(--ptrd)/fact); }}
else { cimg_forV(Ir,k) { ptrr-=off; ptri-=off; *ptri = (T)*(--ptrd); *ptrr = (T)*(--ptrd); }}
}
} break;
}
fftw_destroy_plan(data_plan);
fftw_free(data_in);
#else
switch (cimg::uncase(axis)) {
case 'x' : { // Fourier along X
const unsigned int N = Ir.width, N2 = (N>>1);
if (((N-1)&N) && N!=1)
throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image along 'x' is %d != 2^N",
pixel_type(),N);
for (unsigned int i=0, j=0; i<N2; ++i) {
if (j>i) cimg_forYZV(Ir,y,z,v) { cimg::swap(Ir(i,y,z,v),Ir(j,y,z,v)); cimg::swap(Ii(i,y,z,v),Ii(j,y,z,v));
if (j<N2) {
const unsigned int ri = N-1-i, rj = N-1-j;
cimg::swap(Ir(ri,y,z,v),Ir(rj,y,z,v)); cimg::swap(Ii(ri,y,z,v),Ii(rj,y,z,v));
}}
for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
}
for (unsigned int delta=2; delta<=N; delta<<=1) {
const unsigned int delta2 = (delta>>1);
for (unsigned int i=0; i<N; i+=delta) {
float wr = 1, wi = 0;
const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
ca = (float)cimg_std::cos(angle),
sa = (float)cimg_std::sin(angle);
for (unsigned int k=0; k<delta2; ++k) {
const unsigned int j = i + k, nj = j + delta2;
cimg_forYZV(Ir,y,z,k) {
T &ir = Ir(j,y,z,k), &ii = Ii(j,y,z,k), &nir = Ir(nj,y,z,k), &nii = Ii(nj,y,z,k);
const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
nir = (T)(ir - tmpr);
nii = (T)(ii - tmpi);
ir += (T)tmpr;
ii += (T)tmpi;
}
const float nwr = wr*ca-wi*sa;
wi = wi*ca + wr*sa;
wr = nwr;
}
}
}
if (invert) (*this)/=N;
} break;
case 'y' : { // Fourier along Y
const unsigned int N = Ir.height, N2 = (N>>1);
if (((N-1)&N) && N!=1)
throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'y' is %d != 2^N",
pixel_type(),N);
for (unsigned int i=0, j=0; i<N2; ++i) {
if (j>i) cimg_forXZV(Ir,x,z,v) { cimg::swap(Ir(x,i,z,v),Ir(x,j,z,v)); cimg::swap(Ii(x,i,z,v),Ii(x,j,z,v));
if (j<N2) {
const unsigned int ri = N-1-i, rj = N-1-j;
cimg::swap(Ir(x,ri,z,v),Ir(x,rj,z,v)); cimg::swap(Ii(x,ri,z,v),Ii(x,rj,z,v));
}}
for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
}
for (unsigned int delta=2; delta<=N; delta<<=1) {
const unsigned int delta2 = (delta>>1);
for (unsigned int i=0; i<N; i+=delta) {
float wr = 1, wi = 0;
const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
ca = (float)cimg_std::cos(angle), sa = (float)cimg_std::sin(angle);
for (unsigned int k=0; k<delta2; ++k) {
const unsigned int j = i + k, nj = j + delta2;
cimg_forXZV(Ir,x,z,k) {
T &ir = Ir(x,j,z,k), &ii = Ii(x,j,z,k), &nir = Ir(x,nj,z,k), &nii = Ii(x,nj,z,k);
const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
nir = (T)(ir - tmpr);
nii = (T)(ii - tmpi);
ir += (T)tmpr;
ii += (T)tmpi;
}
const float nwr = wr*ca-wi*sa;
wi = wi*ca + wr*sa;
wr = nwr;
}
}
}
if (invert) (*this)/=N;
} break;
case 'z' : { // Fourier along Z
const unsigned int N = Ir.depth, N2 = (N>>1);
if (((N-1)&N) && N!=1)
throw CImgInstanceException("CImgList<%s>::FFT() : Dimension of instance image(s) along 'z' is %d != 2^N",
pixel_type(),N);
for (unsigned int i=0, j=0; i<N2; ++i) {
if (j>i) cimg_forXYV(Ir,x,y,v) { cimg::swap(Ir(x,y,i,v),Ir(x,y,j,v)); cimg::swap(Ii(x,y,i,v),Ii(x,y,j,v));
if (j<N2) {
const unsigned int ri = N-1-i, rj = N-1-j;
cimg::swap(Ir(x,y,ri,v),Ir(x,y,rj,v)); cimg::swap(Ii(x,y,ri,v),Ii(x,y,rj,v));
}}
for (unsigned int m=N, n=N2; (j+=n)>=m; j-=m, m=n, n>>=1) {}
}
for (unsigned int delta=2; delta<=N; delta<<=1) {
const unsigned int delta2 = (delta>>1);
for (unsigned int i=0; i<N; i+=delta) {
float wr = 1, wi = 0;
const float angle = (float)((invert?+1:-1)*2*cimg::valuePI/delta),
ca = (float)cimg_std::cos(angle), sa = (float)cimg_std::sin(angle);
for (unsigned int k=0; k<delta2; ++k) {
const unsigned int j = i + k, nj = j + delta2;
cimg_forXYV(Ir,x,y,k) {
T &ir = Ir(x,y,j,k), &ii = Ii(x,y,j,k), &nir = Ir(x,y,nj,k), &nii = Ii(x,y,nj,k);
const float tmpr = (float)(wr*nir - wi*nii), tmpi = (float)(wr*nii + wi*nir);
nir = (T)(ir - tmpr);
nii = (T)(ii - tmpi);
ir += (T)tmpr;
ii += (T)tmpi;
}
const float nwr = wr*ca-wi*sa;
wi = wi*ca + wr*sa;
wr = nwr;
}
}
}
if (invert) (*this)/=N;
} break;
default :
throw CImgArgumentException("CImgList<%s>::FFT() : Invalid axis '%c', must be 'x','y' or 'z'.");
}
#endif
return *this;
}
CImgList<Tfloat> get_FFT(const char axis, const bool invert=false) const {
return CImgList<Tfloat>(*this).FFT(axis,invert);
}
//! Compute the Fast Fourier Transform of a complex image.
CImgList<T>& FFT(const bool invert=false) {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::FFT() : Instance list (%u,%p) is empty",
pixel_type(),size,data);
if (size>2)
cimg::warn("CImgList<%s>::FFT() : Instance list (%u,%p) have more than 2 images",
pixel_type(),size,data);
if (size==1) insert(CImg<T>(data->width,data->height,data->depth,data->dim,0));
CImg<T> &Ir = data[0], &Ii = data[1];
if (Ii.width!=Ir.width || Ii.height!=Ir.height || Ii.depth!=Ir.depth || Ii.dim!=Ir.dim)
throw CImgInstanceException("CImgList<%s>::FFT() : Real (%u,%u,%u,%u,%p) and Imaginary (%u,%u,%u,%u,%p) parts "
"of the instance image have different dimensions",
pixel_type(),Ir.width,Ir.height,Ir.depth,Ir.dim,Ir.data,
Ii.width,Ii.height,Ii.depth,Ii.dim,Ii.data);
#ifdef cimg_use_fftw3
fftw_complex *data_in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * Ir.width*Ir.height*Ir.depth);
fftw_plan data_plan;
const unsigned int w = Ir.width, wh = w*Ir.height, whd = wh*Ir.depth;
data_plan = fftw_plan_dft_3d(Ir.width,Ir.height,Ir.depth,data_in,data_in,invert?FFTW_BACKWARD:FFTW_FORWARD,FFTW_ESTIMATE);
cimg_forV(Ir,k) {
T *ptrr = Ir.ptr(0,0,0,k), *ptri = Ii.ptr(0,0,0,k);
double *ptrd = (double*)data_in;
for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
*(ptrd++) = (double)*ptrr; *(ptrd++) = (double)*ptri;
}
fftw_execute(data_plan);
ptrd = (double*)data_in;
ptrr = Ir.ptr(0,0,0,k);
ptri = Ii.ptr(0,0,0,k);
if (!invert) for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
*ptrr = (T)*(ptrd++); *ptri = (T)*(ptrd++);
}
else for (unsigned int x = 0; x<Ir.width; ++x, ptrr-=wh-1, ptri-=wh-1)
for (unsigned int y = 0; y<Ir.height; ++y, ptrr-=whd-w, ptri-=whd-w)
for (unsigned int z = 0; z<Ir.depth; ++z, ptrr+=wh, ptri+=wh) {
*ptrr = (T)(*(ptrd++)/whd); *ptri = (T)(*(ptrd++)/whd);
}
}
fftw_destroy_plan(data_plan);
fftw_free(data_in);
#else
if (Ir.depth>1) FFT('z',invert);
if (Ir.height>1) FFT('y',invert);
if (Ir.width>1) FFT('x',invert);
#endif
return *this;
}
CImgList<Tfloat> get_FFT(const bool invert=false) const {
return CImgList<Tfloat>(*this).FFT(invert);
}
// Return a list where each image has been split along the specified axis.
CImgList<T>& split(const char axis) {
return get_split(axis).transfer_to(*this);
}
CImgList<T> get_split(const char axis) const {
CImgList<T> res;
cimglist_for(*this,l) {
CImgList<T> tmp = data[l].get_split(axis);
const unsigned int pos = res.size;
res.insert(tmp.size);
cimglist_for(tmp,i) tmp[i].transfer_to(data[pos+i]);
}
return res;
}
//! Return a single image which is the concatenation of all images of the current CImgList instance.
/**
\param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
\param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
\return A CImg<T> image corresponding to the concatenation is returned.
**/
CImg<T> get_append(const char axis, const char align='p') const {
if (is_empty()) return CImg<T>();
if (size==1) return +((*this)[0]);
unsigned int dx = 0, dy = 0, dz = 0, dv = 0, pos = 0;
CImg<T> res;
switch (cimg::uncase(axis)) {
case 'x' : {
switch (cimg::uncase(align)) {
case 'x' : { dy = dz = dv = 1; cimglist_for(*this,l) dx+=(*this)[l].size(); } break;
case 'y' : { dx = size; dz = dv = 1; cimglist_for(*this,l) dy = cimg::max(dy,(unsigned int)(*this)[l].size()); } break;
case 'z' : { dx = size; dy = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
case 'v' : { dx = size; dy = dz = 1; cimglist_for(*this,l) dv = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
default :
cimglist_for(*this,l) {
const CImg<T>& img = (*this)[l];
dx += img.width;
dy = cimg::max(dy,img.height);
dz = cimg::max(dz,img.depth);
dv = cimg::max(dv,img.dim);
}
}
res.assign(dx,dy,dz,dv,0);
switch (cimg::uncase(align)) {
case 'x' : {
cimglist_for(*this,l) {
res.draw_image(pos,CImg<T>((*this)[l],true).unroll('x'));
pos+=(*this)[l].size();
}
} break;
case 'y' : {
cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('y'));
} break;
case 'z' : {
cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('z'));
} break;
case 'v' : {
cimglist_for(*this,l) res.draw_image(pos++,CImg<T>((*this)[l],true).unroll('v'));
} break;
case 'p' : {
cimglist_for(*this,l) { res.draw_image(pos,(*this)[l]); pos+=(*this)[l].width; }
} break;
case 'n' : {
cimglist_for(*this,l) {
res.draw_image(pos,dy-(*this)[l].height,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]);
pos+=(*this)[l].width;
}
} break;
default : {
cimglist_for(*this,l) {
res.draw_image(pos,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]);
pos+=(*this)[l].width;
}
} break;
}
} break;
case 'y' : {
switch (cimg::uncase(align)) {
case 'x' : { dy = size; dz = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
case 'y' : { dx = dz = dv = 1; cimglist_for(*this,l) dy+=(*this)[l].size(); } break;
case 'z' : { dy = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
case 'v' : { dy = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
default :
cimglist_for(*this,l) {
const CImg<T>& img = (*this)[l];
dx = cimg::max(dx,img.width);
dy += img.height;
dz = cimg::max(dz,img.depth);
dv = cimg::max(dv,img.dim);
}
}
res.assign(dx,dy,dz,dv,0);
switch (cimg::uncase(align)) {
case 'x' : {
cimglist_for(*this,l) res.draw_image(0,++pos,CImg<T>((*this)[l],true).unroll('x'));
} break;
case 'y' : {
cimglist_for(*this,l) {
res.draw_image(0,pos,CImg<T>((*this)[l],true).unroll('y'));
pos+=(*this)[l].size();
}
} break;
case 'z' : {
cimglist_for(*this,l) res.draw_image(0,pos++,CImg<T>((*this)[l],true).unroll('z'));
} break;
case 'v' : {
cimglist_for(*this,l) res.draw_image(0,pos++,CImg<T>((*this)[l],true).unroll('v'));
} break;
case 'p' : {
cimglist_for(*this,l) { res.draw_image(0,pos,(*this)[l]); pos+=(*this)[l].height; }
} break;
case 'n' : {
cimglist_for(*this,l) {
res.draw_image(dx-(*this)[l].width,pos,dz-(*this)[l].depth,dv-(*this)[l].dim,(*this)[l]);
pos+=(*this)[l].height;
}
} break;
default : {
cimglist_for(*this,l) {
res.draw_image((dx-(*this)[l].width)/2,pos,(dz-(*this)[l].depth)/2,(dv-(*this)[l].dim)/2,(*this)[l]);
pos+=(*this)[l].height;
}
} break;
}
} break;
case 'z' : {
switch (cimg::uncase(align)) {
case 'x' : { dz = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
case 'y' : { dz = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
case 'z' : { dx = dy = dv = 1; cimglist_for(*this,l) dz+=(*this)[l].size(); } break;
case 'v' : { dz = size; dx = dz = 1; cimglist_for(*this,l) dv = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
default :
cimglist_for(*this,l) {
const CImg<T>& img = (*this)[l];
dx = cimg::max(dx,img.width);
dy = cimg::max(dy,img.height);
dz += img.depth;
dv = cimg::max(dv,img.dim);
}
}
res.assign(dx,dy,dz,dv,0);
switch (cimg::uncase(align)) {
case 'x' : {
cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('x'));
} break;
case 'y' : {
cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('y'));
} break;
case 'z' : {
cimglist_for(*this,l) {
res.draw_image(0,0,pos,CImg<T>((*this)[l],true).unroll('z'));
pos+=(*this)[l].size();
}
} break;
case 'v' : {
cimglist_for(*this,l) res.draw_image(0,0,pos++,CImg<T>((*this)[l],true).unroll('v'));
} break;
case 'p' : {
cimglist_for(*this,l) { res.draw_image(0,0,pos,(*this)[l]); pos+=(*this)[l].depth; }
} break;
case 'n' : {
cimglist_for(*this,l) {
res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,pos,dv-(*this)[l].dim,(*this)[l]);
pos+=(*this)[l].depth;
}
} break;
case 'c' : {
cimglist_for(*this,l) {
res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,pos,(dv-(*this)[l].dim)/2,(*this)[l]);
pos+=(*this)[l].depth;
}
} break;
}
} break;
case 'v' : {
switch (cimg::uncase(align)) {
case 'x' : { dv = size; dy = dv = 1; cimglist_for(*this,l) dx = cimg::max(dx,(unsigned int)(*this)[l].size()); } break;
case 'y' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dy = cimg::max(dz,(unsigned int)(*this)[l].size()); } break;
case 'z' : { dv = size; dx = dv = 1; cimglist_for(*this,l) dz = cimg::max(dv,(unsigned int)(*this)[l].size()); } break;
case 'v' : { dx = dy = dz = 1; cimglist_for(*this,l) dv+=(*this)[l].size(); } break;
default :
cimglist_for(*this,l) {
const CImg<T>& img = (*this)[l];
dx = cimg::max(dx,img.width);
dy = cimg::max(dy,img.height);
dz = cimg::max(dz,img.depth);
dv += img.dim;
}
}
res.assign(dx,dy,dz,dv,0);
switch (cimg::uncase(align)) {
case 'x' : {
cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('x'));
} break;
case 'y' : {
cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('y'));
} break;
case 'z' : {
cimglist_for(*this,l) res.draw_image(0,0,0,pos++,CImg<T>((*this)[l],true).unroll('v'));
} break;
case 'v' : {
cimglist_for(*this,l) {
res.draw_image(0,0,0,pos,CImg<T>((*this)[l],true).unroll('z'));
pos+=(*this)[l].size();
}
} break;
case 'p' : {
cimglist_for(*this,l) { res.draw_image(0,0,0,pos,(*this)[l]); pos+=(*this)[l].dim; }
} break;
case 'n' : {
cimglist_for(*this,l) {
res.draw_image(dx-(*this)[l].width,dy-(*this)[l].height,dz-(*this)[l].depth,pos,(*this)[l]);
pos+=(*this)[l].dim;
}
} break;
case 'c' : {
cimglist_for(*this,l) {
res.draw_image((dx-(*this)[l].width)/2,(dy-(*this)[l].height)/2,(dz-(*this)[l].depth)/2,pos,(*this)[l]);
pos+=(*this)[l].dim;
}
} break;
}
} break;
default :
throw CImgArgumentException("CImgList<%s>::get_append() : unknow axis '%c', must be 'x','y','z' or 'v'",
pixel_type(),axis);
}
return res;
}
//! Create an auto-cropped font (along the X axis) from a input font \p font.
CImgList<T>& crop_font() {
return get_crop_font().transfer_to(*this);
}
CImgList<T> get_crop_font() const {
CImgList<T> res;
cimglist_for(*this,l) {
const CImg<T>& letter = (*this)[l];
int xmin = letter.width, xmax = 0;
cimg_forXY(letter,x,y) if (letter(x,y)) { if (x<xmin) xmin=x; if (x>xmax) xmax=x; }
if (xmin>xmax) res.insert(CImg<T>(letter.width,letter.height,1,letter.dim,0));
else res.insert(letter.get_crop(xmin,0,xmax,letter.height-1));
}
res[' '].resize(res['f'].width);
res[' '+256].resize(res['f'].width);
return res;
}
//! Invert primitives orientation of a 3D object.
CImgList<T>& invert_object3d() {
cimglist_for(*this,l) {
CImg<T>& p = data[l];
const unsigned int siz = p.size();
if (siz==2 || siz==3 || siz==6 || siz==9) cimg::swap(p[0],p[1]);
else if (siz==4 || siz==12) cimg::swap(p[0],p[3],p[1],p[2]);
}
return *this;
}
CImgList<T> get_invert_object3d() const {
return (+*this).invert_object3d();
}
//! Return a CImg pre-defined font with desired size.
/**
\param font_height = height of the desired font (can be 11,13,24,38 or 57)
\param fixed_size = tell if the font has a fixed or variable width.
**/
static CImgList<T> font(const unsigned int font_width, const bool variable_size=true) {
if (font_width<=11) {
static CImgList<T> font7x11, nfont7x11;
if (!variable_size && !font7x11) font7x11 = _font(cimg::font7x11,7,11,1,0,false);
if (variable_size && !nfont7x11) nfont7x11 = _font(cimg::font7x11,7,11,1,0,true);
return variable_size?nfont7x11:font7x11;
}
if (font_width<=13) {
static CImgList<T> font10x13, nfont10x13;
if (!variable_size && !font10x13) font10x13 = _font(cimg::font10x13,10,13,1,0,false);
if (variable_size && !nfont10x13) nfont10x13 = _font(cimg::font10x13,10,13,1,0,true);
return variable_size?nfont10x13:font10x13;
}
if (font_width<=17) {
static CImgList<T> font8x17, nfont8x17;
if (!variable_size && !font8x17) font8x17 = _font(cimg::font8x17,8,17,1,0,false);
if (variable_size && !nfont8x17) nfont8x17 = _font(cimg::font8x17,8,17,1,0,true);
return variable_size?nfont8x17:font8x17;
}
if (font_width<=19) {
static CImgList<T> font10x19, nfont10x19;
if (!variable_size && !font10x19) font10x19 = _font(cimg::font10x19,10,19,2,0,false);
if (variable_size && !nfont10x19) nfont10x19 = _font(cimg::font10x19,10,19,2,0,true);
return variable_size?nfont10x19:font10x19;
}
if (font_width<=24) {
static CImgList<T> font12x24, nfont12x24;
if (!variable_size && !font12x24) font12x24 = _font(cimg::font12x24,12,24,2,0,false);
if (variable_size && !nfont12x24) nfont12x24 = _font(cimg::font12x24,12,24,2,0,true);
return variable_size?nfont12x24:font12x24;
}
if (font_width<=32) {
static CImgList<T> font16x32, nfont16x32;
if (!variable_size && !font16x32) font16x32 = _font(cimg::font16x32,16,32,2,0,false);
if (variable_size && !nfont16x32) nfont16x32 = _font(cimg::font16x32,16,32,2,0,true);
return variable_size?nfont16x32:font16x32;
}
if (font_width<=38) {
static CImgList<T> font19x38, nfont19x38;
if (!variable_size && !font19x38) font19x38 = _font(cimg::font19x38,19,38,3,0,false);
if (variable_size && !nfont19x38) nfont19x38 = _font(cimg::font19x38,19,38,3,0,true);
return variable_size?nfont19x38:font19x38;
}
static CImgList<T> font29x57, nfont29x57;
if (!variable_size && !font29x57) font29x57 = _font(cimg::font29x57,29,57,5,0,false);
if (variable_size && !nfont29x57) nfont29x57 = _font(cimg::font29x57,29,57,5,0,true);
return variable_size?nfont29x57:font29x57;
}
static CImgList<T> _font(const unsigned int *const font, const unsigned int w, const unsigned int h,
const unsigned int paddingx, const unsigned int paddingy, const bool variable_size=true) {
CImgList<T> res = CImgList<T>(256,w,h,1,3).insert(CImgList<T>(256,w,h,1,1));
const unsigned int *ptr = font;
unsigned int m = 0, val = 0;
for (unsigned int y=0; y<h; ++y)
for (unsigned int x=0; x<256*w; ++x) {
m>>=1; if (!m) { m = 0x80000000; val = *(ptr++); }
CImg<T>& img = res[x/w], &mask = res[x/w+256];
unsigned int xm = x%w;
img(xm,y,0) = img(xm,y,1) = img(xm,y,2) = mask(xm,y,0) = (T)((val&m)?1:0);
}
if (variable_size) res.crop_font();
if (paddingx || paddingy) cimglist_for(res,l) res[l].resize(res[l].dimx()+paddingx, res[l].dimy()+paddingy,1,-100,0);
return res;
}
//! Display the current CImgList instance in an existing CImgDisplay window (by reference).
/**
This function displays the list images of the current CImgList instance into an existing CImgDisplay window.
Images of the list are concatenated in a single temporarly image for visualization purposes.
The function returns immediately.
\param disp : reference to an existing CImgDisplay instance, where the current image list will be displayed.
\param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
\param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
\return A reference to the current CImgList instance is returned.
**/
const CImgList<T>& display(CImgDisplay& disp, const char axis='x', const char align='p') const {
get_append(axis,align).display(disp);
return *this;
}
//! Display the current CImgList instance in a new display window.
/**
This function opens a new window with a specific title and displays the list images of the current CImgList instance into it.
Images of the list are concatenated in a single temporarly image for visualization purposes.
The function returns when a key is pressed or the display window is closed by the user.
\param title : specify the title of the opening display window.
\param axis : specify the axis for image concatenation. Can be 'x','y','z' or 'v'.
\param align : specify the alignment for image concatenation. Can be 'p' (top), 'c' (center) or 'n' (bottom).
\return A reference to the current CImgList instance is returned.
**/
const CImgList<T>& display(CImgDisplay &disp,
const bool display_info, const char axis='x', const char align='p') const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::display() : Instance list (%u,%u) is empty.",
pixel_type(),size,data);
const CImg<T> visu = get_append(axis,align);
if (display_info) print(disp.title);
visu.display(disp,false);
return *this;
}
//! Display the current CImgList instance in a new display window.
const CImgList<T>& display(const char *const title=0,
const bool display_info=true, const char axis='x', const char align='p') const {
const CImg<T> visu = get_append(axis,align);
char ntitle[64] = { 0 };
if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type());
if (display_info) print(title?title:ntitle);
visu.display(title?title:ntitle,false);
return *this;
}
//@}
//----------------------------------
//
//! \name Input-Output
//@{
//----------------------------------
//! Return a C-string containing the values of all images in the instance list.
CImg<charT> value_string(const char separator=',', const unsigned int max_size=0) const {
if (is_empty()) return CImg<ucharT>(1,1,1,1,0);
CImgList<charT> items;
for (unsigned int l = 0; l<size-1; ++l) {
CImg<charT> item = data[l].value_string(separator,0);
item[item.size()-1] = separator;
items.insert(item);
}
items.insert(data[size-1].value_string(separator,0));
CImg<charT> res = items.get_append('x');
if (max_size) { res.crop(0,max_size); res(max_size) = 0; }
return res;
}
//! Print informations about the list on the standard output.
const CImgList<T>& print(const char* title=0, const bool display_stats=true) const {
unsigned long msiz = 0;
cimglist_for(*this,l) msiz += data[l].size();
msiz*=sizeof(T);
const unsigned int mdisp = msiz<8*1024?0:(msiz<8*1024*1024?1:2);
char ntitle[64] = { 0 };
if (!title) cimg_std::sprintf(ntitle,"CImgList<%s>",pixel_type());
cimg_std::fprintf(cimg_stdout,"%s: this = %p, size = %u [%lu %s], data = (CImg<%s>*)%p.\n",
title?title:ntitle,(void*)this,size,
mdisp==0?msiz:(mdisp==1?(msiz>>10):(msiz>>20)),
mdisp==0?"b":(mdisp==1?"Kb":"Mb"),
pixel_type(),(void*)data);
char tmp[16] = { 0 };
cimglist_for(*this,ll) {
cimg_std::sprintf(tmp,"[%d]",ll);
cimg_std::fprintf(cimg_stdout," ");
data[ll].print(tmp,display_stats);
if (ll==3 && size>8) { ll = size-5; cimg_std::fprintf(cimg_stdout," ...\n"); }
}
return *this;
}
//! Load an image list from a file.
CImgList<T>& load(const char *const filename) {
const char *ext = cimg::split_filename(filename);
const unsigned int odebug = cimg::exception_mode();
cimg::exception_mode() = 0;
assign();
try {
#ifdef cimglist_load_plugin
cimglist_load_plugin(filename);
#endif
#ifdef cimglist_load_plugin1
cimglist_load_plugin1(filename);
#endif
#ifdef cimglist_load_plugin2
cimglist_load_plugin2(filename);
#endif
#ifdef cimglist_load_plugin3
cimglist_load_plugin3(filename);
#endif
#ifdef cimglist_load_plugin4
cimglist_load_plugin4(filename);
#endif
#ifdef cimglist_load_plugin5
cimglist_load_plugin5(filename);
#endif
#ifdef cimglist_load_plugin6
cimglist_load_plugin6(filename);
#endif
#ifdef cimglist_load_plugin7
cimglist_load_plugin7(filename);
#endif
#ifdef cimglist_load_plugin8
cimglist_load_plugin8(filename);
#endif
if (!cimg::strcasecmp(ext,"tif") ||
!cimg::strcasecmp(ext,"tiff")) load_tiff(filename);
if (!cimg::strcasecmp(ext,"cimg") ||
!cimg::strcasecmp(ext,"cimgz") ||
!ext[0]) load_cimg(filename);
if (!cimg::strcasecmp(ext,"rec") ||
!cimg::strcasecmp(ext,"par")) load_parrec(filename);
if (!cimg::strcasecmp(ext,"avi") ||
!cimg::strcasecmp(ext,"mov") ||
!cimg::strcasecmp(ext,"asf") ||
!cimg::strcasecmp(ext,"divx") ||
!cimg::strcasecmp(ext,"flv") ||
!cimg::strcasecmp(ext,"mpg") ||
!cimg::strcasecmp(ext,"m1v") ||
!cimg::strcasecmp(ext,"m2v") ||
!cimg::strcasecmp(ext,"m4v") ||
!cimg::strcasecmp(ext,"mjp") ||
!cimg::strcasecmp(ext,"mkv") ||
!cimg::strcasecmp(ext,"mpe") ||
!cimg::strcasecmp(ext,"movie") ||
!cimg::strcasecmp(ext,"ogm") ||
!cimg::strcasecmp(ext,"qt") ||
!cimg::strcasecmp(ext,"rm") ||
!cimg::strcasecmp(ext,"vob") ||
!cimg::strcasecmp(ext,"wmv") ||
!cimg::strcasecmp(ext,"xvid") ||
!cimg::strcasecmp(ext,"mpeg")) load_ffmpeg(filename);
if (!cimg::strcasecmp(ext,"gz")) load_gzip_external(filename);
if (is_empty()) throw CImgIOException("CImgList<%s>::load()",pixel_type());
} catch (CImgIOException& e) {
if (!cimg::strncasecmp(e.message,"cimg::fopen()",13)) {
cimg::exception_mode() = odebug;
throw CImgIOException("CImgList<%s>::load() : File '%s' cannot be opened.",pixel_type(),filename);
} else try {
assign(1);
data->load(filename);
} catch (CImgException&) {
assign();
}
}
cimg::exception_mode() = odebug;
if (is_empty())
throw CImgIOException("CImgList<%s>::load() : File '%s', format not recognized.",pixel_type(),filename);
return *this;
}
static CImgList<T> get_load(const char *const filename) {
return CImgList<T>().load(filename);
}
//! Load an image list from a .cimg file.
CImgList<T>& load_cimg(const char *const filename) {
return _load_cimg(0,filename);
}
static CImgList<T> get_load_cimg(const char *const filename) {
return CImgList<T>().load_cimg(filename);
}
//! Load an image list from a .cimg file.
CImgList<T>& load_cimg(cimg_std::FILE *const file) {
return _load_cimg(file,0);
}
static CImgList<T> get_load_cimg(cimg_std::FILE *const file) {
return CImgList<T>().load_cimg(file);
}
CImgList<T>& _load_cimg(cimg_std::FILE *const file, const char *const filename) {
#ifdef cimg_use_zlib
#define _cimgz_load_cimg_case(Tss) { \
Bytef *const cbuf = new Bytef[csiz]; \
cimg::fread(cbuf,csiz,nfile); \
raw.assign(W,H,D,V); \
unsigned long destlen = raw.size()*sizeof(T); \
uncompress((Bytef*)raw.data,&destlen,cbuf,csiz); \
delete[] cbuf; \
const Tss *ptrs = raw.data; \
for (unsigned int off = raw.size(); off; --off) *(ptrd++) = (T)*(ptrs++); \
}
#else
#define _cimgz_load_cimg_case(Tss) \
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s' contains compressed data, zlib must be used",\
pixel_type(),filename?filename:"(FILE*)");
#endif
#define _cimg_load_cimg_case(Ts,Tss) \
if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
for (unsigned int l = 0; l<N; ++l) { \
j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \
W = H = D = V = 0; csiz = 0; \
if ((err = cimg_std::sscanf(tmp,"%u %u %u %u #%u",&W,&H,&D,&V,&csiz))<4) \
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
pixel_type(),filename?filename:("(FILE*)"),W,H,D,V); \
if (W*H*D*V>0) { \
CImg<Tss> raw; \
CImg<T> &img = data[l]; \
img.assign(W,H,D,V); \
T *ptrd = img.data; \
if (err==5) _cimgz_load_cimg_case(Tss) \
else for (int toread = (int)img.size(); toread>0; ) { \
raw.assign(cimg::min(toread,cimg_iobuffer)); \
cimg::fread(raw.data,raw.width,nfile); \
if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \
toread-=raw.width; \
const Tss *ptrs = raw.data; \
for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
} \
} \
} \
loaded = true; \
}
if (!filename && !file)
throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.",
pixel_type());
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
const int cimg_iobuffer = 12*1024*1024;
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
bool loaded = false, endian = cimg::endianness();
char tmp[256], str_pixeltype[256], str_endian[256];
unsigned int j, err, N = 0, W, H, D, V, csiz;
int i;
j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
if (err<2) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.",
pixel_type(),filename?filename:"(FILE*)");
}
if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
assign(N);
_cimg_load_cimg_case("bool",bool);
_cimg_load_cimg_case("unsigned_char",uchar);
_cimg_load_cimg_case("uchar",uchar);
_cimg_load_cimg_case("char",char);
_cimg_load_cimg_case("unsigned_short",ushort);
_cimg_load_cimg_case("ushort",ushort);
_cimg_load_cimg_case("short",short);
_cimg_load_cimg_case("unsigned_int",uint);
_cimg_load_cimg_case("uint",uint);
_cimg_load_cimg_case("int",int);
_cimg_load_cimg_case("unsigned_long",ulong);
_cimg_load_cimg_case("ulong",ulong);
_cimg_load_cimg_case("long",long);
_cimg_load_cimg_case("float",float);
_cimg_load_cimg_case("double",double);
if (!loaded) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load a sub-image list from a non compressed .cimg file.
CImgList<T>& load_cimg(const char *const filename,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
return _load_cimg(0,filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
}
static CImgList<T> get_load_cimg(const char *const filename,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
return CImgList<T>().load_cimg(filename,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
}
//! Load a sub-image list from a non compressed .cimg file.
CImgList<T>& load_cimg(cimg_std::FILE *const file,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
return _load_cimg(file,0,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
}
static CImgList<T> get_load_cimg(cimg_std::FILE *const file,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
return CImgList<T>().load_cimg(file,n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
}
CImgList<T>& _load_cimg(cimg_std::FILE *const file, const char *const filename,
const unsigned int n0, const unsigned int n1,
const unsigned int x0, const unsigned int y0, const unsigned int z0, const unsigned int v0,
const unsigned int x1, const unsigned int y1, const unsigned int z1, const unsigned int v1) {
#define _cimg_load_cimg_case2(Ts,Tss) \
if (!loaded && !cimg::strcasecmp(Ts,str_pixeltype)) { \
for (unsigned int l = 0; l<=nn1; ++l) { \
j = 0; while ((i=cimg_std::fgetc(nfile))!='\n' && i>=0) tmp[j++] = (char)i; tmp[j] = '\0'; \
W = H = D = V = 0; \
if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
if (W*H*D*V>0) { \
if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
else { \
const unsigned int \
nx1 = x1>=W?W-1:x1, \
ny1 = y1>=H?H-1:y1, \
nz1 = z1>=D?D-1:z1, \
nv1 = v1>=V?V-1:v1; \
CImg<Tss> raw(1+nx1-x0); \
CImg<T> &img = data[l-n0]; \
img.assign(1+nx1-x0,1+ny1-y0,1+nz1-z0,1+nv1-v0); \
T *ptrd = img.data; \
const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \
for (unsigned int v=1+nv1-v0; v; --v) { \
const unsigned int skipzb = z0*W*H*sizeof(Tss); \
if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \
for (unsigned int z=1+nz1-z0; z; --z) { \
const unsigned int skipyb = y0*W*sizeof(Tss); \
if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \
for (unsigned int y=1+ny1-y0; y; --y) { \
const unsigned int skipxb = x0*sizeof(Tss); \
if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \
cimg::fread(raw.data,raw.width,nfile); \
if (endian!=cimg::endianness()) cimg::invert_endianness(raw.data,raw.width); \
const Tss *ptrs = raw.data; \
for (unsigned int off = raw.width; off; --off) *(ptrd++) = (T)*(ptrs++); \
const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \
} \
const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \
} \
const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \
} \
const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \
if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \
} \
} \
} \
loaded = true; \
}
if (!filename && !file)
throw CImgArgumentException("CImgList<%s>::load_cimg() : Cannot load (null) filename.",
pixel_type());
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
if (n1<n0 || x1<x0 || y1<y0 || z1<z0 || v1<v0)
throw CImgArgumentException("CImgList<%s>::load_cimg() : File '%s', Bad sub-region coordinates [%u->%u] "
"(%u,%u,%u,%u)->(%u,%u,%u,%u).",
pixel_type(),filename?filename:"(FILE*)",
n0,n1,x0,y0,z0,v0,x1,y1,z1,v1);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
bool loaded = false, endian = cimg::endianness();
char tmp[256], str_pixeltype[256], str_endian[256];
unsigned int j, err, N, W, H, D, V;
int i;
j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
if (err<2) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', Unknow CImg RAW header.",
pixel_type(),filename?filename:"(FILE*)");
}
if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
const unsigned int nn1 = n1>=N?N-1:n1;
assign(1+nn1-n0);
_cimg_load_cimg_case2("bool",bool);
_cimg_load_cimg_case2("unsigned_char",uchar);
_cimg_load_cimg_case2("uchar",uchar);
_cimg_load_cimg_case2("char",char);
_cimg_load_cimg_case2("unsigned_short",ushort);
_cimg_load_cimg_case2("ushort",ushort);
_cimg_load_cimg_case2("short",short);
_cimg_load_cimg_case2("unsigned_int",uint);
_cimg_load_cimg_case2("uint",uint);
_cimg_load_cimg_case2("int",int);
_cimg_load_cimg_case2("unsigned_long",ulong);
_cimg_load_cimg_case2("ulong",ulong);
_cimg_load_cimg_case2("long",long);
_cimg_load_cimg_case2("float",float);
_cimg_load_cimg_case2("double",double);
if (!loaded) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::load_cimg() : File '%s', cannot read images of pixels coded as '%s'.",
pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image list from a PAR/REC (Philips) file.
CImgList<T>& load_parrec(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImgList<%s>::load_parrec() : Cannot load (null) filename.",
pixel_type());
char body[1024], filenamepar[1024], filenamerec[1024];
const char *ext = cimg::split_filename(filename,body);
if (!cimg::strcmp(ext,"par")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.rec",body); }
if (!cimg::strcmp(ext,"PAR")) { cimg_std::strcpy(filenamepar,filename); cimg_std::sprintf(filenamerec,"%s.REC",body); }
if (!cimg::strcmp(ext,"rec")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.par",body); }
if (!cimg::strcmp(ext,"REC")) { cimg_std::strcpy(filenamerec,filename); cimg_std::sprintf(filenamepar,"%s.PAR",body); }
cimg_std::FILE *file = cimg::fopen(filenamepar,"r");
// Parse header file
CImgList<floatT> st_slices;
CImgList<uintT> st_global;
int err;
char line[256] = { 0 };
do { err=cimg_std::fscanf(file,"%255[^\n]%*c",line); } while (err!=EOF && (line[0]=='#' || line[0]=='.'));
do {
unsigned int sn,sizex,sizey,pixsize;
float rs,ri,ss;
err = cimg_std::fscanf(file,"%u%*u%*u%*u%*u%*u%*u%u%*u%u%u%g%g%g%*[^\n]",&sn,&pixsize,&sizex,&sizey,&ri,&rs,&ss);
if (err==7) {
st_slices.insert(CImg<floatT>::vector((float)sn,(float)pixsize,(float)sizex,(float)sizey,
ri,rs,ss,0));
unsigned int i; for (i=0; i<st_global.size && sn<=st_global[i][2]; ++i) {}
if (i==st_global.size) st_global.insert(CImg<uintT>::vector(sizex,sizey,sn));
else {
CImg<uintT> &vec = st_global[i];
if (sizex>vec[0]) vec[0] = sizex;
if (sizey>vec[1]) vec[1] = sizey;
vec[2] = sn;
}
st_slices[st_slices.size-1][7] = (float)i;
}
} while (err==7);
// Read data
cimg_std::FILE *file2 = cimg::fopen(filenamerec,"rb");
{ cimglist_for(st_global,l) {
const CImg<uintT>& vec = st_global[l];
insert(CImg<T>(vec[0],vec[1],vec[2]));
}}
cimglist_for(st_slices,l) {
const CImg<floatT>& vec = st_slices[l];
const unsigned int
sn = (unsigned int)vec[0]-1,
pixsize = (unsigned int)vec[1],
sizex = (unsigned int)vec[2],
sizey = (unsigned int)vec[3],
imn = (unsigned int)vec[7];
const float ri = vec[4], rs = vec[5], ss = vec[6];
switch (pixsize) {
case 8 : {
CImg<ucharT> buf(sizex,sizey);
cimg::fread(buf.data,sizex*sizey,file2);
if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
CImg<T>& img = (*this)[imn];
cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
} break;
case 16 : {
CImg<ushortT> buf(sizex,sizey);
cimg::fread(buf.data,sizex*sizey,file2);
if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
CImg<T>& img = (*this)[imn];
cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
} break;
case 32 : {
CImg<uintT> buf(sizex,sizey);
cimg::fread(buf.data,sizex*sizey,file2);
if (cimg::endianness()) cimg::invert_endianness(buf.data,sizex*sizey);
CImg<T>& img = (*this)[imn];
cimg_forXY(img,x,y) img(x,y,sn) = (T)(( buf(x,y)*rs + ri )/(rs*ss));
} break;
default :
cimg::fclose(file);
cimg::fclose(file2);
throw CImgIOException("CImg<%s>::load_parrec() : File '%s', cannot handle image with pixsize = %d bits.",
pixel_type(),filename,pixsize);
}
}
cimg::fclose(file);
cimg::fclose(file2);
if (!size)
throw CImgIOException("CImg<%s>::load_parrec() : File '%s' does not appear to be a valid PAR-REC file.",
pixel_type(),filename);
return *this;
}
static CImgList<T> get_load_parrec(const char *const filename) {
return CImgList<T>().load_parrec(filename);
}
//! Load an image sequence from a YUV file.
CImgList<T>& load_yuv(const char *const filename,
const unsigned int sizex, const unsigned int sizey,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true) {
return _load_yuv(0,filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
}
static CImgList<T> get_load_yuv(const char *const filename,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true) {
return CImgList<T>().load_yuv(filename,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
}
//! Load an image sequence from a YUV file.
CImgList<T>& load_yuv(cimg_std::FILE *const file,
const unsigned int sizex, const unsigned int sizey,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true) {
return _load_yuv(file,0,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
}
static CImgList<T> get_load_yuv(cimg_std::FILE *const file,
const unsigned int sizex, const unsigned int sizey=1,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool yuv2rgb=true) {
return CImgList<T>().load_yuv(file,sizex,sizey,first_frame,last_frame,step_frame,yuv2rgb);
}
CImgList<T>& _load_yuv(cimg_std::FILE *const file, const char *const filename,
const unsigned int sizex, const unsigned int sizey,
const unsigned int first_frame, const unsigned int last_frame,
const unsigned int step_frame, const bool yuv2rgb) {
if (!filename && !file)
throw CImgArgumentException("CImgList<%s>::load_yuv() : Cannot load (null) filename.",
pixel_type());
if (sizex%2 || sizey%2)
throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', image dimensions along X and Y must be "
"even numbers (given are %ux%u)\n",
pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
if (!sizex || !sizey)
throw CImgArgumentException("CImgList<%s>::load_yuv() : File '%s', given image sequence size (%u,%u) is invalid",
pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
const unsigned int
nfirst_frame = first_frame<last_frame?first_frame:last_frame,
nlast_frame = first_frame<last_frame?last_frame:first_frame,
nstep_frame = step_frame?step_frame:1;
CImg<ucharT> tmp(sizex,sizey,1,3), UV(sizex/2,sizey/2,1,2);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb");
bool stopflag = false;
int err;
if (nfirst_frame) {
err = cimg_std::fseek(nfile,nfirst_frame*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
if (err) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::load_yuv() : File '%s' doesn't contain frame number %u "
"(out of range error).",
pixel_type(),filename?filename:"(FILE*)",nfirst_frame);
}
}
unsigned int frame;
for (frame = nfirst_frame; !stopflag && frame<=nlast_frame; frame+=nstep_frame) {
tmp.fill(0);
// *TRY* to read the luminance part, do not replace by cimg::fread !
err = (int)cimg_std::fread((void*)(tmp.data),1,(size_t)(tmp.width*tmp.height),nfile);
if (err!=(int)(tmp.width*tmp.height)) {
stopflag = true;
if (err>0)
cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
" or given image dimensions (%u,%u) are incorrect.",
pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
} else {
UV.fill(0);
// *TRY* to read the luminance part, do not replace by cimg::fread !
err = (int)cimg_std::fread((void*)(UV.data),1,(size_t)(UV.size()),nfile);
if (err!=(int)(UV.size())) {
stopflag = true;
if (err>0)
cimg::warn("CImgList<%s>::load_yuv() : File '%s' contains incomplete data,"
" or given image dimensions (%u,%u) are incorrect.",
pixel_type(),filename?filename:"(FILE*)",sizex,sizey);
} else {
cimg_forXY(UV,x,y) {
const int x2 = x*2, y2 = y*2;
tmp(x2,y2,1) = tmp(x2+1,y2,1) = tmp(x2,y2+1,1) = tmp(x2+1,y2+1,1) = UV(x,y,0);
tmp(x2,y2,2) = tmp(x2+1,y2,2) = tmp(x2,y2+1,2) = tmp(x2+1,y2+1,2) = UV(x,y,1);
}
if (yuv2rgb) tmp.YCbCrtoRGB();
insert(tmp);
if (nstep_frame>1) cimg_std::fseek(nfile,(nstep_frame-1)*(sizex*sizey + sizex*sizey/2),SEEK_CUR);
}
}
}
if (stopflag && nlast_frame!=~0U && frame!=nlast_frame)
cimg::warn("CImgList<%s>::load_yuv() : File '%s', frame %d not reached since only %u frames were found in the file.",
pixel_type(),filename?filename:"(FILE*)",nlast_frame,frame-1,filename);
if (!file) cimg::fclose(nfile);
return *this;
}
//! Load an image from a video file, using ffmpeg libraries.
// This piece of code has been firstly created by David Starweather (starkdg(at)users(dot)sourceforge(dot)net)
// I modified it afterwards for direct inclusion in the library core.
CImgList<T>& load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool pixel_format=true, const bool resume=false) {
if (!filename)
throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : Cannot load (null) filename.",
pixel_type());
const unsigned int
nfirst_frame = first_frame<last_frame?first_frame:last_frame,
nlast_frame = first_frame<last_frame?last_frame:first_frame,
nstep_frame = step_frame?step_frame:1;
assign();
#ifndef cimg_use_ffmpeg
if ((nfirst_frame || nlast_frame!=~0U || nstep_frame>1) || (resume && (pixel_format || !pixel_format)))
throw CImgArgumentException("CImg<%s>::load_ffmpeg() : File '%s', reading sub-frames from a video file requires the use of ffmpeg.\n"
"('cimg_use_ffmpeg' must be defined).",
pixel_type(),filename);
return load_ffmpeg_external(filename);
#else
const unsigned int ffmpeg_pixfmt = pixel_format?PIX_FMT_RGB24:PIX_FMT_GRAY8;
avcodec_register_all();
av_register_all();
static AVFormatContext *format_ctx = 0;
static AVCodecContext *codec_ctx = 0;
static AVCodec *codec = 0;
static AVFrame *avframe = avcodec_alloc_frame(), *converted_frame = avcodec_alloc_frame();
static int vstream = 0;
if (resume) {
if (!format_ctx || !codec_ctx || !codec || !avframe || !converted_frame)
throw CImgArgumentException("CImgList<%s>::load_ffmpeg() : File '%s', cannot resume due to unallocated FFMPEG structures.",
pixel_type(),filename);
} else {
// Open video file, find main video stream and codec.
if (format_ctx) av_close_input_file(format_ctx);
if (av_open_input_file(&format_ctx,filename,0,0,0)!=0)
throw CImgIOException("CImgList<%s>::load_ffmpeg() : File '%s' cannot be opened.",
pixel_type(),filename);
if (!avframe || !converted_frame || av_find_stream_info(format_ctx)<0) {
av_close_input_file(format_ctx); format_ctx = 0;
cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve stream information.\n"
"Trying with external ffmpeg executable.",
pixel_type(),filename);
return load_ffmpeg_external(filename);
}
#if cimg_debug>=3
dump_format(format_ctx,0,0,0);
#endif
// Special command : Return informations on main video stream.
// as a vector 1x4 containing : (nb_frames,width,height,fps).
if (!first_frame && !last_frame && !step_frame) {
for (vstream = 0; vstream<(int)(format_ctx->nb_streams); ++vstream)
if (format_ctx->streams[vstream]->codec->codec_type==CODEC_TYPE_VIDEO) break;
if (vstream==(int)format_ctx->nb_streams) assign();
else {
CImgList<doubleT> timestamps;
int nb_frames;
AVPacket packet;
// Count frames and store timestamps.
for (nb_frames = 0; av_read_frame(format_ctx,&packet)>=0; av_free_packet(&packet))
if (packet.stream_index==vstream) {
timestamps.insert(CImg<doubleT>::vector((double)packet.pts));
++nb_frames;
}
// Get frame with, height and fps.
const int
framew = format_ctx->streams[vstream]->codec->width,
frameh = format_ctx->streams[vstream]->codec->height;
const float
num = (float)(format_ctx->streams[vstream]->r_frame_rate).num,
den = (float)(format_ctx->streams[vstream]->r_frame_rate).den,
fps = num/den;
// Return infos as a list.
assign(2);
(*this)[0].assign(1,4).fill((T)nb_frames,(T)framew,(T)frameh,(T)fps);
(*this)[1] = timestamps.get_append('y');
}
av_close_input_file(format_ctx); format_ctx = 0;
return *this;
}
for (vstream = 0; vstream<(int)(format_ctx->nb_streams) &&
format_ctx->streams[vstream]->codec->codec_type!=CODEC_TYPE_VIDEO; ) ++vstream;
if (vstream==(int)format_ctx->nb_streams) {
cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot retrieve video stream.\n"
"Trying with external ffmpeg executable.",
pixel_type(),filename);
av_close_input_file(format_ctx); format_ctx = 0;
return load_ffmpeg_external(filename);
}
codec_ctx = format_ctx->streams[vstream]->codec;
codec = avcodec_find_decoder(codec_ctx->codec_id);
if (!codec) {
cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot find video codec.\n"
"Trying with external ffmpeg executable.",
pixel_type(),filename);
return load_ffmpeg_external(filename);
}
if (avcodec_open(codec_ctx,codec)<0) { // Open codec
cimg::warn("CImgList<%s>::load_ffmpeg() : File '%s', cannot open video codec.\n"
"Trying with external ffmpeg executable.",
pixel_type(),filename);
return load_ffmpeg_external(filename);
}
}
// Read video frames
const unsigned int numBytes = avpicture_get_size(ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
uint8_t *const buffer = new uint8_t[numBytes];
avpicture_fill((AVPicture *)converted_frame,buffer,ffmpeg_pixfmt,codec_ctx->width,codec_ctx->height);
const T foo = (T)0;
AVPacket packet;
for (unsigned int frame = 0, next_frame = nfirst_frame; frame<=nlast_frame && av_read_frame(format_ctx,&packet)>=0; ) {
if (packet.stream_index==(int)vstream) {
int decoded = 0;
avcodec_decode_video(codec_ctx,avframe,&decoded,packet.data,packet.size);
if (decoded) {
if (frame==next_frame) {
SwsContext *c = sws_getContext(codec_ctx->width,codec_ctx->height,codec_ctx->pix_fmt,codec_ctx->width,
codec_ctx->height,ffmpeg_pixfmt,1,0,0,0);
sws_scale(c,avframe->data,avframe->linesize,0,codec_ctx->height,converted_frame->data,converted_frame->linesize);
if (ffmpeg_pixfmt==PIX_FMT_RGB24) {
CImg<ucharT> next_image(*converted_frame->data,3,codec_ctx->width,codec_ctx->height,1,true);
insert(next_image._get_permute_axes("yzvx",foo));
} else {
CImg<ucharT> next_image(*converted_frame->data,1,codec_ctx->width,codec_ctx->height,1,true);
insert(next_image._get_permute_axes("yzvx",foo));
}
next_frame+=nstep_frame;
}
++frame;
}
av_free_packet(&packet);
if (next_frame>nlast_frame) break;
}
}
delete[] buffer;
#endif
return *this;
}
static CImgList<T> get_load_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1, const bool pixel_format=true) {
return CImgList<T>().load_ffmpeg(filename,first_frame,last_frame,step_frame,pixel_format);
}
//! Load an image from a video file (MPEG,AVI) using the external tool 'ffmpeg'.
CImgList<T>& load_ffmpeg_external(const char *const filename) {
if (!filename)
throw CImgArgumentException("CImgList<%s>::load_ffmpeg_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512], filetmp2[512];
cimg_std::FILE *file = 0;
do {
cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(filetmp2,"%s_%%6d.ppm",filetmp);
#if cimg_OS!=2
cimg_std::sprintf(command,"%s -i \"%s\" %s >/dev/null 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
#else
cimg_std::sprintf(command,"\"%s -i \"%s\" %s\" >NUL 2>&1",cimg::ffmpeg_path(),filename,filetmp2);
#endif
cimg::system(command,0);
const unsigned int odebug = cimg::exception_mode();
cimg::exception_mode() = 0;
assign();
unsigned int i = 1;
for (bool stopflag = false; !stopflag; ++i) {
cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,i);
CImg<T> img;
try { img.load_pnm(filetmp2); }
catch (CImgException&) { stopflag = true; }
if (img) { insert(img); cimg_std::remove(filetmp2); }
}
cimg::exception_mode() = odebug;
if (is_empty())
throw CImgIOException("CImgList<%s>::load_ffmpeg_external() : Failed to open image sequence '%s'.\n"
"Check the filename and if the 'ffmpeg' tool is installed on your system.",
pixel_type(),filename);
return *this;
}
static CImgList<T> get_load_ffmpeg_external(const char *const filename) {
return CImgList<T>().load_ffmpeg_external(filename);
}
//! Load a gzipped list, using external tool 'gunzip'.
CImgList<T>& load_gzip_external(const char *const filename) {
if (!filename)
throw CImgIOException("CImg<%s>::load_gzip_external() : Cannot load (null) filename.",
pixel_type());
char command[1024], filetmp[512], body[512];
const char
*ext = cimg::split_filename(filename,body),
*ext2 = cimg::split_filename(body,0);
cimg_std::FILE *file = 0;
do {
if (!cimg::strcasecmp(ext,"gz")) {
if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext2);
else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
} else {
if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext);
else cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
}
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
cimg_std::sprintf(command,"%s -c \"%s\" > %s",cimg::gunzip_path(),filename,filetmp);
cimg::system(command);
if (!(file = cimg_std::fopen(filetmp,"rb"))) {
cimg::fclose(cimg::fopen(filename,"r"));
throw CImgIOException("CImg<%s>::load_gzip_external() : File '%s' cannot be opened.",
pixel_type(),filename);
} else cimg::fclose(file);
load(filetmp);
cimg_std::remove(filetmp);
return *this;
}
static CImgList<T> get_load_gzip_external(const char *const filename) {
return CImgList<T>().load_gzip_external(filename);
}
//! Load a 3D object from a .OFF file.
template<typename tf, typename tc>
CImgList<T>& load_off(const char *const filename,
CImgList<tf>& primitives, CImgList<tc>& colors,
const bool invert_faces=false) {
return get_load_off(filename,primitives,colors,invert_faces).transfer_to(*this);
}
template<typename tf, typename tc>
static CImgList<T> get_load_off(const char *const filename,
CImgList<tf>& primitives, CImgList<tc>& colors,
const bool invert_faces=false) {
return CImg<T>().load_off(filename,primitives,colors,invert_faces).get_split('x');
}
//! Load a TIFF file.
CImgList<T>& load_tiff(const char *const filename,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1) {
const unsigned int
nfirst_frame = first_frame<last_frame?first_frame:last_frame,
nstep_frame = step_frame?step_frame:1;
unsigned int nlast_frame = first_frame<last_frame?last_frame:first_frame;
#ifndef cimg_use_tiff
if (nfirst_frame || nlast_frame!=~0U || nstep_frame!=1)
throw CImgArgumentException("CImgList<%s>::load_tiff() : File '%s', reading sub-images from a tiff file requires the use of libtiff.\n"
"('cimg_use_tiff' must be defined).",
pixel_type(),filename);
return assign(CImg<T>::get_load_tiff(filename));
#else
TIFF *tif = TIFFOpen(filename,"r");
if (tif) {
unsigned int nb_images = 0;
do ++nb_images; while (TIFFReadDirectory(tif));
if (nfirst_frame>=nb_images || (nlast_frame!=~0U && nlast_frame>=nb_images))
cimg::warn("CImgList<%s>::load_tiff() : File '%s' contains %u image(s), specified frame range is [%u,%u] (step %u).",
pixel_type(),filename,nb_images,nfirst_frame,nlast_frame,nstep_frame);
if (nfirst_frame>=nb_images) return assign();
if (nlast_frame>=nb_images) nlast_frame = nb_images-1;
assign(1+(nlast_frame-nfirst_frame)/nstep_frame);
TIFFSetDirectory(tif,0);
#if cimg_debug>=3
TIFFSetWarningHandler(0);
TIFFSetErrorHandler(0);
#endif
cimglist_for(*this,l) data[l]._load_tiff(tif,nfirst_frame+l*nstep_frame);
TIFFClose(tif);
} else throw CImgException("CImgList<%s>::load_tiff() : File '%s' cannot be opened.",
pixel_type(),filename);
return *this;
#endif
}
static CImgList<T> get_load_tiff(const char *const filename,
const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int step_frame=1) {
return CImgList<T>().load_tiff(filename,first_frame,last_frame,step_frame);
}
//! Save an image list into a file.
/**
Depending on the extension of the given filename, a file format is chosen for the output file.
**/
const CImgList<T>& save(const char *const filename, const int number=-1) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save() : File '%s, instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",size,data);
if (!filename)
throw CImgArgumentException("CImg<%s>::save() : Instance list (%u,%p), specified filename is (null).",
pixel_type(),size,data);
const char *ext = cimg::split_filename(filename);
char nfilename[1024];
const char *const fn = (number>=0)?cimg::number_filename(filename,number,6,nfilename):filename;
#ifdef cimglist_save_plugin
cimglist_save_plugin(fn);
#endif
#ifdef cimglist_save_plugin1
cimglist_save_plugin1(fn);
#endif
#ifdef cimglist_save_plugin2
cimglist_save_plugin2(fn);
#endif
#ifdef cimglist_save_plugin3
cimglist_save_plugin3(fn);
#endif
#ifdef cimglist_save_plugin4
cimglist_save_plugin4(fn);
#endif
#ifdef cimglist_save_plugin5
cimglist_save_plugin5(fn);
#endif
#ifdef cimglist_save_plugin6
cimglist_save_plugin6(fn);
#endif
#ifdef cimglist_save_plugin7
cimglist_save_plugin7(fn);
#endif
#ifdef cimglist_save_plugin8
cimglist_save_plugin8(fn);
#endif
#ifdef cimg_use_tiff
if (!cimg::strcasecmp(ext,"tif") ||
!cimg::strcasecmp(ext,"tiff")) return save_tiff(fn);
#endif
if (!cimg::strcasecmp(ext,"cimgz")) return save_cimg(fn,true);
if (!cimg::strcasecmp(ext,"cimg") || !ext[0]) return save_cimg(fn,false);
if (!cimg::strcasecmp(ext,"yuv")) return save_yuv(fn,true);
if (!cimg::strcasecmp(ext,"avi") ||
!cimg::strcasecmp(ext,"mov") ||
!cimg::strcasecmp(ext,"asf") ||
!cimg::strcasecmp(ext,"divx") ||
!cimg::strcasecmp(ext,"flv") ||
!cimg::strcasecmp(ext,"mpg") ||
!cimg::strcasecmp(ext,"m1v") ||
!cimg::strcasecmp(ext,"m2v") ||
!cimg::strcasecmp(ext,"m4v") ||
!cimg::strcasecmp(ext,"mjp") ||
!cimg::strcasecmp(ext,"mkv") ||
!cimg::strcasecmp(ext,"mpe") ||
!cimg::strcasecmp(ext,"movie") ||
!cimg::strcasecmp(ext,"ogm") ||
!cimg::strcasecmp(ext,"qt") ||
!cimg::strcasecmp(ext,"rm") ||
!cimg::strcasecmp(ext,"vob") ||
!cimg::strcasecmp(ext,"wmv") ||
!cimg::strcasecmp(ext,"xvid") ||
!cimg::strcasecmp(ext,"mpeg")) return save_ffmpeg(fn);
if (!cimg::strcasecmp(ext,"gz")) return save_gzip_external(fn);
if (size==1) data[0].save(fn,-1); else cimglist_for(*this,l) data[l].save(fn,l);
return *this;
}
//! Save an image sequence, using FFMPEG library.
// This piece of code has been originally written by David. G. Starkweather.
const CImgList<T>& save_ffmpeg(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const unsigned int fps=25) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",size,data);
if (!filename)
throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : Instance list (%u,%p), specified filename is (null).",
pixel_type(),size,data);
if (!fps)
throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified framerate is 0.",
pixel_type(),filename);
const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame;
if (first_frame>=size || nlast_frame>=size)
throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', specified frames [%u,%u] are out of list range (%u elements).",
pixel_type(),filename,first_frame,last_frame,size);
for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0]))
throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', images of the sequence have different dimensions.",
pixel_type(),filename);
#ifndef cimg_use_ffmpeg
return save_ffmpeg_external(filename,first_frame,last_frame);
#else
avcodec_register_all();
av_register_all();
const int
frame_dimx = data[first_frame].dimx(),
frame_dimy = data[first_frame].dimy(),
frame_dimv = data[first_frame].dimv();
if (frame_dimv!=1 && frame_dimv!=3)
throw CImgInstanceException("CImgList<%s>::save_ffmpeg() : File '%s', image[0] (%u,%u,%u,%u,%p) has not 1 or 3 channels.",
pixel_type(),filename,data[0].width,data[0].height,data[0].depth,data[0].dim,data);
PixelFormat dest_pxl_fmt = PIX_FMT_YUV420P;
PixelFormat src_pxl_fmt = (frame_dimv == 3)?PIX_FMT_RGB24:PIX_FMT_GRAY8;
int sws_flags = SWS_FAST_BILINEAR; // Interpolation method (keeping same size images for now).
AVOutputFormat *fmt = 0;
fmt = guess_format(0,filename,0);
if (!fmt) fmt = guess_format("mpeg",0,0); // Default format "mpeg".
if (!fmt)
throw CImgArgumentException("CImgList<%s>::save_ffmpeg() : File '%s', could not determine file format from filename.",
pixel_type(),filename);
AVFormatContext *oc = 0;
oc = av_alloc_format_context();
if (!oc) // Failed to allocate format context.
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate structure for format context.",
pixel_type(),filename);
AVCodec *codec = 0;
AVFrame *picture = 0;
AVFrame *tmp_pict = 0;
oc->oformat = fmt;
cimg_std::sprintf(oc->filename,"%s",filename);
// Add video stream.
int stream_index = 0;
AVStream *video_str = 0;
if (fmt->video_codec!=CODEC_ID_NONE) {
video_str = av_new_stream(oc,stream_index);
if (!video_str) { // Failed to allocate stream.
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate video stream structure.",
pixel_type(),filename);
}
} else { // No codec identified.
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no proper codec identified.",
pixel_type(),filename);
}
AVCodecContext *c = video_str->codec;
c->codec_id = fmt->video_codec;
c->codec_type = CODEC_TYPE_VIDEO;
c->bit_rate = 400000;
c->width = frame_dimx;
c->height = frame_dimy;
c->time_base.num = 1;
c->time_base.den = fps;
c->gop_size = 12;
c->pix_fmt = dest_pxl_fmt;
if (c->codec_id == CODEC_ID_MPEG2VIDEO) c->max_b_frames = 2;
if (c->codec_id == CODEC_ID_MPEG1VIDEO) c->mb_decision = 2;
if (av_set_parameters(oc,0)<0) { // Parameters not properly set.
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', parameters for avcodec not properly set.",
pixel_type(),filename);
}
// Open codecs and alloc buffers.
codec = avcodec_find_encoder(c->codec_id);
if (!codec) { // Failed to find codec.
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', no codec found.",
pixel_type(),filename);
}
if (avcodec_open(c,codec)<0) // Failed to open codec.
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to open codec.",
pixel_type(),filename);
tmp_pict = avcodec_alloc_frame();
if (!tmp_pict) { // Failed to allocate memory for tmp_pict frame.
avcodec_close(video_str->codec);
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.",
pixel_type(),filename);
}
tmp_pict->linesize[0] = (src_pxl_fmt==PIX_FMT_RGB24)?3*frame_dimx:frame_dimx;
tmp_pict->type = FF_BUFFER_TYPE_USER;
int tmp_size = avpicture_get_size(src_pxl_fmt,frame_dimx,frame_dimy);
uint8_t *tmp_buffer = (uint8_t*)av_malloc(tmp_size);
if (!tmp_buffer) { // Failed to allocate memory for tmp buffer.
av_free(tmp_pict);
avcodec_close(video_str->codec);
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for data buffer.",
pixel_type(),filename);
}
// Associate buffer with tmp_pict.
avpicture_fill((AVPicture*)tmp_pict,tmp_buffer,src_pxl_fmt,frame_dimx,frame_dimy);
picture = avcodec_alloc_frame();
if (!picture) { // Failed to allocate picture frame.
av_free(tmp_pict->data[0]);
av_free(tmp_pict);
avcodec_close(video_str->codec);
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame.",
pixel_type(),filename);
}
int size = avpicture_get_size(c->pix_fmt,frame_dimx,frame_dimy);
uint8_t *buffer = (uint8_t*)av_malloc(size);
if (!buffer) { // Failed to allocate picture frame buffer.
av_free(picture);
av_free(tmp_pict->data[0]);
av_free(tmp_pict);
avcodec_close(video_str->codec);
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to allocate memory for picture frame buffer.",
pixel_type(),filename);
}
// Associate the buffer with picture.
avpicture_fill((AVPicture*)picture,buffer,c->pix_fmt,frame_dimx,frame_dimy);
// Open file.
if (!(fmt->flags&AVFMT_NOFILE)) {
if (url_fopen(&oc->pb,filename,URL_WRONLY)<0)
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s' cannot be opened.",
pixel_type(),filename);
}
if (av_write_header(oc)<0)
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', could not write header.",
pixel_type(),filename);
double video_pts;
SwsContext *img_convert_context = 0;
img_convert_context = sws_getContext(frame_dimx,frame_dimy,src_pxl_fmt,
c->width,c->height,c->pix_fmt,sws_flags,0,0,0);
if (!img_convert_context) { // Failed to get swscale context.
// if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
av_free(picture->data);
av_free(picture);
av_free(tmp_pict->data[0]);
av_free(tmp_pict);
avcodec_close(video_str->codec);
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%', failed to get conversion context.",
pixel_type(),filename);
}
int ret = 0, out_size;
uint8_t *video_outbuf = 0;
int video_outbuf_size = 1000000;
video_outbuf = (uint8_t*)av_malloc(video_outbuf_size);
if (!video_outbuf) {
// if (!(fmt->flags & AVFMT_NOFILE)) url_fclose(&oc->pb);
av_free(picture->data);
av_free(picture);
av_free(tmp_pict->data[0]);
av_free(tmp_pict);
avcodec_close(video_str->codec);
av_free(oc);
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', memory allocation error.",
pixel_type(),filename);
}
// Loop through each desired image in list.
for (unsigned int i = first_frame; i<=nlast_frame; ++i) {
CImg<uint8_t> currentIm = data[i], red, green, blue, gray;
if (src_pxl_fmt == PIX_FMT_RGB24) {
red = currentIm.get_shared_channel(0);
green = currentIm.get_shared_channel(1);
blue = currentIm.get_shared_channel(2);
cimg_forXY(currentIm,X,Y) { // Assign pizel values to data buffer in interlaced RGBRGB ... format.
tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X] = red(X,Y);
tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 1] = green(X,Y);
tmp_pict->data[0][Y*tmp_pict->linesize[0] + 3*X + 2] = blue(X,Y);
}
} else {
gray = currentIm.get_shared_channel(0);
cimg_forXY(currentIm,X,Y) tmp_pict->data[0][Y*tmp_pict->linesize[0] + X] = gray(X,Y);
}
if (video_str) video_pts = (video_str->pts.val * video_str->time_base.num)/(video_str->time_base.den);
else video_pts = 0.0;
if (!video_str) break;
if (sws_scale(img_convert_context,tmp_pict->data,tmp_pict->linesize,0,c->height,picture->data,picture->linesize)<0) break;
out_size = avcodec_encode_video(c,video_outbuf,video_outbuf_size,picture);
if (out_size>0) {
AVPacket pkt;
av_init_packet(&pkt);
pkt.pts = av_rescale_q(c->coded_frame->pts,c->time_base,video_str->time_base);
if (c->coded_frame->key_frame) pkt.flags|=PKT_FLAG_KEY;
pkt.stream_index = video_str->index;
pkt.data = video_outbuf;
pkt.size = out_size;
ret = av_write_frame(oc,&pkt);
} else if (out_size<0) break;
- if (ret) break; // Error occured in writing frame.
+ if (ret) break; // Error occurred in writing frame.
}
// Close codec.
if (video_str) {
avcodec_close(video_str->codec);
av_free(picture->data[0]);
av_free(picture);
av_free(tmp_pict->data[0]);
av_free(tmp_pict);
}
if (av_write_trailer(oc)<0)
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to write trailer.",
pixel_type(),filename);
av_freep(&oc->streams[stream_index]->codec);
av_freep(&oc->streams[stream_index]);
if (!(fmt->flags&AVFMT_NOFILE)) {
/*if (url_fclose(oc->pb)<0)
throw CImgIOException("CImgList<%s>::save_ffmpeg() : File '%s', failed to close file.",
pixel_type(),filename);
*/
}
av_free(oc);
av_free(video_outbuf);
#endif
return *this;
}
// Save an image sequence into a YUV file (internal).
const CImgList<T>& _save_yuv(cimg_std::FILE *const file, const char *const filename, const bool rgb2yuv) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",size,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_yuv() : Instance list (%u,%p), specified file is (null).",
pixel_type(),size,data);
if ((*this)[0].dimx()%2 || (*this)[0].dimy()%2)
throw CImgInstanceException("CImgList<%s>::save_yuv() : File '%s', image dimensions must be even numbers (current are %ux%u).",
pixel_type(),filename?filename:"(FILE*)",(*this)[0].dimx(),(*this)[0].dimy());
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
cimglist_for(*this,l) {
CImg<ucharT> YCbCr((*this)[l]);
if (rgb2yuv) YCbCr.RGBtoYCbCr();
cimg::fwrite(YCbCr.data,YCbCr.width*YCbCr.height,nfile);
cimg::fwrite(YCbCr.get_resize(YCbCr.width/2, YCbCr.height/2,1,3,3).ptr(0,0,0,1),
YCbCr.width*YCbCr.height/2,nfile);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save an image sequence into a YUV file.
const CImgList<T>& save_yuv(const char *const filename=0, const bool rgb2yuv=true) const {
return _save_yuv(0,filename,rgb2yuv);
}
//! Save an image sequence into a YUV file.
const CImgList<T>& save_yuv(cimg_std::FILE *const file, const bool rgb2yuv=true) const {
return _save_yuv(file,0,rgb2yuv);
}
//! Save an image list into a .cimg file.
/**
A CImg RAW file is a simple uncompressed binary file that may be used to save list of CImg<T> images.
\param filename : name of the output file.
\return A reference to the current CImgList instance is returned.
**/
const CImgList<T>& _save_cimg(cimg_std::FILE *const file, const char *const filename, const bool compression) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",size,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
pixel_type(),size,data);
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
const char *const ptype = pixel_type(), *const etype = cimg::endianness()?"big":"little";
if (cimg_std::strstr(ptype,"unsigned")==ptype) cimg_std::fprintf(nfile,"%u unsigned_%s %s_endian\n",size,ptype+9,etype);
else cimg_std::fprintf(nfile,"%u %s %s_endian\n",size,ptype,etype);
cimglist_for(*this,l) {
const CImg<T>& img = data[l];
cimg_std::fprintf(nfile,"%u %u %u %u",img.width,img.height,img.depth,img.dim);
if (img.data) {
CImg<T> tmp;
if (cimg::endianness()) { tmp = img; cimg::invert_endianness(tmp.data,tmp.size()); }
const CImg<T>& ref = cimg::endianness()?tmp:img;
bool compressed = false;
if (compression) {
#ifdef cimg_use_zlib
const unsigned long siz = sizeof(T)*ref.size();
unsigned long csiz = siz + siz/10 + 16;
Bytef *const cbuf = new Bytef[csiz];
if (compress(cbuf,&csiz,(Bytef*)ref.data,siz)) {
cimg::warn("CImgList<%s>::save_cimg() : File '%s', failed to save compressed data.\n Data will be saved uncompressed.",
pixel_type(),filename?filename:"(FILE*)");
compressed = false;
} else {
cimg_std::fprintf(nfile," #%lu\n",csiz);
cimg::fwrite(cbuf,csiz,nfile);
delete[] cbuf;
compressed = true;
}
#else
cimg::warn("CImgList<%s>::save_cimg() : File '%s', cannot save compressed data unless zlib is used "
"('cimg_use_zlib' must be defined).\n Data will be saved uncompressed.",
pixel_type(),filename?filename:"(FILE*)");
compressed = false;
#endif
}
if (!compressed) {
cimg_std::fputc('\n',nfile);
cimg::fwrite(ref.data,ref.size(),nfile);
}
} else cimg_std::fputc('\n',nfile);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Save an image list into a CImg file (RAW binary file + simple header)
const CImgList<T>& save_cimg(cimg_std::FILE *file, const bool compress=false) const {
return _save_cimg(file,0,compress);
}
//! Save an image list into a CImg file (RAW binary file + simple header)
const CImgList<T>& save_cimg(const char *const filename, const bool compress=false) const {
return _save_cimg(0,filename,compress);
}
// Insert the instance image into an existing .cimg file, at specified coordinates.
const CImgList<T>& _save_cimg(cimg_std::FILE *const file, const char *const filename,
const unsigned int n0,
const unsigned int x0, const unsigned int y0,
const unsigned int z0, const unsigned int v0) const {
#define _cimg_save_cimg_case(Ts,Tss) \
if (!saved && !cimg::strcasecmp(Ts,str_pixeltype)) { \
for (unsigned int l=0; l<lmax; ++l) { \
j = 0; while((i=cimg_std::fgetc(nfile))!='\n') tmp[j++]=(char)i; tmp[j]='\0'; \
W = H = D = V = 0; \
if (cimg_std::sscanf(tmp,"%u %u %u %u",&W,&H,&D,&V)!=4) \
throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Image %u has an invalid size (%u,%u,%u,%u)\n", \
pixel_type(), filename?filename:("(FILE*)"), W, H, D, V); \
if (W*H*D*V>0) { \
if (l<n0 || x0>=W || y0>=H || z0>=D || v0>=D) cimg_std::fseek(nfile,W*H*D*V*sizeof(Tss),SEEK_CUR); \
else { \
const CImg<T>& img = (*this)[l-n0]; \
const T *ptrs = img.data; \
const unsigned int \
x1 = x0 + img.width - 1, \
y1 = y0 + img.height - 1, \
z1 = z0 + img.depth - 1, \
v1 = v0 + img.dim - 1, \
nx1 = x1>=W?W-1:x1, \
ny1 = y1>=H?H-1:y1, \
nz1 = z1>=D?D-1:z1, \
nv1 = v1>=V?V-1:v1; \
CImg<Tss> raw(1+nx1-x0); \
const unsigned int skipvb = v0*W*H*D*sizeof(Tss); \
if (skipvb) cimg_std::fseek(nfile,skipvb,SEEK_CUR); \
for (unsigned int v=1+nv1-v0; v; --v) { \
const unsigned int skipzb = z0*W*H*sizeof(Tss); \
if (skipzb) cimg_std::fseek(nfile,skipzb,SEEK_CUR); \
for (unsigned int z=1+nz1-z0; z; --z) { \
const unsigned int skipyb = y0*W*sizeof(Tss); \
if (skipyb) cimg_std::fseek(nfile,skipyb,SEEK_CUR); \
for (unsigned int y=1+ny1-y0; y; --y) { \
const unsigned int skipxb = x0*sizeof(Tss); \
if (skipxb) cimg_std::fseek(nfile,skipxb,SEEK_CUR); \
raw.assign(ptrs, raw.width); \
ptrs+=img.width; \
if (endian) cimg::invert_endianness(raw.data,raw.width); \
cimg::fwrite(raw.data,raw.width,nfile); \
const unsigned int skipxe = (W-1-nx1)*sizeof(Tss); \
if (skipxe) cimg_std::fseek(nfile,skipxe,SEEK_CUR); \
} \
const unsigned int skipye = (H-1-ny1)*W*sizeof(Tss); \
if (skipye) cimg_std::fseek(nfile,skipye,SEEK_CUR); \
} \
const unsigned int skipze = (D-1-nz1)*W*H*sizeof(Tss); \
if (skipze) cimg_std::fseek(nfile,skipze,SEEK_CUR); \
} \
const unsigned int skipve = (V-1-nv1)*W*H*D*sizeof(Tss); \
if (skipve) cimg_std::fseek(nfile,skipve,SEEK_CUR); \
} \
} \
} \
saved = true; \
}
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save_cimg() : File '%s', instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(FILE*)",size,data);
if (!file && !filename)
throw CImgArgumentException("CImg<%s>::save_cimg() : Instance list (%u,%p), specified file is (null).",
pixel_type(),size,data);
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"rb+");
bool saved = false, endian = cimg::endianness();
char tmp[256], str_pixeltype[256], str_endian[256];
unsigned int j, err, N, W, H, D, V;
int i;
j = 0; while((i=cimg_std::fgetc(nfile))!='\n' && i!=EOF && j<256) tmp[j++] = (char)i; tmp[j] = '\0';
err = cimg_std::sscanf(tmp,"%u%*c%255[A-Za-z_]%*c%255[sA-Za-z_ ]",&N,str_pixeltype,str_endian);
if (err<2) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', Unknow CImg RAW header.",
pixel_type(),filename?filename:"(FILE*)");
}
if (!cimg::strncasecmp("little",str_endian,6)) endian = false;
else if (!cimg::strncasecmp("big",str_endian,3)) endian = true;
const unsigned int lmax = cimg::min(N,n0+size);
_cimg_save_cimg_case("bool",bool);
_cimg_save_cimg_case("unsigned_char",uchar);
_cimg_save_cimg_case("uchar",uchar);
_cimg_save_cimg_case("char",char);
_cimg_save_cimg_case("unsigned_short",ushort);
_cimg_save_cimg_case("ushort",ushort);
_cimg_save_cimg_case("short",short);
_cimg_save_cimg_case("unsigned_int",uint);
_cimg_save_cimg_case("uint",uint);
_cimg_save_cimg_case("int",int);
_cimg_save_cimg_case("unsigned_long",ulong);
_cimg_save_cimg_case("ulong",ulong);
_cimg_save_cimg_case("long",long);
_cimg_save_cimg_case("float",float);
_cimg_save_cimg_case("double",double);
if (!saved) {
if (!file) cimg::fclose(nfile);
throw CImgIOException("CImgList<%s>::save_cimg() : File '%s', cannot save images of pixels coded as '%s'.",
pixel_type(),filename?filename:"(FILE*)",str_pixeltype);
}
if (!file) cimg::fclose(nfile);
return *this;
}
//! Insert the instance image into an existing .cimg file, at specified coordinates.
const CImgList<T>& save_cimg(const char *const filename,
const unsigned int n0,
const unsigned int x0, const unsigned int y0,
const unsigned int z0, const unsigned int v0) const {
return _save_cimg(0,filename,n0,x0,y0,z0,v0);
}
//! Insert the instance image into an existing .cimg file, at specified coordinates.
const CImgList<T>& save_cimg(cimg_std::FILE *const file,
const unsigned int n0,
const unsigned int x0, const unsigned int y0,
const unsigned int z0, const unsigned int v0) const {
return _save_cimg(file,0,n0,x0,y0,z0,v0);
}
// Create an empty .cimg file with specified dimensions (internal)
static void _save_empty_cimg(cimg_std::FILE *const file, const char *const filename,
const unsigned int nb,
const unsigned int dx, const unsigned int dy,
const unsigned int dz, const unsigned int dv) {
cimg_std::FILE *const nfile = file?file:cimg::fopen(filename,"wb");
const unsigned int siz = dx*dy*dz*dv*sizeof(T);
cimg_std::fprintf(nfile,"%u %s\n",nb,pixel_type());
for (unsigned int i=nb; i; --i) {
cimg_std::fprintf(nfile,"%u %u %u %u\n",dx,dy,dz,dv);
for (unsigned int off=siz; off; --off) cimg_std::fputc(0,nfile);
}
if (!file) cimg::fclose(nfile);
}
//! Create an empty .cimg file with specified dimensions.
static void save_empty_cimg(const char *const filename,
const unsigned int nb,
const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1) {
return _save_empty_cimg(0,filename,nb,dx,dy,dz,dv);
}
//! Create an empty .cimg file with specified dimensions.
static void save_empty_cimg(cimg_std::FILE *const file,
const unsigned int nb,
const unsigned int dx, const unsigned int dy=1,
const unsigned int dz=1, const unsigned int dv=1) {
return _save_empty_cimg(file,0,nb,dx,dy,dz,dv);
}
//! Save a file in TIFF format.
#ifdef cimg_use_tiff
const CImgList<T>& save_tiff(const char *const filename) const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save_tiff() : File '%s', instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",size,data);
if (!filename)
throw CImgArgumentException("CImgList<%s>::save_tiff() : Specified filename is (null) for instance list (%u,%p).",
pixel_type(),size,data);
TIFF *tif = TIFFOpen(filename,"w");
if (tif) {
for (unsigned int dir=0, l=0; l<size; ++l) {
const CImg<T>& img = (*this)[l];
if (img) {
if (img.depth==1) img._save_tiff(tif,dir++);
else cimg_forZ(img,z) img.get_slice(z)._save_tiff(tif,dir++);
}
}
TIFFClose(tif);
} else
throw CImgException("CImgList<%s>::save_tiff() : File '%s', error while opening stream for tiff file.",
pixel_type(),filename);
return *this;
}
#endif
//! Save an image list as a gzipped file, using external tool 'gzip'.
const CImgList<T>& save_gzip_external(const char *const filename) const {
if (!filename)
throw CImgIOException("CImg<%s>::save_gzip_external() : Cannot save (null) filename.",
pixel_type());
char command[1024], filetmp[512], body[512];
const char
*ext = cimg::split_filename(filename,body),
*ext2 = cimg::split_filename(body,0);
cimg_std::FILE *file;
do {
if (!cimg::strcasecmp(ext,"gz")) {
if (*ext2) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext2);
else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
} else {
if (*ext) cimg_std::sprintf(filetmp,"%s%s%s.%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand(),ext);
else cimg_std::sprintf(filetmp,"%s%s%s.cimg",cimg::temporary_path(),cimg_OS==2?"\\":"/",
cimg::filenamerand());
}
if ((file=cimg_std::fopen(filetmp,"rb"))!=0) cimg_std::fclose(file);
} while (file);
save(filetmp);
cimg_std::sprintf(command,"%s -c %s > \"%s\"",cimg::gzip_path(),filetmp,filename);
cimg::system(command);
file = cimg_std::fopen(filename,"rb");
if (!file)
throw CImgIOException("CImgList<%s>::save_gzip_external() : File '%s' cannot be saved.",
pixel_type(),filename);
else cimg::fclose(file);
cimg_std::remove(filetmp);
return *this;
}
//! Save an image list into a OFF file.
template<typename tf, typename tc>
const CImgList<T>& save_off(const char *const filename,
const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
get_append('x','y').save_off(filename,primitives,colors,invert_faces);
return *this;
}
//! Save an image list into a OFF file.
template<typename tf, typename tc>
const CImgList<T>& save_off(cimg_std::FILE *const file,
const CImgList<tf>& primitives, const CImgList<tc>& colors, const bool invert_faces=false) const {
get_append('x','y').save_off(file,primitives,colors,invert_faces);
return *this;
}
//! Save an image sequence using the external tool 'ffmpeg'.
const CImgList<T>& save_ffmpeg_external(const char *const filename, const unsigned int first_frame=0, const unsigned int last_frame=~0U,
const char *const codec="mpeg2video") const {
if (is_empty())
throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', instance list (%u,%p) is empty.",
pixel_type(),filename?filename:"(null)",size,data);
if (!filename)
throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : Instance list (%u,%p), specified filename is (null).",
pixel_type(),size,data);
char command[1024], filetmp[512], filetmp2[512];
cimg_std::FILE *file = 0;
const unsigned int nlast_frame = last_frame==~0U?size-1:last_frame;
if (first_frame>=size || nlast_frame>=size)
throw CImgArgumentException("CImgList<%s>::save_ffmpeg_external() : File '%s', specified frames [%u,%u] are out of list range (%u elements).",
pixel_type(),filename,first_frame,last_frame,size);
for (unsigned int ll = first_frame; ll<=nlast_frame; ++ll) if (!data[ll].is_sameXYZ(data[0]))
throw CImgInstanceException("CImgList<%s>::save_ffmpeg_external() : File '%s', all images of the sequence must be of the same dimension.",
pixel_type(),filename);
do {
cimg_std::sprintf(filetmp,"%s%s%s",cimg::temporary_path(),cimg_OS==2?"\\":"/",cimg::filenamerand());
cimg_std::sprintf(filetmp2,"%s_000001.ppm",filetmp);
if ((file=cimg_std::fopen(filetmp2,"rb"))!=0) cimg_std::fclose(file);
} while (file);
for (unsigned int l = first_frame; l<=nlast_frame; ++l) {
cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,l+1);
if (data[l].depth>1 || data[l].dim!=3) data[l].get_resize(-100,-100,1,3).save_pnm(filetmp2);
else data[l].save_pnm(filetmp2);
}
#if cimg_OS!=2
cimg_std::sprintf(command,"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\" >/dev/null 2>&1",filetmp,codec,filename);
#else
cimg_std::sprintf(command,"\"ffmpeg -i %s_%%6d.ppm -vcodec %s -sameq -y \"%s\"\" >NUL 2>&1",filetmp,codec,filename);
#endif
cimg::system(command);
file = cimg_std::fopen(filename,"rb");
if (!file)
throw CImgIOException("CImg<%s>::save_ffmpeg_external() : Failed to save image sequence '%s'.\n\n",
pixel_type(),filename);
else cimg::fclose(file);
cimglist_for(*this,lll) { cimg_std::sprintf(filetmp2,"%s_%.6u.ppm",filetmp,lll+1); cimg_std::remove(filetmp2); }
return *this;
}
};
/*
#---------------------------------------------
#
# Completion of previously declared functions
#
#----------------------------------------------
*/
namespace cimg {
//! Display a dialog box, where a user can click standard buttons.
/**
Up to 6 buttons can be defined in the dialog window.
This function returns when a user clicked one of the button or closed the dialog window.
\param title = Title of the dialog window.
\param msg = Main message displayed inside the dialog window.
\param button1_txt = Label of the 1st button.
\param button2_txt = Label of the 2nd button.
\param button3_txt = Label of the 3rd button.
\param button4_txt = Label of the 4th button.
\param button5_txt = Label of the 5th button.
\param button6_txt = Label of the 6th button.
\param logo = Logo image displayed at the left of the main message. This parameter is optional.
\param centering = Tell to center the dialog window on the screen.
\return The button number (from 0 to 5), or -1 if the dialog window has been closed by the user.
\note If a button text is set to 0, then the corresponding button (and the followings) won't appear in
the dialog box. At least one button is necessary.
**/
template<typename t>
inline int dialog(const char *title, const char *msg,
const char *button1_txt, const char *button2_txt,
const char *button3_txt, const char *button4_txt,
const char *button5_txt, const char *button6_txt,
const CImg<t>& logo, const bool centering = false) {
#if cimg_display!=0
const unsigned char
black[] = { 0,0,0 }, white[] = { 255,255,255 }, gray[] = { 200,200,200 }, gray2[] = { 150,150,150 };
// Create buttons and canvas graphics
CImgList<unsigned char> buttons, cbuttons, sbuttons;
if (button1_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button1_txt,black,gray,1,13));
if (button2_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button2_txt,black,gray,1,13));
if (button3_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button3_txt,black,gray,1,13));
if (button4_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button4_txt,black,gray,1,13));
if (button5_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button5_txt,black,gray,1,13));
if (button6_txt) { buttons.insert(CImg<unsigned char>().draw_text(0,0,button6_txt,black,gray,1,13));
}}}}}}
if (!buttons.size)
throw CImgArgumentException("cimg::dialog() : No buttons have been defined. At least one is necessary");
unsigned int bw = 0, bh = 0;
cimglist_for(buttons,l) { bw = cimg::max(bw,buttons[l].width); bh = cimg::max(bh,buttons[l].height); }
bw+=8; bh+=8;
if (bw<64) bw=64;
if (bw>128) bw=128;
if (bh<24) bh=24;
if (bh>48) bh=48;
CImg<unsigned char> button(bw,bh,1,3);
button.draw_rectangle(0,0,bw-1,bh-1,gray);
button.draw_line(0,0,bw-1,0,white).draw_line(0,bh-1,0,0,white);
button.draw_line(bw-1,0,bw-1,bh-1,black).draw_line(bw-1,bh-1,0,bh-1,black);
button.draw_line(1,bh-2,bw-2,bh-2,gray2).draw_line(bw-2,bh-2,bw-2,1,gray2);
CImg<unsigned char> sbutton(bw,bh,1,3);
sbutton.draw_rectangle(0,0,bw-1,bh-1,gray);
sbutton.draw_line(0,0,bw-1,0,black).draw_line(bw-1,0,bw-1,bh-1,black);
sbutton.draw_line(bw-1,bh-1,0,bh-1,black).draw_line(0,bh-1,0,0,black);
sbutton.draw_line(1,1,bw-2,1,white).draw_line(1,bh-2,1,1,white);
sbutton.draw_line(bw-2,1,bw-2,bh-2,black).draw_line(bw-2,bh-2,1,bh-2,black);
sbutton.draw_line(2,bh-3,bw-3,bh-3,gray2).draw_line(bw-3,bh-3,bw-3,2,gray2);
sbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
sbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
CImg<unsigned char> cbutton(bw,bh,1,3);
cbutton.draw_rectangle(0,0,bw-1,bh-1,black).draw_rectangle(1,1,bw-2,bh-2,gray2).draw_rectangle(2,2,bw-3,bh-3,gray);
cbutton.draw_line(4,4,bw-5,4,black,1,0xAAAAAAAA,true).draw_line(bw-5,4,bw-5,bh-5,black,1,0xAAAAAAAA,false);
cbutton.draw_line(bw-5,bh-5,4,bh-5,black,1,0xAAAAAAAA,false).draw_line(4,bh-5,4,4,black,1,0xAAAAAAAA,false);
cimglist_for(buttons,ll) {
cbuttons.insert(CImg<unsigned char>(cbutton).draw_image(1+(bw-buttons[ll].dimx())/2,1+(bh-buttons[ll].dimy())/2,buttons[ll]));
sbuttons.insert(CImg<unsigned char>(sbutton).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]));
buttons[ll] = CImg<unsigned char>(button).draw_image((bw-buttons[ll].dimx())/2,(bh-buttons[ll].dimy())/2,buttons[ll]);
}
CImg<unsigned char> canvas;
if (msg) canvas = CImg<unsigned char>().draw_text(0,0,msg,black,gray,1,13);
const unsigned int
bwall = (buttons.size-1)*(12+bw) + bw,
w = cimg::max(196U,36+logo.width+canvas.width, 24+bwall),
h = cimg::max(96U,36+canvas.height+bh,36+logo.height+bh),
lx = 12 + (canvas.data?0:((w-24-logo.width)/2)),
ly = (h-12-bh-logo.height)/2,
tx = lx+logo.width+12,
ty = (h-12-bh-canvas.height)/2,
bx = (w-bwall)/2,
by = h-12-bh;
if (canvas.data)
canvas = CImg<unsigned char>(w,h,1,3).
draw_rectangle(0,0,w-1,h-1,gray).
draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black).
draw_image(tx,ty,canvas);
else
canvas = CImg<unsigned char>(w,h,1,3).
draw_rectangle(0,0,w-1,h-1,gray).
draw_line(0,0,w-1,0,white).draw_line(0,h-1,0,0,white).
draw_line(w-1,0,w-1,h-1,black).draw_line(w-1,h-1,0,h-1,black);
if (logo.data) canvas.draw_image(lx,ly,logo);
unsigned int xbuttons[6];
cimglist_for(buttons,lll) { xbuttons[lll] = bx+(bw+12)*lll; canvas.draw_image(xbuttons[lll],by,buttons[lll]); }
// Open window and enter events loop
CImgDisplay disp(canvas,title?title:" ",0,false,centering?true:false);
if (centering) disp.move((CImgDisplay::screen_dimx()-disp.dimx())/2,
(CImgDisplay::screen_dimy()-disp.dimy())/2);
bool stopflag = false, refresh = false;
int oselected = -1, oclicked = -1, selected = -1, clicked = -1;
while (!disp.is_closed && !stopflag) {
if (refresh) {
if (clicked>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[clicked],by,cbuttons[clicked]).display(disp);
else {
if (selected>=0) CImg<unsigned char>(canvas).draw_image(xbuttons[selected],by,sbuttons[selected]).display(disp);
else canvas.display(disp);
}
refresh = false;
}
disp.wait(15);
if (disp.is_resized) disp.resize(disp);
if (disp.button&1) {
oclicked = clicked;
clicked = -1;
cimglist_for(buttons,l)
if (disp.mouse_y>=(int)by && disp.mouse_y<(int)(by+bh) &&
disp.mouse_x>=(int)xbuttons[l] && disp.mouse_x<(int)(xbuttons[l]+bw)) {
clicked = selected = l;
refresh = true;
}
if (clicked!=oclicked) refresh = true;
} else if (clicked>=0) stopflag = true;
if (disp.key) {
oselected = selected;
switch (disp.key) {
case cimg::keyESC : selected=-1; stopflag=true; break;
case cimg::keyENTER : if (selected<0) selected = 0; stopflag = true; break;
case cimg::keyTAB :
case cimg::keyARROWRIGHT :
case cimg::keyARROWDOWN : selected = (selected+1)%buttons.size; break;
case cimg::keyARROWLEFT :
case cimg::keyARROWUP : selected = (selected+buttons.size-1)%buttons.size; break;
}
disp.key = 0;
if (selected!=oselected) refresh = true;
}
}
if (!disp) selected = -1;
return selected;
#else
cimg_std::fprintf(cimg_stdout,"<%s>\n\n%s\n\n",title,msg);
return -1+0*(int)(button1_txt-button2_txt+button3_txt-button4_txt+button5_txt-button6_txt+logo.width+(int)centering);
#endif
}
inline int dialog(const char *title, const char *msg,
const char *button1_txt, const char *button2_txt, const char *button3_txt,
const char *button4_txt, const char *button5_txt, const char *button6_txt,
const bool centering) {
return dialog(title,msg,button1_txt,button2_txt,button3_txt,button4_txt,button5_txt,button6_txt,
CImg<unsigned char>::logo40x38(),centering);
}
// End of cimg:: namespace
}
// End of cimg_library:: namespace
}
#ifdef _cimg_redefine_min
#define min(a,b) (((a)<(b))?(a):(b))
#endif
#ifdef _cimg_redefine_max
#define max(a,b) (((a)>(b))?(a):(b))
#endif
#endif
// Local Variables:
// mode: c++
// End:
diff --git a/core/libs/dimg/filters/icc/digikam-lcms.cpp b/core/libs/dimg/filters/icc/digikam-lcms.cpp
index b1b2f4ae38..215c7d2bea 100644
--- a/core/libs/dimg/filters/icc/digikam-lcms.cpp
+++ b/core/libs/dimg/filters/icc/digikam-lcms.cpp
@@ -1,643 +1,643 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-02-03
* Description : LCMS2 wrapper
*
* Copyright (C) 2012 by Francesco Riosa <francesco+kde at pnpitalia dot it>
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "digikam-lcms.h"
// Lcms includes
#include <lcms2_plugin.h>
// Local includes
#include "digikam_debug.h"
///////////////////////////////////////////////////////////////////////
void _l2tol1MAT3(MAT3* const l2, MAT3* const l1)
{
// TODO: this seem plain wrong and don't provide perfect result
l1->Red.X = static_cast<cmsFloat64Number>( l2->Red.X );
l1->Red.Y = static_cast<cmsFloat64Number>( l2->Green.X );
l1->Red.Z = static_cast<cmsFloat64Number>( l2->Blue.X );
l1->Green.X = static_cast<cmsFloat64Number>( l2->Red.Y );
l1->Green.Y = static_cast<cmsFloat64Number>( l2->Green.Y );
l1->Green.Z = static_cast<cmsFloat64Number>( l2->Blue.Y );
l1->Blue.X = static_cast<cmsFloat64Number>( l2->Red.Z );
l1->Blue.Y = static_cast<cmsFloat64Number>( l2->Green.Z );
l1->Blue.Z = static_cast<cmsFloat64Number>( l2->Blue.Z );
}
void _l1LPMAT3tol2cmsMAT3(LPMAT3 l1, cmsMAT3* const l2)
{
l2->v[0].n[0] = static_cast<cmsFloat64Number>( l1->Red.X );
l2->v[0].n[1] = static_cast<cmsFloat64Number>( l1->Red.Y );
l2->v[0].n[2] = static_cast<cmsFloat64Number>( l1->Red.Z );
l2->v[1].n[0] = static_cast<cmsFloat64Number>( l1->Green.X );
l2->v[1].n[1] = static_cast<cmsFloat64Number>( l1->Green.Y );
l2->v[1].n[2] = static_cast<cmsFloat64Number>( l1->Green.Z );
l2->v[2].n[0] = static_cast<cmsFloat64Number>( l1->Blue.X );
l2->v[2].n[1] = static_cast<cmsFloat64Number>( l1->Blue.Y );
l2->v[2].n[2] = static_cast<cmsFloat64Number>( l1->Blue.Z );
}
void _l2cmsMAT3tol1LPMAT3(cmsMAT3* const l2, LPMAT3 l1)
{
l1->Red.X = static_cast<cmsFloat64Number>( l2->v[0].n[0] );
l1->Red.Y = static_cast<cmsFloat64Number>( l2->v[0].n[1] );
l1->Red.Z = static_cast<cmsFloat64Number>( l2->v[0].n[2] );
l1->Green.X = static_cast<cmsFloat64Number>( l2->v[1].n[0] );
l1->Green.Y = static_cast<cmsFloat64Number>( l2->v[1].n[1] );
l1->Green.Z = static_cast<cmsFloat64Number>( l2->v[1].n[2] );
l1->Blue.X = static_cast<cmsFloat64Number>( l2->v[2].n[0] );
l1->Blue.Y = static_cast<cmsFloat64Number>( l2->v[2].n[1] );
l1->Blue.Z = static_cast<cmsFloat64Number>( l2->v[2].n[2] );
}
///////////////////////////////////////////////////////////////////////
#define MATRIX_DET_TOLERANCE 0.0001
/// Compute chromatic adaptation matrix using Chad as cone matrix
static cmsBool ComputeChromaticAdaptation(cmsMAT3* const Conversion,
const cmsCIEXYZ* const SourceWhitePoint,
const cmsCIEXYZ* const DestWhitePoint,
const cmsMAT3* const Chad)
{
cmsMAT3 Chad_Inv;
cmsVEC3 ConeSourceXYZ, ConeSourceRGB;
cmsVEC3 ConeDestXYZ, ConeDestRGB;
cmsMAT3 Cone, Tmp;
Tmp = *Chad;
if (!_cmsMAT3inverse(&Tmp, &Chad_Inv))
return FALSE;
_cmsVEC3init(&ConeSourceXYZ, SourceWhitePoint -> X,
SourceWhitePoint -> Y,
SourceWhitePoint -> Z);
_cmsVEC3init(&ConeDestXYZ, DestWhitePoint -> X,
DestWhitePoint -> Y,
DestWhitePoint -> Z);
_cmsMAT3eval(&ConeSourceRGB, Chad, &ConeSourceXYZ);
_cmsMAT3eval(&ConeDestRGB, Chad, &ConeDestXYZ);
// Build matrix
_cmsVEC3init(&Cone.v[0], ConeDestRGB.n[0]/ConeSourceRGB.n[0], 0.0, 0.0 );
_cmsVEC3init(&Cone.v[1], 0.0, ConeDestRGB.n[1]/ConeSourceRGB.n[1], 0.0 );
_cmsVEC3init(&Cone.v[2], 0.0, 0.0, ConeDestRGB.n[2]/ConeSourceRGB.n[2]);
// Normalize
_cmsMAT3per(&Tmp, &Cone, Chad);
_cmsMAT3per(Conversion, &Chad_Inv, &Tmp);
return TRUE;
}
/** Returns the final chromatic adaptation from illuminant FromIll to Illuminant ToIll
* The cone matrix can be specified in ConeMatrix. If NULL, Bradford is assumed
*/
cmsBool _cmsAdaptationMatrix(cmsMAT3* const r, const cmsMAT3* ConeMatrix, const cmsCIEXYZ* const FromIll, const cmsCIEXYZ* const ToIll)
{
// Bradford matrix
cmsMAT3 LamRigg =
{{
{{ 0.8951, 0.2664, -0.1614 }},
{{ -0.7502, 1.7135, 0.0367 }},
{{ 0.0389, -0.0685, 1.0296 }}
}};
if (ConeMatrix == NULL)
ConeMatrix = &LamRigg;
return ComputeChromaticAdaptation(r, FromIll, ToIll, ConeMatrix);
}
/// Same as anterior, but assuming D50 destination. White point is given in xyY
static cmsBool _cmsAdaptMatrixToD50(cmsMAT3* const r, const cmsCIExyY* const SourceWhitePt)
{
cmsCIEXYZ Dn;
cmsMAT3 Bradford;
cmsMAT3 Tmp;
cmsxyY2XYZ(&Dn, SourceWhitePt);
if (!_cmsAdaptationMatrix(&Bradford, NULL, &Dn, cmsD50_XYZ()))
return FALSE;
Tmp = *r;
_cmsMAT3per(r, &Bradford, &Tmp);
return TRUE;
}
/** Build a White point, primary chromas transfer matrix from RGB to CIE XYZ
This is just an approximation, I am not handling all the non-linear
- aspects of the RGB to XYZ process, and assumming that the gamma correction
- has transitive property in the tranformation chain.
+ aspects of the RGB to XYZ process, and assuming that the gamma correction
+ has transitive property in the transformation chain.
- the alghoritm:
+ the algorithm:
- First I build the absolute conversion matrix using
primaries in XYZ. This matrix is next inverted
- - Then I eval the source white point across this matrix
- obtaining the coeficients of the transformation
- - Then, I apply these coeficients to the original matrix
+ - Then I evaluate the source white point across this matrix
+ obtaining the coefficients of the transformation
+ - Then, I apply these coefficients to the original matrix
*/
cmsBool _cmsBuildRGB2XYZtransferMatrix(cmsMAT3* const r, const cmsCIExyY* const WhitePt, const cmsCIExyYTRIPLE* const Primrs)
{
cmsVEC3 WhitePoint, Coef;
cmsMAT3 Result, Primaries;
cmsFloat64Number xn, yn;
cmsFloat64Number xr, yr;
cmsFloat64Number xg, yg;
cmsFloat64Number xb, yb;
xn = WhitePt -> x;
yn = WhitePt -> y;
xr = Primrs -> Red.x;
yr = Primrs -> Red.y;
xg = Primrs -> Green.x;
yg = Primrs -> Green.y;
xb = Primrs -> Blue.x;
yb = Primrs -> Blue.y;
// Build Primaries matrix
_cmsVEC3init(&Primaries.v[0], xr, xg, xb );
_cmsVEC3init(&Primaries.v[1], yr, yg, yb );
_cmsVEC3init(&Primaries.v[2], (1-xr-yr), (1-xg-yg), (1-xb-yb));
// Result = Primaries ^ (-1) inverse matrix
if (!_cmsMAT3inverse(&Primaries, &Result))
return FALSE;
_cmsVEC3init(&WhitePoint, xn/yn, 1.0, (1.0-xn-yn)/yn);
// Across inverse primaries ...
_cmsMAT3eval(&Coef, &Result, &WhitePoint);
// Give us the Coefs, then I build transformation matrix
_cmsVEC3init(&r -> v[0], Coef.n[VX]*xr, Coef.n[VY]*xg, Coef.n[VZ]*xb );
_cmsVEC3init(&r -> v[1], Coef.n[VX]*yr, Coef.n[VY]*yg, Coef.n[VZ]*yb );
_cmsVEC3init(&r -> v[2], Coef.n[VX]*(1.0-xr-yr), Coef.n[VY]*(1.0-xg-yg), Coef.n[VZ]*(1.0-xb-yb));
return _cmsAdaptMatrixToD50(r, WhitePt);
}
///////////////////////////////////////////////////////////////////////
/// WAS: Same as anterior, but assuming D50 source. White point is given in xyY
static cmsBool cmsAdaptMatrixFromD50(cmsMAT3* const r, const cmsCIExyY* const DestWhitePt)
{
cmsCIEXYZ Dn;
cmsMAT3 Bradford;
cmsMAT3 Tmp;
cmsxyY2XYZ(&Dn, DestWhitePt);
if (!_cmsAdaptationMatrix(&Bradford, NULL, &Dn, cmsD50_XYZ()))
return FALSE;
Tmp = *r;
_cmsMAT3per(r, &Bradford, &Tmp);
return TRUE;
}
////////////////////////////////////////////////////
int dkCmsErrorAction(int nAction)
{
Q_UNUSED(nAction);
// TODO: Where is error logging?
return 0;
}
DWORD dkCmsGetProfileICCversion(cmsHPROFILE hProfile)
{
return (DWORD) cmsGetEncodedICCversion(hProfile);
}
void dkCmsSetAlarmCodes(int r, int g, int b)
{
cmsUInt16Number NewAlarm[cmsMAXCHANNELS];
NewAlarm[0] = (cmsUInt16Number)r * 256;
NewAlarm[1] = (cmsUInt16Number)g * 256;
NewAlarm[2] = (cmsUInt16Number)b * 256;
cmsSetAlarmCodes(NewAlarm);
}
QString dkCmsTakeProductName(cmsHPROFILE hProfile)
{
static char Name[1024*2+4];
char Manufacturer[1024], Model[1024];
Name[0] = '\0';
Manufacturer[0] = Model[0] = '\0';
cmsMLU* mlu = 0;
if (cmsIsTag(hProfile, cmsSigDeviceMfgDescTag))
{
mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, cmsSigDeviceMfgDescTag) );
cmsMLUgetASCII(mlu, "en", "US", Manufacturer, 1024);
}
if (cmsIsTag(hProfile, cmsSigDeviceModelDescTag))
{
mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, cmsSigDeviceModelDescTag) );
cmsMLUgetASCII(mlu, "en", "US", Model, 1024);
}
if (!Manufacturer[0] && !Model[0])
{
if (cmsIsTag(hProfile, cmsSigProfileDescriptionTag))
{
mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, cmsSigProfileDescriptionTag) );
cmsMLUgetASCII(mlu, "en", "US", Name, 1024);
return QLatin1String(Name);
}
else
{
return QLatin1String("{no name}");
}
}
if (!Manufacturer[0] || strncmp(Model, Manufacturer, 8) == 0 || strlen(Model) > 30)
{
strcpy(Name, Model);
}
else
{
sprintf(Name, "%s - %s", Model, Manufacturer);
}
return QLatin1String(Name);
}
QString dkCmsTakeProductDesc(cmsHPROFILE hProfile)
{
static char Name[2048];
if (cmsIsTag(hProfile, cmsSigProfileDescriptionTag))
{
cmsMLU* const mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, cmsSigProfileDescriptionTag) );
cmsMLUgetASCII(mlu, "en", "US", Name, 1024);
}
else
{
return dkCmsTakeProductName(hProfile);
}
if (strncmp(Name, "Copyrig", 7) == 0)
{
return dkCmsTakeProductName(hProfile);
}
return QLatin1String(Name);
}
QString dkCmsTakeProductInfo(cmsHPROFILE hProfile)
{
static char Info[4096];
cmsMLU* mlu = 0;
Info[0] = '\0';
if (cmsIsTag(hProfile, cmsSigProfileDescriptionTag))
{
char Desc[1024];
mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, cmsSigProfileDescriptionTag) );
cmsMLUgetASCII(mlu, "en", "US", Desc, 1024);
strcat(Info, Desc);
}
if (cmsIsTag(hProfile, cmsSigCopyrightTag))
{
char Copyright[1024];
mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, cmsSigCopyrightTag) );
cmsMLUgetASCII(mlu, "en", "US", Copyright, 1024);
strcat(Info, " - ");
strcat(Info, Copyright);
}
#define K007 static_cast<cmsTagSignature>( 0x4B303037 )
if (cmsIsTag(hProfile, K007))
{
char MonCal[1024];
mlu = static_cast<cmsMLU*>( cmsReadTag(hProfile, K007) );
cmsMLUgetASCII(mlu, "en", "US", MonCal, 1024);
strcat(Info, " - ");
strcat(Info, MonCal);
}
else
{
/*
* _cmsIdentifyWhitePoint is complex and partly redundant
* with cietonguewidget, leave this part off
- * untill the full lcms2 implementation
+ * until the full lcms2 implementation
*
cmsCIEXYZ WhitePt;
char WhiteStr[1024];
dkCmsTakeMediaWhitePoint(&WhitePt, hProfile);
_cmsIdentifyWhitePoint(WhiteStr, &WhitePt);
strcat(Info, " - ");
strcat(Info, WhiteStr);
*/
}
#undef K007
return QLatin1String(Info);
}
QString dkCmsTakeManufacturer(cmsHPROFILE hProfile)
{
char buffer[1024];
buffer[0] = '\0';
cmsGetProfileInfoASCII(hProfile, cmsInfoManufacturer, "en", "US", buffer, 1024);
return QLatin1String(buffer);
}
LCMSBOOL dkCmsTakeMediaWhitePoint(LPcmsCIEXYZ Dest, cmsHPROFILE hProfile)
{
LPcmsCIEXYZ tag = static_cast<LPcmsCIEXYZ>( cmsReadTag(hProfile, cmsSigMediaWhitePointTag) );
if (tag == NULL)
return FALSE;
*Dest = *tag;
return TRUE;
}
QString dkCmsTakeModel(cmsHPROFILE hProfile)
{
char buffer[1024];
const cmsMLU* const mlu = (const cmsMLU* const)cmsReadTag(hProfile, cmsSigDeviceModelDescTag);
buffer[0] = '\0';
if (mlu == NULL)
return QString();
cmsMLUgetASCII(mlu, "en", "US", buffer, 1024);
return QLatin1String(buffer);
}
QString dkCmsTakeCopyright(cmsHPROFILE hProfile)
{
char buffer[1024];
const cmsMLU* const mlu = (const cmsMLU* const)cmsReadTag(hProfile, cmsSigCopyrightTag);
buffer[0] = '\0';
if (mlu == NULL)
return QString();
cmsMLUgetASCII(mlu, "en", "US", buffer, 1024);
return QLatin1String(buffer);
}
DWORD dkCmsTakeHeaderFlags(cmsHPROFILE hProfile)
{
return static_cast<DWORD>( cmsGetHeaderFlags(hProfile) );
}
const BYTE* dkCmsTakeProfileID(cmsHPROFILE hProfile)
{
cmsUInt8Number* const ProfileID = new cmsUInt8Number[16];
cmsGetHeaderProfileID(hProfile, ProfileID);
return static_cast<BYTE*>( ProfileID );
}
int dkCmsTakeRenderingIntent(cmsHPROFILE hProfile)
{
return static_cast<int>( cmsGetHeaderRenderingIntent(hProfile) );
}
// White Point & Primary chromas handling
-// Returns the final chrmatic adaptation from illuminant FromIll to Illuminant ToIll
+// Returns the final chromatic adaptation from illuminant FromIll to Illuminant ToIll
// The cone matrix can be specified in ConeMatrix.
// If NULL, assuming D50 source. White point is given in xyY
LCMSBOOL dkCmsAdaptMatrixFromD50(LPMAT3 r, LPcmsCIExyY DestWhitePt)
{
// TODO: all based on private stuff, need to understand what digikam do in cietonguewidget with dkCmsAdaptMatrixFromD50
cmsMAT3 result;
_l1LPMAT3tol2cmsMAT3(r, &result);
bool ret = cmsAdaptMatrixFromD50(&result, static_cast<const cmsCIExyY*>( DestWhitePt ));
_l2cmsMAT3tol1LPMAT3(&result, r);
return ret;
}
// LCMSBOOL dkCmsAdaptMatrixFromD50(LPMAT3 r, LPcmsCIExyY DestWhitePt)
// {
// // TODO: all based on private stuff, need to understand what digikam do in cietonguewidget with dkCmsAdaptMatrixFromD50
// cmsMAT3 result;
//
// result.v[0].n[0] = r->Red.X ;
// result.v[0].n[1] = r->Red.Y ;
// result.v[0].n[2] = r->Red.Z ;
// result.v[1].n[0] = r->Green.X;
// result.v[1].n[1] = r->Green.Y;
// result.v[1].n[2] = r->Green.Z;
// result.v[2].n[0] = r->Blue.X ;
// result.v[2].n[1] = r->Blue.Y ;
// result.v[2].n[2] = r->Blue.Z ;
//
// bool ret = cmsAdaptMatrixFromD50(&result, static_cast<const cmsCIExyY*>( DestWhitePt ));
//
// r->Red.X = result.v[0].n[0];
// r->Red.Y = result.v[0].n[1];
// r->Red.Z = result.v[0].n[2];
// r->Green.X = result.v[1].n[0];
// r->Green.Y = result.v[1].n[1];
// r->Green.Z = result.v[1].n[2];
// r->Blue.X = result.v[2].n[0];
// r->Blue.Y = result.v[2].n[1];
// r->Blue.Z = result.v[2].n[2];
//
// return ret;
// }
cmsBool GetProfileRGBPrimaries(cmsHPROFILE hProfile, cmsCIEXYZTRIPLE* const result, cmsUInt32Number intent)
{
cmsHPROFILE hXYZ;
cmsHTRANSFORM hTransform;
cmsFloat64Number rgb[3][3] = {{1., 0., 0.},
{0., 1., 0.},
{0., 0., 1.}};
hXYZ = cmsCreateXYZProfile();
if (hXYZ == NULL)
return FALSE;
hTransform = cmsCreateTransform(hProfile, TYPE_RGB_DBL, hXYZ, TYPE_XYZ_DBL,
intent, cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE);
cmsCloseProfile(hXYZ);
if (hTransform == NULL)
return FALSE;
cmsDoTransform(hTransform, rgb, result, 3);
cmsDeleteTransform(hTransform);
return TRUE;
}
LCMSBOOL dkCmsReadICCMatrixRGB2XYZ(LPMAT3 r, cmsHPROFILE hProfile)
{
MAT3 result;
// See README @ Monday, July 27, 2009 @ Less is more
// return static_cast<LCMSBOOL>( GetProfileRGBPrimaries(hProfile, r, INTENT_RELATIVE_COLORIMETRIC) );
LCMSBOOL ret = GetProfileRGBPrimaries(hProfile, &result, INTENT_RELATIVE_COLORIMETRIC);
if (ret)
{
_l2tol1MAT3(&result, r);
}
return ret;
}
cmsHPROFILE dkCmsOpenProfileFromMem(LPVOID MemPtr, DWORD dwSize)
{
return cmsOpenProfileFromMem(MemPtr, static_cast<cmsUInt32Number>( dwSize ));
}
icProfileClassSignature dkCmsGetDeviceClass(cmsHPROFILE hProfile)
{
return static_cast<icProfileClassSignature>( cmsGetDeviceClass(hProfile) );
}
LCMSBOOL dkCmsCloseProfile(cmsHPROFILE hProfile)
{
return static_cast<LCMSBOOL>( cmsCloseProfile(hProfile) );
}
cmsHTRANSFORM dkCmsCreateProofingTransform(cmsHPROFILE Input,
DWORD InputFormat,
cmsHPROFILE Output,
DWORD OutputFormat,
cmsHPROFILE Proofing,
int Intent,
int ProofingIntent,
DWORD dwFlags)
{
return cmsCreateProofingTransform(Input,
static_cast<cmsUInt32Number>( InputFormat ),
static_cast<cmsHPROFILE>( Output ),
static_cast<cmsUInt32Number>( OutputFormat ),
Proofing,
static_cast<cmsUInt32Number>( Intent ),
static_cast<cmsUInt32Number>( ProofingIntent ),
static_cast<cmsUInt32Number>( dwFlags ));
}
cmsHTRANSFORM dkCmsCreateTransform(cmsHPROFILE Input,
DWORD InputFormat,
cmsHPROFILE Output,
DWORD OutputFormat,
int Intent,
DWORD dwFlags)
{
return cmsCreateTransform(Input,
static_cast<cmsUInt32Number>( InputFormat ),
Output,
static_cast<cmsUInt32Number>( OutputFormat ),
static_cast<cmsUInt32Number>( Intent ),
static_cast<cmsUInt32Number>( dwFlags ));
}
cmsHPROFILE dkCmsCreateXYZProfile()
{
return cmsCreateXYZProfile();
}
cmsHPROFILE dkCmsCreate_sRGBProfile()
{
return cmsCreate_sRGBProfile();
}
void dkCmsDeleteTransform(cmsHTRANSFORM hTransform)
{
cmsDeleteTransform(hTransform);
}
double dkCmsDeltaE(LPcmsCIELab Lab1, LPcmsCIELab Lab2)
{
return static_cast<double>( cmsDeltaE(static_cast<cmsCIELab*>( Lab1 ), static_cast<cmsCIELab*>( Lab2 )) );
}
void dkCmsDoTransform(cmsHTRANSFORM Transform,
LPVOID InputBuffer,
LPVOID OutputBuffer,
unsigned int Size)
{
cmsDoTransform(Transform,
static_cast<const void*>( InputBuffer ),
static_cast<void*>( OutputBuffer ),
static_cast<cmsUInt32Number>( Size ));
}
void dkCmsFloat2XYZEncoded(WORD XYZ[3], const cmsCIEXYZ* const fXYZ)
{
cmsFloat2XYZEncoded(XYZ, fXYZ);
}
icColorSpaceSignature dkCmsGetColorSpace(cmsHPROFILE hProfile)
{
return static_cast<icColorSpaceSignature>( cmsGetColorSpace(hProfile) );
}
icColorSpaceSignature dkCmsGetPCS(cmsHPROFILE hProfile)
{
return static_cast<icColorSpaceSignature>( cmsGetPCS(hProfile) );
}
LCMSBOOL dkCmsIsTag(cmsHPROFILE hProfile, icTagSignature sig)
{
return static_cast<LCMSBOOL>( cmsIsTag(hProfile, static_cast<cmsTagSignature>( sig )) );
}
cmsHPROFILE dkCmsOpenProfileFromFile(const char* const ICCProfile, const char* const sAccess)
{
return cmsOpenProfileFromFile(ICCProfile, sAccess);
}
void dkCmsXYZ2xyY(LPcmsCIExyY Dest, const cmsCIEXYZ* const Source)
{
cmsXYZ2xyY(static_cast<cmsCIExyY*>(Dest), Source);
}
diff --git a/core/libs/dimg/filters/icc/iccmanager.cpp b/core/libs/dimg/filters/icc/iccmanager.cpp
index 581e2ffcf4..02a9f40399 100644
--- a/core/libs/dimg/filters/icc/iccmanager.cpp
+++ b/core/libs/dimg/filters/icc/iccmanager.cpp
@@ -1,626 +1,626 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-08-12
* Description : methods that implement color management tasks
*
* Copyright (C) 2005-2006 by F.J. Cruz <fj dot cruz at supercable dot es>
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2009-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "iccmanager.h"
// Qt includes
#include <QImage>
// Local includes
#include "digikam_debug.h"
#include "iccprofile.h"
#include "icctransform.h"
namespace Digikam
{
class Q_DECL_HIDDEN IccManager::Private
{
public:
explicit Private()
: profileMismatch(false),
settings(ICCSettingsContainer()),
observer(0)
{
}
DImg image;
IccProfile embeddedProfile;
IccProfile workspaceProfile;
bool profileMismatch;
ICCSettingsContainer settings;
DImgLoaderObserver* observer;
};
IccManager::IccManager(DImg& image, const ICCSettingsContainer& settings)
: d(new Private)
{
d->image = image;
d->settings = settings;
if (d->image.isNull())
{
return;
}
if (!d->settings.enableCM)
{
return;
}
d->embeddedProfile = d->image.getIccProfile();
d->workspaceProfile = IccProfile(d->settings.workspaceProfile);
if (!d->workspaceProfile.open())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Cannot open workspace color profile" << d->workspaceProfile.filePath();
d->image = DImg();
return;
}
if (!d->embeddedProfile.isNull() && !d->embeddedProfile.open())
{
// Treat as missing profile
d->embeddedProfile = IccProfile();
- qCWarning(DIGIKAM_DIMG_LOG) << "Encountered invalid embbeded color profile in file" << d->image.attribute(QLatin1String("originalFilePath")).toString();
+ qCWarning(DIGIKAM_DIMG_LOG) << "Encountered invalid embedded color profile in file" << d->image.attribute(QLatin1String("originalFilePath")).toString();
}
if (!d->embeddedProfile.isNull())
{
d->profileMismatch = !d->embeddedProfile.isSameProfileAs(d->workspaceProfile);
}
}
IccManager::~IccManager()
{
delete d;
}
DImg IccManager::image() const
{
return d->image;
}
ICCSettingsContainer IccManager::settings() const
{
return d->settings;
}
DImgLoaderObserver* IccManager::observer() const
{
return d->observer;
}
void IccManager::setObserver(DImgLoaderObserver* const observer)
{
d->observer = observer;
}
bool IccManager::hasValidWorkspace() const
{
return d->workspaceProfile.isOpen();
}
void IccManager::transformDefault()
{
if (d->image.isNull())
{
return;
}
if (!d->settings.enableCM)
{
return;
}
if (isUncalibratedColor())
{
transform(d->settings.defaultUncalibratedBehavior);
}
else if (isMissingProfile())
{
transform(d->settings.defaultMissingProfileBehavior);
}
else if (isProfileMismatch())
{
transform(d->settings.defaultMismatchBehavior);
}
}
bool IccManager::isUncalibratedColor() const
{
return d->image.hasAttribute(QLatin1String("uncalibratedColor"));
}
bool IccManager::isMissingProfile() const
{
return d->embeddedProfile.isNull();
}
bool IccManager::isProfileMismatch() const
{
return d->profileMismatch;
}
ICCSettingsContainer::Behavior IccManager::safestBestBehavior() const
{
if (isUncalibratedColor())
{
return ICCSettingsContainer::InputToWorkspace;
}
else if (isMissingProfile())
{
// Assume sRGB (normal, untagged file) and stick with it
return ICCSettingsContainer::UseSRGB | ICCSettingsContainer::KeepProfile;
}
else if (isProfileMismatch())
{
return ICCSettingsContainer::EmbeddedToWorkspace;
}
else
{
return ICCSettingsContainer::PreserveEmbeddedProfile;
}
}
void IccManager::transform(ICCSettingsContainer::Behavior behavior, const IccProfile& specifiedProfile)
{
if (d->image.isNull())
{
return;
}
if (!d->settings.enableCM)
{
return;
}
if (behavior == ICCSettingsContainer::AskUser)
{
if (isUncalibratedColor())
{
d->image.setAttribute(QLatin1String("uncalibratedColorAskUser"), true);
}
else if (isMissingProfile())
{
d->image.setAttribute(QLatin1String("missingProfileAskUser"), true);
}
else if (isProfileMismatch())
{
d->image.setAttribute(QLatin1String("profileMismatchAskUser"), true);
}
return;
}
else if (behavior == ICCSettingsContainer::SafestBestAction)
{
behavior = safestBestBehavior();
}
IccTransform trans;
getTransform(trans, behavior, specifiedProfile);
if (trans.willHaveEffect())
{
trans.apply(d->image, d->observer);
setIccProfile(trans.outputProfile());
}
}
bool IccManager::needsPostLoadingManagement(const DImg& img)
{
return (img.hasAttribute(QLatin1String("missingProfileAskUser")) ||
img.hasAttribute(QLatin1String("profileMismatchAskUser")) ||
img.hasAttribute(QLatin1String("uncalibratedColorAskUser")));
}
/*
* "postLoadingManagement" is implemented in the class IccPostLoadingManager
*/
// -- Behavior implementation ----------------------------------------------------------------------------------
IccProfile IccManager::imageProfile(ICCSettingsContainer::Behavior behavior, const IccProfile& specifiedProfile)
{
if (behavior & ICCSettingsContainer::UseEmbeddedProfile)
{
return d->embeddedProfile;
}
else if (behavior & ICCSettingsContainer::UseWorkspace)
{
return d->workspaceProfile;
}
else if (behavior & ICCSettingsContainer::UseSRGB)
{
return IccProfile::sRGB();
}
else if (behavior & ICCSettingsContainer::UseDefaultInputProfile)
{
return IccProfile(d->settings.defaultInputProfile);
}
else if (behavior & ICCSettingsContainer::UseSpecifiedProfile)
{
return specifiedProfile;
}
else if (behavior & ICCSettingsContainer::AutomaticColors)
{
qCDebug(DIGIKAM_DIMG_LOG) << "Let the RAW loader do automatic color conversion";
return IccProfile();
}
else if (behavior & ICCSettingsContainer::DoNotInterpret)
{
return IccProfile();
}
qCDebug(DIGIKAM_DIMG_LOG) << "No input profile: invalid Behavior flags" << (int)behavior;
return IccProfile();
}
void IccManager::getTransform(IccTransform& trans, ICCSettingsContainer::Behavior behavior, const IccProfile& specifiedProfile)
{
IccProfile inputProfile = imageProfile(behavior, specifiedProfile);
IccProfile outputProfile;
trans.setIntent(d->settings.renderingIntent);
trans.setUseBlackPointCompensation(d->settings.useBPC);
// Output
if (behavior & ICCSettingsContainer::ConvertToWorkspace)
{
outputProfile = d->workspaceProfile;
}
if (inputProfile.isNull())
{
return;
}
// Assigning the _input_ profile, if necessary. If output profile is not null, it needs to be assigned later.
if (inputProfile != d->embeddedProfile && !(behavior & ICCSettingsContainer::LeaveFileUntagged))
{
setIccProfile(inputProfile);
}
if (!outputProfile.isNull())
{
trans.setInputProfile(inputProfile);
trans.setOutputProfile(outputProfile);
}
}
void IccManager::setIccProfile(const IccProfile& profile)
{
d->image.setIccProfile(profile);
d->embeddedProfile = profile;
if (!d->embeddedProfile.isNull())
{
d->profileMismatch = !d->embeddedProfile.isSameProfileAs(d->workspaceProfile);
}
}
// -- Display -------------------------------------------------------------------------------------------
void IccManager::transformForDisplay()
{
transformForDisplay(displayProfile());
}
void IccManager::transformForDisplay(QWidget* widget)
{
transformForDisplay(displayProfile(widget));
}
void IccManager::transformForDisplay(const IccProfile& profile)
{
if (d->image.isNull())
{
return;
}
if (!d->settings.enableCM)
{
return;
}
IccProfile outputProfile = profile;
if (outputProfile.isNull())
{
outputProfile = displayProfile();
}
if (isUncalibratedColor())
{
// set appropriate outputColorSpace in RawLoadingSettings
qCDebug(DIGIKAM_DIMG_LOG) << "Do not use transformForDisplay for uncalibrated data "
"but let the RAW loader do the conversion to sRGB";
}
IccTransform trans = displayTransform(outputProfile);
if (trans.willHaveEffect())
{
trans.apply(d->image, d->observer);
setIccProfile(trans.outputProfile());
}
}
IccProfile IccManager::displayProfile(QWidget* const displayingWidget)
{
if (!IccSettings::instance()->isEnabled())
{
return IccProfile::sRGB();
}
IccProfile profile = IccSettings::instance()->monitorProfile(displayingWidget);
if (profile.open())
{
return profile;
}
return IccProfile::sRGB();
}
IccTransform IccManager::displayTransform(QWidget* const displayingWidget)
{
return displayTransform(displayProfile(displayingWidget));
}
IccTransform IccManager::displayTransform(const IccProfile& displayProfile)
{
if (d->image.isNull())
{
return IccTransform();
}
if (!d->settings.enableCM)
{
return IccTransform();
}
IccTransform trans;
trans.setIntent(d->settings.renderingIntent);
trans.setUseBlackPointCompensation(d->settings.useBPC);
if (isUncalibratedColor())
{
// set appropriate outputColorSpace in RawLoadingSettings
qCDebug(DIGIKAM_DIMG_LOG) << "Do not use transformForDisplay for uncalibrated data "
"but let the RAW loader do the conversion to sRGB";
}
else if (isMissingProfile())
{
ICCSettingsContainer::Behavior missingProfileBehavior = d->settings.defaultMissingProfileBehavior;
if (missingProfileBehavior == ICCSettingsContainer::AskUser ||
missingProfileBehavior == ICCSettingsContainer::SafestBestAction)
{
missingProfileBehavior = safestBestBehavior();
}
IccProfile assumedImageProfile = imageProfile(missingProfileBehavior, IccProfile());
IccProfile outputProfile(displayProfile);
if (!assumedImageProfile.isSameProfileAs(outputProfile))
{
trans.setInputProfile(d->embeddedProfile);
trans.setOutputProfile(outputProfile);
}
}
else
{
IccProfile outputProfile(displayProfile);
if (!d->embeddedProfile.isSameProfileAs(outputProfile))
{
trans.setInputProfile(d->embeddedProfile);
trans.setOutputProfile(outputProfile);
}
}
return trans;
}
IccTransform IccManager::displaySoftProofingTransform(const IccProfile& deviceProfile, QWidget* const displayingWidget)
{
return displaySoftProofingTransform(deviceProfile, displayProfile(displayingWidget));
}
IccTransform IccManager::displaySoftProofingTransform(const IccProfile& deviceProfile, const IccProfile& displayProfile)
{
IccTransform transform = displayTransform(displayProfile);
transform.setProofProfile(deviceProfile);
transform.setCheckGamut(d->settings.doGamutCheck);
transform.setCheckGamutMaskColor(d->settings.gamutCheckMaskColor);
return transform;
}
// -- sRGB and Output -------------------------------------------------------------------------------------------------
bool IccManager::isSRGB(const DImg& image)
{
if (image.isNull() || !IccSettings::instance()->isEnabled())
{
return true; // then, everything is sRGB
}
IccProfile embeddedProfile = image.getIccProfile();
IccProfile outputProfile = IccProfile::sRGB();
if (embeddedProfile.isNull()) // assume sRGB
{
return true;
}
else
{
return embeddedProfile.isSameProfileAs(outputProfile);
}
}
void IccManager::transformToSRGB()
{
if (d->image.isNull())
{
return;
}
if (!d->settings.enableCM)
{
return;
}
if (isUncalibratedColor())
{
// set appropriate outputColorSpace in RawLoadingSettings
qCDebug(DIGIKAM_DIMG_LOG) << "Do not use transformForDisplay for uncalibrated data "
"but let the RAW loader do the conversion to sRGB";
}
else if (isMissingProfile())
{
// all is fine, assume sRGB
//TODO: honour options?
}
else
{
IccProfile outputProfile = IccProfile::sRGB();
if (!d->embeddedProfile.isSameProfileAs(outputProfile))
{
IccTransform trans;
trans.setInputProfile(d->embeddedProfile);
trans.setOutputProfile(outputProfile);
trans.setIntent(d->settings.renderingIntent);
trans.setUseBlackPointCompensation(d->settings.useBPC);
trans.apply(d->image, d->observer);
setIccProfile(trans.outputProfile());
}
}
}
void IccManager::transformToSRGB(QImage& qimage, const IccProfile& input)
{
if (qimage.isNull())
{
return;
}
if (input.isNull())
{
return;
}
IccProfile inputProfile(input);
IccProfile outputProfile = IccProfile::sRGB();
if (!inputProfile.isSameProfileAs(outputProfile))
{
IccTransform trans;
trans.setInputProfile(inputProfile);
trans.setOutputProfile(outputProfile);
trans.setIntent(IccTransform::Perceptual);
trans.apply(qimage);
}
}
void IccManager::transformForDisplay(QImage& qimage, const IccProfile& displayProfile1)
{
if (qimage.isNull())
{
return;
}
if (displayProfile1.isNull())
{
return;
}
IccProfile inputProfile = IccProfile::sRGB();
IccProfile outputProfile(displayProfile1);
if (!inputProfile.isSameProfileAs(outputProfile))
{
IccTransform trans;
trans.setInputProfile(inputProfile);
trans.setOutputProfile(outputProfile);
trans.setIntent(IccTransform::Perceptual);
trans.apply(qimage);
}
}
void IccManager::transformForOutput(const IccProfile& prof)
{
if (d->image.isNull())
{
return;
}
if (!d->settings.enableCM)
{
return;
}
IccProfile inputProfile;
IccProfile outputProfile = prof;
if (isUncalibratedColor())
{
// set appropriate outputColorSpace in RawLoadingSettings
qCDebug(DIGIKAM_DIMG_LOG) << "Do not use transformForOutput for uncalibrated data "
"but let the RAW loader do the conversion to sRGB";
}
else if (isMissingProfile())
{
inputProfile = IccProfile::sRGB();
//TODO: honour options?
}
else
{
if (!d->embeddedProfile.isSameProfileAs(outputProfile))
{
inputProfile = d->embeddedProfile;
}
}
if (!inputProfile.isNull())
{
IccTransform trans;
trans.setInputProfile(inputProfile);
trans.setOutputProfile(outputProfile);
trans.setIntent(d->settings.renderingIntent);
trans.setUseBlackPointCompensation(d->settings.useBPC);
trans.apply(d->image, d->observer);
setIccProfile(trans.outputProfile());
}
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/lc/localcontrastfilter.cpp b/core/libs/dimg/filters/lc/localcontrastfilter.cpp
index 88dbce1da0..99b5619771 100644
--- a/core/libs/dimg/filters/lc/localcontrastfilter.cpp
+++ b/core/libs/dimg/filters/lc/localcontrastfilter.cpp
@@ -1,732 +1,732 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-08-09
* Description : Enhance image with local contrasts (as human eye does).
* LDR ToneMapper <http://zynaddsubfx.sourceforge.net/other/tonemapping>
*
* Copyright (C) 2009 by Nasca Octavian Paul <zynaddsubfx at yahoo dot com>
* Copyright (C) 2009 by Julien Pontabry <julien dot pontabry at gmail dot com>
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "localcontrastfilter.h"
// Qt includes
#include <QtMath>
#include <QtConcurrent> // krazy:exclude=includes
// Local includes
#include "digikam_debug.h"
#include "randomnumbergenerator.h"
namespace Digikam
{
class Q_DECL_HIDDEN LocalContrastFilter::Private
{
public:
explicit Private()
{
current_process_power_value = 20.0;
}
// preprocessed values
float current_process_power_value;
LocalContrastContainer par;
RandomNumberGenerator generator;
};
LocalContrastFilter::LocalContrastFilter(QObject* const parent)
: DImgThreadedFilter(parent),
d(new Private)
{
initFilter();
}
LocalContrastFilter::LocalContrastFilter(DImg* const image, QObject* const parent, const LocalContrastContainer& par)
: DImgThreadedFilter(image, parent, QLatin1String("LocalContrast")),
d(new Private)
{
d->par = par;
d->generator.seedByTime();
initFilter();
}
LocalContrastFilter::~LocalContrastFilter()
{
cancelFilter();
delete d;
}
void LocalContrastFilter::filterImage()
{
if (!m_orgImage.isNull())
{
int size = m_orgImage.width() * m_orgImage.height() * 3;
int i, j;
d->generator.reseed();
if (m_orgImage.sixteenBit())
{
// sixteen bit image
QScopedArrayPointer<unsigned short> data(new unsigned short[size]);
unsigned short* dataImg = reinterpret_cast<unsigned short*>(m_orgImage.bits());
for (i = 0, j = 0; runningFlag() && (i < size); i += 3, j += 4)
{
data[i] = dataImg[j];
data[i + 1] = dataImg[j + 1];
data[i + 2] = dataImg[j + 2];
}
postProgress(10);
process16bitRgbImage(data.data(), m_orgImage.width(), m_orgImage.height());
for (uint x = 0; runningFlag() && (x < m_orgImage.width()); ++x)
{
for (uint y = 0; runningFlag() && (y < m_orgImage.height()); ++y)
{
i = (m_orgImage.width() * y + x) * 3;
m_destImage.setPixelColor(x, y, DColor((unsigned short)data[i + 2],
(unsigned short)data[i + 1],
(unsigned short)data[i],
65535, true));
}
}
postProgress(90);
}
else
{
// eight bit image
QScopedArrayPointer<uchar> data(new uchar[size]);
for (i = 0, j = 0; runningFlag() && (i < size); i += 3, j += 4)
{
data[i] = m_orgImage.bits()[j];
data[i + 1] = m_orgImage.bits()[j + 1];
data[i + 2] = m_orgImage.bits()[j + 2];
}
postProgress(10);
process8bitRgbImage(data.data(), m_orgImage.width(), m_orgImage.height());
for (uint x = 0; runningFlag() && (x < m_orgImage.width()); ++x)
{
for (uint y = 0; runningFlag() && (y < m_orgImage.height()); ++y)
{
i = (m_orgImage.width() * y + x) * 3;
m_destImage.setPixelColor(x, y, DColor(data[i + 2], data[i + 1], data[i], 255, false));
}
}
postProgress(90);
}
}
postProgress(100);
}
void LocalContrastFilter::process8bitRgbImage(unsigned char* const img, int sizex, int sizey)
{
int size = sizex * sizey;
QScopedArrayPointer<float> tmpimage(new float[size * 3]);
for (int i = 0 ; runningFlag() && (i < size * 3) ; ++i)
{
// convert to floating point
tmpimage[i] = (float)(img[i] / 255.0);
}
postProgress(20);
processRgbImage(tmpimage.data(), sizex, sizey);
// convert back to 8 bits (with dithering)
int pos = 0;
for (int i = 0 ; runningFlag() && (i < size) ; ++i)
{
float dither = d->generator.number(0.0, 1.0);
img[pos] = (int)(tmpimage[pos] * 255.0 + dither);
img[pos + 1] = (int)(tmpimage[pos + 1] * 255.0 + dither);
img[pos + 2] = (int)(tmpimage[pos + 2] * 255.0 + dither);
pos += 3;
}
postProgress(80);
}
void LocalContrastFilter::process16bitRgbImage(unsigned short* const img, int sizex, int sizey)
{
int size = sizex * sizey;
QScopedArrayPointer<float> tmpimage(new float[size * 3]);
for (int i = 0 ; runningFlag() && (i < size * 3) ; ++i)
{
// convert to floating point
tmpimage[i] = (float)(img[i] / 65535.0);
}
postProgress(20);
processRgbImage(tmpimage.data(), sizex, sizey);
// convert back to 16 bits (with dithering)
int pos = 0;
for (int i = 0 ; runningFlag() && (i < size) ; ++i)
{
float dither = d->generator.number(0.0, 1.0);
img[pos] = (int)(tmpimage[pos] * 65535.0 + dither);
img[pos + 1] = (int)(tmpimage[pos + 1] * 65535.0 + dither);
img[pos + 2] = (int)(tmpimage[pos + 2] * 65535.0 + dither);
pos += 3;
}
postProgress(80);
}
float LocalContrastFilter::func(float x1, float x2)
{
float result = 0.5;
float p;
switch (d->par.functionId)
{
case 0: // power function
{
p = (float)(qPow((double)10.0, (double)qFabs((x2 * 2.0 - 1.0)) * d->current_process_power_value * 0.02));
if (x2 >= 0.5)
{
result = qPow(x1, p);
}
else
{
result = (float)(1.0 - qPow((double)1.0 - x1, (double)p));
}
break;
}
case 1: // linear function
{
p = (float)(1.0 / (1 + qExp(-(x2 * 2.0 - 1.0) * d->current_process_power_value * 0.04)));
result = (x1 < p) ? (float)(x1 * (1.0 - p) / p) : (float)((1.0 - p) + (x1 - p) * p / (1.0 - p));
break;
}
}
return result;
}
void LocalContrastFilter::blurMultithreaded(uint start, uint stop, float* const img, float* const blurimage)
{
uint pos = start * 3;
for (uint i = start ; runningFlag() && (i < stop) ; ++i)
{
float src_r = img[pos];
float src_g = img[pos + 1];
float src_b = img[pos + 2];
float blur = blurimage[i];
float dest_r = func(src_r, blur);
float dest_g = func(src_g, blur);
float dest_b = func(src_b, blur);
img[pos] = dest_r;
img[pos + 1] = dest_g;
img[pos + 2] = dest_b;
pos += 3;
}
}
void LocalContrastFilter::saturationMultithreaded(uint start, uint stop, float* const img, float* const srcimg)
{
float src_h, src_s, src_v;
float dest_h, dest_s, dest_v;
float destSaturation, s1;
uint pos = start * 3;
int highSaturationValue = 100 - d->par.highSaturation;
int lowSaturationValue = 100 - d->par.lowSaturation;
for (uint i = start ; runningFlag() && (i < stop) ; ++i)
{
rgb2hsv(srcimg[pos], srcimg[pos + 1], srcimg[pos + 2], src_h, src_s, src_v);
rgb2hsv(img[pos], img[pos + 1], img[pos + 2], dest_h, dest_s, dest_v);
destSaturation = (float)((src_s * highSaturationValue + dest_s * (100.0 - highSaturationValue)) * 0.01);
if (dest_v > src_v)
{
s1 = (float)(destSaturation * src_v / (dest_v + 1.0 / 255.0));
destSaturation = (float)((lowSaturationValue * s1 + d->par.lowSaturation * destSaturation) * 0.01);
}
hsv2rgb(dest_h, destSaturation, dest_v, img[pos], img[pos + 1], img[pos + 2]);
pos += 3;
}
}
void LocalContrastFilter::processRgbImage(float* const img, int sizex, int sizey)
{
int size = sizex * sizey;
QScopedArrayPointer<float> blurimage(new float[size]);
QScopedArrayPointer<float> srcimg(new float[size * 3]);
for (int i = 0 ; i < (size * 3) ; ++i)
{
srcimg[i] = img[i];
}
postProgress(30);
if (d->par.stretchContrast)
{
stretchContrast(img, size * 3);
}
postProgress(40);
QList<int> vals = multithreadedSteps(size);
int pos = 0;
for (int nstage = 0 ; runningFlag() && (nstage < TONEMAPPING_MAX_STAGES) ; ++nstage)
{
if (d->par.stage[nstage].enabled)
{
// compute the desatured image
pos = 0;
for (int i = 0 ; runningFlag() && (i < size) ; ++i)
{
blurimage[i] = (float)((img[pos] + img[pos + 1] + img[pos + 2]) / 3.0);
pos += 3;
}
d->current_process_power_value = d->par.getPower(nstage);
// blur
inplaceBlur(blurimage.data(), sizex, sizey, d->par.getBlur(nstage));
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LocalContrastFilter::blurMultithreaded,
vals[j],
vals[j+1],
img,
blurimage.data()
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
postProgress(50 + nstage * 5);
}
if ((d->par.highSaturation != 100) || (d->par.lowSaturation != 100))
{
qCDebug(DIGIKAM_DIMG_LOG) << "highSaturation : " << d->par.highSaturation;
qCDebug(DIGIKAM_DIMG_LOG) << "lowSaturation : " << d->par.lowSaturation;
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LocalContrastFilter::saturationMultithreaded,
vals[j],
vals[j+1],
img,
srcimg.data()
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
postProgress(70);
}
void LocalContrastFilter::inplaceBlurYMultithreaded(const Args& prm)
{
for (uint y = prm.start ; runningFlag() && (y < prm.stop) ; ++y)
{
uint pos = y * prm.sizex;
float old = prm.data[pos];
++pos;
for (int x = 1 ; runningFlag() && (x < prm.sizex) ; ++x)
{
old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove;
prm.data[pos] = old;
++pos;
}
pos = y * prm.sizex + prm.sizex - 1;
for (int x = 1 ; runningFlag() && (x < prm.sizex) ; ++x)
{
old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove;
prm.data[pos] = old;
pos--;
}
}
}
void LocalContrastFilter::inplaceBlurXMultithreaded(const Args& prm)
{
for (uint x = prm.start ; runningFlag() && (x < prm.stop) ; ++x)
{
uint pos = x;
float old = prm.data[pos];
for (int y = 1 ; runningFlag() && (y < prm.sizey) ; ++y)
{
old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove;
prm.data[pos] = old;
pos += prm.sizex;
}
pos = x + prm.sizex * (prm.sizey - 1);
for (int y = 1 ; runningFlag() && (y < prm.sizey) ; ++y)
{
old = (prm.data[pos] * (1 - prm.a) + old * prm.a) + prm.denormal_remove;
prm.data[pos] = old;
pos -= prm.sizex;
}
}
}
void LocalContrastFilter::inplaceBlur(float* const data, int sizex, int sizey, float blur)
{
if (blur < 0.3)
{
return;
}
Args prm;
prm.a = (float)(qExp(log(0.25) / blur));
if ((prm.a <= 0.0) || (prm.a >= 1.0))
{
return;
}
prm.a *= prm.a;
prm.data = data;
prm.sizex = sizex;
prm.sizey = sizey;
prm.blur = blur;
prm.denormal_remove = (float)(1e-15);
QList<int> valsx = multithreadedSteps(prm.sizex);
QList<int> valsy = multithreadedSteps(prm.sizey);
for (uint stage = 0 ; runningFlag() && (stage < 2) ; ++stage)
{
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < valsy.count()-1) ; ++j)
{
prm.start = valsy[j];
prm.stop = valsy[j+1];
tasks.append(QtConcurrent::run(this,
&LocalContrastFilter::inplaceBlurYMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
tasks.clear();
for (int j = 0 ; runningFlag() && (j < valsx.count()-1) ; ++j)
{
prm.start = valsx[j];
prm.stop = valsx[j+1];
tasks.append(QtConcurrent::run(this,
&LocalContrastFilter::inplaceBlurXMultithreaded,
prm
));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
}
}
void LocalContrastFilter::stretchContrast(float* const data, int datasize)
{
// stretch the contrast
const unsigned int histogram_size = 256;
// first, we compute the histogram
unsigned int histogram[histogram_size];
for (unsigned int i = 0 ; i < histogram_size ; ++i)
{
histogram[i] = 0;
}
for (unsigned int i = 0 ; runningFlag() && (i < (unsigned int)datasize) ; ++i)
{
int m = (int)(data[i] * (histogram_size - 1));
if (m < 0)
{
m = 0;
}
if (m > (int)(histogram_size - 1))
{
m = histogram_size - 1;
}
histogram[m]++;
}
- // I want to strip the lowest and upper 0.1 procents (in the histogram) of the pixels
+ // I want to strip the lowest and upper 0.1 percents (in the histogram) of the pixels
int min = 0;
int max = 255;
unsigned int desired_sum = datasize / 1000;
unsigned int sum_min = 0;
unsigned int sum_max = 0;
for (unsigned int i = 0 ; runningFlag() && (i < histogram_size) ; ++i)
{
sum_min += histogram[i];
if (sum_min > desired_sum)
{
min = i;
break;
}
}
for (int i = histogram_size - 1 ; runningFlag() && (i >= 0) ; i--)
{
sum_max += histogram[i];
if (sum_max > desired_sum)
{
max = i;
break;
}
}
if (min >= max)
{
min = 0;
max = 255;
}
float min_src_val = (float)(min / 255.0);
float max_src_val = (float)(max / 255.0);
for (int i = 0 ; runningFlag() && (i < datasize) ; ++i)
{
// stretch the contrast
float x = data[i];
x = (x - min_src_val) / (max_src_val - min_src_val);
if (x < 0.0)
{
x = 0.0;
}
if (x > 1.0)
{
x = 1.0;
}
data[i] = x;
}
}
void LocalContrastFilter::rgb2hsv(const float& r, const float& g, const float& b, float& h, float& s, float& v)
{
float maxrg = (r > g) ? r : g;
float max = (maxrg > b) ? maxrg : b;
float minrg = (r < g) ? r : g;
float min = (minrg < b) ? minrg : b;
float delta = max - min;
//hue
if (min == max)
{
h = 0.0;
}
else
{
if (max == r)
{
h = (float)(fmod(60.0 * (g - b) / delta + 360.0, 360.0));
}
else
{
if (max == g)
{
h = (float)(60.0 * (b - r) / delta + 120.0);
}
else
{
//max==b
h = (float)(60.0 * (r - g) / delta + 240.0);
}
}
}
//saturation
if (max < 1e-6)
{
s = 0;
}
else
{
s = (float)(1.0 - min / max);
}
//value
v = max;
}
void LocalContrastFilter::hsv2rgb(const float& h, const float& s, const float& v, float& r, float& g, float& b)
{
float hfi = (float)(floor(h / 60.0));
float f = (float)((h / 60.0) - hfi);
int hi = ((int)hfi) % 6;
float p = (float)(v * (1.0 - s));
float q = (float)(v * (1.0 - f * s));
float t = (float)(v * (1.0 - (1.0 - f) * s));
switch (hi)
{
case 0:
r = v ;
g = t ;
b = p;
break;
case 1:
r = q ;
g = v ;
b = p;
break;
case 2:
r = p ;
g = v ;
b = t;
break;
case 3:
r = p ;
g = q ;
b = v;
break;
case 4:
r = t ;
g = p;
b = v;
break;
case 5:
r = v ;
g = p ;
b = q;
break;
}
}
FilterAction LocalContrastFilter::filterAction()
{
FilterAction action(FilterIdentifier(), CurrentVersion());
action.setDisplayableName(DisplayableName());
action.addParameter(QLatin1String("functionId"), d->par.functionId);
action.addParameter(QLatin1String("highSaturation"), d->par.highSaturation);
action.addParameter(QLatin1String("lowSaturation"), d->par.lowSaturation);
action.addParameter(QLatin1String("stretchContrast"), d->par.stretchContrast);
for (int nstage = 0 ; nstage < TONEMAPPING_MAX_STAGES ; ++nstage)
{
QString stage = QString::fromLatin1("stage[%1]:").arg(nstage);
action.addParameter(stage + QLatin1String("enabled"), d->par.stage[nstage].enabled);
if (d->par.stage[nstage].enabled)
{
action.addParameter(stage + QLatin1String("power"), d->par.stage[nstage].power);
action.addParameter(stage + QLatin1String("blur"), d->par.stage[nstage].blur);
}
}
action.addParameter(QLatin1String("randomSeed"), d->generator.currentSeed());
return action;
}
void LocalContrastFilter::readParameters(const FilterAction& action)
{
d->par.functionId = action.parameter(QLatin1String("functionId")).toInt();
d->par.highSaturation = action.parameter(QLatin1String("highSaturation")).toInt();
d->par.lowSaturation = action.parameter(QLatin1String("lowSaturation")).toInt();
d->par.stretchContrast = action.parameter(QLatin1String("stretchContrast")).toBool();
for (int nstage = 0 ; nstage < TONEMAPPING_MAX_STAGES ; ++nstage)
{
QString stage = QString::fromLatin1("stage[%1]:").arg(nstage);
d->par.stage[nstage].enabled = action.parameter(stage + QLatin1String("enabled")).toBool();
if (d->par.stage[nstage].enabled)
{
d->par.stage[nstage].power = action.parameter(stage + QLatin1String("power")).toFloat();
d->par.stage[nstage].blur = action.parameter(stage + QLatin1String("blur")).toFloat();
}
}
d->generator.seed(action.parameter(QLatin1String("randomSeed")).toUInt());
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/lens/lensfunfilter.cpp b/core/libs/dimg/filters/lens/lensfunfilter.cpp
index ef6376594d..46c9d0e664 100644
--- a/core/libs/dimg/filters/lens/lensfunfilter.cpp
+++ b/core/libs/dimg/filters/lens/lensfunfilter.cpp
@@ -1,395 +1,395 @@
/* ============================================================
*
* Date : 2008-02-10
* Description : a tool to fix automatically camera lens aberrations
*
* Copyright (C) 2008 by Adrian Schroeter <adrian at suse dot de>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "lensfunfilter.h"
// Qt includes
#include <QByteArray>
#include <QCheckBox>
#include <QString>
#include <QtConcurrent> // krazy:exclude=includes
// Local includes
#include "digikam_debug.h"
#include "lensfuniface.h"
#include "dmetadata.h"
namespace Digikam
{
class Q_DECL_HIDDEN LensFunFilter::Private
{
public:
explicit Private()
{
iface = 0;
modifier = 0;
loop = 0;
}
DImg tempImage;
LensFunIface* iface;
lfModifier* modifier;
int loop;
};
LensFunFilter::LensFunFilter(QObject* const parent)
: DImgThreadedFilter(parent),
d(new Private)
{
d->iface = new LensFunIface;
initFilter();
}
LensFunFilter::LensFunFilter(DImg* const orgImage, QObject* const parent, const LensFunContainer& settings)
: DImgThreadedFilter(orgImage, parent, QLatin1String("LensCorrection")),
d(new Private)
{
d->iface = new LensFunIface;
d->iface->setSettings(settings);
initFilter();
}
LensFunFilter::~LensFunFilter()
{
cancelFilter();
if (d->modifier)
{
d->modifier->Destroy();
}
delete d->iface;
delete d;
}
void LensFunFilter::filterCCAMultithreaded(uint start, uint stop)
{
QScopedArrayPointer<float> pos(new float[m_orgImage.width() * 2 * 3]);
for (unsigned int y = start; runningFlag() && (y < stop); ++y)
{
if (d->modifier->ApplySubpixelDistortion(0.0, y, m_orgImage.width(), 1, pos.data()))
{
float* src = pos.data();
for (unsigned x = 0; runningFlag() && (x < m_destImage.width()); ++x)
{
DColor destPixel(0, 0, 0, 0xFFFF, m_destImage.sixteenBit());
destPixel.setRed(m_orgImage.getSubPixelColorFast(src[0], src[1]).red());
destPixel.setGreen(m_orgImage.getSubPixelColorFast(src[2], src[3]).green());
destPixel.setBlue(m_orgImage.getSubPixelColorFast(src[4], src[5]).blue());
m_destImage.setPixelColor(x, y, destPixel);
src += 2 * 3;
}
}
}
}
void LensFunFilter::filterVIGMultithreaded(uint start, uint stop)
{
uchar* data = m_destImage.bits();
data += m_destImage.width() * m_destImage.bytesDepth() * start;
for (unsigned int y = start; runningFlag() && (y < stop); ++y)
{
if (d->modifier->ApplyColorModification(data,
0.0,
y,
m_destImage.width(),
1,
LF_CR_4(RED, GREEN, BLUE, UNKNOWN),
0))
{
data += m_destImage.width() * m_destImage.bytesDepth();
}
}
}
void LensFunFilter::filterDSTMultithreaded(uint start, uint stop)
{
QScopedArrayPointer<float> pos(new float[m_orgImage.width() * 2 * 3]);
for (unsigned int y = start; runningFlag() && (y < stop); ++y)
{
if (d->modifier->ApplyGeometryDistortion(0.0, y, d->tempImage.width(), 1, pos.data()))
{
float* src = pos.data();
for (unsigned int x = 0; runningFlag() && (x < d->tempImage.width()); ++x, ++d->loop)
{
d->tempImage.setPixelColor(x, y, m_destImage.getSubPixelColor(src[0], src[1]));
src += 2;
}
}
}
}
void LensFunFilter::filterImage()
{
m_destImage.bitBltImage(&m_orgImage, 0, 0);
if (!d->iface)
{
qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: LensFun Interface is null.";
return;
}
if (!d->iface->usedLens())
{
qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: LensFun Interface Lens device is null.";
return;
}
// Lensfun Modifier flags to process
int modifyFlags = 0;
if (d->iface->settings().filterDST)
{
modifyFlags |= LF_MODIFY_DISTORTION;
}
if (d->iface->settings().filterGEO)
{
modifyFlags |= LF_MODIFY_GEOMETRY;
}
if (d->iface->settings().filterCCA)
{
modifyFlags |= LF_MODIFY_TCA;
}
if (d->iface->settings().filterVIG)
{
modifyFlags |= LF_MODIFY_VIGNETTING;
}
// Init lensfun lib, we are working on the full image.
lfPixelFormat colorDepth = m_orgImage.bytesDepth() == 4 ? LF_PF_U8 : LF_PF_U16;
d->modifier = lfModifier::Create(d->iface->usedLens(),
d->iface->settings().cropFactor,
m_orgImage.width(),
m_orgImage.height());
int modflags = d->modifier->Initialize(d->iface->usedLens(),
colorDepth,
d->iface->settings().focalLength,
d->iface->settings().aperture,
d->iface->settings().subjectDistance,
1.0, /* no scaling */
LF_RECTILINEAR,
modifyFlags,
0 /*no inverse*/);
if (!d->modifier)
{
qCDebug(DIGIKAM_DIMG_LOG) << "ERROR: cannot initialize LensFun Modifier.";
return;
}
// Calc necessary steps for progress bar
int steps = ((d->iface->settings().filterCCA) ? 1 : 0) +
((d->iface->settings().filterVIG) ? 1 : 0) +
((d->iface->settings().filterDST || d->iface->settings().filterGEO) ? 1 : 0);
qCDebug(DIGIKAM_DIMG_LOG) << "LensFun Modifier Flags: " << modflags << " Steps:" << steps;
if (steps < 1)
{
qCDebug(DIGIKAM_DIMG_LOG) << "No LensFun Modifier steps. There is nothing to process...";
return;
}
qCDebug(DIGIKAM_DIMG_LOG) << "Image size to process: ("
<< m_orgImage.width() << ", "
<< m_orgImage.height() << ")";
QList<int> vals = multithreadedSteps(m_destImage.height());
- // Stage 1: Chromatic Aberation Corrections
+ // Stage 1: Chromatic Aberration Corrections
if (d->iface->settings().filterCCA)
{
m_orgImage.prepareSubPixelAccess(); // init lanczos kernel
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LensFunFilter::filterCCAMultithreaded,
vals[j],
vals[j+1]));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
- qCDebug(DIGIKAM_DIMG_LOG) << "Chromatic Aberation Corrections applied.";
+ qCDebug(DIGIKAM_DIMG_LOG) << "Chromatic Aberration Corrections applied.";
}
postProgress(30);
// Stage 2: Color Corrections: Vignetting and Color Contribution Index
if (d->iface->settings().filterVIG)
{
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LensFunFilter::filterVIGMultithreaded,
vals[j],
vals[j+1]));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
qCDebug(DIGIKAM_DIMG_LOG) << "Vignetting and Color Corrections applied.";
}
postProgress(60);
// Stage 3: Distortion and Geometry Corrections
if (d->iface->settings().filterDST || d->iface->settings().filterGEO)
{
d->loop = 0;
// we need a deep copy first
d->tempImage = DImg(m_destImage.width(),
m_destImage.height(),
m_destImage.sixteenBit(),
m_destImage.hasAlpha());
m_destImage.prepareSubPixelAccess(); // init lanczos kernel
QList <QFuture<void> > tasks;
for (int j = 0 ; runningFlag() && (j < vals.count()-1) ; ++j)
{
tasks.append(QtConcurrent::run(this,
&LensFunFilter::filterDSTMultithreaded,
vals[j],
vals[j+1]));
}
foreach(QFuture<void> t, tasks)
t.waitForFinished();
qCDebug(DIGIKAM_DIMG_LOG) << "Distortion and Geometry Corrections applied.";
if (d->loop)
{
m_destImage = d->tempImage;
}
}
postProgress(90);
}
bool LensFunFilter::registerSettingsToXmp(MetaEngineData& data) const
{
// Register in digiKam Xmp namespace all information about Lens corrections.
QString str;
LensFunContainer prm = d->iface->settings();
str.append(i18n("Camera: %1-%2", prm.cameraMake, prm.cameraModel));
str.append(QLatin1Char('\n'));
str.append(i18n("Lens: %1", prm.lensModel));
str.append(QLatin1Char('\n'));
str.append(i18n("Subject Distance: <numid>%1</numid>", prm.subjectDistance));
str.append(QLatin1Char('\n'));
str.append(i18n("Aperture: <numid>%1</numid>", prm.aperture));
str.append(QLatin1Char('\n'));
str.append(i18n("Focal Length: <numid>%1</numid>", prm.focalLength));
str.append(QLatin1Char('\n'));
str.append(i18n("Crop Factor: <numid>%1</numid>", prm.cropFactor));
str.append(QLatin1Char('\n'));
str.append(i18n("CCA Correction: %1", prm.filterCCA && d->iface->supportsCCA() ? i18n("enabled") : i18n("disabled")));
str.append(QLatin1Char('\n'));
str.append(i18n("VIG Correction: %1", prm.filterVIG && d->iface->supportsVig() ? i18n("enabled") : i18n("disabled")));
str.append(QLatin1Char('\n'));
str.append(i18n("DST Correction: %1", prm.filterDST && d->iface->supportsDistortion() ? i18n("enabled") : i18n("disabled")));
str.append(QLatin1Char('\n'));
str.append(i18n("GEO Correction: %1", prm.filterGEO && d->iface->supportsGeometry() ? i18n("enabled") : i18n("disabled")));
DMetadata meta(data);
bool ret = meta.setXmpTagString("Xmp.digiKam.LensCorrectionSettings",
str.replace(QLatin1Char('\n'), QLatin1String(" ; ")));
data = meta.data();
return ret;
}
FilterAction LensFunFilter::filterAction()
{
FilterAction action(FilterIdentifier(), CurrentVersion());
action.setDisplayableName(DisplayableName());
LensFunContainer prm = d->iface->settings();
action.addParameter(QLatin1String("ccaCorrection"), prm.filterCCA);
action.addParameter(QLatin1String("vigCorrection"), prm.filterVIG);
action.addParameter(QLatin1String("dstCorrection"), prm.filterDST);
action.addParameter(QLatin1String("geoCorrection"), prm.filterGEO);
action.addParameter(QLatin1String("cropFactor"), prm.cropFactor);
action.addParameter(QLatin1String("focalLength"), prm.focalLength);
action.addParameter(QLatin1String("aperture"), prm.aperture);
action.addParameter(QLatin1String("subjectDistance"), prm.subjectDistance);
action.addParameter(QLatin1String("cameraMake"), prm.cameraMake);
action.addParameter(QLatin1String("cameraModel"), prm.cameraModel);
action.addParameter(QLatin1String("lensModel"), prm.lensModel);
return action;
}
void LensFunFilter::readParameters(const Digikam::FilterAction& action)
{
LensFunContainer prm = d->iface->settings();
prm.filterCCA = action.parameter(QLatin1String("ccaCorrection")).toBool();
prm.filterVIG = action.parameter(QLatin1String("vigCorrection")).toBool();
prm.filterDST = action.parameter(QLatin1String("dstCorrection")).toBool();
prm.filterGEO = action.parameter(QLatin1String("geoCorrection")).toBool();
prm.cropFactor = action.parameter(QLatin1String("cropFactor")).toDouble();
prm.focalLength = action.parameter(QLatin1String("focalLength")).toDouble();
prm.aperture = action.parameter(QLatin1String("aperture")).toDouble();
prm.subjectDistance = action.parameter(QLatin1String("subjectDistance")).toDouble();
prm.cameraMake = action.parameter(QLatin1String("cameraMake")).toString();
prm.cameraModel = action.parameter(QLatin1String("cameraModel")).toString();
prm.lensModel = action.parameter(QLatin1String("lensModel")).toString();
d->iface->setSettings(prm);
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/lens/lensfunfilter.h b/core/libs/dimg/filters/lens/lensfunfilter.h
index 54aa99599c..6bc438ddde 100644
--- a/core/libs/dimg/filters/lens/lensfunfilter.h
+++ b/core/libs/dimg/filters/lens/lensfunfilter.h
@@ -1,129 +1,129 @@
/* ============================================================
*
* Date : 2008-02-10
* Description : a tool to fix automatically camera lens aberrations
*
* Copyright (C) 2008 by Adrian Schroeter <adrian at suse dot de>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_LENS_FUN_FILTER_H
#define DIGIKAM_LENS_FUN_FILTER_H
// Local includes
#include "digikam_config.h"
#include "dimgthreadedfilter.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT LensFunContainer
{
public:
explicit LensFunContainer()
{
filterCCA = true;
filterVIG = true;
filterDST = true;
filterGEO = true;
focalLength = -1.0;
aperture = -1.0;
subjectDistance = -1.0;
cropFactor = -1.0;
cameraMake = QString();
cameraModel = QString();
lensModel = QString();
};
~LensFunContainer()
{
};
public:
- bool filterCCA; /// Chromatic Aberation Corrections
- bool filterVIG; /// Vigneting Corrections
+ bool filterCCA; /// Chromatic Aberration Corrections
+ bool filterVIG; /// Vignetting Corrections
bool filterDST; /// Distortion Corrections
bool filterGEO; /// Geometry Corrections
double cropFactor;
double focalLength;
double aperture;
double subjectDistance;
QString cameraMake;
QString cameraModel;
QString lensModel;
};
class DIGIKAM_EXPORT LensFunFilter : public DImgThreadedFilter
{
public:
explicit LensFunFilter(QObject* const parent = 0);
explicit LensFunFilter(DImg* const origImage, QObject* const parent, const LensFunContainer& settings);
~LensFunFilter();
bool registerSettingsToXmp(MetaEngineData& data) const;
void readParameters(const FilterAction& action);
static QString FilterIdentifier()
{
return QLatin1String("digikam:LensFunFilter");
}
static QString DisplayableName()
{
return QString::fromUtf8(I18N_NOOP("Lens Auto-Correction Tool"));
}
static QList<int> SupportedVersions()
{
return QList<int>() << 1 << 2;
}
static int CurrentVersion()
{
return 2;
}
virtual QString filterIdentifier() const
{
return FilterIdentifier();
}
virtual FilterAction filterAction();
private:
void filterImage();
void filterCCAMultithreaded(uint start, uint stop);
void filterVIGMultithreaded(uint start, uint stop);
void filterDSTMultithreaded(uint start, uint stop);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_LENS_FUN_FILTER_H
diff --git a/core/libs/dimg/filters/lens/lensfuniface.cpp b/core/libs/dimg/filters/lens/lensfuniface.cpp
index 74359bb28d..249ed29af6 100644
--- a/core/libs/dimg/filters/lens/lensfuniface.cpp
+++ b/core/libs/dimg/filters/lens/lensfuniface.cpp
@@ -1,560 +1,560 @@
/* ============================================================
*
* Date : 2008-02-10
* Description : a tool to fix automatically camera lens aberrations
*
* Copyright (C) 2008 by Adrian Schroeter <adrian at suse dot de>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "lensfuniface.h"
// Qt includes
#include <QStandardPaths>
#include <QFile>
#include <QDir>
// Local includes
#include "digikam_debug.h"
namespace Digikam
{
class Q_DECL_HIDDEN LensFunIface::Private
{
public:
explicit Private()
{
usedLens = 0;
usedCamera = 0;
lfDb = 0;
lfCameras = 0;
}
// To be used for modification
LensFunContainer settings;
// Database items
lfDatabase* lfDb;
const lfCamera* const* lfCameras;
QString makeDescription;
QString modelDescription;
QString lensDescription;
LensPtr usedLens;
DevicePtr usedCamera;
};
LensFunIface::LensFunIface()
: d(new Private)
{
d->lfDb = lf_db_new();
// Lensfun host XML files in a dedicated sub-directory.
QString lensPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QLatin1String("lensfun"),
QStandardPaths::LocateDirectory);
QDir lensDir;
// In first try to use last Lensfun version data dir.
lensDir = QDir(lensPath + QLatin1String("/version_2"), QLatin1String("*.xml"));
if (lensDir.entryList().isEmpty())
{
// Fail-back to revision 1.
lensDir = QDir(lensPath + QLatin1String("/version_1"), QLatin1String("*.xml"));
if (lensDir.entryList().isEmpty())
{
// Fail-back to revision 0 which host XML data in root data directory.
lensDir = QDir(lensPath, QLatin1String("*.xml"));
}
}
qCDebug(DIGIKAM_DIMG_LOG) << "Using root lens database dir: " << lensPath;
foreach(const QString& lens, lensDir.entryList())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Load lens database file: " << lens;
d->lfDb->Load(QFile::encodeName(lensDir.absoluteFilePath(lens)).constData());
}
d->lfDb->Load();
d->lfCameras = d->lfDb->GetCameras();
}
LensFunIface::~LensFunIface()
{
lf_db_destroy(d->lfDb);
delete d;
}
void LensFunIface::setSettings(const LensFunContainer& other)
{
d->settings = other;
d->usedCamera = findCamera(d->settings.cameraMake, d->settings.cameraModel);
d->usedLens = findLens(d->settings.lensModel);
}
LensFunContainer LensFunIface::settings() const
{
return d->settings;
}
LensFunIface::DevicePtr LensFunIface::usedCamera() const
{
return d->usedCamera;
}
void LensFunIface::setUsedCamera(DevicePtr cam)
{
d->usedCamera = cam;
d->settings.cameraMake = d->usedCamera ? QLatin1String(d->usedCamera->Maker) : QString();
d->settings.cameraModel = d->usedCamera ? QLatin1String(d->usedCamera->Model) : QString();
d->settings.cropFactor = d->usedCamera ? d->usedCamera->CropFactor : -1.0;
}
LensFunIface::LensPtr LensFunIface::usedLens() const
{
return d->usedLens;
}
void LensFunIface::setUsedLens(LensPtr lens)
{
d->usedLens = lens;
d->settings.lensModel = d->usedLens ? QLatin1String(d->usedLens->Model) : QString();
}
lfDatabase* LensFunIface::lensFunDataBase() const
{
return d->lfDb;
}
QString LensFunIface::makeDescription() const
{
return d->makeDescription;
}
QString LensFunIface::modelDescription() const
{
return d->modelDescription;
}
QString LensFunIface::lensDescription() const
{
return d->lensDescription;
}
const lfCamera* const* LensFunIface::lensFunCameras() const
{
return d->lfCameras;
}
void LensFunIface::setFilterSettings(const LensFunContainer& other)
{
d->settings.filterCCA = other.filterCCA;
d->settings.filterVIG = other.filterVIG;
d->settings.filterDST = other.filterDST;
d->settings.filterGEO = other.filterGEO;
}
LensFunIface::DevicePtr LensFunIface::findCamera(const QString& make, const QString& model) const
{
const lfCamera* const* cameras = d->lfDb->GetCameras();
while (cameras && *cameras)
{
DevicePtr cam = *cameras;
// qCDebug(DIGIKAM_DIMG_LOG) << "Query camera:" << cam->Maker << "-" << cam->Model;
if (QString::fromLatin1(cam->Maker).toLower() == make.toLower() && QString::fromLatin1(cam->Model).toLower() == model.toLower())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Search for camera " << make << "-" << model << " ==> true";
return cam;
}
++cameras;
}
qCDebug(DIGIKAM_DIMG_LOG) << "Search for camera " << make << "-" << model << " ==> false";
return 0;
}
LensFunIface::LensPtr LensFunIface::findLens(const QString& model) const
{
const lfLens* const* lenses = d->lfDb->GetLenses();
while (lenses && *lenses)
{
LensPtr lens = *lenses;
if (QString::fromLatin1(lens->Model) == model)
{
qCDebug(DIGIKAM_DIMG_LOG) << "Search for lens " << model << " ==> true";
return lens;
}
++lenses;
}
qCDebug(DIGIKAM_DIMG_LOG) << "Search for lens " << model << " ==> false";
return 0;
}
LensFunIface::LensList LensFunIface::findLenses(const lfCamera* const lfCamera, const QString& lensDesc,
const QString& lensMaker) const
{
LensList lensList;
const lfLens** lfLens = 0;
if (lfCamera)
{
if (!lensMaker.isEmpty())
{
lfLens = d->lfDb->FindLenses(lfCamera, lensMaker.toLatin1().constData(), lensDesc.toLatin1().constData());
}
else
{
lfLens = d->lfDb->FindLenses(lfCamera, NULL, lensDesc.toLatin1().constData());
}
while (lfLens && *lfLens)
{
lensList << (*lfLens);
++lfLens;
}
}
return lensList;
}
LensFunIface::MetadataMatch LensFunIface::findFromMetadata(const DMetadata& meta)
{
MetadataMatch ret = MetadataNoMatch;
d->settings = LensFunContainer();
d->usedCamera = 0;
d->usedLens = 0;
d->lensDescription.clear();
if (meta.isEmpty())
{
qCDebug(DIGIKAM_DIMG_LOG) << "No metadata available";
return LensFunIface::MetadataUnavailable;
}
PhotoInfoContainer photoInfo = meta.getPhotographInformation();
d->makeDescription = photoInfo.make.trimmed();
d->modelDescription = photoInfo.model.trimmed();
bool exactMatch = true;
if (d->makeDescription.isEmpty())
{
qCDebug(DIGIKAM_DIMG_LOG) << "No camera maker info available";
exactMatch = false;
}
else
{
// NOTE: see bug #184156:
// Some rules to wrap unknown camera device from Lensfun database, which have equivalent in fact.
if (d->makeDescription == QLatin1String("Canon"))
{
if (d->modelDescription == QLatin1String("Canon EOS Kiss Digital X"))
{
d->modelDescription = QLatin1String("Canon EOS 400D DIGITAL");
}
if (d->modelDescription == QLatin1String("G1 X"))
{
d->modelDescription = QLatin1String("G1X");
}
}
d->lensDescription = photoInfo.lens.trimmed();
// ------------------------------------------------------------------------------------------------
DevicePtr lfCamera = findCamera(d->makeDescription, d->modelDescription);
if (lfCamera)
{
setUsedCamera(lfCamera);
qCDebug(DIGIKAM_DIMG_LOG) << "Camera maker : " << d->settings.cameraMake;
qCDebug(DIGIKAM_DIMG_LOG) << "Camera model : " << d->settings.cameraModel;
// ------------------------------------------------------------------------------------------------
// -- Performing lens description searches.
if (!d->lensDescription.isEmpty())
{
LensList lensMatches;
QString lensCutted;
LensList lensList;
// STAGE 1, search in LensFun database as well.
lensList = findLenses(d->usedCamera, d->lensDescription);
qCDebug(DIGIKAM_DIMG_LOG) << "* Check for lens by direct query (" << d->lensDescription << " : " << lensList.count() << ")";
lensMatches.append(lensList);
// STAGE 2, Adapt exiv2 strings to lensfun strings for Nikon.
lensCutted = d->lensDescription;
if (lensCutted.contains(QLatin1String("Nikon")))
{
lensCutted.remove(QLatin1String("Nikon "));
lensCutted.remove(QLatin1String("Zoom-"));
lensCutted.replace(QLatin1String("IF-ID"), QLatin1String("ED-IF"));
lensList = findLenses(d->usedCamera, lensCutted);
qCDebug(DIGIKAM_DIMG_LOG) << "* Check for Nikon lens (" << lensCutted << " : " << lensList.count() << ")";
lensMatches.append(lensList);
}
// TODO : Add here more specific lens maker rules.
// LAST STAGE, Adapt exiv2 strings to lensfun strings. Some lens description use something like that :
// "10.0 - 20.0 mm". This must be adapted like this : "10-20mm"
lensCutted = d->lensDescription;
lensCutted.replace(QRegExp(QLatin1String("\\.[0-9]")), QLatin1String("")); //krazy:exclude=doublequote_chars
lensCutted.replace(QLatin1String(" - "), QLatin1String("-"));
lensCutted.replace(QLatin1String(" mm"), QLatin1String("mn"));
lensList = findLenses(d->usedCamera, lensCutted);
qCDebug(DIGIKAM_DIMG_LOG) << "* Check for no maker lens (" << lensCutted << " : " << lensList.count() << ")";
lensMatches.append(lensList);
// Remove all duplicate lenses in the list by using QSet.
lensMatches = lensMatches.toSet().toList();
// Display the results.
if (lensMatches.isEmpty())
{
qCDebug(DIGIKAM_DIMG_LOG) << "lens matches : NOT FOUND";
exactMatch &= false;
}
else
{
// Best case for an exact match is to have only one item returned by Lensfun searches.
if(lensMatches.count() == 1)
{
setUsedLens(lensMatches.first());
qCDebug(DIGIKAM_DIMG_LOG) << "Lens found : " << d->settings.lensModel;
qCDebug(DIGIKAM_DIMG_LOG) << "Crop Factor : " << d->settings.cropFactor;
}
else
{
qCDebug(DIGIKAM_DIMG_LOG) << "lens matches : more than one...";
const lfLens* exact = 0;
foreach(const lfLens* const l, lensMatches)
{
if(QLatin1String(l->Model) == d->lensDescription)
{
- qCDebug(DIGIKAM_DIMG_LOG) << "found exact match from" << lensMatches.count() << "possitibilites:" << l->Model;
+ qCDebug(DIGIKAM_DIMG_LOG) << "found exact match from" << lensMatches.count() << "possibilities:" << l->Model;
exact = l;
}
}
if(exact)
{
setUsedLens(exact);
}
else
{
exactMatch &= false;
}
}
}
}
else
{
qCDebug(DIGIKAM_DIMG_LOG) << "Lens description string is empty";
exactMatch &= false;
}
}
else
{
qCDebug(DIGIKAM_DIMG_LOG) << "Cannot find Lensfun camera device for (" << d->makeDescription << " - " << d->modelDescription << ")";
exactMatch &= false;
}
}
// ------------------------------------------------------------------------------------------------
// Performing Lens settings searches.
QString temp = photoInfo.focalLength;
if (temp.isEmpty())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Focal Length : NOT FOUND";
exactMatch &= false;
}
d->settings.focalLength = temp.mid(0, temp.length() - 3).toDouble(); // HACK: strip the " mm" at the end ...
qCDebug(DIGIKAM_DIMG_LOG) << "Focal Length : " << d->settings.focalLength;
// ------------------------------------------------------------------------------------------------
temp = photoInfo.aperture;
if (temp.isEmpty())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Aperture : NOT FOUND";
exactMatch &= false;
}
d->settings.aperture = temp.mid(1).toDouble();
qCDebug(DIGIKAM_DIMG_LOG) << "Aperture : " << d->settings.aperture;
// ------------------------------------------------------------------------------------------------
// Try to get subject distance value.
// From standard Exif.
temp = meta.getExifTagString("Exif.Photo.SubjectDistance");
if (temp.isEmpty())
{
// From standard XMP.
temp = meta.getXmpTagString("Xmp.exif.SubjectDistance");
}
if (temp.isEmpty())
{
// From Canon Makernote.
temp = meta.getExifTagString("Exif.CanonSi.SubjectDistance");
}
if (temp.isEmpty())
{
// From Nikon Makernote.
temp = meta.getExifTagString("Exif.NikonLd2.FocusDistance");
}
if (temp.isEmpty())
{
// From Nikon Makernote.
temp = meta.getExifTagString("Exif.NikonLd3.FocusDistance");
}
if (temp.isEmpty())
{
// From Olympus Makernote.
temp = meta.getExifTagString("Exif.OlympusFi.FocusDistance");
}
// TODO: Add here others Makernotes tags.
if (temp.isEmpty())
{
qCDebug(DIGIKAM_DIMG_LOG) << "Subject dist. : NOT FOUND : Use default value.";
temp = QLatin1String("1000");
}
temp = temp.remove(QLatin1String(" m"));
bool ok;
d->settings.subjectDistance = temp.toDouble(&ok);
if (!ok)
{
d->settings.subjectDistance = -1.0;
}
qCDebug(DIGIKAM_DIMG_LOG) << "Subject dist. : " << d->settings.subjectDistance;
// ------------------------------------------------------------------------------------------------
ret = exactMatch ? MetadataExactMatch : MetadataPartialMatch;
qCDebug(DIGIKAM_DIMG_LOG) << "Metadata match : " << metadataMatchDebugStr(ret);
return ret;
}
QString LensFunIface::metadataMatchDebugStr(MetadataMatch val) const
{
QString ret;
switch (val)
{
case MetadataNoMatch:
ret = QLatin1String("No Match");
break;
case MetadataPartialMatch:
ret = QLatin1String("Partial Match");
break;
default:
ret = QLatin1String("Exact Match");
break;
}
return ret;
}
bool LensFunIface::supportsDistortion() const
{
if (!d->usedLens)
{
return false;
}
lfLensCalibDistortion res;
return d->usedLens->InterpolateDistortion(d->settings.focalLength, res);
}
bool LensFunIface::supportsCCA() const
{
if (!d->usedLens)
{
return false;
}
lfLensCalibTCA res;
return d->usedLens->InterpolateTCA(d->settings.focalLength, res);
}
bool LensFunIface::supportsVig() const
{
if (!d->usedLens)
{
return false;
}
lfLensCalibVignetting res;
return d->usedLens->InterpolateVignetting(d->settings.focalLength,
d->settings.aperture,
d->settings.subjectDistance, res);
}
bool LensFunIface::supportsGeometry() const
{
return supportsDistortion();
}
QString LensFunIface::lensFunVersion()
{
return QString::fromLatin1("%1.%2.%3-%4").arg(LF_VERSION_MAJOR).arg(LF_VERSION_MINOR).arg(LF_VERSION_MICRO).arg(LF_VERSION_BUGFIX);
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/nr/nrestimate.cpp b/core/libs/dimg/filters/nr/nrestimate.cpp
index f76072adfb..4011c01a2e 100644
--- a/core/libs/dimg/filters/nr/nrestimate.cpp
+++ b/core/libs/dimg/filters/nr/nrestimate.cpp
@@ -1,518 +1,518 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-10-18
* Description : Wavelets YCrCb Noise Reduction settings estimation by image content analys.
* Wavelets theory is based on "À Trous" Discrete Wavelet Transform
* described into "The Handbook of Astronomical Image Processing" book
* from Richard Berry and James Burnell, chapter 18.
* See this wiki page for details:
* http://community.kde.org/Digikam/SoK2012/AutoNR
*
* Copyright (C) 2012-2013 by Sayantan Datta <sayantan dot knz at gmail dot com>
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "nrestimate.h"
// C++ includes
#include <cmath>
#include <cfloat>
// OpenCV includes
#include "digikam_opencv.h"
// Qt includes.
#include <QTextStream>
#include <QFile>
// Local includes
#include "digikam_debug.h"
#include "nrfilter.h"
namespace Digikam
{
class Q_DECL_HIDDEN NREstimate::Private
{
public:
explicit Private()
: clusterCount(30),
size(512)
{
for (int c = 0 ; c < 3 ; c++)
{
fimg[c] = 0;
}
}
NRContainer prm;
QString path; // Path to host log files.
float* fimg[3];
const uint clusterCount;
const uint size; // Size of squared original image.
};
NREstimate::NREstimate(DImg* const img, QObject* const parent)
: DImgThreadedAnalyser(parent, QLatin1String("NREstimate")),
d(new Private)
{
// Use the Top/Left corner of 256x256 pixels to analys noise contents from image.
// This will speed-up computation time with OpenCV
int w = (img->width() > d->size) ? d->size : img->width();
int h = (img->height() > d->size) ? d->size : img->height();
setOriginalImage(img->copy(0, 0, w, h));
}
NREstimate::~NREstimate()
{
delete d;
}
void NREstimate::setLogFilesPath(const QString& path)
{
d->path = path;
}
void NREstimate::readImage() const
{
DColor col;
for (int c = 0 ; runningFlag() && (c < 3) ; c++)
{
d->fimg[c] = new float[m_orgImage.numPixels()];
}
int j = 0;
for (uint y = 0 ; runningFlag() && (y < m_orgImage.height()) ; y++)
{
for (uint x = 0 ; runningFlag() && (x < m_orgImage.width()) ; x++)
{
col = m_orgImage.getPixelColor(x, y);
d->fimg[0][j] = col.red();
d->fimg[1][j] = col.green();
d->fimg[2][j] = col.blue();
j++;
}
}
}
NRContainer NREstimate::settings() const
{
return d->prm;
}
void NREstimate::startAnalyse()
{
readImage();
postProgress(5);
//--convert fimg to CvMat*-------------------------------------------------------------------------------
// convert the image into YCrCb color model
NRFilter::srgb2ycbcr(d->fimg, m_orgImage.numPixels());
- // One dimentional CvMat which stores the image
+ // One dimensional CvMat which stores the image
CvMat* points = cvCreateMat(m_orgImage.numPixels(), 3, CV_32FC1);
// matrix to store the index of the clusters
CvMat* clusters = cvCreateMat(m_orgImage.numPixels(), 1, CV_32SC1);
// pointer variable to handle the CvMat* points (the image in CvMat format)
float* pointsPtr = reinterpret_cast<float*>(points->data.ptr);
for (uint x = 0 ; runningFlag() && (x < m_orgImage.numPixels()) ; x++)
{
for (int y = 0 ; runningFlag() && (y < 3) ; y++)
{
*pointsPtr++ = (float)d->fimg[y][x];
}
}
// Array to store the centers of the clusters
CvArr* centers = 0;
qCDebug(DIGIKAM_DIMG_LOG) << "Everything ready for the cvKmeans2 or as it seems to";
postProgress(10);
//-- KMEANS ---------------------------------------------------------------------------------------------
if (runningFlag())
{
cvKMeans2(points, d->clusterCount, clusters,
cvTermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 10, 1.0), 3, 0, 0, centers, 0);
}
qCDebug(DIGIKAM_DIMG_LOG) << "cvKmeans2 successfully run";
postProgress(15);
//-- Divide into cluster->columns, sample->rows, in matrix standard deviation ---------------------------
QScopedArrayPointer<int> rowPosition(new int[d->clusterCount]);
//the row position array would just make the hold the number of elements in each cluster
for (uint i = 0 ; runningFlag() && (i < d->clusterCount) ; i++)
{
//initializing the cluster count array
rowPosition[i] = 0;
}
int rowIndex, columnIndex;
for (uint i = 0 ; runningFlag() && (i < m_orgImage.numPixels()) ; i++)
{
columnIndex = clusters->data.i[i];
rowPosition[columnIndex]++;
}
/*
qCDebug(DIGIKAM_DIMG_LOG) << "Lets see what the rowPosition array looks like : ";
for(uint i = 0 ; runningFlag() && (i < d->clusterCount) ; i++)
{
qCDebug(DIGIKAM_DIMG_LOG) << "Cluster : "<< i << " the count is :" << rowPosition[i];
}
*/
qCDebug(DIGIKAM_DIMG_LOG) << "array indexed, and ready to find maximum";
postProgress(20);
//-- Finding maximum of the rowPosition array ------------------------------------------------------------
int max = rowPosition[0];
for (uint i = 1 ; runningFlag() && (i < d->clusterCount) ; i++)
{
if (rowPosition[i] > max)
{
max = rowPosition[i];
}
}
QString maxString;
maxString.append(QString::number(max));
qCDebug(DIGIKAM_DIMG_LOG) << QString::fromLatin1("maximum declared = %1").arg(maxString);
postProgress(25);
//-- Divide and conquer ---------------------------------------------------------------------------------
CvMat* sd = 0;
if (runningFlag())
{
sd = cvCreateMat(max, (d->clusterCount * points->cols), CV_32FC1);
}
postProgress(30);
//-- Initialize the rowPosition array -------------------------------------------------------------------
QScopedArrayPointer<int> rPosition(new int[d->clusterCount]);
for (uint i = 0 ; runningFlag() && (i < d->clusterCount) ; i++)
{
rPosition[i] = 0;
}
float* ptr = 0;
qCDebug(DIGIKAM_DIMG_LOG) << "The rowPosition array is ready!";
postProgress(40);
for (uint i=0 ; runningFlag() && (i < m_orgImage.numPixels()) ; i++)
{
columnIndex = clusters->data.i[i];
rowIndex = rPosition[columnIndex];
//moving to the right row
ptr = reinterpret_cast<float*>(sd->data.ptr + rowIndex*(sd->step));
//moving to the right column
for (int j = 0 ; runningFlag() && (j < columnIndex) ; j++)
{
for (int z = 0 ; runningFlag() && (z < (points->cols)) ; z++)
{
ptr++;
}
}
for (int z = 0 ; runningFlag() && (z < (points->cols)) ; z++)
{
*ptr++ = cvGet2D(points, i, z).val[0];
}
rPosition[columnIndex] = rPosition[columnIndex] + 1;
}
qCDebug(DIGIKAM_DIMG_LOG) << "sd matrix creation over!";
postProgress(50);
//-- This part of the code would involve the sd matrix and make the mean and the std of the data -------------------
CvScalar std;
CvScalar mean;
CvMat* meanStore = 0;
CvMat* stdStore = 0;
float* meanStorePtr = 0;
float* stdStorePtr = 0;
int totalcount = 0; // Number of non-empty clusters
if (runningFlag())
{
meanStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1);
stdStore = cvCreateMat(d->clusterCount, points->cols, CV_32FC1);
meanStorePtr = reinterpret_cast<float*>(meanStore->data.ptr);
stdStorePtr = reinterpret_cast<float*>(stdStore->data.ptr);
}
for (int i = 0 ; runningFlag() && (i < sd->cols) ; i++)
{
if (runningFlag() && (rowPosition[(i/points->cols)] >= 1))
{
CvMat* workingArr = cvCreateMat(rowPosition[(i / points->cols)], 1, CV_32FC1);
ptr = reinterpret_cast<float*>(workingArr->data.ptr);
for (int j = 0 ; runningFlag() && (j < rowPosition[(i / (points->cols))]) ; j++)
{
*ptr++ = cvGet2D(sd, j, i).val[0];
}
cvAvgSdv(workingArr, &mean, &std);
*meanStorePtr++ = (float)mean.val[0];
*stdStorePtr++ = (float)std.val[0];
totalcount++;
cvReleaseMat(&workingArr);
}
}
qCDebug(DIGIKAM_DIMG_LOG) << "Make the mean and the std of the data";
postProgress(60);
// -----------------------------------------------------------------------------------------------------------------
if (runningFlag())
{
meanStorePtr = reinterpret_cast<float*>(meanStore->data.ptr);
stdStorePtr = reinterpret_cast<float*>(stdStore->data.ptr);
}
if (runningFlag() && !d->path.isEmpty())
{
QString logFile = d->path;
logFile = logFile.section(QLatin1Char('/'), -1);
logFile = logFile.left(logFile.indexOf(QLatin1Char('.')));
logFile.append(QLatin1String("logMeanStd.txt"));
QFile filems(logFile);
if (filems.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream oms(&filems);
oms << "Mean Data\n";
for (int i = 0 ; i < totalcount ; i++)
{
oms << *meanStorePtr++;
oms << "\t";
if ((i+1)%3 == 0)
{
oms << "\n";
}
}
oms << "\nStd Data\n";
for (int i = 0 ; i < totalcount ; i++)
{
oms << *stdStorePtr++;
oms << "\t";
if ((i+1)%3 == 0)
{
oms << "\n";
}
}
filems.close();
qCDebug(DIGIKAM_DIMG_LOG) << "Done with the basic work of storing the mean and the std";
}
}
postProgress(70);
//-- Calculating weighted mean, and weighted std -----------------------------------------------------------
QTextStream owms;
QFile filewms;
if (runningFlag() && !d->path.isEmpty())
{
QString logFile2 = d->path;
logFile2 = logFile2.section(QLatin1Char('/'), -1);
logFile2 = logFile2.left(logFile2.indexOf(QLatin1Char('.')));
logFile2.append(QLatin1String("logWeightedMeanStd.txt"));
filewms.setFileName(logFile2);
if (filewms.open(QIODevice::WriteOnly | QIODevice::Text))
owms.setDevice(&filewms);
}
QString info;
float weightedMean = 0.0f;
float weightedStd = 0.0f;
float datasd[3] = {0.0f, 0.0f, 0.0f};
for (int j = 0 ; runningFlag() && (j < points->cols) ; j++)
{
meanStorePtr = reinterpret_cast<float*>(meanStore->data.ptr);
stdStorePtr = reinterpret_cast<float*>(stdStore->data.ptr);
for (int moveToChannel = 0 ; moveToChannel <= j ; moveToChannel++)
{
meanStorePtr++;
stdStorePtr++;
}
for (uint i = 0 ; i < d->clusterCount ; i++)
{
if (rowPosition[i] >= 1)
{
weightedMean += (*meanStorePtr) * rowPosition[i];
weightedStd += (*stdStorePtr) * rowPosition[i];
meanStorePtr += points->cols;
stdStorePtr += points->cols;
}
}
weightedMean = weightedMean / (m_orgImage.numPixels());
weightedStd = weightedStd / (m_orgImage.numPixels());
datasd[j] = weightedStd;
if (!d->path.isEmpty())
{
owms << QLatin1String("\nChannel : ") << j << QLatin1Char('\n');
owms << QLatin1String("Weighted Mean : ") << weightedMean << QLatin1Char('\n');
owms << QLatin1String("Weighted Std : ") << weightedStd << QLatin1Char('\n');
}
info.append(QLatin1String("\n\nChannel: "));
info.append(QString::number(j));
info.append(QLatin1String("\nWeighted Mean: "));
info.append(QString::number(weightedMean));
info.append(QLatin1String("\nWeighted Standard Deviation: "));
info.append(QString::number(weightedStd));
}
if (runningFlag() && !d->path.isEmpty())
{
filewms.close();
}
qCDebug(DIGIKAM_DIMG_LOG) << "Info : " << info;
postProgress(80);
// -- adaptation ---------------------------------------------------------------------------------------
double L = 1.2, LSoft = 0.9, Cr = 1.2, CrSoft = 0.9, Cb = 1.2, CbSoft = 0.9;
if (runningFlag())
{
// for 16 bits images only
if (m_orgImage.sixteenBit())
{
for (int i = 0 ; i < points->cols ; i++)
{
datasd[i] = datasd[i] / 256;
}
}
if (datasd[0] < 7)
L = datasd[0] - 0.98;
else if (datasd[0] >= 7 && datasd[0] < 8)
L = datasd[0] - 1.2;
else if (datasd[0] >= 8 && datasd[0] < 9)
L = datasd[0] - 1.5;
else
L = datasd[0] - 1.7;
if (L < 0)
L = 0;
if (L > 9)
L = 9;
Cr = datasd[2] * 0.8;
Cb = datasd[1] * 0.8;
if (Cr > 7)
Cr = 7;
if (Cb > 7)
Cb = 7;
L = floorf(L * 100) / 100;
Cb = floorf(Cb * 100) / 100;
Cr = floorf(Cr * 100) / 100;
if ( L > 9 )
LSoft = CrSoft = CbSoft = 0.8;
else if ( L > 3)
LSoft = CrSoft = CbSoft = 0.7;
else
LSoft = CrSoft = CbSoft = 0.6;
}
d->prm.thresholds[0] = L;
d->prm.thresholds[1] = Cb;
d->prm.thresholds[2] = Cr;
d->prm.softness[0] = LSoft;
d->prm.softness[1] = CbSoft;
d->prm.softness[2] = CrSoft;
qCDebug(DIGIKAM_DIMG_LOG) << "All is completed";
postProgress(90);
//-- releasing matrices and closing files ----------------------------------------------------------------------
cvReleaseMat(&sd);
cvReleaseMat(&stdStore);
cvReleaseMat(&meanStore);
cvReleaseMat(&points);
cvReleaseMat(&clusters);
for (uint i = 0 ; i < 3 ; i++)
{
delete [] d->fimg[i];
}
postProgress(100);
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.cpp b/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.cpp
index b4dd72036b..d615cd83fa 100644
--- a/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.cpp
+++ b/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.cpp
@@ -1,61 +1,61 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-12-15
- * Description : Red Eyes auto conrrection settings container.
+ * Description : Red Eyes auto correction settings container.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2016 by Omar Amin <Omar dot moh dot amin at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
// Local includes
#include "redeyecorrectioncontainer.h"
#include "dimgthreadedfilter.h"
namespace Digikam
{
RedEyeCorrectionContainer::RedEyeCorrectionContainer()
{
m_redToAvgRatio = 2.1;
}
bool RedEyeCorrectionContainer::isDefault() const
{
return (*this == RedEyeCorrectionContainer());
}
bool RedEyeCorrectionContainer::operator==(const RedEyeCorrectionContainer& other) const
{
return (m_redToAvgRatio == other.m_redToAvgRatio);
}
void RedEyeCorrectionContainer::writeToFilterAction(FilterAction& action, const QString& prefix) const
{
action.addParameter(prefix + QLatin1String("redtoavgratio"), m_redToAvgRatio);
}
RedEyeCorrectionContainer RedEyeCorrectionContainer::fromFilterAction(const FilterAction& action, const QString& prefix)
{
RedEyeCorrectionContainer settings;
settings.m_redToAvgRatio = action.parameter(prefix + QLatin1String("redtoavgratio"), settings.m_redToAvgRatio);
return settings;
}
} // namespace Digikam
diff --git a/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.h b/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.h
index f556e5826c..5e4a8e2d54 100644
--- a/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.h
+++ b/core/libs/dimg/filters/redeye/redeyecorrectioncontainer.h
@@ -1,61 +1,61 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-12-15
- * Description : Red Eyes auto conrrection settings container.
+ * Description : Red Eyes auto correction settings container.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2016 by Omar Amin <Omar dot moh dot amin at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_RED_EYE_CORRECTION_CONTAINER_H
#define DIGIKAM_RED_EYE_CORRECTION_CONTAINER_H
// Qt includes
#include <QString>
// Local includes
#include "digikam_export.h"
namespace Digikam
{
class FilterAction;
class DIGIKAM_EXPORT RedEyeCorrectionContainer
{
public:
explicit RedEyeCorrectionContainer();
bool isDefault() const;
bool operator==(const RedEyeCorrectionContainer& other) const;
void writeToFilterAction(FilterAction& action, const QString& prefix = QString()) const;
static RedEyeCorrectionContainer fromFilterAction(const FilterAction& action, const QString& prefix = QString());
public:
double m_redToAvgRatio;
};
} // namespace Digikam
#endif // DIGIKAM_RED_EYE_CORRECTION_CONTAINER_H
diff --git a/core/libs/dimg/filters/redeye/redeyecorrectionfilter.cpp b/core/libs/dimg/filters/redeye/redeyecorrectionfilter.cpp
index 123bcf3d93..95b7d6a778 100644
--- a/core/libs/dimg/filters/redeye/redeyecorrectionfilter.cpp
+++ b/core/libs/dimg/filters/redeye/redeyecorrectionfilter.cpp
@@ -1,265 +1,265 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 17-8-2016
* Description : A Red-Eye automatic detection and correction filter.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2016 by Omar Amin <Omar dot moh dot amin at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "redeyecorrectionfilter.h"
// C++ includes
#include <iterator>
// Qt includes
#include <QFile>
#include <QDataStream>
#include <QListIterator>
#include <QStandardPaths>
// Local includes
#include "digikam_debug.h"
#include "facedetector.h"
#include "shapepredictor.h"
namespace Digikam
{
class Q_DECL_HIDDEN RedEyeCorrectionFilter::Private
{
public:
explicit Private()
{
}
FaceDetector facedetector;
static redeye::ShapePredictor* sp;
RedEyeCorrectionContainer settings;
};
redeye::ShapePredictor* RedEyeCorrectionFilter::Private::sp = 0;
RedEyeCorrectionFilter::RedEyeCorrectionFilter(QObject* const parent)
: DImgThreadedFilter(parent),
d(new Private)
{
initFilter();
}
RedEyeCorrectionFilter::RedEyeCorrectionFilter(DImg* const orgImage,
QObject* const parent,
const RedEyeCorrectionContainer& settings)
: DImgThreadedFilter(orgImage, parent,
QLatin1String("RedEyeCorrection")),
d(new Private)
{
d->settings = settings;
initFilter();
}
RedEyeCorrectionFilter::RedEyeCorrectionFilter(const RedEyeCorrectionContainer& settings,
DImgThreadedFilter* const parentFilter,
const DImg& orgImage,
const DImg& destImage,
int progressBegin,
int progressEnd)
: DImgThreadedFilter(parentFilter, orgImage, destImage,
progressBegin, progressEnd,
parentFilter->filterName() + QLatin1String(": RedEyeCorrection")),
d(new Private)
{
d->settings = settings;
filterImage();
}
RedEyeCorrectionFilter::~RedEyeCorrectionFilter()
{
cancelFilter();
delete d;
}
void RedEyeCorrectionFilter::filterImage()
{
if (!d->sp)
{
// Loading the shape predictor model
QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
QLatin1String("digikam/facesengine/shapepredictor.dat"));
QFile model(path);
if (model.open(QIODevice::ReadOnly))
{
redeye::ShapePredictor* const temp = new redeye::ShapePredictor();
QDataStream dataStream(&model);
dataStream.setFloatingPointPrecision(QDataStream::SinglePrecision);
dataStream >> *temp;
d->sp = temp;
}
else
{
qCDebug(DIGIKAM_DIMG_LOG) << "Error open file shapepredictor.dat";
return;
}
}
cv::Mat intermediateImage;
// Deep copy
DImg temp = m_orgImage.copy();
int type = m_orgImage.sixteenBit() ? CV_16UC3 : CV_8UC3;
type = m_orgImage.hasAlpha() ? type : type + 8;
intermediateImage = cv::Mat(m_orgImage.height(), m_orgImage.width(),
type, m_orgImage.bits());
cv::Mat gray;
if (type == CV_8UC3 || type == CV_16UC3)
{
cv::cvtColor(intermediateImage, gray, CV_RGB2GRAY); // 3 channels
}
else
{
cv::cvtColor(intermediateImage, gray, CV_RGBA2GRAY); // 4 channels
}
if (type == CV_16UC3 || type == CV_16UC4)
{
gray.convertTo(gray, CV_8UC1, 1 / 255.0);
}
QList<QRectF> qrectfdets = d->facedetector.detectFaces(temp);
redeye::ShapePredictor& sp = *(d->sp);
if (runningFlag() && (qrectfdets.size() != 0))
{
std::vector<cv::Rect> dets;
QList<QRect> qrectdets = FaceDetector::toAbsoluteRects(qrectfdets, temp.size());
QRectFtocvRect(qrectdets, dets);
// Eye Detection
for (unsigned int i = 0 ; runningFlag() && (i < dets.size()) ; ++i)
{
FullObjectDetection object = sp(gray,dets[i]);
std::vector<cv::Rect> eyes = geteyes(object);
for (unsigned int j = 0 ; runningFlag() && (j < eyes.size()) ; ++j)
{
correctRedEye(intermediateImage.data,
intermediateImage.type(),
eyes[j],
cv::Rect(0, 0, intermediateImage.size().width ,
intermediateImage.size().height));
}
}
}
if (runningFlag())
{
m_destImage.putImageData(m_orgImage.width(), m_orgImage.height(), temp.sixteenBit(),
!temp.hasAlpha(), intermediateImage.data, true);
}
}
void RedEyeCorrectionFilter::correctRedEye(uchar* data, int type,
cv::Rect eyerect, cv::Rect imgRect)
{
uchar* onebytedata = data;
ushort* twobytedata = reinterpret_cast<ushort*>(data);
int pixeldepth = 0;
if (type == CV_8UC3 || type == CV_16UC3 )
{
pixeldepth = 3;
}
else if (type == CV_8UC4 || type == CV_16UC4)
{
pixeldepth = 4;
}
else
{
- qCDebug(DIGIKAM_DIMG_LOG) << "Insupported Type in redeye correction filter";
+ qCDebug(DIGIKAM_DIMG_LOG) << "Unsupported Type in redeye correction filter";
}
bool sixteendepth = (type == CV_8UC3) || (type == CV_8UC4) ? false : true;
double redratio = d->settings.m_redToAvgRatio;
for (int i = eyerect.y ; i < eyerect.y + eyerect.height ; ++i)
{
for (int j = eyerect.x ; j < eyerect.x + eyerect.width ; ++j)
{
int pixelindex = (i*imgRect.width + j) * pixeldepth;
onebytedata = &(reinterpret_cast<uchar*> (data)[pixelindex]);
twobytedata = &(reinterpret_cast<ushort*>(data)[pixelindex]);
if (sixteendepth)
{
float redIntensity = ((float)twobytedata[2] / (( (unsigned int)twobytedata[1]
+ (unsigned int)twobytedata[0]) / 2));
if (redIntensity > redratio)
{
// reduce red to the average of blue and green
twobytedata[2] = ((int)twobytedata[1] + (int)twobytedata[0]) / 2;
}
}
else
{
float redIntensity = ((float)onebytedata[2] / (( (unsigned int)onebytedata[1]
+ (unsigned int)onebytedata[0]) / 2));
if (redIntensity > redratio)
{
// reduce red to the average of blue and green
onebytedata[2] = ((int)onebytedata[1] + (int)onebytedata[0]) / 2;
}
}
}
}
}
void RedEyeCorrectionFilter::QRectFtocvRect(const QList<QRect>& faces, std::vector<cv::Rect>& result)
{
QListIterator<QRect> listit(faces);
while (listit.hasNext())
{
QRect temp = listit.next();
result.push_back(cv::Rect(temp.topLeft().rx(), temp.topLeft().ry(),
temp.width() , temp.height()) );
}
}
FilterAction RedEyeCorrectionFilter::filterAction()
{
DefaultFilterAction<RedEyeCorrectionFilter> action;
d->settings.writeToFilterAction(action);
return action;
}
void RedEyeCorrectionFilter::readParameters(const FilterAction& action)
{
d->settings = RedEyeCorrectionContainer::fromFilterAction(action);
}
} // namespace Digikam
diff --git a/core/libs/dimg/loaders/pgfloader.cpp b/core/libs/dimg/loaders/pgfloader.cpp
index 8efda2e02d..953c7342e3 100644
--- a/core/libs/dimg/loaders/pgfloader.cpp
+++ b/core/libs/dimg/loaders/pgfloader.cpp
@@ -1,546 +1,546 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-06-03
* Description : A PGF IO file for DImg framework
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This implementation use LibPGF API <http://www.libpgf.org>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "digikam_config.h"
#include "pgfloader.h" // krazy:exclude=includes
// C Ansi includes
extern "C"
{
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
// C++ includes
#include <iostream>
#include <cmath>
#include <cstdio>
// Qt includes
#include <QFile>
#include <QVariant>
#include <QByteArray>
#include <QTextStream>
#include <QDataStream>
#include <qplatformdefs.h>
// Windows includes
#ifdef Q_OS_WIN32
#include <windows.h>
#endif
// Libpgf includes
#include <PGFimage.h>
// Local includes
#include "digikam_debug.h"
#include "dimg.h"
#include "dimgloaderobserver.h"
#include "pgfutils.h"
#include "metaengine.h"
namespace Digikam
{
static bool CallbackForLibPGF(double percent, bool escapeAllowed, void* data)
{
if (data)
{
PGFLoader* d = static_cast<PGFLoader*>(data);
if (d)
{
return d->progressCallback(percent, escapeAllowed);
}
}
return false;
}
PGFLoader::PGFLoader(DImg* const image)
: DImgLoader(image)
{
m_hasAlpha = false;
m_sixteenBit = false;
m_observer = 0;
}
bool PGFLoader::load(const QString& filePath, DImgLoaderObserver* const observer)
{
m_observer = observer;
readMetadata(filePath, DImg::PGF);
FILE* file = fopen(QFile::encodeName(filePath).constData(), "rb");
if (!file)
{
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open source file.";
loadingFailed();
return false;
}
unsigned char header[3];
if (fread(&header, 3, 1, file) != 1)
{
fclose(file);
loadingFailed();
return false;
}
unsigned char pgfID[3] = { 0x50, 0x47, 0x46 };
if (memcmp(&header[0], &pgfID, 3) != 0)
{
// not a PGF file
fclose(file);
loadingFailed();
return false;
}
fclose(file);
// -------------------------------------------------------------------
// Initialize PGF API.
#ifdef Q_OS_WIN32
#ifdef UNICODE
HANDLE fd = CreateFile((LPCWSTR)(QFile::encodeName(filePath).constData()), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#else
HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#endif
if (fd == INVALID_HANDLE_VALUE)
{
loadingFailed();
return false;
}
#else
int fd = QT_OPEN(QFile::encodeName(filePath).constData(), O_RDONLY);
if (fd == -1)
{
loadingFailed();
return false;
}
#endif
CPGFFileStream stream(fd);
CPGFImage pgf;
int colorModel = DImg::COLORMODELUNKNOWN;
try
{
// open pgf image
pgf.Open(&stream);
switch (pgf.Mode())
{
case ImageModeRGBColor:
case ImageModeRGB48:
m_hasAlpha = false;
colorModel = DImg::RGB;
break;
case ImageModeRGBA:
m_hasAlpha = true;
colorModel = DImg::RGB;
break;
default:
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color mode not supported (" << pgf.Mode() << ")";
loadingFailed();
return false;
break;
}
switch (pgf.Channels())
{
case 3:
case 4:
break;
default:
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color channels number not supported (" << pgf.Channels() << ")";
loadingFailed();
return false;
break;
}
int bitDepth = pgf.BPP();
switch (bitDepth)
{
case 24: // RGB 8 bits.
case 32: // RGBA 8 bits.
m_sixteenBit = false;
break;
case 48: // RGB 16 bits.
case 64: // RGBA 16 bits.
m_sixteenBit = true;
break;
default:
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Cannot load PGF image: color bits depth not supported (" << bitDepth << ")";
loadingFailed();
return false;
break;
}
if(DIGIKAM_DIMG_LOG_PGF().isDebugEnabled()) {
const PGFHeader* header = pgf.GetHeader();
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF width = " << header->width;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF height = " << header->height;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF bbp = " << header->bpp;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF channels = " << header->channels;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality = " << header->quality;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF mode = " << header->mode;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Has Alpha = " << m_hasAlpha;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Is 16 bits = " << m_sixteenBit;
}
- // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properlly with libppgf 6.11.24
+ // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24
pgf.ConfigureDecoder(false);
int width = pgf.Width();
int height = pgf.Height();
uchar* data = 0;
QSize originalSize(width, height);
if (m_loadFlags & LoadImageData)
{
// -------------------------------------------------------------------
// Find out if we do the fast-track loading with reduced size. PGF specific.
int level = 0;
QVariant attribute = imageGetAttribute(QLatin1String("scaledLoadingSize"));
if (attribute.isValid() && pgf.Levels() > 0)
{
int scaledLoadingSize = attribute.toInt();
int i, w, h;
for (i = pgf.Levels() - 1 ; i >= 0 ; --i)
{
w = pgf.Width(i);
h = pgf.Height(i);
if (qMin(w, h) >= scaledLoadingSize)
{
break;
}
}
if (i >= 0)
{
width = w;
height = h;
level = i;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Loading PGF scaled version at level " << i
<< " (" << w << " x " << h << ") for size "
<< scaledLoadingSize;
}
}
if (m_sixteenBit)
{
data = new_failureTolerant(width, height, 8); // 16 bits/color/pixel
}
else
{
data = new_failureTolerant(width, height, 4); // 8 bits/color/pixel
}
// Fill all with 255 including alpha channel.
memset(data, 0xFF, width * height * (m_sixteenBit ? 8 : 4));
pgf.Read(level, CallbackForLibPGF, this);
pgf.GetBitmap(m_sixteenBit ? width * 8 : width * 4,
(UINT8*)data,
m_sixteenBit ? 64 : 32,
NULL,
CallbackForLibPGF, this);
if (observer)
{
observer->progressInfo(m_image, 1.0);
}
}
// -------------------------------------------------------------------
// Get ICC color profile.
if (m_loadFlags & LoadICCData)
{
// TODO: Implement proper storage in PGF for color profiles
checkExifWorkingColorSpace();
}
imageWidth() = width;
imageHeight() = height;
imageData() = data;
imageSetAttribute(QLatin1String("format"), QLatin1String("PGF"));
imageSetAttribute(QLatin1String("originalColorModel"), colorModel);
imageSetAttribute(QLatin1String("originalBitDepth"), bitDepth);
imageSetAttribute(QLatin1String("originalSize"), originalSize);
#ifdef Q_OS_WIN32
CloseHandle(fd);
#else
close(fd);
#endif
return true;
}
catch (IOException& e)
{
int err = e.error;
if (err >= AppError)
{
err -= AppError;
}
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Opening and reading PGF image failed (" << err << ")!";
#ifdef Q_OS_WIN32
CloseHandle(fd);
#else
close(fd);
#endif
loadingFailed();
return false;
}
catch (std::bad_alloc& e)
{
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Failed to allocate memory for loading" << filePath << e.what();
#ifdef Q_OS_WIN32
CloseHandle(fd);
#else
close(fd);
#endif
loadingFailed();
return false;
}
return true;
}
bool PGFLoader::save(const QString& filePath, DImgLoaderObserver* const observer)
{
m_observer = observer;
#ifdef Q_OS_WIN32
#ifdef UNICODE
HANDLE fd = CreateFile((LPCWSTR)(QFile::encodeName(filePath).constData()), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#else
HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#endif
if (fd == INVALID_HANDLE_VALUE)
{
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open destination file.";
return false;
}
#elif defined(__POSIX__)
int fd = QT_OPEN(QFile::encodeName(filePath).constData(), O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Could not open destination file.";
return false;
}
#endif
try
{
QVariant qualityAttr = imageGetAttribute(QLatin1String("quality"));
int quality = qualityAttr.isValid() ? qualityAttr.toInt() : 3;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality: " << quality;
CPGFFileStream stream(fd);
CPGFImage pgf;
PGFHeader header;
header.width = imageWidth();
header.height = imageHeight();
header.quality = quality;
if (imageHasAlpha())
{
if (imageSixteenBit())
{
// NOTE : there is no PGF color mode in 16 bits with alpha.
header.channels = 3;
header.bpp = 48;
header.mode = ImageModeRGB48;
}
else
{
header.channels = 4;
header.bpp = 32;
header.mode = ImageModeRGBA;
}
}
else
{
if (imageSixteenBit())
{
header.channels = 3;
header.bpp = 48;
header.mode = ImageModeRGB48;
}
else
{
header.channels = 3;
header.bpp = 24;
header.mode = ImageModeRGBColor;
}
}
#ifdef PGFCodecVersionID
# if PGFCodecVersionID < 0x061142
header.background.rgbtBlue = 0;
header.background.rgbtGreen = 0;
header.background.rgbtRed = 0;
# endif
#endif
pgf.SetHeader(header);
- // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properlly with libppgf 6.11.24
+ // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24
pgf.ConfigureEncoder(false);
pgf.ImportBitmap(4 * imageWidth() * (imageSixteenBit() ? 2 : 1),
(UINT8*)imageData(),
imageBitsDepth() * 4,
NULL,
CallbackForLibPGF, this);
UINT32 nWrittenBytes = 0;
#ifdef PGFCodecVersionID
# if PGFCodecVersionID >= 0x061124
pgf.Write(&stream, &nWrittenBytes, CallbackForLibPGF, this);
# endif
#else
pgf.Write(&stream, 0, CallbackForLibPGF, &nWrittenBytes, this);
#endif
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF width = " << header.width;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF height = " << header.height;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF bbp = " << header.bpp;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF channels = " << header.channels;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF quality = " << header.quality;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "PGF mode = " << header.mode;
qCDebug(DIGIKAM_DIMG_LOG_PGF) << "Bytes Written = " << nWrittenBytes;
#ifdef Q_OS_WIN32
CloseHandle(fd);
#else
close(fd);
#endif
// TODO: Store ICC profile in an appropriate place in the image
storeColorProfileInMetadata();
if (observer)
{
observer->progressInfo(m_image, 1.0);
}
imageSetAttribute(QLatin1String("savedformat"), QLatin1String("PGF"));
saveMetadata(filePath);
return true;
}
catch (IOException& e)
{
int err = e.error;
if (err >= AppError)
{
err -= AppError;
}
qCWarning(DIGIKAM_DIMG_LOG_PGF) << "Error: Opening and saving PGF image failed (" << err << ")!";
#ifdef Q_OS_WIN32
CloseHandle(fd);
#else
close(fd);
#endif
return false;
}
return true;
}
bool PGFLoader::hasAlpha() const
{
return m_hasAlpha;
}
bool PGFLoader::sixteenBit() const
{
return m_sixteenBit;
}
bool PGFLoader::progressCallback(double percent, bool escapeAllowed)
{
if (m_observer)
{
m_observer->progressInfo(m_image, percent);
if (escapeAllowed)
{
return (!m_observer->continueQuery(m_image));
}
}
return false;
}
bool PGFLoader::isReadOnly() const
{
return false;
}
} // namespace Digikam
diff --git a/core/libs/dmetadata/dmetadata.cpp b/core/libs/dmetadata/dmetadata.cpp
index f3843662e5..042c690fd8 100644
--- a/core/libs/dmetadata/dmetadata.cpp
+++ b/core/libs/dmetadata/dmetadata.cpp
@@ -1,3443 +1,3443 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-02-23
* Description : image metadata interface
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2011 by Leif Huhn <leif at dkstat dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dmetadata.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QFile>
#include <QFileInfo>
#include <QLocale>
#include <QUuid>
// Local includes
#include "rawinfo.h"
#include "drawdecoder.h"
#include "filereadwritelock.h"
#include "metadatasettings.h"
#include "template.h"
#include "dimg.h"
#include "digikam_version.h"
#include "digikam_globals.h"
#include "digikam_debug.h"
namespace Digikam
{
DMetadata::DMetadata()
: MetaEngine()
{
registerMetadataSettings();
}
DMetadata::DMetadata(const QString& filePath)
: MetaEngine()
{
registerMetadataSettings();
load(filePath);
}
DMetadata::DMetadata(const MetaEngineData& data)
: MetaEngine(data)
{
registerMetadataSettings();
}
DMetadata::~DMetadata()
{
}
void DMetadata::registerMetadataSettings()
{
setSettings(MetadataSettings::instance()->settings());
}
void DMetadata::setSettings(const MetadataSettingsContainer& settings)
{
setUseXMPSidecar4Reading(settings.useXMPSidecar4Reading);
setWriteRawFiles(settings.writeRawFiles);
setMetadataWritingMode(settings.metadataWritingMode);
setUpdateFileTimeStamp(settings.updateFileTimeStamp);
}
bool DMetadata::load(const QString& filePath)
{
// In first, we trying to get metadata using Exiv2,
// else we will use other engine to extract minimal information.
FileReadLocker lock(filePath);
if (!MetaEngine::load(filePath))
{
if (!loadUsingRawEngine(filePath))
{
if (!loadUsingFFmpeg(filePath))
{
return false;
}
}
}
return true;
}
bool DMetadata::save(const QString& filePath, bool setVersion) const
{
FileWriteLocker lock(filePath);
return MetaEngine::save(filePath, setVersion);
}
bool DMetadata::applyChanges() const
{
FileWriteLocker lock(getFilePath());
return MetaEngine::applyChanges();
}
bool DMetadata::loadUsingRawEngine(const QString& filePath)
{
RawInfo identify;
if (DRawDecoder::rawFileIdentify(identify, filePath))
{
long int num=1, den=1;
if (!identify.model.isNull())
{
setExifTagString("Exif.Image.Model", identify.model);
}
if (!identify.make.isNull())
{
setExifTagString("Exif.Image.Make", identify.make);
}
if (!identify.owner.isNull())
{
setExifTagString("Exif.Image.Artist", identify.owner);
}
if (identify.sensitivity != -1)
{
setExifTagLong("Exif.Photo.ISOSpeedRatings", lroundf(identify.sensitivity));
}
if (identify.dateTime.isValid())
{
setImageDateTime(identify.dateTime, false);
}
if (identify.exposureTime != -1.0)
{
convertToRationalSmallDenominator(identify.exposureTime, &num, &den);
setExifTagRational("Exif.Photo.ExposureTime", num, den);
}
if (identify.aperture != -1.0)
{
convertToRational(identify.aperture, &num, &den, 8);
setExifTagRational("Exif.Photo.ApertureValue", num, den);
}
if (identify.focalLength != -1.0)
{
convertToRational(identify.focalLength, &num, &den, 8);
setExifTagRational("Exif.Photo.FocalLength", num, den);
}
if (identify.imageSize.isValid())
{
setImageDimensions(identify.imageSize);
}
// A RAW image is always uncalibrated. */
setImageColorWorkSpace(WORKSPACE_UNCALIBRATED);
return true;
}
return false;
}
int DMetadata::getMSecsInfo() const
{
int ms = 0;
bool ok = mSecTimeStamp("Exif.Photo.SubSecTime", ms);
if (ok) return ms;
ok = mSecTimeStamp("Exif.Photo.SubSecTimeOriginal", ms);
if (ok) return ms;
ok = mSecTimeStamp("Exif.Photo.SubSecTimeDigitized", ms);
if (ok) return ms;
return 0;
}
bool DMetadata::mSecTimeStamp(const char* const exifTagName, int& ms) const
{
bool ok = false;
QString val = getExifTagString(exifTagName);
if (!val.isEmpty())
{
int sub = val.toUInt(&ok);
if (ok)
{
int _ms = (int)(QString::fromLatin1("0.%1").arg(sub).toFloat(&ok) * 1000.0);
if (ok)
{
ms = _ms;
qCDebug(DIGIKAM_METAENGINE_LOG) << "msec timestamp: " << ms;
}
}
}
return ok;
}
CaptionsMap DMetadata::getImageComments(const DMetadataSettingsContainer& settings) const
{
if (getFilePath().isEmpty())
{
return CaptionsMap();
}
CaptionsMap captionsMap;
MetaEngine::AltLangMap authorsMap;
MetaEngine::AltLangMap datesMap;
MetaEngine::AltLangMap commentsMap;
QString commonAuthor;
// In first try to get captions properties from digiKam XMP namespace
if (supportXmp())
{
authorsMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", false);
datesMap = getXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", false);
if (authorsMap.isEmpty() && commonAuthor.isEmpty())
{
QString xmpAuthors = getXmpTagString("Xmp.acdsee.author", false);
if (!xmpAuthors.isEmpty())
{
authorsMap.insert(QLatin1String("x-default"), xmpAuthors);
}
}
}
// Get author name from IPTC DescriptionWriter. Private namespace above gets precedence.
QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter);
if (!descriptionWriter.isNull())
{
commonAuthor = descriptionWriter.toString();
}
// In first, we check XMP alternative language tags to create map of values.
bool xmpSupported = hasXmp();
bool iptcSupported = hasIptc();
bool exivSupported = hasExif();
for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER)))
{
if (entry.isDisabled)
continue;
QString commentString;
const std::string myStr = entry.namespaceName.toStdString();
const char* nameSpace = myStr.data();
switch(entry.subspace)
{
case NamespaceEntry::XMP:
switch(entry.specialOpts)
{
case NamespaceEntry::COMMENT_ALTLANG:
if (xmpSupported)
commentString = getXmpTagStringLangAlt(nameSpace, QString(), false);
break;
case NamespaceEntry::COMMENT_ATLLANGLIST:
if (xmpSupported)
commentsMap = getXmpTagStringListLangAlt(nameSpace, false);
break;
case NamespaceEntry::COMMENT_XMP:
if (xmpSupported)
commentString = getXmpTagString("Xmp.acdsee.notes", false);
break;
case NamespaceEntry::COMMENT_JPEG:
// Now, we trying to get image comments, outside of XMP.
// For JPEG, string is extracted from JFIF Comments section.
// For PNG, string is extracted from iTXt chunk.
commentString = getCommentsDecoded();
default:
break;
}
break;
case NamespaceEntry::IPTC:
if (iptcSupported)
commentString = getIptcTagString(nameSpace, false);
break;
case NamespaceEntry::EXIF:
if (exivSupported)
commentString = getExifComment();
break;
default:
break;
}
if (!commentString.isEmpty() &&!commentString.trimmed().isEmpty())
{
commentsMap.insert(QLatin1String("x-default"), commentString);
captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap);
return captionsMap;
}
if (!commentsMap.isEmpty())
{
captionsMap.setData(commentsMap, authorsMap, commonAuthor, datesMap);
return captionsMap;
}
}
return captionsMap;
}
bool DMetadata::setImageComments(const CaptionsMap& comments, const DMetadataSettingsContainer& settings) const
{
/*
// See bug #139313: An empty string is also a valid value
if (comments.isEmpty())
return false;
*/
qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Comment: " << comments;
// In first, set captions properties to digiKam XMP namespace
if (supportXmp())
{
if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsAuthorNames", comments.authorsList()))
{
return false;
}
QString defaultAuthor = comments.value(QLatin1String("x-default")).author;
removeXmpTag("Xmp.acdsee.author");
if (!defaultAuthor.isNull())
{
if (!setXmpTagString("Xmp.acdsee.author", defaultAuthor))
{
return false;
}
}
if (!setXmpTagStringListLangAlt("Xmp.digiKam.CaptionsDateTimeStamps", comments.datesList()))
{
return false;
}
}
QString defaultComment = comments.value(QLatin1String("x-default")).caption;
QList<NamespaceEntry> toWrite = settings.getReadMapping(QString::fromUtf8(DM_COMMENT_CONTAINER));
if (!settings.unifyReadWrite())
toWrite = settings.getWriteMapping(QString::fromUtf8(DM_COMMENT_CONTAINER));
for (NamespaceEntry entry : toWrite)
{
if (entry.isDisabled)
continue;
const std::string myStr = entry.namespaceName.toStdString();
const char* nameSpace = myStr.data();
switch(entry.subspace)
{
case NamespaceEntry::XMP:
if (entry.namespaceName.contains(QLatin1String("Xmp.")))
removeXmpTag(nameSpace);
switch(entry.specialOpts)
{
case NamespaceEntry::COMMENT_ALTLANG:
if (!defaultComment.isNull())
{
if (!setXmpTagStringLangAlt(nameSpace, defaultComment, QString()))
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image comment failed" << nameSpace;
return false;
}
}
break;
case NamespaceEntry::COMMENT_ATLLANGLIST:
if (!setXmpTagStringListLangAlt(nameSpace, comments.toAltLangMap()))
{
return false;
}
break;
case NamespaceEntry::COMMENT_XMP:
if (!defaultComment.isNull())
{
if (!setXmpTagString(nameSpace, defaultComment))
{
return false;
}
}
break;
case NamespaceEntry::COMMENT_JPEG:
// In first we set image comments, outside of Exif, XMP, and IPTC.
if (!setComments(defaultComment.toUtf8()))
{
return false;
}
break;
default:
break;
}
break;
case NamespaceEntry::IPTC:
removeIptcTag(nameSpace);
if (!defaultComment.isNull())
{
defaultComment.truncate(2000);
if (!setIptcTagString(nameSpace, defaultComment))
{
return false;
}
}
break;
case NamespaceEntry::EXIF:
if (!setExifComment(defaultComment))
{
return false;
}
break;
default:
break;
}
}
return true;
}
int DMetadata::getImagePickLabel() const
{
if (getFilePath().isEmpty())
{
return -1;
}
if (hasXmp())
{
QString value = getXmpTagString("Xmp.digiKam.PickLabel", false);
if (!value.isEmpty())
{
bool ok = false;
long pickId = value.toLong(&ok);
if (ok && pickId >= NoPickLabel && pickId <= AcceptedLabel)
{
return pickId;
}
}
}
return -1;
}
int DMetadata::getImageColorLabel() const
{
if (getFilePath().isEmpty())
{
return -1;
}
if (hasXmp())
{
QString value = getXmpTagString("Xmp.digiKam.ColorLabel", false);
if (value.isEmpty())
{
// Nikon NX use this XMP tags to store Color Labels
value = getXmpTagString("Xmp.photoshop.Urgency", false);
}
if (!value.isEmpty())
{
bool ok = false;
long colorId = value.toLong(&ok);
if (ok && colorId >= NoColorLabel && colorId <= WhiteLabel)
{
return colorId;
}
}
// LightRoom use this tag to store color name as string.
// Values are limited : see bug #358193.
value = getXmpTagString("Xmp.xmp.Label", false);
if (value == QLatin1String("Blue"))
{
return BlueLabel;
}
else if (value == QLatin1String("Green"))
{
return GreenLabel;
}
else if (value == QLatin1String("Red"))
{
return RedLabel;
}
else if (value == QLatin1String("Yellow"))
{
return YellowLabel;
}
else if (value == QLatin1String("Purple"))
{
return MagentaLabel;
}
}
return -1;
}
CaptionsMap DMetadata::getImageTitles() const
{
if (getFilePath().isEmpty())
return CaptionsMap();
CaptionsMap captionsMap;
MetaEngine::AltLangMap authorsMap;
MetaEngine::AltLangMap datesMap;
MetaEngine::AltLangMap titlesMap;
QString commonAuthor;
// Get author name from IPTC DescriptionWriter. Private namespace above gets precedence.
QVariant descriptionWriter = getMetadataField(MetadataInfo::DescriptionWriter);
if (!descriptionWriter.isNull())
commonAuthor = descriptionWriter.toString();
// In first, we check XMP alternative language tags to create map of values.
if (hasXmp())
{
titlesMap = getXmpTagStringListLangAlt("Xmp.dc.title", false);
if (!titlesMap.isEmpty())
{
captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap);
return captionsMap;
}
QString xmpTitle = getXmpTagString("Xmp.acdsee.caption" ,false);
if (!xmpTitle.isEmpty() && !xmpTitle.trimmed().isEmpty())
{
titlesMap.insert(QLatin1String("x-default"), xmpTitle);
captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap);
return captionsMap;
}
}
// We trying to get IPTC title
if (hasIptc())
{
QString iptcTitle = getIptcTagString("Iptc.Application2.ObjectName", false);
if (!iptcTitle.isEmpty() && !iptcTitle.trimmed().isEmpty())
{
titlesMap.insert(QLatin1String("x-default"), iptcTitle);
captionsMap.setData(titlesMap, authorsMap, commonAuthor, datesMap);
return captionsMap;
}
}
return captionsMap;
}
bool DMetadata::setImageTitles(const CaptionsMap& titles) const
{
qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Title: " << titles;
QString defaultTitle = titles[QLatin1String("x-default")].caption;
// In First we write comments into XMP. Language Alternative rule is not yet used.
if (supportXmp())
{
// NOTE : setXmpTagStringListLangAlt remove xmp tag before to add new values
if (!setXmpTagStringListLangAlt("Xmp.dc.title", titles.toAltLangMap()))
{
return false;
}
removeXmpTag("Xmp.acdsee.caption");
if (!defaultTitle.isEmpty())
{
if (!setXmpTagString("Xmp.acdsee.caption", defaultTitle))
{
return false;
}
}
}
// In Second we write comments into IPTC.
// Note that Caption IPTC tag is limited to 64 char and ASCII charset.
removeIptcTag("Iptc.Application2.ObjectName");
if (!defaultTitle.isNull())
{
defaultTitle.truncate(64);
// See if we have any non printable chars in there. If so, skip IPTC
// to avoid confusing other apps and web services with invalid tags.
bool hasInvalidChar = false;
for (QString::const_iterator c = defaultTitle.constBegin(); c != defaultTitle.constEnd(); ++c)
{
if (!(*c).isPrint())
{
hasInvalidChar = true;
break;
}
}
if (!hasInvalidChar)
{
if (!setIptcTagString("Iptc.Application2.ObjectName", defaultTitle))
return false;
}
}
return true;
}
int DMetadata::getImageRating(const DMetadataSettingsContainer& settings) const
{
if (getFilePath().isEmpty())
{
return -1;
}
long rating = -1;
bool xmpSupported = hasXmp();
bool iptcSupported = hasIptc();
bool exivSupported = hasExif();
for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER)))
{
if (entry.isDisabled)
continue;
const std::string myStr = entry.namespaceName.toStdString();
const char* nameSpace = myStr.data();
QString value;
switch(entry.subspace)
{
case NamespaceEntry::XMP:
if (xmpSupported)
value = getXmpTagString(nameSpace, false);
break;
case NamespaceEntry::IPTC:
if (iptcSupported)
value = QString::fromUtf8(getIptcTagData(nameSpace));
break;
case NamespaceEntry::EXIF:
if (exivSupported)
getExifTagLong(nameSpace, rating);
break;
default:
break;
}
if (!value.isEmpty())
{
bool ok = false;
rating = value.toLong(&ok);
if (!ok)
{
return -1;
}
}
int index = entry.convertRatio.indexOf(rating);
// Exact value was not found,but rating is in range,
- // so we try to aproximate it
+ // so we try to approximate it
if ((index == -1) &&
(rating > entry.convertRatio.first()) &&
(rating < entry.convertRatio.last()))
{
for (int i = 0 ; i < entry.convertRatio.size() ; i++)
{
if (rating > entry.convertRatio.at(i))
{
index = i;
}
}
}
if (index != -1)
{
return index;
}
}
return -1;
}
bool DMetadata::setImagePickLabel(int pickId) const
{
if (pickId < NoPickLabel || pickId > AcceptedLabel)
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Pick Label value to write is out of range!";
return false;
}
qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Pick Label: " << pickId;
if (supportXmp())
{
if (!setXmpTagString("Xmp.digiKam.PickLabel", QString::number(pickId)))
{
return false;
}
}
return true;
}
bool DMetadata::setImageColorLabel(int colorId) const
{
if (colorId < NoColorLabel || colorId > WhiteLabel)
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Color Label value to write is out of range!";
return false;
}
qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Color Label: " << colorId;
if (supportXmp())
{
if (!setXmpTagString("Xmp.digiKam.ColorLabel", QString::number(colorId)))
{
return false;
}
// Nikon NX use this XMP tags to store Color Labels
if (!setXmpTagString("Xmp.photoshop.Urgency", QString::number(colorId)))
{
return false;
}
// LightRoom use this XMP tags to store Color Labels name
// Values are limited : see bug #358193.
QString LRLabel;
switch(colorId)
{
case BlueLabel:
LRLabel = QLatin1String("Blue");
break;
case GreenLabel:
LRLabel = QLatin1String("Green");
break;
case RedLabel:
LRLabel = QLatin1String("Red");
break;
case YellowLabel:
LRLabel = QLatin1String("Yellow");
break;
case MagentaLabel:
LRLabel = QLatin1String("Purple");
break;
}
if (!LRLabel.isEmpty())
{
if (!setXmpTagString("Xmp.xmp.Label", LRLabel))
{
return false;
}
}
}
return true;
}
bool DMetadata::setImageRating(int rating, const DMetadataSettingsContainer& settings) const
{
// NOTE : with digiKam 0.9.x, we have used IPTC Urgency to store Rating.
// Now this way is obsolete, and we use standard XMP rating tag instead.
if (rating < RatingMin || rating > RatingMax)
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Rating value to write is out of range!";
return false;
}
qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> Rating:" << rating;
QList<NamespaceEntry> toWrite = settings.getReadMapping(QString::fromUtf8(DM_RATING_CONTAINER));
if (!settings.unifyReadWrite())
toWrite = settings.getWriteMapping(QString::fromUtf8(DM_RATING_CONTAINER));
for (NamespaceEntry entry : toWrite)
{
if (entry.isDisabled)
continue;
const std::string myStr = entry.namespaceName.toStdString();
const char* nameSpace = myStr.data();
switch(entry.subspace)
{
case NamespaceEntry::XMP:
if (!setXmpTagString(nameSpace, QString::number(entry.convertRatio.at(rating))))
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting rating failed" << nameSpace;
return false;
}
break;
case NamespaceEntry::EXIF:
if (!setExifTagLong(nameSpace, rating))
{
return false;
}
break;
case NamespaceEntry::IPTC: // IPTC rating deprecated
default:
break;
}
}
// Set Exif rating tag used by Windows Vista.
if (!setExifTagLong("Exif.Image.0x4746", rating))
{
return false;
}
// Wrapper around rating percents managed by Windows Vista.
int ratePercents = 0;
switch (rating)
{
case 0:
ratePercents = 0;
break;
case 1:
ratePercents = 1;
break;
case 2:
ratePercents = 25;
break;
case 3:
ratePercents = 50;
break;
case 4:
ratePercents = 75;
break;
case 5:
ratePercents = 99;
break;
}
if (!setExifTagLong("Exif.Image.0x4749", ratePercents))
{
return false;
}
return true;
}
bool DMetadata::setImageHistory(QString& imageHistoryXml) const
{
if (supportXmp())
{
if (!setXmpTagString("Xmp.digiKam.ImageHistory", imageHistoryXml))
{
return false;
}
else
{
return true;
}
}
return false;
}
QString DMetadata::getImageHistory() const
{
if (hasXmp())
{
QString value = getXmpTagString("Xmp.digiKam.ImageHistory", false);
qCDebug(DIGIKAM_METAENGINE_LOG) << "Loading image history " << value;
return value;
}
return QString();
}
bool DMetadata::hasImageHistoryTag() const
{
if (hasXmp())
{
if (QString(getXmpTagString("Xmp.digiKam.ImageHistory", false)).length() > 0)
{
return true;
}
else
{
return false;
}
}
return false;
}
QString DMetadata::getImageUniqueId() const
{
QString exifUid;
if (hasXmp())
{
QString uuid = getXmpTagString("Xmp.digiKam.ImageUniqueID");
if (!uuid.isEmpty())
{
return uuid;
}
exifUid = getXmpTagString("Xmp.exif.ImageUniqueId");
}
if (exifUid.isEmpty())
{
exifUid = getExifTagString("Exif.Photo.ImageUniqueID");
}
// same makers may choose to use a "click counter" to generate the id,
// which is then weak and not a universally unique id
// The Exif ImageUniqueID is 128bit, or 32 hex digits.
// If the first 20 are zero, it's probably a counter,
// the left 12 are sufficient for more then 10^14 clicks.
if (!exifUid.isEmpty() && !exifUid.startsWith(QLatin1String("00000000000000000000")))
{
if (getExifTagString("Exif.Image.Make").contains(QLatin1String("SAMSUNG"), Qt::CaseInsensitive))
{
// Generate for Samsung a new random 32 hex digits unique ID.
QString imageUniqueID(QUuid::createUuid().toString());
imageUniqueID.remove(QLatin1Char('-'));
imageUniqueID.remove(0, 1).chop(1);
return imageUniqueID;
}
return exifUid;
}
// Exif.Image.ImageID can also be a pathname, so it's not sufficiently unique
QString dngUid = getExifTagString("Exif.Image.RawDataUniqueID");
if (!dngUid.isEmpty())
{
return dngUid;
}
return QString();
}
bool DMetadata::setImageUniqueId(const QString& uuid) const
{
if (supportXmp())
{
return setXmpTagString("Xmp.digiKam.ImageUniqueID", uuid);
}
return false;
}
PhotoInfoContainer DMetadata::getPhotographInformation() const
{
PhotoInfoContainer photoInfo;
if (hasExif() || hasXmp())
{
photoInfo.dateTime = getImageDateTime();
// -----------------------------------------------------------------------------------
photoInfo.make = getExifTagString("Exif.Image.Make");
if (photoInfo.make.isEmpty())
{
photoInfo.make = getXmpTagString("Xmp.tiff.Make");
}
// -----------------------------------------------------------------------------------
photoInfo.model = getExifTagString("Exif.Image.Model");
if (photoInfo.model.isEmpty())
{
photoInfo.model = getXmpTagString("Xmp.tiff.Model");
}
// -----------------------------------------------------------------------------------
photoInfo.lens = getLensDescription();
// -----------------------------------------------------------------------------------
photoInfo.aperture = getExifTagString("Exif.Photo.FNumber");
if (photoInfo.aperture.isEmpty())
{
photoInfo.aperture = getExifTagString("Exif.Photo.ApertureValue");
}
if (photoInfo.aperture.isEmpty())
{
photoInfo.aperture = getXmpTagString("Xmp.exif.FNumber");
}
if (photoInfo.aperture.isEmpty())
{
photoInfo.aperture = getXmpTagString("Xmp.exif.ApertureValue");
}
// -----------------------------------------------------------------------------------
photoInfo.exposureTime = getExifTagString("Exif.Photo.ExposureTime");
if (photoInfo.exposureTime.isEmpty())
{
photoInfo.exposureTime = getExifTagString("Exif.Photo.ShutterSpeedValue");
}
if (photoInfo.exposureTime.isEmpty())
{
photoInfo.exposureTime = getXmpTagString("Xmp.exif.ExposureTime");
}
if (photoInfo.exposureTime.isEmpty())
{
photoInfo.exposureTime = getXmpTagString("Xmp.exif.ShutterSpeedValue");
}
// -----------------------------------------------------------------------------------
photoInfo.exposureMode = getExifTagString("Exif.Photo.ExposureMode");
if (photoInfo.exposureMode.isEmpty())
{
photoInfo.exposureMode = getXmpTagString("Xmp.exif.ExposureMode");
}
if (photoInfo.exposureMode.isEmpty())
{
photoInfo.exposureMode = getExifTagString("Exif.CanonCs.MeteringMode");
}
// -----------------------------------------------------------------------------------
photoInfo.exposureProgram = getExifTagString("Exif.Photo.ExposureProgram");
if (photoInfo.exposureProgram.isEmpty())
{
photoInfo.exposureProgram = getXmpTagString("Xmp.exif.ExposureProgram");
}
if (photoInfo.exposureProgram.isEmpty())
{
photoInfo.exposureProgram = getExifTagString("Exif.CanonCs.ExposureProgram");
}
// -----------------------------------------------------------------------------------
photoInfo.focalLength = getExifTagString("Exif.Photo.FocalLength");
if (photoInfo.focalLength.isEmpty())
{
photoInfo.focalLength = getXmpTagString("Xmp.exif.FocalLength");
}
if (photoInfo.focalLength.isEmpty())
{
photoInfo.focalLength = getExifTagString("Exif.Canon.FocalLength");
}
// -----------------------------------------------------------------------------------
photoInfo.focalLength35mm = getExifTagString("Exif.Photo.FocalLengthIn35mmFilm");
if (photoInfo.focalLength35mm.isEmpty())
{
photoInfo.focalLength35mm = getXmpTagString("Xmp.exif.FocalLengthIn35mmFilm");
}
// -----------------------------------------------------------------------------------
QStringList ISOSpeedTags;
ISOSpeedTags << QLatin1String("Exif.Photo.ISOSpeedRatings");
ISOSpeedTags << QLatin1String("Exif.Photo.ExposureIndex");
ISOSpeedTags << QLatin1String("Exif.Image.ISOSpeedRatings");
ISOSpeedTags << QLatin1String("Xmp.exif.ISOSpeedRatings");
ISOSpeedTags << QLatin1String("Xmp.exif.ExposureIndex");
ISOSpeedTags << QLatin1String("Exif.CanonSi.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.CanonCs.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.Nikon1.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.Nikon2.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.Nikon3.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO");
ISOSpeedTags << QLatin1String("Exif.NikonIi.ISO2");
ISOSpeedTags << QLatin1String("Exif.MinoltaCsNew.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.MinoltaCsOld.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.MinoltaCs5D.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.MinoltaCs7D.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.Sony1Cs.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.Sony2Cs.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.Sony1Cs2.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.Sony2Cs2.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.Sony1MltCsA100.ISOSetting");
ISOSpeedTags << QLatin1String("Exif.Pentax.ISO");
ISOSpeedTags << QLatin1String("Exif.Olympus.ISOSpeed");
ISOSpeedTags << QLatin1String("Exif.Samsung2.ISO");
photoInfo.sensitivity = getExifTagStringFromTagsList(ISOSpeedTags);
// -----------------------------------------------------------------------------------
photoInfo.flash = getExifTagString("Exif.Photo.Flash");
if (photoInfo.flash.isEmpty())
{
photoInfo.flash = getXmpTagString("Xmp.exif.Flash");
}
if (photoInfo.flash.isEmpty())
{
photoInfo.flash = getExifTagString("Exif.CanonCs.FlashActivity");
}
// -----------------------------------------------------------------------------------
photoInfo.whiteBalance = getExifTagString("Exif.Photo.WhiteBalance");
if (photoInfo.whiteBalance.isEmpty())
{
photoInfo.whiteBalance = getXmpTagString("Xmp.exif.WhiteBalance");
}
// -----------------------------------------------------------------------------------
double l, L, a;
photoInfo.hasCoordinates = getGPSInfo(a, l, L);
}
return photoInfo;
}
VideoInfoContainer DMetadata::getVideoInformation() const
{
VideoInfoContainer videoInfo;
if (hasXmp())
{
if (videoInfo.aspectRatio.isEmpty())
{
videoInfo.aspectRatio = getMetadataField(MetadataInfo::AspectRatio).toString();
}
if (videoInfo.audioBitRate.isEmpty())
{
videoInfo.audioBitRate = getXmpTagString("Xmp.audio.SampleRate");
}
if (videoInfo.audioChannelType.isEmpty())
{
videoInfo.audioChannelType = getXmpTagString("Xmp.audio.ChannelType");
}
if (videoInfo.audioCodec.isEmpty())
{
videoInfo.audioCodec = getXmpTagString("Xmp.audio.Codec");
}
if (videoInfo.duration.isEmpty())
{
videoInfo.duration = getXmpTagString("Xmp.video.duration");
}
if (videoInfo.frameRate.isEmpty())
{
videoInfo.frameRate = getXmpTagString("Xmp.video.FrameRate");
}
if (videoInfo.videoCodec.isEmpty())
{
videoInfo.videoCodec = getXmpTagString("Xmp.video.Codec");
}
}
return videoInfo;
}
bool DMetadata::getImageTagsPath(QStringList& tagsPath,
const DMetadataSettingsContainer& settings) const
{
for (NamespaceEntry entry : settings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER)))
{
if (entry.isDisabled)
continue;
int index = 0;
QString currentNamespace = entry.namespaceName;
NamespaceEntry::SpecialOptions currentOpts = entry.specialOpts;
// Some namespaces have altenative paths, we must search them both
switch(entry.subspace)
{
case NamespaceEntry::XMP:
while(index < 2)
{
const std::string myStr = currentNamespace.toStdString();
const char* nameSpace = myStr.data();
switch(currentOpts)
{
case NamespaceEntry::TAG_XMPBAG:
tagsPath = getXmpTagStringBag(nameSpace, false);
break;
case NamespaceEntry::TAG_XMPSEQ:
tagsPath = getXmpTagStringSeq(nameSpace, false);
break;
case NamespaceEntry::TAG_ACDSEE:
getACDSeeTagsPath(tagsPath);
break;
// not used here, to suppress warnings
case NamespaceEntry::COMMENT_XMP:
case NamespaceEntry::COMMENT_ALTLANG:
case NamespaceEntry::COMMENT_ATLLANGLIST:
case NamespaceEntry::NO_OPTS:
default:
break;
}
if (!tagsPath.isEmpty())
{
if (entry.separator != QLatin1String("/"))
{
tagsPath = tagsPath.replaceInStrings(entry.separator, QLatin1String("/"));
}
return true;
}
else if (!entry.alternativeName.isEmpty())
{
currentNamespace = entry.alternativeName;
currentOpts = entry.secondNameOpts;
}
else
{
break; // no alternative namespace, go to next one
}
index++;
}
break;
case NamespaceEntry::IPTC:
// Try to get Tags Path list from IPTC keywords.
// digiKam 0.9.x has used IPTC keywords to store Tags Path list.
// This way is obsolete now since digiKam support XMP because IPTC
// do not support UTF-8 and have strings size limitation. But we will
// let the capability to import it for interworking issues.
tagsPath = getIptcKeywords();
if (!tagsPath.isEmpty())
{
// Work around to Imach tags path list hosted in IPTC with '.' as separator.
QStringList ntp = tagsPath.replaceInStrings(entry.separator, QLatin1String("/"));
if (ntp != tagsPath)
{
tagsPath = ntp;
qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from Imach: " << tagsPath;
}
return true;
}
break;
case NamespaceEntry::EXIF:
{
// Try to get Tags Path list from Exif Windows keywords.
QString keyWords = getExifTagString("Exif.Image.XPKeywords", false);
if (!keyWords.isEmpty())
{
tagsPath = keyWords.split(entry.separator);
if (!tagsPath.isEmpty())
{
return true;
}
}
break;
}
default:
break;
}
}
return false;
}
bool DMetadata::setImageTagsPath(const QStringList& tagsPath, const DMetadataSettingsContainer& settings) const
{
// NOTE : with digiKam 0.9.x, we have used IPTC Keywords for that.
// Now this way is obsolete, and we use XMP instead.
// Set the new Tags path list. This is set, not add-to like setXmpKeywords.
// Unlike the other keyword fields, we do not need to merge existing entries.
QList<NamespaceEntry> toWrite = settings.getReadMapping(QString::fromUtf8(DM_TAG_CONTAINER));
if (!settings.unifyReadWrite())
toWrite = settings.getWriteMapping(QString::fromUtf8(DM_TAG_CONTAINER));
for (NamespaceEntry entry : toWrite)
{
if (entry.isDisabled)
continue;
QStringList newList;
// get keywords from tags path, for type tag
for (QString tagPath : tagsPath)
{
newList.append(tagPath.split(QLatin1Char('/')).last());
}
switch(entry.subspace)
{
case NamespaceEntry::XMP:
if (supportXmp())
{
if (entry.tagPaths != NamespaceEntry::TAG)
{
newList = tagsPath;
if (entry.separator.compare(QLatin1String("/")) != 0)
{
newList = newList.replaceInStrings(QLatin1String("/"), entry.separator);
}
}
const std::string myStr = entry.namespaceName.toStdString();
const char* nameSpace = myStr.data();
switch(entry.specialOpts)
{
case NamespaceEntry::TAG_XMPSEQ:
if (!setXmpTagStringSeq(nameSpace, newList))
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace;
return false;
}
break;
case NamespaceEntry::TAG_XMPBAG:
if (!setXmpTagStringBag(nameSpace, newList))
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace;
return false;
}
break;
case NamespaceEntry::TAG_ACDSEE:
if (!setACDSeeTagsPath(newList))
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << nameSpace;
return false;
}
default:
break;
}
}
break;
case NamespaceEntry::IPTC:
if (entry.namespaceName == QLatin1String("Iptc.Application2.Keywords"))
{
if (!setIptcKeywords(getIptcKeywords(), newList))
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Setting image paths failed" << entry.namespaceName;
return false;
}
}
default:
break;
}
}
return true;
}
bool DMetadata::getACDSeeTagsPath(QStringList &tagsPath) const
{
// Try to get Tags Path list from ACDSee 8 Pro categories.
QString xmlACDSee = getXmpTagString("Xmp.acdsee.categories", false);
if (!xmlACDSee.isEmpty())
{
xmlACDSee.remove(QLatin1String("</Categories>"));
xmlACDSee.remove(QLatin1String("<Categories>"));
xmlACDSee.replace(QLatin1Char('/'), QLatin1Char('\\'));
QStringList xmlTags = xmlACDSee.split(QLatin1String("<Category Assigned"));
int category = 0;
foreach(const QString& tags, xmlTags)
{
if (!tags.isEmpty())
{
int count = tags.count(QLatin1String("<\\Category>"));
int length = tags.length() - (11 * count) - 5;
if (category == 0)
{
tagsPath << tags.mid(5, length);
}
else
{
tagsPath.last().append(QLatin1Char('/') + tags.mid(5, length));
}
category = category - count + 1;
if (tags.left(5) == QLatin1String("=\"1\">") && category > 0)
{
tagsPath << tagsPath.last().section(QLatin1Char('/'), 0, category - 1);
}
}
}
if (!tagsPath.isEmpty())
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Tags Path imported from ACDSee: " << tagsPath;
return true;
}
}
return false;
}
bool DMetadata::setACDSeeTagsPath(const QStringList &tagsPath) const
{
// Converting Tags path list to ACDSee 8 Pro categories.
const QString category(QLatin1String("<Category Assigned=\"%1\">"));
QStringList splitTags;
QStringList xmlTags;
foreach(const QString& tags, tagsPath)
{
splitTags = tags.split(QLatin1Char('/'));
int current = 0;
for (int index = 0; index < splitTags.size(); index++)
{
int tagIndex = xmlTags.indexOf(category.arg(0) + splitTags[index]);
if (tagIndex == -1)
{
tagIndex = xmlTags.indexOf(category.arg(1) + splitTags[index]);
}
splitTags[index].insert(0, category.arg(index == splitTags.size() - 1 ? 1 : 0));
if (tagIndex == -1)
{
if (index == 0)
{
xmlTags << splitTags[index];
xmlTags << QLatin1String("</Category>");
current = xmlTags.size() - 1;
}
else
{
xmlTags.insert(current, splitTags[index]);
xmlTags.insert(current + 1, QLatin1String("</Category>"));
current++;
}
}
else
{
if (index == splitTags.size() - 1)
{
xmlTags[tagIndex] = splitTags[index];
}
current = tagIndex + 1;
}
}
}
QString xmlACDSee = QLatin1String("<Categories>") + xmlTags.join(QLatin1String("")) + QLatin1String("</Categories>");
qCDebug(DIGIKAM_METAENGINE_LOG) << "xmlACDSee" << xmlACDSee;
removeXmpTag("Xmp.acdsee.categories");
if (!xmlTags.isEmpty())
{
if (!setXmpTagString("Xmp.acdsee.categories", xmlACDSee))
{
return false;
}
}
return true;
}
bool DMetadata::getImageFacesMap(QMultiMap<QString,QVariant>& faces) const
{
faces.clear();
// The example code for Exiv2 says:
// > There are no specialized values for structures, qualifiers and nested
// > types. However, these can be added by using an XmpTextValue and a path as
// > the key.
// I think that means I have to iterate over the WLPG face tags in the clunky
// way below (guess numbers and look them up as strings). (Leif)
const QString personPathTemplate = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions[%1]/MPReg:PersonDisplayName");
const QString rectPathTemplate = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions[%1]/MPReg:Rectangle");
for (int i = 1; ; i++)
{
QString person = getXmpTagString(personPathTemplate.arg(i).toLatin1().constData(), false);
if (person.isEmpty())
break;
// The WLPG tags have the format X.XX, Y.YY, W.WW, H.HH
// That is, four decimal numbers ranging from 0-1.
// The top left position is indicated by X.XX, Y.YY (as a
// percentage of the width/height of the entire image).
// Similarly the width and height of the face's box are
// indicated by W.WW and H.HH.
QString rectString = getXmpTagString(rectPathTemplate.arg(i).toLatin1().constData(), false);
QStringList list = rectString.split(QLatin1Char(','));
if (list.size() < 4)
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Cannot parse WLPG rectangle string" << rectString;
continue;
}
QRectF rect(list.at(0).toFloat(),
list.at(1).toFloat(),
list.at(2).toFloat(),
list.at(3).toFloat());
faces.insertMulti(person, rect);
}
/** Read face tags only if libkexiv can write them, otherwise
* garbage tags will be generated on image transformation
*/
// Read face tags as saved by Picasa
// http://www.exiv2.org/tags-xmp-mwg-rs.html
const QString mwg_personPathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Name");
const QString mwg_rect_x_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:x");
const QString mwg_rect_y_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:y");
const QString mwg_rect_w_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:w");
const QString mwg_rect_h_PathTemplate = QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList[%1]/mwg-rs:Area/stArea:h");
for (int i = 1; ; i++)
{
QString person = getXmpTagString(mwg_personPathTemplate.arg(i).toLatin1().constData(), false);
if (person.isEmpty())
break;
// x and y is the center point
float x = getXmpTagString(mwg_rect_x_PathTemplate.arg(i).toLatin1().constData(), false).toFloat();
float y = getXmpTagString(mwg_rect_y_PathTemplate.arg(i).toLatin1().constData(), false).toFloat();
float w = getXmpTagString(mwg_rect_w_PathTemplate.arg(i).toLatin1().constData(), false).toFloat();
float h = getXmpTagString(mwg_rect_h_PathTemplate.arg(i).toLatin1().constData(), false).toFloat();
QRectF rect(x - w/2,
y - h/2,
w,
h);
faces.insertMulti(person, rect);
qCDebug(DIGIKAM_METAENGINE_LOG) << "Found new rect " << person << " "<< rect;
}
return !faces.isEmpty();
}
bool DMetadata::setImageFacesMap(QMultiMap< QString, QVariant >& facesPath, bool write) const
{
QString qxmpTagName(QLatin1String("Xmp.mwg-rs.Regions/mwg-rs:RegionList"));
QString nameTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Name");
QString typeTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Type");
QString areaTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area");
QString areaxTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:x");
QString areayTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:y");
QString areawTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:w");
QString areahTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:h");
QString areanormTagKey = qxmpTagName + QLatin1String("[%1]/mwg-rs:Area/stArea:unit");
QString winQxmpTagName = QLatin1String("Xmp.MP.RegionInfo/MPRI:Regions");
QString winRectTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:Rectangle");
QString winNameTagKey = winQxmpTagName + QLatin1String("[%1]/MPReg:PersonDisplayName");
if (!write)
{
QString check = getXmpTagString(nameTagKey.arg(1).toLatin1().constData());
if (check.isEmpty())
return true;
}
setXmpTagString(qxmpTagName.toLatin1().constData(),
QString(), MetaEngine::XmpTagType(1));
setXmpTagString(winQxmpTagName.toLatin1().constData(),
QString(), MetaEngine::XmpTagType(1));
QMap<QString, QVariant>::const_iterator it = facesPath.constBegin();
int i = 1;
bool ok = true;
while (it != facesPath.constEnd())
{
qreal x, y, w, h;
it.value().toRectF().getRect(&x, &y, &w, &h);
qCDebug(DIGIKAM_METAENGINE_LOG) << "Set face region:" << x << y << w << h;
/** Write face tags in Windows Live Photo format **/
QString rectString;
rectString.append(QString::number(x) + QLatin1String(", "));
rectString.append(QString::number(y) + QLatin1String(", "));
rectString.append(QString::number(w) + QLatin1String(", "));
rectString.append(QString::number(h));
/** Set tag rect **/
setXmpTagString(winRectTagKey.arg(i).toLatin1().constData(), rectString,
MetaEngine::XmpTagType(0));
/** Set tag name **/
setXmpTagString(winNameTagKey.arg(i).toLatin1().constData(),it.key(),
MetaEngine::XmpTagType(0));
/** Writing rectangle in Metadata Group format **/
x += w/2;
y += h/2;
/** Set tag name **/
ok &= setXmpTagString(nameTagKey.arg(i).toLatin1().constData(),
it.key(),MetaEngine::XmpTagType(0));
/** Set tag type as Face **/
ok &= setXmpTagString(typeTagKey.arg(i).toLatin1().constData(),
QLatin1String("Face"), MetaEngine::XmpTagType(0));
/** Set tag Area, with xmp type struct **/
ok &= setXmpTagString(areaTagKey.arg(i).toLatin1().constData(),
QString(), MetaEngine::XmpTagType(2));
/** Set stArea:x inside Area structure **/
ok &= setXmpTagString(areaxTagKey.arg(i).toLatin1().constData(),
QString::number(x), MetaEngine::XmpTagType(0));
/** Set stArea:y inside Area structure **/
ok &= setXmpTagString(areayTagKey.arg(i).toLatin1().constData(),
QString::number(y), MetaEngine::XmpTagType(0));
/** Set stArea:w inside Area structure **/
ok &= setXmpTagString(areawTagKey.arg(i).toLatin1().constData(),
QString::number(w), MetaEngine::XmpTagType(0));
/** Set stArea:h inside Area structure **/
ok &= setXmpTagString(areahTagKey.arg(i).toLatin1().constData(),
QString::number(h), MetaEngine::XmpTagType(0));
/** Set stArea:unit inside Area structure as normalized **/
ok &= setXmpTagString(areanormTagKey.arg(i).toLatin1().constData(),
QLatin1String("normalized"), MetaEngine::XmpTagType(0));
++it;
++i;
}
return ok;
}
bool DMetadata::setMetadataTemplate(const Template& t) const
{
if (t.isNull())
{
return false;
}
QStringList authors = t.authors();
QString authorsPosition = t.authorsPosition();
QString credit = t.credit();
QString source = t.source();
MetaEngine::AltLangMap copyright = t.copyright();
MetaEngine::AltLangMap rightUsage = t.rightUsageTerms();
QString instructions = t.instructions();
qCDebug(DIGIKAM_METAENGINE_LOG) << "Applying Metadata Template: " << t.templateTitle() << " :: " << authors;
// Set XMP tags. XMP<->IPTC Schema from Photoshop 7.0
if (supportXmp())
{
if (!setXmpTagStringSeq("Xmp.dc.creator", authors))
{
return false;
}
if (!setXmpTagStringSeq("Xmp.tiff.Artist", authors))
{
return false;
}
if (!setXmpTagString("Xmp.photoshop.AuthorsPosition", authorsPosition))
{
return false;
}
if (!setXmpTagString("Xmp.photoshop.Credit", credit))
{
return false;
}
if (!setXmpTagString("Xmp.photoshop.Source", source))
{
return false;
}
if (!setXmpTagString("Xmp.dc.source", source))
{
return false;
}
if (!setXmpTagStringListLangAlt("Xmp.dc.rights", copyright))
{
return false;
}
if (!setXmpTagStringListLangAlt("Xmp.tiff.Copyright", copyright))
{
return false;
}
if (!setXmpTagStringListLangAlt("Xmp.xmpRights.UsageTerms", rightUsage))
{
return false;
}
if (!setXmpTagString("Xmp.photoshop.Instructions", instructions))
{
return false;
}
}
// Set IPTC tags.
if (!setIptcTagsStringList("Iptc.Application2.Byline", 32,
getIptcTagsStringList("Iptc.Application2.Byline"),
authors))
{
return false;
}
if (!setIptcTag(authorsPosition, 32, "Authors Title", "Iptc.Application2.BylineTitle"))
{
return false;
}
if (!setIptcTag(credit, 32, "Credit", "Iptc.Application2.Credit"))
{
return false;
}
if (!setIptcTag(source, 32, "Source", "Iptc.Application2.Source"))
{
return false;
}
if (!setIptcTag(copyright[QLatin1String("x-default")], 128, "Copyright", "Iptc.Application2.Copyright"))
{
return false;
}
if (!setIptcTag(instructions, 256, "Instructions", "Iptc.Application2.SpecialInstructions"))
{
return false;
}
if (!setIptcCoreLocation(t.locationInfo()))
{
return false;
}
if (!setCreatorContactInfo(t.contactInfo()))
{
return false;
}
if (supportXmp())
{
if (!setXmpSubjects(t.IptcSubjects()))
{
return false;
}
}
// Synchronize Iptc subjects tags with Xmp subjects tags.
QStringList list = t.IptcSubjects();
QStringList newList;
foreach(QString str, list) // krazy:exclude=foreach
{
if (str.startsWith(QLatin1String("XMP")))
{
str.replace(0, 3, QLatin1String("IPTC"));
}
newList.append(str);
}
if (!setIptcSubjects(getIptcSubjects(), newList))
{
return false;
}
return true;
}
bool DMetadata::removeMetadataTemplate() const
{
// Remove Rights info.
removeXmpTag("Xmp.dc.creator");
removeXmpTag("Xmp.tiff.Artist");
removeXmpTag("Xmp.photoshop.AuthorsPosition");
removeXmpTag("Xmp.photoshop.Credit");
removeXmpTag("Xmp.photoshop.Source");
removeXmpTag("Xmp.dc.source");
removeXmpTag("Xmp.dc.rights");
removeXmpTag("Xmp.tiff.Copyright");
removeXmpTag("Xmp.xmpRights.UsageTerms");
removeXmpTag("Xmp.photoshop.Instructions");
removeIptcTag("Iptc.Application2.Byline");
removeIptcTag("Iptc.Application2.BylineTitle");
removeIptcTag("Iptc.Application2.Credit");
removeIptcTag("Iptc.Application2.Source");
removeIptcTag("Iptc.Application2.Copyright");
removeIptcTag("Iptc.Application2.SpecialInstructions");
// Remove Location info.
removeXmpTag("Xmp.photoshop.Country");
removeXmpTag("Xmp.iptc.CountryCode");
removeXmpTag("Xmp.photoshop.City");
removeXmpTag("Xmp.iptc.Location");
removeXmpTag("Xmp.photoshop.State");
removeIptcTag("Iptc.Application2.CountryName");
removeIptcTag("Iptc.Application2.CountryCode");
removeIptcTag("Iptc.Application2.City");
removeIptcTag("Iptc.Application2.SubLocation");
removeIptcTag("Iptc.Application2.ProvinceState");
// Remove Contact info.
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork");
removeXmpTag("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork");
// Remove IPTC Subjects.
removeXmpTag("Xmp.iptc.SubjectCode");
removeIptcTag("Iptc.Application2.Subject");
return true;
}
Template DMetadata::getMetadataTemplate() const
{
Template t;
getCopyrightInformation(t);
t.setLocationInfo(getIptcCoreLocation());
t.setIptcSubjects(getIptcCoreSubjects()); // get from XMP or Iptc
return t;
}
static bool hasValidField(const QVariantList& list)
{
for (QVariantList::const_iterator it = list.constBegin();
it != list.constEnd(); ++it)
{
if (!(*it).isNull())
{
return true;
}
}
return false;
}
bool DMetadata::getCopyrightInformation(Template& t) const
{
MetadataFields fields;
fields << MetadataInfo::IptcCoreCopyrightNotice
<< MetadataInfo::IptcCoreCreator
<< MetadataInfo::IptcCoreProvider
<< MetadataInfo::IptcCoreRightsUsageTerms
<< MetadataInfo::IptcCoreSource
<< MetadataInfo::IptcCoreCreatorJobTitle
<< MetadataInfo::IptcCoreInstructions;
QVariantList metadataInfos = getMetadataFields(fields);
IptcCoreContactInfo contactInfo = getCreatorContactInfo();
if (!hasValidField(metadataInfos) && contactInfo.isNull())
{
return false;
}
t.setCopyright(toAltLangMap(metadataInfos.at(0)));
t.setAuthors(metadataInfos.at(1).toStringList());
t.setCredit(metadataInfos.at(2).toString());
t.setRightUsageTerms(toAltLangMap(metadataInfos.at(3)));
t.setSource(metadataInfos.at(4).toString());
t.setAuthorsPosition(metadataInfos.at(5).toString());
t.setInstructions(metadataInfos.at(6).toString());
t.setContactInfo(contactInfo);
return true;
}
IptcCoreContactInfo DMetadata::getCreatorContactInfo() const
{
MetadataFields fields;
fields << MetadataInfo::IptcCoreContactInfoCity
<< MetadataInfo::IptcCoreContactInfoCountry
<< MetadataInfo::IptcCoreContactInfoAddress
<< MetadataInfo::IptcCoreContactInfoPostalCode
<< MetadataInfo::IptcCoreContactInfoProvinceState
<< MetadataInfo::IptcCoreContactInfoEmail
<< MetadataInfo::IptcCoreContactInfoPhone
<< MetadataInfo::IptcCoreContactInfoWebUrl;
QVariantList metadataInfos = getMetadataFields(fields);
IptcCoreContactInfo info;
if (metadataInfos.size() == 8)
{
info.city = metadataInfos.at(0).toString();
info.country = metadataInfos.at(1).toString();
info.address = metadataInfos.at(2).toString();
info.postalCode = metadataInfos.at(3).toString();
info.provinceState = metadataInfos.at(4).toString();
info.email = metadataInfos.at(5).toString();
info.phone = metadataInfos.at(6).toString();
info.webUrl = metadataInfos.at(7).toString();
}
return info;
}
bool DMetadata::setCreatorContactInfo(const IptcCoreContactInfo& info) const
{
if (!supportXmp())
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity", info.city))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry", info.country))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr", info.address))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode", info.postalCode))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion", info.provinceState))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork", info.email))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork", info.phone))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork", info.webUrl))
{
return false;
}
return true;
}
IptcCoreLocationInfo DMetadata::getIptcCoreLocation() const
{
MetadataFields fields;
fields << MetadataInfo::IptcCoreCountry
<< MetadataInfo::IptcCoreCountryCode
<< MetadataInfo::IptcCoreCity
<< MetadataInfo::IptcCoreLocation
<< MetadataInfo::IptcCoreProvinceState;
QVariantList metadataInfos = getMetadataFields(fields);
IptcCoreLocationInfo location;
if (fields.size() == 5)
{
location.country = metadataInfos.at(0).toString();
location.countryCode = metadataInfos.at(1).toString();
location.city = metadataInfos.at(2).toString();
location.location = metadataInfos.at(3).toString();
location.provinceState = metadataInfos.at(4).toString();
}
return location;
}
bool DMetadata::setIptcCoreLocation(const IptcCoreLocationInfo& location) const
{
if (supportXmp())
{
if (!setXmpTagString("Xmp.photoshop.Country", location.country))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.CountryCode", location.countryCode))
{
return false;
}
if (!setXmpTagString("Xmp.photoshop.City", location.city))
{
return false;
}
if (!setXmpTagString("Xmp.iptc.Location", location.location))
{
return false;
}
if (!setXmpTagString("Xmp.photoshop.State", location.provinceState))
{
return false;
}
}
if (!setIptcTag(location.country, 64, "Country", "Iptc.Application2.CountryName"))
{
return false;
}
if (!setIptcTag(location.countryCode, 3, "Country Code", "Iptc.Application2.CountryCode"))
{
return false;
}
if (!setIptcTag(location.city, 32, "City", "Iptc.Application2.City"))
{
return false;
}
if (!setIptcTag(location.location, 32, "SubLocation", "Iptc.Application2.SubLocation"))
{
return false;
}
if (!setIptcTag(location.provinceState, 32, "Province/State", "Iptc.Application2.ProvinceState"))
{
return false;
}
return true;
}
QStringList DMetadata::getIptcCoreSubjects() const
{
QStringList list = getXmpSubjects();
if (!list.isEmpty())
{
return list;
}
return getIptcSubjects();
}
QString DMetadata::getLensDescription() const
{
QString lens;
QStringList lensExifTags;
// In first, try to get Lens information from makernotes.
lensExifTags.append(QLatin1String("Exif.CanonCs.LensType")); // Canon Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.CanonCs.Lens")); // Canon Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Canon.0x0095")); // Alternative Canon Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.NikonLd1.LensIDNumber")); // Nikon Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.NikonLd2.LensIDNumber")); // Nikon Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.NikonLd3.LensIDNumber")); // Nikon Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Minolta.LensID")); // Minolta Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Photo.LensModel")); // Sony Cameras Makernote (and others?).
lensExifTags.append(QLatin1String("Exif.Sony1.LensID")); // Sony Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Sony2.LensID")); // Sony Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.SonyMinolta.LensID")); // Sony Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Pentax.LensType")); // Pentax Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.PentaxDng.LensType")); // Pentax Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Panasonic.0x0051")); // Panasonic Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Panasonic.0x0310")); // Panasonic Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Sigma.LensRange")); // Sigma Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Samsung2.LensType")); // Samsung Cameras Makernote.
lensExifTags.append(QLatin1String("Exif.Photo.0xFDEA")); // Non-standard Exif tag set by Camera Raw.
lensExifTags.append(QLatin1String("Exif.OlympusEq.LensModel")); // Olympus Cameras Makernote.
// Olympus Cameras Makernote. FIXME is this necessary? exiv2 returns complete name, which doesn't match with lensfun information, see bug #311295
//lensExifTags.append("Exif.OlympusEq.LensType");
// TODO : add Fuji camera Makernotes.
// -------------------------------------------------------------------
// Try to get Lens Data information from Exif.
for (QStringList::const_iterator it = lensExifTags.constBegin(); it != lensExifTags.constEnd(); ++it)
{
lens = getExifTagString((*it).toLatin1().constData());
if ( !lens.isEmpty() &&
!(lens.startsWith(QLatin1Char('(')) &&
lens.endsWith(QLatin1Char(')'))
)
) // To prevent undecoded tag values from Exiv2 as "(65535)".
{
return lens;
}
}
// -------------------------------------------------------------------
// Try to get Lens Data information from XMP.
// XMP aux tags.
lens = getXmpTagString("Xmp.aux.Lens");
if (lens.isEmpty())
{
// XMP M$ tags (Lens Maker + Lens Model).
lens = getXmpTagString("Xmp.MicrosoftPhoto.LensManufacturer");
if (!lens.isEmpty())
{
lens.append(QLatin1Char(' '));
}
lens.append(getXmpTagString("Xmp.MicrosoftPhoto.LensModel"));
}
return lens;
}
IccProfile DMetadata::getIccProfile() const
{
// Check if Exif data contains an ICC color profile.
QByteArray data = getExifTagData("Exif.Image.InterColorProfile");
if (!data.isNull())
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Found an ICC profile in Exif metadata";
return IccProfile(data);
}
// Else check the Exif color-space tag and use default profiles that we ship
switch (getImageColorWorkSpace())
{
case DMetadata::WORKSPACE_SRGB:
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is sRGB. Using default sRGB ICC profile.";
return IccProfile::sRGB();
}
case DMetadata::WORKSPACE_ADOBERGB:
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif color-space tag is AdobeRGB. Using default AdobeRGB ICC profile.";
return IccProfile::adobeRGB();
}
default:
break;
}
return IccProfile();
}
bool DMetadata::setIccProfile(const IccProfile& profile)
{
if (profile.isNull())
{
removeExifTag("Exif.Image.InterColorProfile");
}
else
{
QByteArray data = IccProfile(profile).data();
if (!setExifTagData("Exif.Image.InterColorProfile", data))
{
return false;
}
}
removeExifColorSpace();
return true;
}
bool DMetadata::setIptcTag(const QString& text, int maxLength,
const char* const debugLabel, const char* const tagKey) const
{
QString truncatedText = text;
truncatedText.truncate(maxLength);
qCDebug(DIGIKAM_METAENGINE_LOG) << getFilePath() << " ==> " << debugLabel << ": " << truncatedText;
return setIptcTagString(tagKey, truncatedText); // returns false if failed
}
inline QVariant DMetadata::fromExifOrXmp(const char* const exifTagName, const char* const xmpTagName) const
{
QVariant var;
if (exifTagName)
{
var = getExifTagVariant(exifTagName, false);
if (!var.isNull())
{
return var;
}
}
if (xmpTagName)
{
var = getXmpTagVariant(xmpTagName);
if (!var.isNull())
{
return var;
}
}
return var;
}
inline QVariant DMetadata::fromIptcOrXmp(const char* const iptcTagName, const char* const xmpTagName) const
{
if (iptcTagName)
{
QString iptcValue = getIptcTagString(iptcTagName);
if (!iptcValue.isNull())
{
return iptcValue;
}
}
if (xmpTagName)
{
QVariant var = getXmpTagVariant(xmpTagName);
if (!var.isNull())
{
return var;
}
}
return QVariant(QVariant::String);
}
inline QVariant DMetadata::fromIptcEmulateList(const char* const iptcTagName) const
{
return toStringListVariant(getIptcTagsStringList(iptcTagName));
}
inline QVariant DMetadata::fromXmpList(const char* const xmpTagName) const
{
QVariant var = getXmpTagVariant(xmpTagName);
if (var.isNull())
{
return QVariant(QVariant::StringList);
}
return var;
}
inline QVariant DMetadata::fromIptcEmulateLangAlt(const char* const iptcTagName) const
{
QString str = getIptcTagString(iptcTagName);
if (str.isNull())
{
return QVariant(QVariant::Map);
}
QMap<QString, QVariant> map;
map[QLatin1String("x-default")] = str;
return map;
}
inline QVariant DMetadata::fromXmpLangAlt(const char* const xmpTagName) const
{
QVariant var = getXmpTagVariant(xmpTagName);
if (var.isNull())
{
return QVariant(QVariant::Map);
}
return var;
}
inline QVariant DMetadata::toStringListVariant(const QStringList& list) const
{
if (list.isEmpty())
{
return QVariant(QVariant::StringList);
}
return list;
}
QVariant DMetadata::getMetadataField(MetadataInfo::Field field) const
{
switch (field)
{
case MetadataInfo::Comment:
return getImageComments()[QLatin1String("x-default")].caption;
case MetadataInfo::CommentJfif:
return getCommentsDecoded();
case MetadataInfo::CommentExif:
return getExifComment();
case MetadataInfo::CommentIptc:
return fromIptcOrXmp("Iptc.Application2.Caption", 0);
case MetadataInfo::Description:
{
QVariant var = fromXmpLangAlt("Xmp.dc.description");
if (!var.isNull())
{
return var;
}
var = fromXmpLangAlt("Xmp.tiff.ImageDescription");
if (!var.isNull())
{
return var;
}
return fromIptcEmulateLangAlt("Iptc.Application2.Caption");
}
case MetadataInfo::Headline:
return fromIptcOrXmp("Iptc.Application2.Headline", "Xmp.photoshop.Headline");
case MetadataInfo::Title:
{
QString str = getImageTitles()[QLatin1String("x-default")].caption;
if (str.isEmpty())
{
return QVariant(QVariant::Map);
}
QMap<QString, QVariant> map;
map[QLatin1String("x-default")] = str;
return map;
}
case MetadataInfo::DescriptionWriter:
return fromIptcOrXmp("Iptc.Application2.Writer", "Xmp.photoshop.CaptionWriter");
case MetadataInfo::Keywords:
{
QStringList list;
getImageTagsPath(list);
return toStringListVariant(list);
}
case MetadataInfo::Faces:
{
QMultiMap<QString,QVariant> faceMap;
getImageFacesMap(faceMap);
QVariant var(faceMap);
return var;
}
case MetadataInfo::Rating:
return getImageRating();
case MetadataInfo::CreationDate:
return getImageDateTime();
case MetadataInfo::DigitizationDate:
return getDigitizationDateTime(true);
case MetadataInfo::Orientation:
return (int)getImageOrientation();
case MetadataInfo::Make:
{
QVariant var = fromExifOrXmp("Exif.Image.Make", "Xmp.tiff.Make");
return QVariant(var.toString().trimmed());
}
case MetadataInfo::Model:
{
QVariant var = fromExifOrXmp("Exif.Image.Model", "Xmp.tiff.Model");
return QVariant(var.toString().trimmed());
}
case MetadataInfo::Lens:
return getLensDescription();
case MetadataInfo::Aperture:
{
QVariant var = fromExifOrXmp("Exif.Photo.FNumber", "Xmp.exif.FNumber");
if (var.isNull())
{
var = fromExifOrXmp("Exif.Photo.ApertureValue", "Xmp.exif.ApertureValue");
if (!var.isNull())
{
var = apexApertureToFNumber(var.toDouble());
}
}
return var;
}
case MetadataInfo::FocalLength:
return fromExifOrXmp("Exif.Photo.FocalLength", "Xmp.exif.FocalLength");
case MetadataInfo::FocalLengthIn35mm:
return fromExifOrXmp("Exif.Photo.FocalLengthIn35mmFilm", "Xmp.exif.FocalLengthIn35mmFilm");
case MetadataInfo::ExposureTime:
{
QVariant var = fromExifOrXmp("Exif.Photo.ExposureTime", "Xmp.exif.ExposureTime");
if (var.isNull())
{
var = fromExifOrXmp("Exif.Photo.ShutterSpeedValue", "Xmp.exif.ShutterSpeedValue");
if (!var.isNull())
{
var = apexShutterSpeedToExposureTime(var.toDouble());
}
}
return var;
}
case MetadataInfo::ExposureProgram:
return fromExifOrXmp("Exif.Photo.ExposureProgram", "Xmp.exif.ExposureProgram");
case MetadataInfo::ExposureMode:
return fromExifOrXmp("Exif.Photo.ExposureMode", "Xmp.exif.ExposureMode");
case MetadataInfo::Sensitivity:
{
QVariant var = fromExifOrXmp("Exif.Photo.ISOSpeedRatings", "Xmp.exif.ISOSpeedRatings");
//if (var.isNull())
// TODO: has this ISO format??? We must convert to the format of ISOSpeedRatings!
// var = fromExifOrXmp("Exif.Photo.ExposureIndex", "Xmp.exif.ExposureIndex");
return var;
}
case MetadataInfo::FlashMode:
return fromExifOrXmp("Exif.Photo.Flash", "Xmp.exif.Flash");
case MetadataInfo::WhiteBalance:
return fromExifOrXmp("Exif.Photo.WhiteBalance", "Xmp.exif.WhiteBalance");
case MetadataInfo::MeteringMode:
return fromExifOrXmp("Exif.Photo.MeteringMode", "Xmp.exif.MeteringMode");
case MetadataInfo::SubjectDistance:
return fromExifOrXmp("Exif.Photo.SubjectDistance", "Xmp.exif.SubjectDistance");
case MetadataInfo::SubjectDistanceCategory:
return fromExifOrXmp("Exif.Photo.SubjectDistanceRange", "Xmp.exif.SubjectDistanceRange");
case MetadataInfo::WhiteBalanceColorTemperature:
//TODO: ??
return QVariant(QVariant::Int);
case MetadataInfo::Longitude:
return getGPSLongitudeString();
case MetadataInfo::LongitudeNumber:
{
double longitude;
if (getGPSLongitudeNumber(&longitude))
{
return longitude;
}
else
{
return QVariant(QVariant::Double);
}
}
case MetadataInfo::Latitude:
return getGPSLatitudeString();
case MetadataInfo::LatitudeNumber:
{
double latitude;
if (getGPSLatitudeNumber(&latitude))
{
return latitude;
}
else
{
return QVariant(QVariant::Double);
}
}
case MetadataInfo::Altitude:
{
double altitude;
if (getGPSAltitude(&altitude))
{
return altitude;
}
else
{
return QVariant(QVariant::Double);
}
}
case MetadataInfo::PositionOrientation:
case MetadataInfo::PositionTilt:
case MetadataInfo::PositionRoll:
case MetadataInfo::PositionAccuracy:
// TODO or unsupported?
return QVariant(QVariant::Double);
case MetadataInfo::PositionDescription:
// TODO or unsupported?
return QVariant(QVariant::String);
case MetadataInfo::IptcCoreCopyrightNotice:
{
QVariant var = fromXmpLangAlt("Xmp.dc.rights");
if (!var.isNull())
{
return var;
}
var = fromXmpLangAlt("Xmp.tiff.Copyright");
if (!var.isNull())
{
return var;
}
return fromIptcEmulateLangAlt("Iptc.Application2.Copyright");
}
case MetadataInfo::IptcCoreCreator:
{
QVariant var = fromXmpList("Xmp.dc.creator");
if (!var.isNull())
{
return var;
}
QString artist = getXmpTagString("Xmp.tiff.Artist");
if (!artist.isNull())
{
QStringList list;
list << artist;
return list;
}
return fromIptcEmulateList("Iptc.Application2.Byline");
}
case MetadataInfo::IptcCoreProvider:
return fromIptcOrXmp("Iptc.Application2.Credit", "Xmp.photoshop.Credit");
case MetadataInfo::IptcCoreRightsUsageTerms:
return fromXmpLangAlt("Xmp.xmpRights.UsageTerms");
case MetadataInfo::IptcCoreSource:
return fromIptcOrXmp("Iptc.Application2.Source", "Xmp.photoshop.Source");
case MetadataInfo::IptcCoreCreatorJobTitle:
return fromIptcOrXmp("Iptc.Application2.BylineTitle", "Xmp.photoshop.AuthorsPosition");
case MetadataInfo::IptcCoreInstructions:
return fromIptcOrXmp("Iptc.Application2.SpecialInstructions", "Xmp.photoshop.Instructions");
case MetadataInfo::IptcCoreLocationInfo:
{
IptcCoreLocationInfo location = getIptcCoreLocation();
if (location.isNull())
{
return QVariant();
}
return QVariant::fromValue(location);
}
case MetadataInfo::IptcCoreCountryCode:
return fromIptcOrXmp("Iptc.Application2.CountryCode", "Xmp.iptc.CountryCode");
case MetadataInfo::IptcCoreCountry:
return fromIptcOrXmp("Iptc.Application2.CountryName", "Xmp.photoshop.Country");
case MetadataInfo::IptcCoreCity:
return fromIptcOrXmp("Iptc.Application2.City", "Xmp.photoshop.City");
case MetadataInfo::IptcCoreLocation:
return fromIptcOrXmp("Iptc.Application2.SubLocation", "Xmp.iptc.Location");
case MetadataInfo::IptcCoreProvinceState:
return fromIptcOrXmp("Iptc.Application2.ProvinceState", "Xmp.photoshop.State");
case MetadataInfo::IptcCoreIntellectualGenre:
return fromIptcOrXmp("Iptc.Application2.ObjectAttribute", "Xmp.iptc.IntellectualGenre");
case MetadataInfo::IptcCoreJobID:
return fromIptcOrXmp("Iptc.Application2.TransmissionReference", "Xmp.photoshop.TransmissionReference");
case MetadataInfo::IptcCoreScene:
return fromXmpList("Xmp.iptc.Scene");
case MetadataInfo::IptcCoreSubjectCode:
return toStringListVariant(getIptcCoreSubjects());
case MetadataInfo::IptcCoreContactInfo:
{
IptcCoreContactInfo info = getCreatorContactInfo();
if (info.isNull())
{
return QVariant();
}
return QVariant::fromValue(info);
}
case MetadataInfo::IptcCoreContactInfoCity:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCity");
case MetadataInfo::IptcCoreContactInfoCountry:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrCtry");
case MetadataInfo::IptcCoreContactInfoAddress:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrExtadr");
case MetadataInfo::IptcCoreContactInfoPostalCode:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrPcode");
case MetadataInfo::IptcCoreContactInfoProvinceState:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiAdrRegion");
case MetadataInfo::IptcCoreContactInfoEmail:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiEmailWork");
case MetadataInfo::IptcCoreContactInfoPhone:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiTelWork");
case MetadataInfo::IptcCoreContactInfoWebUrl:
return getXmpTagVariant("Xmp.iptc.CreatorContactInfo/Iptc4xmpCore:CiUrlWork");
case MetadataInfo::AspectRatio:
{
long num = 0;
long den = 1;
// NOTE: there is a bug in Exiv2 xmp::video tag definition as "Rational" value is defined as "Ratio"...
//QList<QVariant> list = getXmpTagVariant("Xmp.video.AspectRatio").toList();
QString ar = getXmpTagString("Xmp.video.AspectRatio");
QStringList list = ar.split(QLatin1Char('/'));
if (list.size() >= 1)
num = list[0].toInt();
if (list.size() >= 2)
den = list[1].toInt();
return QString::number((double)num / (double)den);
}
case MetadataInfo::AudioBitRate:
return fromXmpLangAlt("Xmp.audio.SampleRate");
case MetadataInfo::AudioChannelType:
return fromXmpLangAlt("Xmp.audio.ChannelType");
case MetadataInfo::AudioCodec:
return fromXmpLangAlt("Xmp.audio.Codec");
case MetadataInfo::Duration:
return fromXmpLangAlt("Xmp.video.duration"); // duration is in ms
case MetadataInfo::FrameRate:
return fromXmpLangAlt("Xmp.video.FrameRate");
case MetadataInfo::VideoCodec:
return fromXmpLangAlt("Xmp.video.Codec");
case MetadataInfo::VideoBitDepth:
return fromXmpLangAlt("Xmp.video.BitDepth");
case MetadataInfo::VideoHeight:
return fromXmpLangAlt("Xmp.video.Height");
case MetadataInfo::VideoWidth:
return fromXmpLangAlt("Xmp.video.Width");
case MetadataInfo::VideoColorSpace:
{
QString cs = getXmpTagString("Xmp.video.ColorSpace");
if (cs == QLatin1String("sRGB"))
return QString::number(VIDEOCOLORMODEL_SRGB);
else if (cs == QLatin1String("CCIR-601"))
return QString::number(VIDEOCOLORMODEL_BT601);
else if (cs == QLatin1String("CCIR-709"))
return QString::number(VIDEOCOLORMODEL_BT709);
else if (cs == QLatin1String("Other"))
return QString::number(VIDEOCOLORMODEL_OTHER);
else
return QVariant(QVariant::Int);
}
default:
return QVariant();
}
}
QVariantList DMetadata::getMetadataFields(const MetadataFields& fields) const
{
QVariantList list;
foreach(MetadataInfo::Field field, fields) // krazy:exclude=foreach
{
list << getMetadataField(field);
}
return list;
}
QString DMetadata::valueToString(const QVariant& value, MetadataInfo::Field field)
{
MetaEngine exiv2Iface;
switch (field)
{
case MetadataInfo::Rating:
return value.toString();
case MetadataInfo::CreationDate:
case MetadataInfo::DigitizationDate:
return value.toDateTime().toString(Qt::LocaleDate);
case MetadataInfo::Orientation:
{
switch (value.toInt())
{
// Example why the English text differs from the enum names: ORIENTATION_ROT_90.
// Rotation by 90 degrees is right (clockwise) rotation.
// But: The enum names describe what needs to be done to get the image right again.
// And an image that needs to be rotated 90 degrees is currently rotated 270 degrees = left.
case ORIENTATION_UNSPECIFIED:
return i18n("Unspecified");
case ORIENTATION_NORMAL:
return i18nc("Rotation of an unrotated image", "Normal");
case ORIENTATION_HFLIP:
return i18n("Flipped Horizontally");
case ORIENTATION_ROT_180:
return i18n("Rotated by 180 Degrees");
case ORIENTATION_VFLIP:
return i18n("Flipped Vertically");
case ORIENTATION_ROT_90_HFLIP:
return i18n("Flipped Horizontally and Rotated Left");
case ORIENTATION_ROT_90:
return i18n("Rotated Left");
case ORIENTATION_ROT_90_VFLIP:
return i18n("Flipped Vertically and Rotated Left");
case ORIENTATION_ROT_270:
return i18n("Rotated Right");
default:
return i18n("Unknown");
}
break;
}
case MetadataInfo::Make:
return exiv2Iface.createExifUserStringFromValue("Exif.Image.Make", value);
case MetadataInfo::Model:
return exiv2Iface.createExifUserStringFromValue("Exif.Image.Model", value);
case MetadataInfo::Lens:
// heterogeneous source, non-standardized string
return value.toString();
case MetadataInfo::Aperture:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FNumber", value);
case MetadataInfo::FocalLength:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLength", value);
case MetadataInfo::FocalLengthIn35mm:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.FocalLengthIn35mmFilm", value);
case MetadataInfo::ExposureTime:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureTime", value);
case MetadataInfo::ExposureProgram:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureProgram", value);
case MetadataInfo::ExposureMode:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ExposureMode", value);
case MetadataInfo::Sensitivity:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.ISOSpeedRatings", value);
case MetadataInfo::FlashMode:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.Flash", value);
case MetadataInfo::WhiteBalance:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.WhiteBalance", value);
case MetadataInfo::MeteringMode:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.MeteringMode", value);
case MetadataInfo::SubjectDistance:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistance", value);
case MetadataInfo::SubjectDistanceCategory:
return exiv2Iface.createExifUserStringFromValue("Exif.Photo.SubjectDistanceRange", value);
case MetadataInfo::WhiteBalanceColorTemperature:
return i18nc("Temperature in Kelvin", "%1 K", value.toInt());
case MetadataInfo::AspectRatio:
case MetadataInfo::AudioBitRate:
case MetadataInfo::AudioChannelType:
case MetadataInfo::AudioCodec:
case MetadataInfo::Duration:
case MetadataInfo::FrameRate:
case MetadataInfo::VideoCodec:
return value.toString();
case MetadataInfo::Longitude:
{
int degrees, minutes;
double seconds;
char directionRef;
if (!convertToUserPresentableNumbers(value.toString(), &degrees, &minutes, &seconds, &directionRef))
{
return QString();
}
QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ?
i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East");
return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0))
.arg(minutes).arg(QChar(0x2032))
.arg(seconds, 'f').arg(QChar(0x2033)).arg(direction);
}
case MetadataInfo::LongitudeNumber:
{
int degrees, minutes;
double seconds;
char directionRef;
convertToUserPresentableNumbers(false, value.toDouble(), &degrees, &minutes, &seconds, &directionRef);
QString direction = (QLatin1Char(directionRef) == QLatin1Char('W')) ?
i18nc("For use in longitude coordinate", "West") : i18nc("For use in longitude coordinate", "East");
return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0))
.arg(minutes).arg(QChar(0x2032))
.arg(seconds, 'f').arg(QChar(0x2033)).arg(direction);
}
case MetadataInfo::Latitude:
{
int degrees, minutes;
double seconds;
char directionRef;
if (!convertToUserPresentableNumbers(value.toString(), &degrees, &minutes, &seconds, &directionRef))
{
return QString();
}
QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ?
i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South");
return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0))
.arg(minutes).arg(QChar(0x2032))
.arg(seconds, 'f').arg(QChar(0x2033)).arg(direction);
}
case MetadataInfo::LatitudeNumber:
{
int degrees, minutes;
double seconds;
char directionRef;
convertToUserPresentableNumbers(false, value.toDouble(), &degrees, &minutes, &seconds, &directionRef);
QString direction = (QLatin1Char(directionRef) == QLatin1Char('N')) ?
i18nc("For use in latitude coordinate", "North") : i18nc("For use in latitude coordinate", "South");
return QString::fromLatin1("%1%2%3%4%L5%6 %7").arg(degrees).arg(QChar(0xB0))
.arg(minutes).arg(QChar(0x2032))
.arg(seconds, 'f').arg(QChar(0x2033)).arg(direction);
}
case MetadataInfo::Altitude:
{
QString meters = QString::fromLatin1("%L1").arg(value.toDouble(), 0, 'f', 2);
// xgettext: no-c-format
return i18nc("Height in meters", "%1m", meters);
}
case MetadataInfo::PositionOrientation:
case MetadataInfo::PositionTilt:
case MetadataInfo::PositionRoll:
case MetadataInfo::PositionAccuracy:
//TODO
return value.toString();
case MetadataInfo::PositionDescription:
return value.toString();
// Lang Alt
case MetadataInfo::IptcCoreCopyrightNotice:
case MetadataInfo::IptcCoreRightsUsageTerms:
case MetadataInfo::Description:
case MetadataInfo::Title:
{
QMap<QString, QVariant> map = value.toMap();
// the most common cases
if (map.isEmpty())
{
return QString();
}
else if (map.size() == 1)
{
return map.begin().value().toString();
}
// Try "en-us"
QString spec = QLocale().name().toLower().replace(QLatin1Char('_'), QLatin1Char('-'));
if (map.contains(spec))
{
return map[spec].toString();
}
// Try "en-"
QStringList keys = map.keys();
QString spec2 = QLocale().name().toLower();
QRegExp exp(spec2.left(spec2.indexOf(QLatin1Char('_'))) + QLatin1Char('-'));
QStringList matches = keys.filter(exp);
if (!matches.isEmpty())
{
return map[matches.first()].toString();
}
// return default
if (map.contains(QLatin1String("x-default")))
{
return map[QLatin1String("x-default")].toString();
}
// return first entry
return map.begin().value().toString();
}
// List
case MetadataInfo::IptcCoreCreator:
case MetadataInfo::IptcCoreScene:
case MetadataInfo::IptcCoreSubjectCode:
return value.toStringList().join(QLatin1Char(' '));
// Text
case MetadataInfo::Comment:
case MetadataInfo::CommentJfif:
case MetadataInfo::CommentExif:
case MetadataInfo::CommentIptc:
case MetadataInfo::Headline:
case MetadataInfo::DescriptionWriter:
case MetadataInfo::IptcCoreProvider:
case MetadataInfo::IptcCoreSource:
case MetadataInfo::IptcCoreCreatorJobTitle:
case MetadataInfo::IptcCoreInstructions:
case MetadataInfo::IptcCoreCountryCode:
case MetadataInfo::IptcCoreCountry:
case MetadataInfo::IptcCoreCity:
case MetadataInfo::IptcCoreLocation:
case MetadataInfo::IptcCoreProvinceState:
case MetadataInfo::IptcCoreIntellectualGenre:
case MetadataInfo::IptcCoreJobID:
return value.toString();
default:
break;
}
return QString();
}
QStringList DMetadata::valuesToString(const QVariantList& values, const MetadataFields& fields)
{
int size = values.size();
Q_ASSERT(size == values.size());
QStringList list;
for (int i = 0; i < size; ++i)
{
list << valueToString(values.at(i), fields.at(i));
}
return list;
}
QMap<int, QString> DMetadata::possibleValuesForEnumField(MetadataInfo::Field field)
{
QMap<int, QString> map;
int min, max;
switch (field)
{
case MetadataInfo::Orientation: /// Int, enum from libMetaEngine
min = ORIENTATION_UNSPECIFIED;
max = ORIENTATION_ROT_270;
break;
case MetadataInfo::ExposureProgram: /// Int, enum from Exif
min = 0;
max = 8;
break;
case MetadataInfo::ExposureMode: /// Int, enum from Exif
min = 0;
max = 2;
break;
case MetadataInfo::WhiteBalance: /// Int, enum from Exif
min = 0;
max = 1;
break;
case MetadataInfo::MeteringMode: /// Int, enum from Exif
min = 0;
max = 6;
map[255] = valueToString(255, field);
break;
case MetadataInfo::SubjectDistanceCategory: /// int, enum from Exif
min = 0;
max = 3;
break;
case MetadataInfo::FlashMode: /// Int, bit mask from Exif
// This one is a bit special.
// We return a bit mask for binary AND searching.
map[0x1] = i18n("Flash has been fired");
map[0x40] = i18n("Flash with red-eye reduction mode");
//more: TODO?
return map;
default:
qCWarning(DIGIKAM_METAENGINE_LOG) << "Unsupported field " << field << " in DMetadata::possibleValuesForEnumField";
return map;
}
for (int i = min; i <= max; ++i)
{
map[i] = valueToString(i, field);
}
return map;
}
double DMetadata::apexApertureToFNumber(double aperture)
{
// convert from APEX. See Exif spec, Annex C.
if (aperture == 0.0)
{
return 1;
}
else if (aperture == 1.0)
{
return 1.4;
}
else if (aperture == 2.0)
{
return 2;
}
else if (aperture == 3.0)
{
return 2.8;
}
else if (aperture == 4.0)
{
return 4;
}
else if (aperture == 5.0)
{
return 5.6;
}
else if (aperture == 6.0)
{
return 8;
}
else if (aperture == 7.0)
{
return 11;
}
else if (aperture == 8.0)
{
return 16;
}
else if (aperture == 9.0)
{
return 22;
}
else if (aperture == 10.0)
{
return 32;
}
return exp(log(2) * aperture / 2.0);
}
double DMetadata::apexShutterSpeedToExposureTime(double shutterSpeed)
{
// convert from APEX. See Exif spec, Annex C.
if (shutterSpeed == -5.0)
{
return 30;
}
else if (shutterSpeed == -4.0)
{
return 15;
}
else if (shutterSpeed == -3.0)
{
return 8;
}
else if (shutterSpeed == -2.0)
{
return 4;
}
else if (shutterSpeed == -1.0)
{
return 2;
}
else if (shutterSpeed == 0.0)
{
return 1;
}
else if (shutterSpeed == 1.0)
{
return 0.5;
}
else if (shutterSpeed == 2.0)
{
return 0.25;
}
else if (shutterSpeed == 3.0)
{
return 0.125;
}
else if (shutterSpeed == 4.0)
{
return 1.0 / 15.0;
}
else if (shutterSpeed == 5.0)
{
return 1.0 / 30.0;
}
else if (shutterSpeed == 6.0)
{
return 1.0 / 60.0;
}
else if (shutterSpeed == 7.0)
{
return 0.008; // 1/125
}
else if (shutterSpeed == 8.0)
{
return 0.004; // 1/250
}
else if (shutterSpeed == 9.0)
{
return 0.002; // 1/500
}
else if (shutterSpeed == 10.0)
{
return 0.001; // 1/1000
}
else if (shutterSpeed == 11.0)
{
return 0.0005; // 1/2000
}
// additions by me
else if (shutterSpeed == 12.0)
{
return 0.00025; // 1/4000
}
else if (shutterSpeed == 13.0)
{
return 0.000125; // 1/8000
}
return exp( - log(2) * shutterSpeed);
}
MetaEngine::AltLangMap DMetadata::toAltLangMap(const QVariant& var)
{
MetaEngine::AltLangMap map;
if (var.isNull())
{
return map;
}
switch (var.type())
{
case QVariant::String:
map.insert(QLatin1String("x-default"), var.toString());
break;
case QVariant::Map:
{
QMap<QString, QVariant> varMap = var.toMap();
for (QMap<QString, QVariant>::const_iterator it = varMap.constBegin(); it != varMap.constEnd(); ++it)
{
map.insert(it.key(), it.value().toString());
}
break;
}
default:
break;
}
return map;
}
bool DMetadata::addToXmpTagStringBag(const char* const xmpTagName, const QStringList& entriesToAdd) const
{
//#ifdef _XMP_SUPPORT_
QStringList oldEntries = getXmpTagStringBag(xmpTagName, false);
QStringList newEntries = entriesToAdd;
// Create a list of keywords including old one which already exists.
for (QStringList::const_iterator it = oldEntries.constBegin(); it != oldEntries.constEnd(); ++it )
{
if (!newEntries.contains(*it))
{
newEntries.append(*it);
}
}
if (setXmpTagStringBag(xmpTagName, newEntries))
{
return true;
}
//#endif // _XMP_SUPPORT_
return false;
}
bool DMetadata::removeFromXmpTagStringBag(const char* const xmpTagName, const QStringList& entriesToRemove) const
{
//#ifdef _XMP_SUPPORT_
QStringList currentEntries = getXmpTagStringBag(xmpTagName, false);
QStringList newEntries;
// Create a list of current keywords except those that shall be removed
for (QStringList::const_iterator it = currentEntries.constBegin(); it != currentEntries.constEnd(); ++it )
{
if (!entriesToRemove.contains(*it))
{
newEntries.append(*it);
}
}
if (setXmpTagStringBag(xmpTagName, newEntries))
{
return true;
}
//#endif // _XMP_SUPPORT_
return false;
}
QStringList DMetadata::getXmpKeywords() const
{
return (getXmpTagStringBag("Xmp.dc.subject", false));
}
bool DMetadata::setXmpKeywords(const QStringList& newKeywords) const
{
return setXmpTagStringBag("Xmp.dc.subject", newKeywords);
}
bool DMetadata::removeXmpKeywords(const QStringList& keywordsToRemove)
{
return removeFromXmpTagStringBag("Xmp.dc.subject", keywordsToRemove);
}
QStringList DMetadata::getXmpSubCategories() const
{
return (getXmpTagStringBag("Xmp.photoshop.SupplementalCategories", false));
}
bool DMetadata::setXmpSubCategories(const QStringList& newSubCategories) const
{
return addToXmpTagStringBag("Xmp.photoshop.SupplementalCategories", newSubCategories);
}
bool DMetadata::removeXmpSubCategories(const QStringList& subCategoriesToRemove)
{
return removeFromXmpTagStringBag("Xmp.photoshop.SupplementalCategories", subCategoriesToRemove);
}
QStringList DMetadata::getXmpSubjects() const
{
return (getXmpTagStringBag("Xmp.iptc.SubjectCode", false));
}
bool DMetadata::setXmpSubjects(const QStringList& newSubjects) const
{
return addToXmpTagStringBag("Xmp.iptc.SubjectCode", newSubjects);
}
bool DMetadata::removeXmpSubjects(const QStringList& subjectsToRemove)
{
return removeFromXmpTagStringBag("Xmp.iptc.SubjectCode", subjectsToRemove);
}
bool DMetadata::removeExifColorSpace() const
{
bool ret = true;
ret &= removeExifTag("Exif.Photo.ColorSpace");
ret &= removeXmpTag("Xmp.exif.ColorSpace");
return ret;
}
QString DMetadata::getExifTagStringFromTagsList(const QStringList& tagsList) const
{
QString val;
foreach(const QString& tag, tagsList)
{
val = getExifTagString(tag.toLatin1().constData());
if (!val.isEmpty())
return val;
}
return QString();
}
bool DMetadata::removeExifTags(const QStringList& tagFilters)
{
MetaDataMap m = getExifTagsDataList(tagFilters);
if (m.isEmpty())
return false;
for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it)
{
removeExifTag(it.key().toLatin1().constData());
}
return true;
}
bool DMetadata::removeIptcTags(const QStringList& tagFilters)
{
MetaDataMap m = getIptcTagsDataList(tagFilters);
if (m.isEmpty())
return false;
for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it)
{
removeIptcTag(it.key().toLatin1().constData());
}
return true;
}
bool DMetadata::removeXmpTags(const QStringList& tagFilters)
{
MetaDataMap m = getXmpTagsDataList(tagFilters);
if (m.isEmpty())
return false;
for (MetaDataMap::iterator it = m.begin() ; it != m.end() ; ++it)
{
removeXmpTag(it.key().toLatin1().constData());
}
return true;
}
} // namespace Digikam
diff --git a/core/libs/dmetadata/dmetadata_ffmpeg.cpp b/core/libs/dmetadata/dmetadata_ffmpeg.cpp
index a1d6cd158a..d7dc72fbbf 100644
--- a/core/libs/dmetadata/dmetadata_ffmpeg.cpp
+++ b/core/libs/dmetadata/dmetadata_ffmpeg.cpp
@@ -1,1613 +1,1613 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2018-02-26
* Description : metadata extraction with FFMpeg (libav)
*
* References :
*
* FFMpeg metadata review: https://wiki.multimedia.cx/index.php/FFmpeg_Metadata
* FFMpeg MP4 parser : https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/mov.c#L298
* Exiv2 XMP video : https://github.com/Exiv2/exiv2/blob/master/src/properties.cpp#L1331
* Exiv2 RIFF tags : https://github.com/Exiv2/exiv2/blob/master/src/riffvideo.cpp#L83
* Apple metadata desc : https://developer.apple.com/library/content/documentation/QuickTime/QTFF/Metadata/Metadata.html
* Matroska metadata desc: https://matroska.org/technical/specs/tagging/index.html
* FFMpeg metadata writer: https://github.com/kritzikratzi/ofxAvCodec/blob/master/src/ofxAvUtils.cpp#L61
*
* FFMpeg tags names origin:
*
* Generic : common tags generated by FFMpeg codecs.
* RIFF files : Resource Interchange File Format tags (as AVI).
* MKV files : Matroska container tags.
* QT files : Quicktime container tags (Apple).
*
* Copyright (C) 2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dmetadata.h"
// C Ansi includes
#include <stdint.h>
// Qt includes
#include <QDateTime>
#include <QFileInfo>
#include <QMimeDatabase>
#include <QStringList>
// KDE includes
#include <klocalizedstring.h>
-// Local incudes
+// Local includes
#include "captionvalues.h"
#include "digikam_debug.h"
#include "digikam_config.h"
#ifdef HAVE_MEDIAPLAYER
// Libav includes
extern "C"
{
#include <libavformat/avformat.h>
#include <libavutil/dict.h>
#include <libavutil/pixdesc.h>
#include <libavcodec/avcodec.h>
}
#endif
namespace Digikam
{
/** Search first occurrence of string in 'map' with keys given by 'lst'.
* Return the string match.
* If 'xmpTags' is not empty, register XMP tags value with string.
*/
QString s_setXmpTagStringFromEntry(DMetadata* const meta,
const QStringList& lst,
const DMetadata::MetaDataMap& map,
const QStringList& xmpTags=QStringList())
{
foreach (const QString& tag, lst)
{
DMetadata::MetaDataMap::const_iterator it = map.find(tag);
if (it != map.end())
{
if (meta && // Protection.
!xmpTags.isEmpty()) // If xmpTags is empty, we only return the matching value from the map.
{
foreach (const QString& tag, xmpTags)
{
// Only register the tag value if it doesn't exists yet.
if (meta->getXmpTagString(tag.toLatin1().data()).isNull())
{
meta->setXmpTagString(tag.toLatin1().data(), it.value());
}
}
}
return it.value();
}
}
return QString();
}
QStringList s_keywordsSeparation(const QString& data)
{
QStringList keywords = data.split(QLatin1Char('/'));
if (keywords.isEmpty())
{
keywords = data.split(QLatin1Char(','));
if (keywords.isEmpty())
{
keywords = data.split(QLatin1Char(' '));
}
}
return keywords;
}
qint64 s_secondsSinceJanuary1904(const QDateTime dt)
{
QDateTime dt1904(QDate(1904, 1, 1), QTime(0, 0, 0));
return dt1904.secsTo(dt);
}
#ifdef HAVE_MEDIAPLAYER
QString s_convertFFMpegFormatToXMP(int format)
{
QString data;
switch (format)
{
case AV_SAMPLE_FMT_U8:
case AV_SAMPLE_FMT_U8P:
data = QLatin1String("8Int");
break;
case AV_SAMPLE_FMT_S16:
case AV_SAMPLE_FMT_S16P:
data = QLatin1String("16Int");
break;
case AV_SAMPLE_FMT_S32:
case AV_SAMPLE_FMT_S32P:
data = QLatin1String("32Int");
break;
case AV_SAMPLE_FMT_FLT:
case AV_SAMPLE_FMT_FLTP:
data = QLatin1String("32Float");
break;
case AV_SAMPLE_FMT_DBL: // Not supported by XMP spec.
case AV_SAMPLE_FMT_DBLP: // Not supported by XMP spec.
case AV_SAMPLE_FMT_S64: // Not supported by XMP spec.
case AV_SAMPLE_FMT_S64P: // Not supported by XMP spec.
case AV_SAMPLE_FMT_NONE:
case AV_SAMPLE_FMT_NB:
default:
data = QLatin1String("Other");
break;
// NOTE: where are 'Compressed' and 'Packed' type from XMP spec into FFMPEG ?
}
return data;
}
DMetadata::MetaDataMap s_extractFFMpegMetadataEntriesFromDictionary(AVDictionary* const dict)
{
AVDictionaryEntry* entry = 0;
DMetadata::MetaDataMap meta;
do
{
entry = av_dict_get(dict, "", entry, AV_DICT_IGNORE_SUFFIX);
if (entry)
{
if (QString::fromUtf8(entry->key) != QLatin1String("creation_time") ||
QDateTime::fromString(QString::fromUtf8(entry->value), Qt::ISODate).toMSecsSinceEpoch() != 0)
{
meta.insert(QString::fromUtf8(entry->key), QString::fromUtf8(entry->value));
}
}
}
while (entry);
return meta;
}
#endif
bool DMetadata::loadUsingFFmpeg(const QString& filePath)
{
#ifdef HAVE_MEDIAPLAYER
qCDebug(DIGIKAM_METAENGINE_LOG) << "Parse metadada with FFMpeg:" << filePath;
#if LIBAVFORMAT_VERSION_MAJOR < 58
av_register_all();
#endif
AVFormatContext* fmt_ctx = avformat_alloc_context();
int ret = avformat_open_input(&fmt_ctx, filePath.toUtf8().data(), NULL, NULL);
if (ret < 0)
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "avformat_open_input error: " << ret;
return false;
}
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0)
{
qCDebug(DIGIKAM_METAENGINE_LOG) << "avform_find_stream_info error: " << ret;
return false;
}
QString data;
setXmpTagString("Xmp.video.duration",
QString::number((int)(1000.0 * (double)fmt_ctx->duration / (double)AV_TIME_BASE)));
setXmpTagString("Xmp.xmpDM.duration",
QString::number((int)(1000.0 * (double)fmt_ctx->duration / (double)AV_TIME_BASE)));
if (fmt_ctx->bit_rate > 0)
setXmpTagString("Xmp.video.MaxBitRate", QString::number(fmt_ctx->bit_rate));
setXmpTagString("Xmp.video.StreamCount",
QString::number(fmt_ctx->nb_streams));
// To only register one video, one audio stream, and one subtitle stream in XMP metadata.
bool vstream = false;
bool astream = false;
bool sstream = false;
for (uint i = 0 ; i < fmt_ctx->nb_streams ; i++)
{
const AVStream* const stream = fmt_ctx->streams[i];
AVCodecParameters* const codec = stream->codecpar;
// -----------------------------------------
// Audio stream parsing
// -----------------------------------------
if (!astream && codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
astream = true;
const char* cname = avcodec_get_name(codec->codec_id);
setXmpTagString("Xmp.audio.Codec",
QString::fromUtf8(cname));
setXmpTagString("Xmp.audio.CodecDescription",
QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name));
setXmpTagString("Xmp.audio.SampleRate",
QString::number(codec->sample_rate));
setXmpTagString("Xmp.xmpDM.audioSampleRate",
QString::number(codec->sample_rate));
// See XMP Dynamic Media properties from Adobe.
// Audio Channel type is a limited untranslated string choice depending of amount of audio channels
data = QString();
switch (codec->channels)
{
case 0:
break;
case 1:
data = QLatin1String("Mono");
break;
case 2:
data = QLatin1String("Stereo");
break;
case 6:
data = QLatin1String("5.1");
break;
case 8:
data = QLatin1String("7.1");
break;
case 16:
data = QLatin1String("16 Channel");
break;
default:
data = QLatin1String("Other");
break;
}
if (!data.isEmpty())
{
setXmpTagString("Xmp.audio.ChannelType", data);
setXmpTagString("Xmp.xmpDM.audioChannelType", data);
}
setXmpTagString("Xmp.audio.Format",
QString::fromUtf8(av_get_sample_fmt_name((AVSampleFormat)codec->format)));
// See XMP Dynamic Media properties from Adobe.
// Audio Sample type is a limited untranslated string choice depending of amount of audio samples
data = s_convertFFMpegFormatToXMP(codec->format);
if (!data.isEmpty())
{
setXmpTagString("Xmp.audio.SampleType", data);
setXmpTagString("Xmp.xmpDM.audioSampleType", data);
}
// --------------
MetaDataMap ameta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata);
qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg audio stream metadata entries :";
qCDebug(DIGIKAM_METAENGINE_LOG) << ameta;
qCDebug(DIGIKAM_METAENGINE_LOG) << "-----------------------------------------";
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("language"), // Generic.
ameta,
QStringList() << QLatin1String("Xmp.audio.TrackLang"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("creation_time"), // Generic.
ameta);
if (!data.isEmpty())
{
QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime();
setXmpTagString("Xmp.audio.TrackCreateDate",
QString::number(s_secondsSinceJanuary1904(dt)));
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("handler_name"), // Generic.
ameta,
QStringList() << QLatin1String("Xmp.audio.HandlerDescription"));
}
// -----------------------------------------
// Video stream parsing
// -----------------------------------------
if (!vstream && codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
vstream = true;
const char* cname = avcodec_get_name(codec->codec_id);
setXmpTagString("Xmp.video.Codec",
QString::fromUtf8(cname));
setXmpTagString("Xmp.video.CodecDescription",
QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name));
setXmpTagString("Xmp.video.Format",
QString::fromUtf8(av_get_pix_fmt_name((AVPixelFormat)codec->format)));
// Store in this tag the full description off FFMPEG video color space.
setXmpTagString("Xmp.video.ColorMode",
QString::fromUtf8(av_color_space_name((AVColorSpace)codec->color_space)));
VIDEOCOLORMODEL cm = VIDEOCOLORMODEL_OTHER;
switch (codec->color_space)
{
case AVCOL_SPC_RGB:
cm = VIDEOCOLORMODEL_SRGB;
break;
case AVCOL_SPC_BT470BG:
case AVCOL_SPC_SMPTE170M:
case AVCOL_SPC_SMPTE240M:
cm = VIDEOCOLORMODEL_BT601;
break;
case AVCOL_SPC_BT709:
cm = VIDEOCOLORMODEL_BT709;
break;
case AVCOL_SPC_UNSPECIFIED:
case AVCOL_SPC_RESERVED:
case AVCOL_SPC_NB:
cm = VIDEOCOLORMODEL_UNKNOWN;
break;
default:
break;
}
// See XMP Dynamic Media properties from Adobe.
// Video Color Space is a limited untranslated string choice depending of video color space value.
data = videoColorModelToString(cm);
if (!data.isEmpty())
{
setXmpTagString("Xmp.video.ColorSpace", data);
setXmpTagString("Xmp.xmpDM.videoColorSpace", data);
}
// ----------
QString fo;
switch (codec->field_order)
{
case AV_FIELD_PROGRESSIVE:
fo = QLatin1String("Progressive");
break;
case AV_FIELD_TT: // Top coded first, top displayed first
case AV_FIELD_BT: // Bottom coded first, top displayed first
fo = QLatin1String("Upper");
break;
case AV_FIELD_BB: // Bottom coded first, bottom displayed first
case AV_FIELD_TB: // Top coded first, bottom displayed first
fo = QLatin1String("Lower");
break;
default:
break;
}
if (!fo.isEmpty())
{
setXmpTagString("Xmp.xmpDM.FieldOrder", fo);
}
// ----------
QString aspectRatio;
int frameRate = -1.0;
if (codec->sample_aspect_ratio.num != 0) // Check if undefined by ffmpeg
{
AVRational displayAspectRatio;
av_reduce(&displayAspectRatio.num, &displayAspectRatio.den,
codec->width * (int64_t)codec->sample_aspect_ratio.num,
codec->height * (int64_t)codec->sample_aspect_ratio.den,
1024 * 1024);
aspectRatio = QString::fromLatin1("%1/%2").arg(displayAspectRatio.num)
.arg(displayAspectRatio.den);
}
else if (codec->height)
{
aspectRatio = QString::fromLatin1("%1/%2").arg(codec->width)
.arg(codec->height);
}
if (stream->avg_frame_rate.den)
{
frameRate = (double)stream->avg_frame_rate.num / (double)stream->avg_frame_rate.den;
}
setXmpTagString("Xmp.video.Width",
QString::number(codec->width));
setXmpTagString("Xmp.video.FrameWidth",
QString::number(codec->width));
setXmpTagString("Xmp.video.SourceImageWidth",
QString::number(codec->width));
setXmpTagString("Xmp.video.Height",
QString::number(codec->height));
setXmpTagString("Xmp.video.FrameHeight",
QString::number(codec->height));
setXmpTagString("Xmp.video.SourceImageHeight",
QString::number(codec->height));
setXmpTagString("Xmp.video.FrameSize",
QString::fromLatin1("w:%1, h:%2, unit:pixels").arg(codec->width).arg(codec->height));
setXmpTagString("Xmp.xmpDM.videoFrameSize",
QString::fromLatin1("w:%1, h:%2, unit:pixels").arg(codec->width).arg(codec->height));
// Backport size in Exif and Iptc
setImageDimensions(QSize(codec->width, codec->height));
if (!aspectRatio.isEmpty())
{
setXmpTagString("Xmp.video.AspectRatio", aspectRatio);
setXmpTagString("Xmp.xmpDM.videoPixelAspectRatio", aspectRatio);
}
if (frameRate != -1.0)
{
setXmpTagString("Xmp.video.FrameRate", QString::number(frameRate));
// See XMP Dynamic Media properties from Adobe.
// Video Color Space is a limited untranslated string choice depending of video frame rate.
// https://documentation.apple.com/en/finalcutpro/usermanual/index.html#chapter=D%26section=4%26tasks=true
data = QLatin1String("Other");
if (frameRate == 24.0)
data = QLatin1String("24");
else if (frameRate == 23.98 || frameRate == 29.97 ||
frameRate == 30.0 || frameRate == 59.94)
data = QLatin1String("NTSC");
else if (frameRate == 25 || frameRate == 50)
data = QLatin1String("PAL");
setXmpTagString("Xmp.xmpDM.videoFrameRate", data);
}
setXmpTagString("Xmp.video.BitDepth", QString::number(codec->bits_per_coded_sample));
// See XMP Dynamic Media properties from Adobe.
// Video Pixel Depth is a limited untranslated string choice depending of amount of samples format.
data = s_convertFFMpegFormatToXMP(codec->format);
if (!data.isEmpty())
setXmpTagString("Xmp.xmpDM.videoPixelDepth", data);
// -----------------------------------------
MetaDataMap vmeta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata);
qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg video stream metadata entries :";
qCDebug(DIGIKAM_METAENGINE_LOG) << vmeta;
qCDebug(DIGIKAM_METAENGINE_LOG) << "-----------------------------------------";
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("rotate"), // Generic.
vmeta);
if (!data.isEmpty())
{
bool b = false;
int val = data.toInt(&b);
ImageOrientation ori = ORIENTATION_UNSPECIFIED;
if (b)
{
switch (val)
{
case 0:
ori = ORIENTATION_NORMAL;
break;
case 90:
ori = ORIENTATION_ROT_90;
break;
case 180:
ori = ORIENTATION_ROT_180;
break;
case 270:
ori = ORIENTATION_ROT_270;
break;
default:
break;
}
setXmpTagString("Xmp.video.Orientation", QString::number(ori));
// Backport orientation in Exif
setImageOrientation(ori);
}
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("language") // Generic.
<< QLatin1String("ILNG") // RIFF files.
<< QLatin1String("LANG"), // RIFF files.
vmeta,
QStringList() << QLatin1String("Xmp.video.Language"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("creation_time") // Generic.
<< QLatin1String("_STATISTICS_WRITING_DATE_UTC"), // MKV files.
vmeta);
if (!data.isEmpty())
{
QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime();
setXmpTagString("Xmp.video.TrackCreateDate",
QString::number(s_secondsSinceJanuary1904(dt)));
setXmpTagString("Xmp.xmpDM.shotDate", dt.toString());
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("handler_name"), // Generic.
vmeta,
QStringList() << QLatin1String("Xmp.video.HandlerDescription"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("TVER") // RIFF files.
<< QLatin1String("_STATISTICS_WRITING_APP"), // MKV files.
vmeta,
QStringList() << QLatin1String("Xmp.video.SoftwareVersion"));
}
// -----------------------------------------
// Subtitle stream parsing
// -----------------------------------------
if (!sstream && codec->codec_type == AVMEDIA_TYPE_SUBTITLE)
{
sstream = true;
const char* cname = avcodec_get_name(codec->codec_id);
setXmpTagString("Xmp.video.SubTCodec",
QString::fromUtf8(cname));
setXmpTagString("Xmp.video.SubTCodecInfo",
QString::fromUtf8(avcodec_descriptor_get_by_name(cname)->long_name));
// -----------------------------------------
MetaDataMap smeta = s_extractFFMpegMetadataEntriesFromDictionary(stream->metadata);
qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg subtitle stream metadata entries :";
qCDebug(DIGIKAM_METAENGINE_LOG) << smeta;
qCDebug(DIGIKAM_METAENGINE_LOG) << "--------------------------------------------";
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("subtitle") // Generic.
<< QLatin1String("title"), // Generic.
smeta,
QStringList() << QLatin1String("Xmp.video.Subtitle"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("language"), // Generic.
smeta,
QStringList() << QLatin1String("Xmp.video.SubTLang"));
}
}
// -----------------------------------------
// Root container parsing
// -----------------------------------------
MetaDataMap rmeta = s_extractFFMpegMetadataEntriesFromDictionary(fmt_ctx->metadata);
qCDebug(DIGIKAM_METAENGINE_LOG) << "-- FFMpeg root container metadata entries :";
qCDebug(DIGIKAM_METAENGINE_LOG) << rmeta;
qCDebug(DIGIKAM_METAENGINE_LOG) << "------------------------------------------";
// ----------------------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("major_brand"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.MajorBrand"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("compatible_brands"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.CompatibleBrands"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("minor_version"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.MinorVersion"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("keywords") // Generic.
<< QLatin1String("IMIT") // RIFF files.
<< QLatin1String("KEYWORDS") // MKV files.
<< QLatin1String("com.apple.quicktime.keywords"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.InfoText"));
if (!data.isEmpty())
{
QStringList keywords = s_keywordsSeparation(data);
if (!keywords.isEmpty())
{
setXmpKeywords(keywords);
setIptcKeywords(QStringList(), keywords);
}
}
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("category") // Generic.
<< QLatin1String("ISBJ") // RIFF files.
<< QLatin1String("SUBJECT"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Subject"));
if (!data.isEmpty())
{
QStringList categories = s_keywordsSeparation(data);
if (!categories.isEmpty())
{
setXmpSubCategories(categories);
setIptcSubCategories(QStringList(), categories);
}
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("premiere_version") // Generic.
<< QLatin1String("quicktime_version") // Generic.
<< QLatin1String("ISFT") // Riff files
<< QLatin1String("com.apple.quicktime.software"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.SoftwareVersion"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("firmware") // Generic.
<< QLatin1String("com.apple.proapps.serialno"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.FirmwareVersion"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("composer") // Generic.
<< QLatin1String("COMPOSER"), // MKV files
rmeta,
QStringList() << QLatin1String("Xmp.video.Composer")
<< QLatin1String("Xmp.xmpDM.composer"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("com.apple.quicktime.displayname"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Name"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("playback_requirements"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.Requirements"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("lyrics"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.Lyrics"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("filename"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.FileName"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("disk"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.xmpDM.discNumber"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("performers"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.Performers"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("producer") // Generic.
<< QLatin1String("PRODUCER") // MKV files.
<< QLatin1String("com.apple.quicktime.producer"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Producer"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("artist") // Generic.
<< QLatin1String("album_artist") // Generic.
<< QLatin1String("original_artist") // Generic.
<< QLatin1String("com.apple.quicktime.artist") // QT files.
<< QLatin1String("IART") // RIFF files.
<< QLatin1String("ARTIST") // MKV files.
<< QLatin1String("author") // Generic.
<< QLatin1String("com.apple.quicktime.author"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Artist")
<< QLatin1String("Xmp.xmpDM.artist"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("director") // Generic.
<< QLatin1String("DIRC") // RIFF files.
<< QLatin1String("DIRECTOR") // MKV files.
<< QLatin1String("com.apple.quicktime.director"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Director")
<< QLatin1String("Xmp.xmpDM.director"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("media_type") // Generic.
<< QLatin1String("IMED") // RIFF files.
<< QLatin1String("ORIGINAL_MEDIA_TYPE"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Medium"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("grouping"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.Grouping"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("BPS"), // MKV files
rmeta,
QStringList() << QLatin1String("Xmp.video.MaxBitRate"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ISRC"), // MKV files
rmeta,
QStringList() << QLatin1String("Xmp.video.ISRCCode"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("CONTENT_TYPE"), // MKV files
rmeta,
QStringList() << QLatin1String("Xmp.video.ExtendedContentDescription"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("FPS"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.videoFrameRate")
<< QLatin1String("Xmp.xmpDM.FrameRate"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("encoder") // Generic.
<< QLatin1String("ENCODER"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Encoder"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("com.apple.proapps.clipID"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.FileID"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("original_source") // Generic.
<< QLatin1String("ISRC") // Riff files
<< QLatin1String("com.apple.proapps.cameraName"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Source"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("original_format") // Generic.
<< QLatin1String("com.apple.proapps.originalFormat"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Format"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("rating") // Generic.
<< QLatin1String("IRTD") // RIFF files.
<< QLatin1String("RATE") // RIFF files.
<< QLatin1String("RATING") // MKV files.
<< QLatin1String("com.apple.quicktime.rating.user"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Rating")
<< QLatin1String("Xmp.video.Rate"));
if (!data.isEmpty())
{
// Backport rating in Exif and Iptc
bool b = false;
int rating = data.toInt(&b);
if (b)
{
setImageRating(rating);
}
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("make") // Generic.
<< QLatin1String("com.apple.quicktime.make"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Make"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("model") // Generic.
<< QLatin1String("com.apple.quicktime.model"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Model")
<< QLatin1String("Xmp.xmpDM.cameraModel"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("URL") // Generic.
<< QLatin1String("TURL"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.URL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("title") // Generic.
<< QLatin1String("INAM") // RIFF files.
<< QLatin1String("TITL") // RIFF files.
<< QLatin1String("TITLE") // MKV files.
<< QLatin1String("com.apple.quicktime.title"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Title")
<< QLatin1String("Xmp.xmpDM.shotName"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("copyright") // Generic.
<< QLatin1String("ICOP") // RIFF files.
<< QLatin1String("COPYRIGHT") // MKV files.
<< QLatin1String("com.apple.quicktime.copyright"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Copyright")
<< QLatin1String("Xmp.xmpDM.copyright"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("comment") // Generic.
<< QLatin1String("description") // Generic.
<< QLatin1String("CMNT") // Riff Files.
<< QLatin1String("COMN") // Riff Files.
<< QLatin1String("ICMT") // Riff Files.
<< QLatin1String("COMMENT") // MKV Files.
<< QLatin1String("DESCRIPTION") // MKV Files.
<< QLatin1String("com.apple.quicktime.description"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Comment")
<< QLatin1String("Xmp.xmpDM.logComment"));
if (!data.isEmpty())
{
// Backport comment in Exif and Iptc
CaptionsMap capMap;
MetaEngine::AltLangMap comMap;
comMap.insert(QLatin1String("x-default"), data);
capMap.setData(comMap, MetaEngine::AltLangMap(), QString(), MetaEngine::AltLangMap());
setImageComments(capMap);
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("synopsis") // Generic.
<< QLatin1String("SUMMARY") // MKV files.
<< QLatin1String("SYNOPSIS"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Information"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("lyrics") // Generic.
<< QLatin1String("LYRICS"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.xmpDM.lyrics"));
// --------------
for (int i = 1 ; i <= 9 ; i++)
{
s_setXmpTagStringFromEntry(this,
QStringList() << QString::fromLatin1("IAS%1").arg(i), // RIFF files.
rmeta,
QStringList() << QString::fromLatin1("Xmp.video.Edit%1").arg(i));
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("encoded_by") // Generic.
<< QLatin1String("CODE") // RIFF files.
<< QLatin1String("IECN") // RIFF files.
<< QLatin1String("ENCODED_BY"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.EncodedBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("DISP"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.SchemeTitle"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("AGES") // RIFF files.
<< QLatin1String("ICRA") // MKV files.
<< QLatin1String("LAW_RATING"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Rated"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IBSU"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.BaseURL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICAS"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.DefaultStream"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICDS"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.CostumeDesigner"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICMS"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Commissioned"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICNM"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Cinematographer"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICNT"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Country"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IARL"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.ArchivalLocation"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICRP"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Cropped"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IDIM"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Dimensions"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IDPI"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.DotsPerInch"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IDST") // RIFF files.
<< QLatin1String("DISTRIBUTED_BY"), // MKV files.
rmeta,
QStringList() << QLatin1String("Xmp.video.DistributedBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IEDT"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.EditedBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IENG"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Engineer")
<< QLatin1String("Xmp.xmpDM.engineer"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IKEY"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.PerformerKeywords"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ILGT"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Lightness"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ILGU"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.LogoURL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ILIU"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.LogoIconURL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IMBI"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.InfoBannerImage"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IMBU"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.InfoBannerURL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IMIU"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.InfoURL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IMUS"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.MusicBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IPDS"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.ProductionDesigner"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IPLT"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.NumOfColors"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IPRD"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Product"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IPRO"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.ProducedBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IRIP"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.RippedBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ISGN"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.SecondaryGenre"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ISHP"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Sharpness"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ISRF"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.SourceForm"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ISTD"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.ProductionStudio"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ISTR") // RIFF files.
<< QLatin1String("STAR"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Starring"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ITCH"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Technician"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IWMU"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.WatermarkURL"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("IWRI"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.WrittenBy"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("PRT1"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Part"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("PRT2"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.NumOfParts"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("STAT"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Statistics"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("TAPE"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.TapeName"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("TCDO"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.EndTimecode"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("TCOD"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.StartTimecode"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("TLEN"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Length"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("TORG"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Organization"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("VMAJ"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.VegasVersionMajor"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("VMIN"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.VegasVersionMinor"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("LOCA"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.LocationInfo")
<< QLatin1String("Xmp.xmpDM.shotLocation"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("album") // Generic.
<< QLatin1String("com.apple.quicktime.album"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Album")
<< QLatin1String("Xmp.xmpDM.album"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("genre") // Generic.
<< QLatin1String("GENR") // RIFF files.
<< QLatin1String("IGNR") // RIFF files.
<< QLatin1String("GENRE") // MKV files.
<< QLatin1String("com.apple.quicktime.genre"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.Genre")
<< QLatin1String("Xmp.xmpDM.genre"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("track") // Generic.
<< QLatin1String("TRCK"), // RIFF files.
rmeta,
QStringList() << QLatin1String("Xmp.video.TrackNumber")
<< QLatin1String("Xmp.xmpDM.trackNumber"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("year") // Generic.
<< QLatin1String("YEAR") // RIFF files.
<< QLatin1String("com.apple.quicktime.year"),
rmeta,
QStringList() << QLatin1String("Xmp.video.Year"));
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("ICRD") // Riff files
<< QLatin1String("DATE_DIGITIZED"), // MKV files
rmeta,
QStringList() << QLatin1String("Xmp.video.DateTimeDigitized"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("creation_time") // Generic.
<< QLatin1String("DTIM") // RIFF files.
<< QLatin1String("DATE_RECORDED") // MKV files.
<< QLatin1String("com.apple.quicktime.creationdate"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.DateTimeOriginal"));
if (!data.isEmpty())
{
// Backport date in Exif and Iptc.
QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime();
setImageDateTime(dt, true);
}
// --------------
s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("edit_date"), // Generic.
rmeta,
QStringList() << QLatin1String("Xmp.video.ModificationDate")
<< QLatin1String("Xmp.xmpDM.videoModDate"));
// --------------
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("date") // Generic.
<< QLatin1String("DATE_RELEASED"), // MKV files.
rmeta);
if (!data.isEmpty())
{
QDateTime dt = QDateTime::fromString(data, Qt::ISODate).toLocalTime();
setXmpTagString("Xmp.video.MediaCreateDate",
QString::number(s_secondsSinceJanuary1904(dt)));
}
// --------------
// GPS info as string. ex: "+44.8511-000.6229/"
// Defined in ISO 6709:2008.
// Notes: altitude can be passed as 3rd values.
// each value is separated from others by '-' or '+'.
// '/' is always the terminaison character.
data = s_setXmpTagStringFromEntry(this,
QStringList() << QLatin1String("location") // Generic.
<< QLatin1String("RECORDING_LOCATION") // MKV files.
<< QLatin1String("com.apple.quicktime.location.ISO6709"), // QT files.
rmeta,
QStringList() << QLatin1String("Xmp.video.GPSCoordinates")
<< QLatin1String("Xmp.xmpDM.shotLocation"));
if (!data.isEmpty())
{
// Backport location to Exif.
QList<int> digits;
for (int i = 0 ; i < data.length() ; i++)
{
QChar c = data[i];
if (c == QLatin1Char('+') || c == QLatin1Char('-') || c == QLatin1Char('/'))
{
digits << i;
}
}
QString coord;
double lattitude = 0.0;
double longitude = 0.0;
double altitude = 0.0;
bool b1 = false;
bool b2 = false;
bool b3 = false;
if (digits.size() > 1)
{
coord = data.mid(digits[0], digits[1] - digits[0]);
lattitude = coord.toDouble(&b1);
}
if (digits.size() > 2)
{
coord = data.mid(digits[1], digits[2] - digits[1]);
longitude = coord.toDouble(&b2);
}
if (digits.size() > 3)
{
coord = data.mid(digits[2], digits[3] - digits[2]);
altitude = coord.toDouble(&b3);
}
if (b1 && b2)
{
if (b3)
{
// All GPS values are available.
setGPSInfo(altitude, lattitude, longitude);
setXmpTagString("Xmp.video.GPSAltitude",
getXmpTagString("Xmp.exif.GPSAltitude"));
setXmpTagString("Xmp.exif.GPSAltitude",
getXmpTagString("Xmp.exif.GPSAltitude"));
}
else
{
// No altitude available.
double* alt = 0;
setGPSInfo(alt, lattitude, longitude);
}
setXmpTagString("Xmp.video.GPSLatitude",
getXmpTagString("Xmp.exif.GPSLatitude"));
setXmpTagString("Xmp.video.GPSLongitude",
getXmpTagString("Xmp.exif.GPSLongitude"));
setXmpTagString("Xmp.video.GPSMapDatum",
getXmpTagString("Xmp.exif.GPSMapDatum"));
setXmpTagString("Xmp.video.GPSVersionID",
getXmpTagString("Xmp.exif.GPSVersionID"));
setXmpTagString("Xmp.exif.GPSLatitude",
getXmpTagString("Xmp.exif.GPSLatitude"));
setXmpTagString("Xmp.exif.GPSLongitude",
getXmpTagString("Xmp.exif.GPSLongitude"));
setXmpTagString("Xmp.exif.GPSMapDatum",
getXmpTagString("Xmp.exif.GPSMapDatum"));
setXmpTagString("Xmp.exif.GPSVersionID",
getXmpTagString("Xmp.exif.GPSVersionID"));
}
}
avformat_close_input(&fmt_ctx);
QFileInfo fi(filePath);
if (getXmpTagString("Xmp.video.FileName").isNull())
setXmpTagString("Xmp.video.FileName", fi.fileName());
if (getXmpTagString("Xmp.video.FileSize").isNull())
setXmpTagString("Xmp.video.FileSize", QString::number(fi.size() / (1024*1024)));
if (getXmpTagString("Xmp.video.FileType").isNull())
setXmpTagString("Xmp.video.FileType", fi.suffix());
if (getXmpTagString("Xmp.video.MimeType").isNull())
setXmpTagString("Xmp.video.MimeType", QMimeDatabase().mimeTypeForFile(filePath).name());
return true;
#else
Q_UNUSED(filePath);
return false;
#endif
}
QString DMetadata::videoColorModelToString(VIDEOCOLORMODEL videoColorModel)
{
QString cs;
switch (videoColorModel)
{
case VIDEOCOLORMODEL_SRGB:
cs = QLatin1String("sRGB");
break;
case VIDEOCOLORMODEL_BT601:
cs = QLatin1String("CCIR-601");
break;
case VIDEOCOLORMODEL_BT709:
cs = QLatin1String("CCIR-709");
break;
case VIDEOCOLORMODEL_OTHER:
cs = QLatin1String("Other");
break;
default: // VIDEOCOLORMODEL_UNKNOWN
break;
}
return cs;
}
} // namespace Digikam
diff --git a/core/libs/dmetadata/geodetictools.cpp b/core/libs/dmetadata/geodetictools.cpp
index 0c5b45945d..4b622aa0da 100644
--- a/core/libs/dmetadata/geodetictools.cpp
+++ b/core/libs/dmetadata/geodetictools.cpp
@@ -1,854 +1,854 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-05-05
* Description : Geodetic tools based from an implementation written by
* Daniele Franzoni and Martin Desruisseaux from
* GeoTools Project Managment Committee (PMC), http://geotools.org
*
* Copyright (C) 2008-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "geodetictools.h"
// C++ includes
#include <cstdlib>
#include <cfloat>
namespace Digikam
{
using namespace Coordinates;
GeodeticCalculator::GeodeticCalculator(const Ellipsoid& e)
: m_ellipsoid(e),
m_lat1(0),
m_long1(0),
m_lat2(0),
m_long2(0),
m_distance(0),
m_azimuth(0),
m_destinationValid(false),
m_directionValid(false)
{
m_semiMajorAxis = m_ellipsoid.semiMajorAxis();
m_semiMinorAxis = m_ellipsoid.semiMinorAxis();
// constants
TOLERANCE_0 = 5.0e-15,
TOLERANCE_1 = 5.0e-14,
TOLERANCE_2 = 5.0e-13,
TOLERANCE_3 = 7.0e-3;
TOLERANCE_CHECK = 1E-8;
/* calculation of GPNHRI parameters */
f = (m_semiMajorAxis-m_semiMinorAxis) / m_semiMajorAxis;
fo = 1.0 - f;
f2 = f*f;
f3 = f*f2;
f4 = f*f3;
m_eccentricitySquared = f * (2.0-f);
/* Calculation of GNPARC parameters */
const double E2 = m_eccentricitySquared;
const double E4 = E2*E2;
const double E6 = E4*E2;
const double E8 = E6*E2;
const double EX = E8*E2;
A = 1.0+0.75*E2+0.703125*E4+0.68359375 *E6+0.67291259765625*E8+0.6661834716796875 *EX;
B = 0.75*E2+0.9375 *E4+1.025390625*E6+1.07666015625 *E8+1.1103057861328125 *EX;
C = 0.234375*E4+0.41015625 *E6+0.538330078125 *E8+0.63446044921875 *EX;
D = 0.068359375*E6+0.15380859375 *E8+0.23792266845703125*EX;
E = 0.01922607421875*E8+0.0528717041015625 *EX;
F = 0.00528717041015625*EX;
m_maxOrthodromicDistance = m_semiMajorAxis * (1.0-E2) * M_PI * A - 1.0;
T1 = 1.0;
T2 = -0.25*f*(1.0 + f + f2);
T4 = 0.1875 * f2 * (1.0+2.25*f);
T6 = 0.1953125 * f3;
const double a = f3*(1.0+2.25*f);
a01 = -f2*(1.0+f+f2)/4.0;
a02 = 0.1875*a;
a03 = -0.1953125*f4;
a21 = -a01;
a22 = -0.25*a;
a23 = 0.29296875*f4;
a42 = 0.03125*a;
a43 = 0.05859375*f4;
a63 = 5.0*f4/768.0;
}
double GeodeticCalculator::castToAngleRange(const double alpha)
{
return alpha - (2*M_PI) * floor(alpha/(2*M_PI) + 0.5);
}
bool GeodeticCalculator::checkLatitude(double* latitude)
{
if (*latitude >= -90.0 && *latitude <= 90.0)
{
*latitude = toRadians(*latitude);
return true;
}
return false;
}
bool GeodeticCalculator::checkLongitude(double* longitude)
{
if (*longitude >= -180.0 && *longitude <= 180.0)
{
*longitude = toRadians(*longitude);
return true;
}
return false;
}
bool GeodeticCalculator::checkAzimuth(double* azimuth)
{
if (*azimuth >= -180.0 && *azimuth <= 180.0)
{
*azimuth = toRadians(*azimuth);
return true;
}
return false;
}
bool GeodeticCalculator::checkOrthodromicDistance(const double distance)
{
return distance >= 0.0 && distance <= m_maxOrthodromicDistance;
}
Ellipsoid GeodeticCalculator::ellipsoid() const
{
return m_ellipsoid;
}
void GeodeticCalculator::setStartingGeographicPoint(double longitude, double latitude)
{
if (!checkLongitude(&longitude) || !checkLatitude(&latitude))
{
return;
}
// Check passed. Now performs the changes in this object.
m_long1 = longitude;
m_lat1 = latitude;
m_destinationValid = false;
m_directionValid = false;
}
void GeodeticCalculator::setDestinationGeographicPoint(double longitude, double latitude)
{
if (!checkLongitude(&longitude) || !checkLatitude(&latitude))
{
return;
}
// Check passed. Now performs the changes in this object.
m_long2 = longitude;
m_lat2 = latitude;
m_destinationValid = true;
m_directionValid = false;
}
bool GeodeticCalculator::destinationGeographicPoint(double* longitude, double* latitude)
{
if (!m_destinationValid)
{
if (!computeDestinationPoint())
{
return false;
}
}
*longitude = toDegrees(m_long2);
*latitude = toDegrees(m_lat2);
return true;
}
QPointF GeodeticCalculator::destinationGeographicPoint()
{
double x = 0.0;
double y = 0.0;
destinationGeographicPoint(&x, &y);
QPointF point;
point.setX(x);
point.setY(y);
return point;
}
void GeodeticCalculator::setDirection(double azimuth, double distance)
{
// Check first in case an exception is raised
// (in other words, we change all or nothing).
if (!checkAzimuth(&azimuth))
{
return;
}
if (!checkOrthodromicDistance(distance))
{
return;
}
// Check passed. Now performs the changes in this object.
m_azimuth = azimuth;
m_distance = distance;
m_destinationValid = false;
m_directionValid = true;
}
double GeodeticCalculator::azimuth()
{
if (!m_directionValid)
{
computeDirection();
}
return toDegrees(m_azimuth);
}
double GeodeticCalculator::orthodromicDistance()
{
if (!m_directionValid)
{
computeDirection();
checkOrthodromicDistance();
}
return m_distance;
}
bool GeodeticCalculator::checkOrthodromicDistance()
{
double check = m_ellipsoid.orthodromicDistance(toDegrees(m_long1), toDegrees(m_lat1),
toDegrees(m_long2), toDegrees(m_lat2));
check = fabs(m_distance - check);
return (check <= (m_distance+1) * TOLERANCE_CHECK);
}
bool GeodeticCalculator::computeDestinationPoint()
{
if (!m_directionValid)
{
return false;
}
// Protect internal variables from changes
const double lat1 = m_lat1;
const double long1 = m_long1;
const double azimuth = m_azimuth;
const double distance = m_distance;
/*
* Solution of the geodetic direct problem after T.Vincenty.
* Modified Rainsford's method with Helmert's elliptical terms.
* Effective in any azimuth and at any distance short of antipodal.
*
* Latitudes and longitudes in radians positive North and East.
* Forward azimuths at both points returned in radians from North.
*
* Programmed for CDC-6600 by LCDR L.Pfeifer NGS ROCKVILLE MD 18FEB75
* Modified for IBM SYSTEM 360 by John G.Gergen NGS ROCKVILLE MD 7507
* Ported from Fortran to Java by Daniele Franzoni.
*
* Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/forward.for
* subroutine DIRECT1
*/
double TU = fo*sin(lat1) / cos(lat1);
double SF = sin(azimuth);
double CF = cos(azimuth);
double BAZ = (CF!=0) ? atan2(TU,CF)*2.0 : 0;
double CU = 1/sqrt(TU*TU + 1.0);
double SU = TU*CU;
double SA = CU*SF;
double C2A = 1.0 - SA*SA;
double X = sqrt((1.0/fo/fo-1)*C2A+1.0) + 1.0;
X = (X-2.0)/X;
double C = 1.0-X;
C = (X*X/4.0+1.0)/C;
double D = (0.375*X*X-1.0)*X;
TU = distance / fo / m_semiMajorAxis / C;
double Y = TU;
double SY, CY, CZ, E;
do
{
SY = sin(Y);
CY = cos(Y);
CZ = cos(BAZ+Y);
E = CZ*CZ*2.0-1.0;
C = Y;
X = E*CY;
Y = E+E-1.0;
Y = (((SY*SY*4.0-3.0)*Y*CZ*D/6.0+X)*D/4.0-CZ)*SY*D+TU;
}
while (fabs(Y-C) > TOLERANCE_1);
BAZ = CU*CY*CF - SU*SY;
C = fo*sqrt(SA*SA+BAZ*BAZ);
D = SU*CY + CU*SY*CF;
m_lat2 = atan2(D,C);
C = CU*CY-SU*SY*CF;
X = atan2(SY*SF,C);
C = ((-3.0*C2A+4.0)*f+4.0)*C2A*f/16.0;
D = ((E*CY*C+CZ)*SY*C+Y)*SA;
m_long2 = long1+X - (1.0-C)*D*f;
m_long2 = castToAngleRange(m_long2);
m_destinationValid = true;
return true;
}
double GeodeticCalculator::meridianArcLength(double latitude1, double latitude2)
{
if (!checkLatitude(&latitude1) || !checkLatitude(&latitude2))
{
return 0.0;
}
return meridianArcLengthRadians(latitude1, latitude2);
}
double GeodeticCalculator::meridianArcLengthRadians(double P1, double P2)
{
/*
* Latitudes P1 and P2 in radians positive North and East.
* Forward azimuths at both points returned in radians from North.
*
* Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/inverse.for
* subroutine GPNARC
* version 200005.26
* written by Robert (Sid) Safford
*
* Ported from Fortran to Java by Daniele Franzoni.
*/
double S1 = fabs(P1);
double S2 = fabs(P2);
double DA = (P2-P1);
// Check for a 90 degree lookup
if (S1 > TOLERANCE_0 || S2 <= (M_PI/2-TOLERANCE_0) || S2 >= (M_PI/2+TOLERANCE_0))
{
const double DB = sin(P2* 2.0) - sin(P1* 2.0);
const double DC = sin(P2* 4.0) - sin(P1* 4.0);
const double DD = sin(P2* 6.0) - sin(P1* 6.0);
const double DE = sin(P2* 8.0) - sin(P1* 8.0);
const double DF = sin(P2*10.0) - sin(P1*10.0);
// Compute the S2 part of the series expansion
S2 = -DB*B/2.0 + DC*C/4.0 - DD*D/6.0 + DE*E/8.0 - DF*F/10.0;
}
// Compute the S1 part of the series expansion
S1 = DA*A;
// Compute the arc length
return fabs(m_semiMajorAxis * (1.0-m_eccentricitySquared) * (S1+S2));
}
/**
* Computes the azimuth and orthodromic distance from the
* startingGeographicPoint() and the
* destinationGeographicPoint().
*/
bool GeodeticCalculator::computeDirection()
{
if (!m_destinationValid)
{
return false;
}
// Protect internal variables from change.
const double long1 = m_long1;
const double lat1 = m_lat1;
const double long2 = m_long2;
const double lat2 = m_lat2;
/*
* Solution of the geodetic inverse problem after T.Vincenty.
* Modified Rainsford's method with Helmert's elliptical terms.
* Effective in any azimuth and at any distance short of antipodal.
*
* Latitudes and longitudes in radians positive North and East.
* Forward azimuths at both points returned in radians from North.
*
* Programmed for CDC-6600 by LCDR L.Pfeifer NGS ROCKVILLE MD 18FEB75
* Modified for IBM SYSTEM 360 by John G.Gergen NGS ROCKVILLE MD 7507
* Ported from Fortran to Java by Daniele Franzoni.
*
* Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/inverse.for
* subroutine GPNHRI
* version 200208.09
* written by robert (sid) safford
*/
const double dlon = castToAngleRange(long2-long1);
const double ss = fabs(dlon);
if (ss < TOLERANCE_1)
{
m_distance = meridianArcLengthRadians(lat1, lat2);
m_azimuth = (lat2>lat1) ? 0.0 : M_PI;
m_directionValid = true;
return true;
}
/*
* Computes the limit in longitude (alimit), it is equal
* to twice the distance from the equator to the pole,
* as measured along the equator
*/
// tests for antinodal difference
const double ESQP = m_eccentricitySquared / (1.0-m_eccentricitySquared);
const double alimit = M_PI*fo;
if (ss >= alimit &&
lat1 < TOLERANCE_3 && lat1 > -TOLERANCE_3 &&
lat2 < TOLERANCE_3 && lat2 > -TOLERANCE_3)
{
// Computes an approximate AZ
const double CONS = (M_PI-ss)/(M_PI*f);
double AZ = asin(CONS);
int iter = 0;
double AZ_TEMP, S, AO;
do
{
if (++iter > 8)
{
//ERROR
return false;
}
S = cos(AZ);
const double C2 = S*S;
// Compute new AO
AO = T1 + T2*C2 + T4*C2*C2 + T6*C2*C2*C2;
const double _CS_ = CONS/AO;
S = asin(_CS_);
AZ_TEMP = AZ;
AZ = S;
}
while (fabs(S-AZ_TEMP) >= TOLERANCE_2);
const double AZ1 = (dlon < 0.0) ? 2.0*M_PI - S : S;
m_azimuth = castToAngleRange(AZ1);
//const double AZ2 = 2.0*M_PI - AZ1;
S = cos(AZ1);
// Equatorial - geodesic(S-s) SMS
const double U2 = ESQP*S*S;
const double U4 = U2*U2;
const double U6 = U4*U2;
const double U8 = U6*U2;
const double BO = 1.0 +
0.25 *U2 +
0.046875 *U4 +
0.01953125 *U6 +
-0.01068115234375*U8;
S = sin(AZ1);
const double SMS = m_semiMajorAxis*M_PI*(1.0 - f*fabs(S)*AO - BO*fo);
m_distance = m_semiMajorAxis*ss - SMS;
m_directionValid = true;
return true;
}
// the reduced latitudes
const double u1 = atan(fo*sin(lat1)/cos(lat1));
const double u2 = atan(fo*sin(lat2)/cos(lat2));
const double su1 = sin(u1);
const double cu1 = cos(u1);
const double su2 = sin(u2);
const double cu2 = cos(u2);
double xy, w, q2, q4, q6, r2, r3, sig, ssig, slon, clon, sinalf, ab=dlon;
int kcount = 0;
do
{
if (++kcount > 8)
{
//ERROR
return false;
}
clon = cos(ab);
slon = sin(ab);
const double csig = su1*su2 + cu1*cu2*clon;
ssig = sqrt(slon*cu2*slon*cu2 + (su2*cu1-su1*cu2*clon)*(su2*cu1-su1*cu2*clon));
sig = atan2(ssig, csig);
sinalf = cu1*cu2*slon/ssig;
w = (1.0 - sinalf*sinalf);
const double t4 = w*w;
const double t6 = w*t4;
- // the coefficents of type a
+ // the coefficients of type a
const double ao = f+a01*w+a02*t4+a03*t6;
const double a2 = a21*w+a22*t4+a23*t6;
const double a4 = a42*t4+a43*t6;
const double a6 = a63*t6;
// the multiple angle functions
double qo = 0.0;
if (w > TOLERANCE_0)
{
qo = -2.0*su1*su2/w;
}
q2 = csig + qo;
q4 = 2.0*q2*q2 - 1.0;
q6 = q2*(4.0*q2*q2 - 3.0);
r2 = 2.0*ssig*csig;
r3 = ssig*(3.0 - 4.0*ssig*ssig);
// the longitude difference
const double s = sinalf*(ao*sig + a2*ssig*q2 + a4*r2*q4 + a6*r3*q6);
double xz = dlon+s;
xy = fabs(xz-ab);
ab = dlon+s;
}
while (xy >= TOLERANCE_1);
const double z = ESQP*w;
const double bo = 1.0 + z*( 1.0/4.0 + z*(-3.0/ 64.0 + z*( 5.0/256.0 - z*(175.0/16384.0))));
const double b2 = z*(-1.0/4.0 + z*( 1.0/ 16.0 + z*(-15.0/512.0 + z*( 35.0/ 2048.0))));
const double b4 = z*z*(-1.0/ 128.0 + z*( 3.0/512.0 - z*( 35.0/ 8192.0)));
const double b6 = z*z*z*(-1.0/1536.0 + z*( 5.0/ 6144.0));
// The distance in ellispoid axis units.
m_distance = m_semiMinorAxis * (bo*sig + b2*ssig*q2 + b4*r2*q4 + b6*r3*q6);
double az1 = (dlon < 0.0) ? M_PI*(3.0/2.0) : M_PI/2.0;
// now compute the az1 & az2 for latitudes not on the equator
if ((fabs(su1)>=TOLERANCE_0) || (fabs(su2)>=TOLERANCE_0))
{
const double tana1 = slon*cu2 / (su2*cu1 - clon*su1*cu2);
const double sina1 = sinalf/cu1;
// azimuths from north,longitudes positive east
az1 = atan2(sina1, sina1/tana1);
}
m_azimuth = castToAngleRange(az1);
m_directionValid = true;
return true;
}
/*
/ **
* Calculates the geodetic curve between two points in the referenced ellipsoid.
* A curve in the ellipsoid is a path which points contain the longitude and latitude
* of the points in the geodetic curve. The geodetic curve is computed from the
* {@linkplain #getStartingGeographicPoint starting point} to the
* {@linkplain #getDestinationGeographicPoint destination point}.
*
* @param numberOfPoints The number of vertex in the geodetic curve.
* NOTE: This argument is only a hint and may be ignored
* in future version (if we compute a real curve rather than a list of line
* segments).
* @return The path that represents the geodetic curve from the
* {@linkplain #getStartingGeographicPoint starting point} to the
* {@linkplain #getDestinationGeographicPoint destination point}.
*
* @todo We should check for cases where the path cross the 90N, 90S, 90E or 90W boundaries.
* /
public Shape getGeodeticCurve(const int numberOfPoints) {
if (numberOfPoints < 0)
return Shape;
if (!directionValid) {
computeDirection();
}
if (!destinationValid) {
computeDestinationPoint();
}
const double long2 = this->long2;
const double lat2 = this->lat2;
const double distance = this->distance;
const double deltaDistance = distance / (numberOfPoints+1);
final GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD, numberOfPoints+1);
path.moveTo((float)toDegrees(long1),
(float)toDegrees(lat1));
for (int i=1; i<numberOfPoints; ++i) {
this->distance = i*deltaDistance;
computeDestinationPoint();
path.lineTo((float)toDegrees(this->long2),
(float)toDegrees(this->lat2));
}
this->long2 = long2;
this->lat2 = lat2;
this->distance = distance;
path.lineTo((float)toDegrees(long2),
(float)toDegrees(lat2));
return path;
}
/ **
* Calculates the geodetic curve between two points in the referenced ellipsoid.
* A curve in the ellipsoid is a path which points contain the longitude and latitude
* of the points in the geodetic curve. The geodetic curve is computed from the
* {@linkplain #getStartingGeographicPoint starting point} to the
* {@linkplain #getDestinationGeographicPoint destination point}.
*
* @return The path that represents the geodetic curve from the
* {@linkplain #getStartingGeographicPoint starting point} to the
* {@linkplain #getDestinationGeographicPoint destination point}.
* /
public Shape getGeodeticCurve() {
return getGeodeticCurve(10);
}
*/
// ---------------------------------------------------------------------------------
Ellipsoid Ellipsoid::WGS84()
{
return createFlattenedSphere(QLatin1String("WGS84"), 6378137.0, 298.257223563);
}
Ellipsoid Ellipsoid::GRS80()
{
return createFlattenedSphere(QLatin1String("GRS80"), 6378137.0, 298.257222101);
}
Ellipsoid Ellipsoid::INTERNATIONAL_1924()
{
return createFlattenedSphere(QLatin1String("International 1924"), 6378388.0, 297.0);
}
Ellipsoid Ellipsoid::CLARKE_1866()
{
return createFlattenedSphere(QLatin1String("Clarke 1866"), 6378206.4, 294.9786982);
}
Ellipsoid Ellipsoid::SPHERE()
{
return createEllipsoid(QLatin1String("SPHERE"), 6371000, 6371000);
}
Ellipsoid::Ellipsoid(const QString& name,
double semiMajorAxis,
double semiMinorAxis,
double inverseFlattening,
bool ivfDefinitive)
: name(name),
m_semiMajorAxis(semiMajorAxis),
m_semiMinorAxis(semiMinorAxis),
m_inverseFlattening(inverseFlattening),
m_ivfDefinitive(ivfDefinitive),
m_isSphere(false)
{
}
Ellipsoid::Ellipsoid(const QString& name,
double radius,
bool ivfDefinitive)
: name(name),
m_semiMajorAxis(radius),
m_semiMinorAxis(radius),
m_inverseFlattening(DBL_MAX),
m_ivfDefinitive(ivfDefinitive),
m_isSphere(true)
{
}
Ellipsoid Ellipsoid::createEllipsoid(const QString& name,
double m_semiMajorAxis,
double m_semiMinorAxis)
{
if (m_semiMajorAxis == m_semiMinorAxis)
{
return Ellipsoid(name, m_semiMajorAxis, false);
}
else
{
return Ellipsoid(name, m_semiMajorAxis, m_semiMinorAxis,
m_semiMajorAxis/(m_semiMajorAxis-m_semiMinorAxis), false);
}
}
Ellipsoid Ellipsoid::createFlattenedSphere(const QString& name,
double m_semiMajorAxis,
double m_inverseFlattening)
{
if (m_inverseFlattening == DBL_MAX)
{
return Ellipsoid(name, m_semiMajorAxis, true);
}
else
{
return Ellipsoid(name, m_semiMajorAxis,
m_semiMajorAxis*(1-1/m_inverseFlattening),
m_inverseFlattening, true);
}
}
double Ellipsoid::semiMajorAxis() const
{
return m_semiMajorAxis;
}
double Ellipsoid::semiMinorAxis() const
{
return m_semiMinorAxis;
}
double Ellipsoid::eccentricity() const
{
if (m_isSphere)
{
return 0.0;
}
const double f = 1-m_semiMinorAxis/m_semiMajorAxis;
return sqrt(2*f - f*f);
}
double Ellipsoid::inverseFlattening() const
{
return m_inverseFlattening;
}
bool Ellipsoid::isIvfDefinitive() const
{
return m_ivfDefinitive;
}
bool Ellipsoid::isSphere() const
{
return (m_semiMajorAxis == m_semiMinorAxis);
}
double Ellipsoid::orthodromicDistance(double x1, double y1, double x2, double y2)
{
x1 = toRadians(x1);
y1 = toRadians(y1);
x2 = toRadians(x2);
y2 = toRadians(y2);
/*
* Solution of the geodetic inverse problem after T.Vincenty.
* Modified Rainsford's method with Helmert's elliptical terms.
* Effective in any azimuth and at any distance short of antipodal.
*
* Latitudes and longitudes in radians positive North and East.
* Forward azimuths at both points returned in radians from North.
*
* Programmed for CDC-6600 by LCDR L.Pfeifer NGS ROCKVILLE MD 18FEB75
* Modified for IBM SYSTEM 360 by John G.Gergen NGS ROCKVILLE MD 7507
* Ported from Fortran to Java by Martin Desruisseaux.
*
* Source: ftp://ftp.ngs.noaa.gov/pub/pcsoft/for_inv.3d/source/inverse.for
* subroutine INVER1
*/
const int MAX_ITERATIONS = 100;
const double EPS = 0.5E-13;
const double F = 1/m_inverseFlattening;
const double R = 1-F;
double tu1 = R * sin(y1) / cos(y1);
double tu2 = R * sin(y2) / cos(y2);
double cu1 = 1 / sqrt(tu1*tu1 + 1);
double cu2 = 1 / sqrt(tu2*tu2 + 1);
double su1 = cu1*tu1;
double s = cu1*cu2;
double baz = s*tu2;
double faz = baz*tu1;
double x = x2-x1;
for (int i = 0 ; i < MAX_ITERATIONS ; ++i)
{
const double sx = sin(x);
const double cx = cos(x);
tu1 = cu2*sx;
tu2 = baz - su1*cu2*cx;
const double sy = sqrt(tu1*tu1 + tu2*tu2);
const double cy = s*cx + faz;
const double y = atan2(sy, cy);
const double SA = s*sx/sy;
const double c2a = 1 - SA*SA;
double cz = faz+faz;
if (c2a > 0)
{
cz = -cz/c2a + cy;
}
double e = cz*cz*2 - 1;
double c = ((-3*c2a+4)*F+4)*c2a*F/16;
double d = x;
x = ((e*cy*c+cz)*sy*c+y)*SA;
x = (1-c)*x*F + x2-x1;
if (fabs(d-x) <= EPS)
{
if (false)
{
// 'faz' and 'baz' are forward azimuths at both points.
// Since the current API can't returns this result, it
// doesn't worth to compute it at this time.
faz = atan2(tu1, tu2);
baz = atan2(cu1*sx, baz*cx - su1*cu2)+M_PI;
}
x = sqrt((1/(R*R)-1) * c2a + 1)+1;
x = (x-2)/x;
c = 1-x;
c = (x*x/4 + 1)/c;
d = (0.375*x*x - 1)*x;
x = e*cy;
s = 1-2*e;
s = ((((sy*sy*4 - 3)*s*cz*d/6-x)*d/4+cz)*sy*d+y)*c*R*m_semiMajorAxis;
return s;
}
}
// No convergence. It may be because coordinate points
// are equals or because they are at antipodes.
const double LEPS = 1E-10;
if (fabs(x1-x2) <= LEPS && fabs(y1-y2) <= LEPS)
{
return 0.0; // Coordinate points are equals
}
if (fabs(y1) <= LEPS && fabs(y2) <= LEPS)
{
return fabs(x1-x2) * m_semiMajorAxis; // Points are on the equator.
}
// Other cases: no solution for this algorithm.
return 0.0;
}
double Ellipsoid::radiusOfCurvature(double latitude)
{
// WARNING: Code not from geotools
double esquare = pow(eccentricity(), 2);
return m_semiMajorAxis * sqrt(1 - esquare) / (1 - esquare * pow( sin(toRadians(latitude)), 2));
}
} // namespace Digikam
diff --git a/core/libs/dmetadata/metadatasettingscontainer.h b/core/libs/dmetadata/metadatasettingscontainer.h
index ccd7f10990..0d85a6d421 100644
--- a/core/libs/dmetadata/metadatasettingscontainer.h
+++ b/core/libs/dmetadata/metadatasettingscontainer.h
@@ -1,117 +1,117 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-08-20
* Description : Metadata Settings Container.
*
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_META_DATA_SETTINGS_CONTAINER_H
#define DIGIKAM_META_DATA_SETTINGS_CONTAINER_H
// Qt includes
#include <QFlags>
// Local includes
#include "digikam_export.h"
#include "metaengine.h"
class QStringList;
class KConfigGroup;
namespace Digikam
{
/**
* The class MetadataSettingsContainer encapsulates all metadata related settings.
* NOTE: this allows supply changed arguments to MetadataHub without changing the global settings.
*/
class DIGIKAM_EXPORT MetadataSettingsContainer
{
public:
explicit MetadataSettingsContainer();
~MetadataSettingsContainer()
{
};
/**
* Describes the allowed and desired operation when rotating a picture.
* The modes are in escalating order and describe if an operation is allowed.
- * What is actually done will be goverend by what is possible:
+ * What is actually done will be governed by what is possible:
* 1) RAW files cannot by rotated by content, setting the metadata may be problematic
* 2) Read-Only files cannot edited, neither content nor metadata
- * 3) Writable files wil have lossy compression
+ * 3) Writable files will have lossy compression
* 4) Only JPEG and PGF offer lossless rotation
* Using a contents-based rotation always implies resetting the flag.
*/
enum RotationBehaviorFlag
{
NoRotation = 0,
RotateByInternalFlag = 1 << 0,
RotateByMetadataFlag = 1 << 1,
RotateByLosslessRotation = 1 << 2,
RotateByLossyRotation = 1 << 3,
RotatingFlags = RotateByInternalFlag | RotateByMetadataFlag,
RotatingPixels = RotateByLosslessRotation | RotateByLossyRotation
};
Q_DECLARE_FLAGS(RotationBehaviorFlags, RotationBehaviorFlag)
public:
void readFromConfig(KConfigGroup& group);
void writeToConfig(KConfigGroup& group) const;
public:
bool exifRotate;
bool exifSetOrientation;
bool saveComments;
bool saveDateTime;
bool savePickLabel;
bool saveColorLabel;
bool saveRating;
bool saveTemplate;
bool saveTags;
bool saveFaceTags;
bool writeRawFiles;
bool updateFileTimeStamp;
bool rescanImageIfModified;
bool clearMetadataIfRescan;
bool useXMPSidecar4Reading;
bool useLazySync;
MetaEngine::MetadataWritingMode metadataWritingMode;
RotationBehaviorFlags rotationBehavior;
QStringList sidecarExtensions;
};
} // namespace Digikam
Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::MetadataSettingsContainer::RotationBehaviorFlags)
#endif // DIGIKAM_META_DATA_SETTINGS_CONTAINER_H
diff --git a/core/libs/dmetadata/metaengine.h b/core/libs/dmetadata/metaengine.h
index 645c8712a8..d7a9f6a1eb 100644
--- a/core/libs/dmetadata/metaengine.h
+++ b/core/libs/dmetadata/metaengine.h
@@ -1,1088 +1,1088 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-09-15
* Description : Exiv2 library interface
* Exiv2: http://www.exiv2.org
* Exif : http://www.exif.org/Exif2-2.PDF
* Iptc : http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
* Xmp : http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf
* http://www.iptc.org/std/Iptc4xmpCore/1.0/specification/Iptc4xmpCore_1.0-spec-XMPSchema_8.pdf
* Paper: http://www.metadataworkinggroup.com/pdf/mwg_guidance.pdf
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_META_ENGINE_H
#define DIGIKAM_META_ENGINE_H
// QT includes
#include <QByteArray>
#include <QString>
#include <QDateTime>
#include <QMap>
#include <QSharedDataPointer>
#include <QStringList>
#include <QVariant>
#include <QRegExp>
#include <QUrl>
#include <QImage>
// Local includes
#include "metaengine_data.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT MetaEngine
{
public:
/**
* The image metadata writing mode, between image file metadata and XMP sidecar file, depending on the context.
* @sa MetadataWritingMode(), metadataWritingMode()
*/
enum MetadataWritingMode
{
/// Write metadata to image file only.
WRITETOIMAGEONLY = 0,
/// Write metadata to sidecar file only.
WRITETOSIDECARONLY = 1,
/// Write metadata to image and sidecar files.
WRITETOSIDECARANDIMAGE = 2,
/// Write metadata to sidecar file only for read only images such as RAW files for example.
WRITETOSIDECARONLY4READONLYFILES = 3
};
/**
* The image color workspace values given by Exif metadata.
*/
enum ImageColorWorkSpace
{
WORKSPACE_UNSPECIFIED = 0,
WORKSPACE_SRGB = 1,
WORKSPACE_ADOBERGB = 2,
WORKSPACE_UNCALIBRATED = 65535
};
/**
* The image orientation values given by Exif metadata.
*/
enum ImageOrientation
{
ORIENTATION_UNSPECIFIED = 0,
ORIENTATION_NORMAL = 1,
ORIENTATION_HFLIP = 2,
ORIENTATION_ROT_180 = 3,
ORIENTATION_VFLIP = 4,
ORIENTATION_ROT_90_HFLIP = 5,
ORIENTATION_ROT_90 = 6,
ORIENTATION_ROT_90_VFLIP = 7,
ORIENTATION_ROT_270 = 8
};
/**
* Xmp tag types, used by setXmpTag, only first three types are used
*/
enum XmpTagType
{
NormalTag = 0,
ArrayBagTag = 1,
StructureTag = 2,
ArrayLangTag = 3,
ArraySeqTag = 4
};
/**
* A map used to store Tags Key and Tags Value.
*/
typedef QMap<QString, QString> MetaDataMap;
/**
* A map used to store a list of Alternative Language values.
* The map key is the language code following RFC3066 notation
* (like "fr-FR" for French), and the map value the text.
*/
typedef QMap<QString, QString> AltLangMap;
/**
* A map used to store Tags Key and a list of Tags properties :
* - name,
* - title,
* - description.
*/
typedef QMap<QString, QStringList> TagsMap;
public:
/**
* Standard constructor.
*/
MetaEngine();
/**
* Copy constructor.
*/
MetaEngine(const MetaEngine& metadata);
/**
* Constructor to load from parsed data.
*/
explicit MetaEngine(const MetaEngineData& data);
/**
* Contructor to Load Metadata from image file.
*/
explicit MetaEngine(const QString& filePath);
/**
* Standard destructor
*/
virtual ~MetaEngine();
/**
* Create a copy of container
*/
MetaEngine& operator=(const MetaEngine& metadata);
public:
//-----------------------------------------------------------------
/// @name Static methods
//@{
/** Return true if Exiv2 library initialization is done properly.
This method must be called before using libMetaEngine with multithreading.
It initialize several non re-entrancy code from Adobe XMP SDK
See B.K.O #166424 for details. Call cleanupExiv2() to clean things up later.
*/
static bool initializeExiv2();
/** Return true if Exiv2 library memory allocations are cleaned properly.
This method must be called after using libMetaEngine with multithreading.
It cleans up memory used by Adobe XMP SDK
See B.K.O #166424 for details.
*/
static bool cleanupExiv2();
/** Return true if library can handle Xmp metadata
*/
static bool supportXmp();
/** Return true if library can write metadata to typeMime file format.
*/
static bool supportMetadataWritting(const QString& typeMime);
/** Return a string version of Exiv2 release in format "major.minor.patch"
*/
static QString Exiv2Version();
/** Return the XMP Sidecar file path for a image file path.
* If image file path do not include a file name or is empty, this function return a null string.
*/
static QString sidecarFilePathForFile(const QString& path);
/** Like sidecarFilePathForFile(), but works for local file path.
*/
static QString sidecarPath(const QString& path);
/** Like sidecarFilePathForFile(), but works for remote URLs.
*/
static QUrl sidecarUrl(const QUrl& url);
/** Gives a file url for a local path.
*/
static QUrl sidecarUrl(const QString& path);
/** Performs a QFileInfo based check if the given local file has a sidecar.
*/
static bool hasSidecar(const QString& path);
//@}
//-----------------------------------------------------------------
/// @name General methods
//@{
MetaEngineData data() const;
void setData(const MetaEngineData& data);
/** Load all metadata (Exif, Iptc, Xmp, and JFIF Comments) from a byte array.
Return true if metadata have been loaded successfully from image data.
*/
bool loadFromData(const QByteArray& imgData);
/** Load all metadata (Exif, Iptc, Xmp, and JFIF Comments) from a picture (JPEG, RAW, TIFF, PNG,
DNG, etc...). Return true if metadata have been loaded successfully from file.
*/
virtual bool load(const QString& filePath);
/** Save all metadata to a file. This one can be different than original picture to perform
transfert operation Return true if metadata have been saved into file.
*/
bool save(const QString& filePath, bool setVersion = true) const;
/** The same than save() method, but it apply on current image. Return true if metadata
have been saved into file.
*/
bool applyChanges() const;
/** Return 'true' if metadata container in memory as no Comments, Exif, Iptc, and Xmp.
*/
bool isEmpty() const;
/** Set the file path of current image.
*/
void setFilePath(const QString& path);
/** Return the file path of current image.
*/
QString getFilePath() const;
/** Returns the pixel size of the current image. This information is read from the file,
* not from the metadata. The returned QSize is valid if the MetaEngine object was _constructed_
* by reading a file or image data; the information is not available when the object
* was created from MetaEngineData.
* Note that in the Exif or XMP metadata, there may be fields describing the image size.
* These fields are not accessed by this method.
* When replacing the metadata with setData(), the metadata may change; this information
* always keeps referring to the file it was initially read from.
*/
QSize getPixelSize() const;
/** Returns the mime type of this image. The information is read from the file;
* see the docs for getPixelSize() to know when it is available.
*/
QString getMimeType() const;
/** Enable or disable writing metadata operations to RAW tiff based files.
It requires Exiv2 0.18. By default RAW files are untouched.
*/
void setWriteRawFiles(const bool on);
/** Return true if writing metadata operations on RAW tiff based files is enabled.
It's require Exiv2 0.18.
*/
bool writeRawFiles() const;
/** Enable or disable using XMP sidecar for reading metadata
*/
void setUseXMPSidecar4Reading(const bool on);
/** Return true if using XMP sidecar for reading metadata is enabled.
*/
bool useXMPSidecar4Reading() const;
/** Set metadata writing mode.
* @param mode Metadata writing mode as defined by the #MetadataWritingMode enum.
* @sa MetadataWritingMode, metadataWritingMode()
*/
void setMetadataWritingMode(const int mode);
/** Return the metadata writing mode.
* @returns Metadata writing mode as defined by the #MetadataWritingMode enum.
* @sa MetadataWritingMode, setMetadataWritingMode()
*/
int metadataWritingMode() const;
/** Enable or disable file timestamp updating when metadata are saved.
By default files timestamp are untouched.
*/
void setUpdateFileTimeStamp(bool on);
/** Return true if file timestamp is updated when metadata are saved.
*/
bool updateFileTimeStamp() const;
//@}
//-------------------------------------------------------------------
/// @name Metadata image information manipulation methods
//@{
/** Set Program name and program version in Exif and Iptc Metadata. Return true if information
have been changed in metadata.
*/
bool setImageProgramId(const QString& program, const QString& version) const;
- /** Return the size of image in pixels using Exif tags. Return a null dimmension if size cannot
+ /** Return the size of image in pixels using Exif tags. Return a null dimension if size cannot
be found.
*/
QSize getImageDimensions() const;
/** Set the size of image in pixels in Exif tags. Return true if size have been changed
in metadata.
*/
bool setImageDimensions(const QSize& size) const;
/** Return the image orientation set in Exif metadata. The makernotes of image are also parsed to
get this information. See ImageOrientation values for details.
*/
MetaEngine::ImageOrientation getImageOrientation() const;
/** Set the Exif orientation tag of image. See ImageOrientation values for details
Return true if orientation have been changed in metadata.
*/
bool setImageOrientation(ImageOrientation orientation) const;
/** Return the image color-space set in Exif metadata. The makernotes of image are also parsed to
get this information. See ImageColorWorkSpace values for details.
*/
MetaEngine::ImageColorWorkSpace getImageColorWorkSpace() const;
/** Set the Exif color-space tag of image. See ImageColorWorkSpace values for details
Return true if work-space have been changed in metadata.
*/
bool setImageColorWorkSpace(ImageColorWorkSpace workspace) const;
/** Return the time stamp of image. Exif information are check in first, IPTC in second
if image don't have Exif information. If no time stamp is found, a null date is returned.
*/
QDateTime getImageDateTime() const;
/** Set the Exif and Iptc time stamp. If 'setDateTimeDigitized' parameter is true, the 'Digitalized'
time stamp is set, else only 'Created' time stamp is set.
*/
bool setImageDateTime(const QDateTime& dateTime, bool setDateTimeDigitized=false) const;
/** Return the digitization time stamp of the image. First Exif information is checked, then IPTC.
If no digitization time stamp is found, getImageDateTime() is called if fallbackToCreationTime
is true, or a null QDateTime is returned if fallbackToCreationTime is false.
*/
QDateTime getDigitizationDateTime(bool fallbackToCreationTime=false) const;
/** Return a QImage copy of Iptc preview image. Return a null image if preview cannot
be found.
*/
bool getImagePreview(QImage& preview) const;
/** Set the Iptc preview image. The thumbnail image must have the right size before (64Kb max
with JPEG file, else 256Kb). Look Iptc specification for details. Return true if preview
have been changed in metadata.
- Re-implemente this method if you want to use another image file format than JPEG to
+ Re-implement this method if you want to use another image file format than JPEG to
save preview.
*/
virtual bool setImagePreview(const QImage& preview) const;
//@}
//-----------------------------------------------------------------
/// @name Comments manipulation methods
//@{
/** Return 'true' if Comments can be written in file.
*/
static bool canWriteComment(const QString& filePath);
/** Return 'true' if metadata container in memory as Comments.
*/
bool hasComments() const;
/** Clear the Comments metadata container in memory.
*/
bool clearComments() const;
/** Return a Qt byte array copy of Comments container get from current image.
Comments are JFIF section of JPEG images. Look Exiv2 API for more information.
Return a null Qt byte array if there is no Comments metadata in memory.
*/
QByteArray getComments() const;
/** Return a Qt string object of Comments from current image decoded using
the 'detectEncodingAndDecode()' method. Return a null string if there is no
Comments metadata available.
*/
QString getCommentsDecoded() const;
/** Set the Comments data using a Qt byte array. Return true if Comments metadata
have been changed in memory.
*/
bool setComments(const QByteArray& data) const;
/** Language Alternative autodetection. Return a QString without language alternative
header. Header is saved into 'lang'. If no language alternative is founf, value is returned
as well and 'lang' is set to a null string.
*/
static QString detectLanguageAlt(const QString& value, QString& lang);
//@}
//-----------------------------------------------------------------
/// @name Exif manipulation methods
//@{
/** Return a map of all standard Exif tags supported by Exiv2.
*/
TagsMap getStdExifTagsList() const;
/** Return a map of all non-standard Exif tags (makernotes) supported by Exiv2.
*/
TagsMap getMakernoteTagsList() const;
/** Return 'true' if Exif can be written in file.
*/
static bool canWriteExif(const QString& filePath);
/** Return 'true' if metadata container in memory as Exif.
*/
bool hasExif() const;
/** Clear the Exif metadata container in memory.
*/
bool clearExif() const;
/** Returns the exif data encoded to a QByteArray in a form suitable
for storage in a JPEG image.
Note that this encoding is a lossy operation.
Set true 'addExifHeader' parameter to add an Exif header to Exif metadata.
Returns a null Qt byte array if there is no Exif metadata in memory.
*/
QByteArray getExifEncoded(bool addExifHeader=false) const;
/** Set the Exif data using a Qt byte array. Return true if Exif metadata
have been changed in memory.
*/
bool setExif(const QByteArray& data) const;
/** Return a QImage copy of Exif thumbnail image. Return a null image if thumbnail cannot
be found. The 'fixOrientation' parameter will rotate automatically the thumbnail if Exif
orientation tags information are attached with thumbnail.
*/
QImage getExifThumbnail(bool fixOrientation) const;
/** Fix orientation of a QImage image accordingly with Exif orientation tag.
Return true if image is rotated, else false.
*/
bool rotateExifQImage(QImage& image, ImageOrientation orientation) const;
/** Set the Exif Thumbnail image. The thumbnail image must have the right dimensions before.
Look Exif specification for details. Return true if thumbnail have been changed in metadata.
*/
bool setExifThumbnail(const QImage& thumb) const;
/** Remove the Exif Thumbnail from the image */
bool removeExifThumbnail() const;
/** Adds a JPEG thumbnail to a TIFF images. Use this instead of setExifThumbnail for TIFF images. */
bool setTiffThumbnail(const QImage& thumb) const;
/** Return a QString copy of Exif user comments. Return a null string if user comments cannot
be found.
*/
QString getExifComment(bool readDescription = true) const;
/** Set the Exif user comments from image. Look Exif specification for more details about this tag.
Return true if Exif user comments have been changed in metadata.
*/
bool setExifComment(const QString& comment, bool writeDescription = true) const;
/** Get an Exif tags content like a string. If 'escapeCR' parameter is true, the CR characters
will be removed. If Exif tag cannot be found a null string is returned.
*/
QString getExifTagString(const char* exifTagName, bool escapeCR=true) const;
/** Set an Exif tag content using a string. Return true if tag is set successfully.
*/
bool setExifTagString(const char* exifTagName, const QString& value) const;
/** Get an Exif tag content like a long value. Return true if Exif tag be found.
*/
bool getExifTagLong(const char* exifTagName, long &val) const;
/** Get an Exif tag content like a long value. Return true if Exif tag be found.
*/
bool getExifTagLong(const char* exifTagName, long &val, int component) const;
/** Set an Exif tag content using a long value. Return true if tag is set successfully.
*/
bool setExifTagLong(const char* exifTagName, long val) const;
/** Get the 'component' index of an Exif tags content like a rational value.
'num' and 'den' are the numerator and the denominator of the rational value.
Return true if Exif tag be found.
*/
bool getExifTagRational(const char* exifTagName, long int& num, long int& den, int component=0) const;
/** Set an Exif tag content using a rational value.
'num' and 'den' are the numerator and the denominator of the rational value.
Return true if tag is set successfully.
*/
bool setExifTagRational(const char* exifTagName, long int num, long int den) const;
/** Get an Exif tag content like a bytes array. Return an empty bytes array if Exif
tag cannot be found.
*/
QByteArray getExifTagData(const char* exifTagName) const;
/** Set an Exif tag content using a bytes array. Return true if tag is set successfully.
*/
bool setExifTagData(const char* exifTagName, const QByteArray& data) const;
/** Get an Exif tags content as a QVariant. Returns a null QVariant if the Exif
tag cannot be found.
For string and integer values the matching QVariant types will be used,
for date and time values QVariant::DateTime.
Rationals will be returned as QVariant::List with two integer QVariants (numerator, denominator)
if rationalAsListOfInts is true, as double if rationalAsListOfInts is false.
An exif tag of numerical type may contain more than one value; set component to the desired index.
*/
QVariant getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts=true, bool escapeCR=true, int component=0) const;
/** Set an Exif tag content using a QVariant. Returns true if tag is set successfully.
All types described for the getExifTagVariant() method are supported.
Calling with a QVariant of type ByteArray is equivalent to calling setExifTagData.
For the meaning of rationalWantSmallDenominator, see the documentation of the convertToRational methods.
Setting a value with multiple components is currently not supported.
*/
bool setExifTagVariant(const char* exifTagName, const QVariant& data,
bool rationalWantSmallDenominator=true) const;
/** Remove the Exif tag 'exifTagName' from Exif metadata. Return true if tag is
removed successfully or if no tag was present.
*/
bool removeExifTag(const char* exifTagName) const;
/** Return the Exif Tag title or a null string.
*/
QString getExifTagTitle(const char* exifTagName);
/** Return the Exif Tag description or a null string.
*/
QString getExifTagDescription(const char* exifTagName);
/** Takes a QVariant value as it could have been retrieved by getExifTagVariant with the given exifTagName,
and returns its value properly converted to a string (including translations from Exiv2).
This is equivalent to calling getExifTagString directly.
If escapeCR is true CR characters will be removed from the result.
*/
QString createExifUserStringFromValue(const char* exifTagName, const QVariant& val, bool escapeCR=true);
/** Return a map of Exif tags name/value found in metadata sorted by
Exif keys given by 'exifKeysFilter'.
'exifKeysFilter' is a QStringList of Exif keys.
For example, if you use the string list given below:
"Iop"
"Thumbnail"
"Image"
"Photo"
List can be empty to not filter output.
- ... this method will return a map of all Exif tags witch :
+ ... this method will return a map of all Exif tags which :
- include "Iop", or "Thumbnail", or "Image", or "Photo" in the Exif tag keys
if 'inverSelection' is false.
- not include "Iop", or "Thumbnail", or "Image", or "Photo" in the Exif tag keys
if 'inverSelection' is true.
*/
MetaEngine::MetaDataMap getExifTagsDataList(const QStringList& exifKeysFilter=QStringList(), bool invertSelection=false) const;
//@}
//-------------------------------------------------------------
/// @name IPTC manipulation methods
//@{
/** Return a map of all standard Iptc tags supported by Exiv2.
*/
MetaEngine::TagsMap getIptcTagsList() const;
/** Return 'true' if Iptc can be written in file.
*/
static bool canWriteIptc(const QString& filePath);
/** Return 'true' if metadata container in memory as Iptc.
*/
bool hasIptc() const;
/** Clear the Iptc metadata container in memory.
*/
bool clearIptc() const;
/** Return a Qt byte array copy of Iptc container get from current image.
Set true 'addIrbHeader' parameter to add an Irb header to Iptc metadata.
Return a null Qt byte array if there is no Iptc metadata in memory.
*/
QByteArray getIptc(bool addIrbHeader=false) const;
/** Set the Iptc data using a Qt byte array. Return true if Iptc metadata
have been changed in memory.
*/
bool setIptc(const QByteArray& data) const;
/** Get an Iptc tag content like a string. If 'escapeCR' parameter is true, the CR characters
will be removed. If Iptc tag cannot be found a null string is returned.
*/
QString getIptcTagString(const char* iptcTagName, bool escapeCR=true) const;
/** Set an Iptc tag content using a string. Return true if tag is set successfully.
*/
bool setIptcTagString(const char* iptcTagName, const QString& value) const;
/** Returns a strings list with of multiple Iptc tags from the image. Return an empty list if no tag is found. */
/** Get the values of all IPTC tags with the given tag name in a string list.
(In Iptc, there can be multiple tags with the same name)
If the 'escapeCR' parameter is true, the CR characters
will be removed.
If no tag can be found an empty list is returned.
*/
QStringList getIptcTagsStringList(const char* iptcTagName, bool escapeCR=true) const;
/** Set multiple Iptc tags contents using a strings list. 'maxSize' is the max characters size
of one entry. Return true if all tags have been set successfully.
*/
bool setIptcTagsStringList(const char* iptcTagName, int maxSize,
const QStringList& oldValues, const QStringList& newValues) const;
/** Get an Iptc tag content as a bytes array. Return an empty bytes array if Iptc
tag cannot be found.
*/
QByteArray getIptcTagData(const char* iptcTagName) const;
/** Set an Iptc tag content using a bytes array. Return true if tag is set successfully.
*/
bool setIptcTagData(const char* iptcTagName, const QByteArray& data) const;
/** Remove the all instance of Iptc tags 'iptcTagName' from Iptc metadata. Return true if all
tags have been removed successfully (or none were present).
*/
bool removeIptcTag(const char* iptcTagName) const;
/** Return the Iptc Tag title or a null string.
*/
QString getIptcTagTitle(const char* iptcTagName);
/** Return the Iptc Tag description or a null string.
*/
QString getIptcTagDescription(const char* iptcTagName);
/** Return a map of Iptc tags name/value found in metadata sorted by
Iptc keys given by 'iptcKeysFilter'.
'iptcKeysFilter' is a QStringList of Iptc keys.
For example, if you use the string list given below:
"Envelope"
"Application2"
List can be empty to not filter output.
- ... this method will return a map of all Iptc tags witch :
+ ... this method will return a map of all Iptc tags which :
- include "Envelope", or "Application2" in the Iptc tag keys
if 'inverSelection' is false.
- not include "Envelope", or "Application2" in the Iptc tag keys
if 'inverSelection' is true.
*/
MetaEngine::MetaDataMap getIptcTagsDataList(const QStringList& iptcKeysFilter=QStringList(), bool invertSelection=false) const;
/** Return a strings list of Iptc keywords from image. Return an empty list if no keyword are set.
*/
QStringList getIptcKeywords() const;
/** Set Iptc keywords using a list of strings defined by 'newKeywords' parameter. Use 'getImageKeywords()'
method to set 'oldKeywords' parameter with existing keywords from image. The method will compare
all new keywords with all old keywords to prevent duplicate entries in image. Return true if keywords
have been changed in metadata.
*/
bool setIptcKeywords(const QStringList& oldKeywords, const QStringList& newKeywords) const;
/** Return a strings list of Iptc subjects from image. Return an empty list if no subject are set.
*/
QStringList getIptcSubjects() const;
/** Set Iptc subjects using a list of strings defined by 'newSubjects' parameter. Use 'getImageSubjects()'
method to set 'oldSubjects' parameter with existing subjects from image. The method will compare
all new subjects with all old subjects to prevent duplicate entries in image. Return true if subjects
have been changed in metadata.
*/
bool setIptcSubjects(const QStringList& oldSubjects, const QStringList& newSubjects) const;
/** Return a strings list of Iptc sub-categories from image. Return an empty list if no sub-category
are set.
*/
QStringList getIptcSubCategories() const;
/** Set Iptc sub-categories using a list of strings defined by 'newSubCategories' parameter. Use
'getImageSubCategories()' method to set 'oldSubCategories' parameter with existing sub-categories
from image. The method will compare all new sub-categories with all old sub-categories to prevent
duplicate entries in image. Return true if sub-categories have been changed in metadata.
*/
bool setIptcSubCategories(const QStringList& oldSubCategories, const QStringList& newSubCategories) const;
//@}
//------------------------------------------------------------
/// @name XMP manipulation methods
//@{
/** Return a map of all standard Xmp tags supported by Exiv2.
*/
MetaEngine::TagsMap getXmpTagsList() const;
/** Return 'true' if Xmp can be written in file.
*/
static bool canWriteXmp(const QString& filePath);
/** Return 'true' if metadata container in memory as Xmp.
*/
bool hasXmp() const;
/** Clear the Xmp metadata container in memory.
*/
bool clearXmp() const;
/** Return a Qt byte array copy of XMp container get from current image.
Return a null Qt byte array if there is no Xmp metadata in memory.
*/
QByteArray getXmp() const;
/** Set the Xmp data using a Qt byte array. Return true if Xmp metadata
have been changed in memory.
*/
bool setXmp(const QByteArray& data) const;
/** Get a Xmp tag content like a string. If 'escapeCR' parameter is true, the CR characters
will be removed. If Xmp tag cannot be found a null string is returned.
*/
QString getXmpTagString(const char* xmpTagName, bool escapeCR=true) const;
/** Set a Xmp tag content using a string. Return true if tag is set successfully.
*/
bool setXmpTagString(const char* xmpTagName, const QString& value) const;
/** Set a Xmp tag with a specific type. Return true if tag is set successfully.
* This method only accept NormalTag, ArrayBagTag and StructureTag.
* Other XmpTagTypes do nothing
*/
bool setXmpTagString(const char* xmpTagName, const QString& value,
XmpTagType type) const;
/** Return the Xmp Tag title or a null string.
*/
QString getXmpTagTitle(const char* xmpTagName);
/** Return the Xmp Tag description or a null string.
*/
QString getXmpTagDescription(const char* xmpTagName);
/** Return a map of Xmp tags name/value found in metadata sorted by
Xmp keys given by 'xmpKeysFilter'.
'xmpKeysFilter' is a QStringList of Xmp keys.
For example, if you use the string list given below:
"dc" // Dubling Core schema.
"xmp" // Standard Xmp schema.
List can be empty to not filter output.
- ... this method will return a map of all Xmp tags witch :
+ ... this method will return a map of all Xmp tags which :
- include "dc", or "xmp" in the Xmp tag keys
if 'inverSelection' is false.
- not include "dc", or "xmp" in the Xmp tag keys
if 'inverSelection' is true.
*/
MetaEngine::MetaDataMap getXmpTagsDataList(const QStringList& xmpKeysFilter=QStringList(), bool invertSelection=false) const;
/** Get all redondant Alternative Language Xmp tags content like a map.
See AltLangMap class description for details.
If 'escapeCR' parameter is true, the CR characters will be removed from strings.
If Xmp tag cannot be found a null string list is returned.
*/
MetaEngine::AltLangMap getXmpTagStringListLangAlt(const char* xmpTagName, bool escapeCR=true) const;
/** Set an Alternative Language Xmp tag content using a map. See AltLangMap class
- description for details. If tag already exist, it wil be removed before.
+ description for details. If tag already exist, it will be removed before.
Return true if tag is set successfully.
*/
bool setXmpTagStringListLangAlt(const char* xmpTagName, const MetaEngine::AltLangMap& values) const;
/** Get a Xmp tag content like a string set with an alternative language
header 'langAlt' (like "fr-FR" for French - RFC3066 notation)
If 'escapeCR' parameter is true, the CR characters will be removed.
If Xmp tag cannot be found a null string is returned.
*/
QString getXmpTagStringLangAlt(const char* xmpTagName, const QString& langAlt, bool escapeCR) const;
/** Set a Xmp tag content using a string with an alternative language header. 'langAlt' contain the
language alternative information (like "fr-FR" for French - RFC3066 notation) or is null to
set alternative language to default settings ("x-default").
Return true if tag is set successfully.
*/
bool setXmpTagStringLangAlt(const char* xmpTagName, const QString& value,
const QString& langAlt) const;
/** Get a Xmp tag content like a sequence of strings. If 'escapeCR' parameter is true, the CR characters
will be removed from strings. If Xmp tag cannot be found a null string list is returned.
*/
QStringList getXmpTagStringSeq(const char* xmpTagName, bool escapeCR=true) const;
/** Set a Xmp tag content using the sequence of strings 'seq'.
Return true if tag is set successfully.
*/
bool setXmpTagStringSeq(const char* xmpTagName, const QStringList& seq) const;
/** Get a Xmp tag content like a bag of strings. If 'escapeCR' parameter is true, the CR characters
will be removed from strings. If Xmp tag cannot be found a null string list is returned.
*/
QStringList getXmpTagStringBag(const char* xmpTagName, bool escapeCR) const;
/** Set a Xmp tag content using the bag of strings 'bag'.
Return true if tag is set successfully.
*/
bool setXmpTagStringBag(const char* xmpTagName, const QStringList& bag) const;
/** Set an Xmp tag content using a list of strings defined by the 'entriesToAdd' parameter.
The existing entries are preserved. The method will compare
all new with all already existing entries to prevent duplicates in the image.
Return true if the entries have been added to metadata.
*/
bool addToXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToAdd) const;
/** Remove those Xmp tag entries that are listed in entriesToRemove from the entries in metadata.
Return true if tag entries are no longer contained in metadata.
All other entries are preserved.
*/
bool removeFromXmpTagStringBag(const char* xmpTagName, const QStringList& entriesToRemove) const;
/** Get an Xmp tag content as a QVariant. Returns a null QVariant if the Xmp
tag cannot be found.
For string and integer values the matching QVariant types will be used,
for date and time values QVariant::DateTime.
Rationals will be returned as QVariant::List with two integer QVariants (numerator, denominator)
if rationalAsListOfInts is true, as double if rationalAsListOfInts is false.
Arrays (ordered, unordered, alternative) are returned as type StringList.
LangAlt values will have type Map (QMap<QString, QVariant>) with the language
code as key and the contents as value, of type String.
*/
QVariant getXmpTagVariant(const char* xmpTagName, bool rationalAsListOfInts=true, bool stringEscapeCR=true) const;
/** Return a strings list of Xmp keywords from image. Return an empty list if no keyword are set.
*/
QStringList getXmpKeywords() const;
/** Set Xmp keywords using a list of strings defined by 'newKeywords' parameter.
The existing keywords from image are preserved. The method will compare
all new keywords with all already existing keywords to prevent duplicate entries in image.
Return true if keywords have been changed in metadata.
*/
bool setXmpKeywords(const QStringList& newKeywords) const;
/** Remove those Xmp keywords that are listed in keywordsToRemove from the keywords in metadata.
Return true if keywords are no longer contained in metadata.
*/
bool removeXmpKeywords(const QStringList& keywordsToRemove);
/** Return a strings list of Xmp subjects from image. Return an empty list if no subject are set.
*/
QStringList getXmpSubjects() const;
/** Set Xmp subjects using a list of strings defined by 'newSubjects' parameter.
The existing subjects from image are preserved. The method will compare
all new subject with all already existing subject to prevent duplicate entries in image.
Return true if subjects have been changed in metadata.
*/
bool setXmpSubjects(const QStringList& newSubjects) const;
/** Remove those Xmp subjects that are listed in subjectsToRemove from the subjects in metadata.
Return true if subjects are no longer contained in metadata.
*/
bool removeXmpSubjects(const QStringList& subjectsToRemove);
/** Return a strings list of Xmp sub-categories from image. Return an empty list if no sub-category
are set.
*/
QStringList getXmpSubCategories() const;
/** Set Xmp sub-categories using a list of strings defined by 'newSubCategories' parameter.
The existing sub-categories from image are preserved. The method will compare
all new sub-categories with all already existing sub-categories to prevent duplicate entries in image.
Return true if sub-categories have been changed in metadata.
*/
bool setXmpSubCategories(const QStringList& newSubCategories) const;
/** Remove those Xmp sub-categories that are listed in categoriesToRemove from the sub-categories in metadata.
Return true if subjects are no longer contained in metadata.
*/
bool removeXmpSubCategories(const QStringList& categoriesToRemove);
/** Remove the Xmp tag 'xmpTagName' from Xmp metadata. Return true if tag is
removed successfully or if no tag was present.
*/
bool removeXmpTag(const char* xmpTagName) const;
/** Register a namespace which Exiv2 doesn't know yet. This is only needed
when new Xmp properties are added manually. 'uri' is the namespace url and prefix the
string used to construct new Xmp key (ex. "Xmp.digiKam.tagList").
NOTE: If the Xmp metadata is read from an image, namespaces are decoded and registered
by Exiv2 at the same time.
*/
static bool registerXmpNameSpace(const QString& uri, const QString& prefix);
/** Unregister a previously registered custom namespace */
static bool unregisterXmpNameSpace(const QString& uri);
//@}
//------------------------------------------------------------
/// @name GPS manipulation methods
//@{
/** Make sure all static required GPS EXIF and XMP tags exist
*/
bool initializeGPSInfo();
/** Get all GPS location information set in image. Return true if all information can be found.
*/
bool getGPSInfo(double& altitude, double& latitude, double& longitude) const;
/** Get GPS location information set in the image, in the GPSCoordinate format
as described in the XMP specification. Returns a null string in the information cannot be found.
*/
QString getGPSLatitudeString() const;
QString getGPSLongitudeString() const;
/** Get GPS location information set in the image, as a double floating point number as in degrees
where the sign determines the direction ref (North + / South - ; East + / West -).
Returns true if the information is available.
*/
bool getGPSLatitudeNumber(double* const latitude) const;
bool getGPSLongitudeNumber(double* const longitude) const;
/** Get GPS altitude information, in meters, relative to sea level (positive sign above sea level)
*/
bool getGPSAltitude(double* const altitude) const;
/** Set all GPS location information into image. Return true if all information have been
changed in metadata.
*/
bool setGPSInfo(const double altitude, const double latitude, const double longitude);
/** Set all GPS location information into image. Return true if all information have been
changed in metadata. If you do not want altitude to be set, pass a null pointer.
*/
bool setGPSInfo(const double* const altitude, const double latitude, const double longitude);
/** Set all GPS location information into image. Return true if all information have been
changed in metadata.
*/
bool setGPSInfo(const double altitude, const QString& latitude, const QString& longitude);
/** Remove all Exif tags relevant of GPS location information. Return true if all tags have been
removed successfully in metadata.
*/
bool removeGPSInfo();
/** This method converts 'number' to a rational value, returned in the 'numerator' and
'denominator' parameters. Set the precision using 'rounding' parameter.
Use this method if you want to retrieve a most exact rational for a number
without further properties, without any requirements to the denominator.
*/
static void convertToRational(const double number, long int* const numerator,
long int* const denominator, const int rounding);
/** This method convert a 'number' to a rational value, returned in 'numerator' and
'denominator' parameters.
This method will be able to retrieve a rational number from a double - if you
constructed your double with 1.0 / 4786.0, this method will retrieve 1 / 4786.
If your number is not expected to be rational, use the method above which is just as
exact with rounding = 4 and more exact with rounding > 4.
*/
static void convertToRationalSmallDenominator(const double number, long int* const numerator,
long int* const denominator);
/** Converts a GPS position stored as rationals in Exif to the form described
as GPSCoordinate in the XMP specification, either in the from "256,45,34N" or "256,45.566667N"
*/
static QString convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees,
const long int numeratorMinutes, const long int denominatorMinutes,
const long int numeratorSeconds, long int denominatorSeconds,
const char directionReference);
/** Converts a GPS position stored as double floating point number in degrees to the form described
as GPSCoordinate in the XMP specification.
*/
static QString convertToGPSCoordinateString(const bool isLatitude, double coordinate);
/** Converts a GPSCoordinate string as defined by XMP to three rationals and the direction reference.
Returns true if the conversion was successful.
If minutes is given in the fractional form, a denominator of 1000000 for the minutes will be used.
*/
static bool convertFromGPSCoordinateString(const QString& coordinate,
long int* const numeratorDegrees, long int* const denominatorDegrees,
long int* const numeratorMinutes, long int* const denominatorMinutes,
long int* const numeratorSeconds, long int* const denominatorSeconds,
char* const directionReference);
/** Convert a GPSCoordinate string as defined by XMP to a double floating point number in degrees
where the sign determines the direction ref (North + / South - ; East + / West -).
Returns true if the conversion was successful.
*/
static bool convertFromGPSCoordinateString(const QString& gpsString, double* const coordinate);
/** Converts a GPSCoordinate string to user presentable numbers, integer degrees and minutes and
double floating point seconds, and a direction reference ('N' or 'S', 'E' or 'W')
*/
static bool convertToUserPresentableNumbers(const QString& coordinate,
int* const degrees, int* const minutes,
double* const seconds, char* const directionReference);
/** Converts a double floating point number to user presentable numbers, integer degrees and minutes and
double floating point seconds, and a direction reference ('N' or 'S', 'E' or 'W').
The method needs to know for the direction reference
if the latitude or the longitude is meant by the double parameter.
*/
static void convertToUserPresentableNumbers(const bool isLatitude, double coordinate,
int* const degrees, int* const minutes,
double* const seconds, char* const directionReference);
//@}
protected:
/** Set the Program Name and Program Version
information in Exif and Iptc metadata
*/
bool setProgramId() const;
private:
/** Internal container to store private members. Used to improve binary compatibility
*/
class Private;
Private* const d;
friend class MetaEnginePreviews;
};
} // namespace Digikam
#endif // DIGIKAM_META_ENGINE_H
diff --git a/core/libs/dmetadata/metaengine_exif.cpp b/core/libs/dmetadata/metaengine_exif.cpp
index 30dfefef8a..079fa96929 100644
--- a/core/libs/dmetadata/metaengine_exif.cpp
+++ b/core/libs/dmetadata/metaengine_exif.cpp
@@ -1,1190 +1,1190 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-09-15
* Description : Exiv2 library interface.
* Exif manipulation methods
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "metaengine.h"
#include "metaengine_p.h"
// C++ includes
#include <cctype>
// Qt includes
#include <QTextCodec>
#include <QBuffer>
// Local includes
#include "metaengine_rotation.h"
#include "digikam_debug.h"
namespace Digikam
{
bool MetaEngine::canWriteExif(const QString& filePath)
{
try
{
Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open((const char*)
(QFile::encodeName(filePath).constData()));
Exiv2::AccessMode mode = image->checkMode(Exiv2::mdExif);
return (mode == Exiv2::amWrite || mode == Exiv2::amReadWrite);
}
catch( Exiv2::Error& e )
{
std::string s(e.what());
qCCritical(DIGIKAM_METAENGINE_LOG) << "Cannot check Exif access mode using Exiv2 (Error #"
<< e.code() << ": " << s.c_str() << ")";
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::hasExif() const
{
return !d->exifMetadata().empty();
}
bool MetaEngine::clearExif() const
{
try
{
d->exifMetadata().clear();
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot clear Exif data using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
QByteArray MetaEngine::getExifEncoded(bool addExifHeader) const
{
try
{
if (!d->exifMetadata().empty())
{
QByteArray data;
Exiv2::ExifData& exif = d->exifMetadata();
Exiv2::Blob blob;
Exiv2::ExifParser::encode(blob, Exiv2::bigEndian, exif);
QByteArray ba((const char*)&blob[0], blob.size());
if (addExifHeader)
{
const uchar ExifHeader[] = {0x45, 0x78, 0x69, 0x66, 0x00, 0x00};
data.resize(ba.size() + sizeof(ExifHeader));
memcpy(data.data(), ExifHeader, sizeof(ExifHeader));
memcpy(data.data() + sizeof(ExifHeader), ba.data(), ba.size());
}
else
{
data = ba;
}
return data;
}
}
catch( Exiv2::Error& e )
{
if (!d->filePath.isEmpty())
qCDebug(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData();
d->printExiv2ExceptionError(QLatin1String("Cannot get Exif data using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QByteArray();
}
bool MetaEngine::setExif(const QByteArray& data) const
{
try
{
if (!data.isEmpty())
{
Exiv2::ExifParser::decode(d->exifMetadata(), (const Exiv2::byte*)data.data(), data.size());
return (!d->exifMetadata().empty());
}
}
catch( Exiv2::Error& e )
{
if (!d->filePath.isEmpty())
qCCritical(DIGIKAM_METAENGINE_LOG) << "From file " << d->filePath.toLatin1().constData();
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif data using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
MetaEngine::MetaDataMap MetaEngine::getExifTagsDataList(const QStringList& exifKeysFilter, bool invertSelection) const
{
if (d->exifMetadata().empty())
return MetaDataMap();
try
{
Exiv2::ExifData exifData = d->exifMetadata();
exifData.sortByKey();
QString ifDItemName;
MetaDataMap metaDataMap;
for (Exiv2::ExifData::const_iterator md = exifData.begin() ; md != exifData.end() ; ++md)
{
QString key = QLatin1String(md->key().c_str());
// Decode the tag value with a user friendly output.
QString tagValue;
if (key == QLatin1String("Exif.Photo.UserComment"))
{
tagValue = d->convertCommentValue(*md);
}
else if (key == QLatin1String("Exif.Image.0x935c"))
{
tagValue = QString::number(md->value().size());
}
else if (key == QLatin1String("Exif.CanonCs.LensType") && md->toLong() == 65535)
{
// FIXME: workaround for a possible crash in Exiv2 pretty-print function for the Exif.CanonCs.LensType.
tagValue = QString::fromLocal8Bit(md->toString().c_str());
}
else
{
std::ostringstream os;
md->write(os, &exifData);
// Exif tag contents can be an translated strings, no only simple ascii.
tagValue = QString::fromLocal8Bit(os.str().c_str());
}
tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
// We apply a filter to get only the Exif tags that we need.
if (!exifKeysFilter.isEmpty())
{
if (!invertSelection)
{
if (exifKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1)))
metaDataMap.insert(key, tagValue);
}
else
{
if (!exifKeysFilter.contains(key.section(QLatin1Char('.'), 1, 1)))
metaDataMap.insert(key, tagValue);
}
}
else // else no filter at all.
{
metaDataMap.insert(key, tagValue);
}
}
return metaDataMap;
}
catch (Exiv2::Error& e)
{
d->printExiv2ExceptionError(QLatin1String("Cannot parse EXIF metadata using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return MetaDataMap();
}
QString MetaEngine::getExifComment(bool readDescription) const
{
try
{
if (!d->exifMetadata().empty())
{
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifKey key("Exif.Photo.UserComment");
Exiv2::ExifData::const_iterator it = exifData.findKey(key);
if (it != exifData.end())
{
QString exifComment = d->convertCommentValue(*it);
// some cameras fill the UserComment with whitespace
if (!exifComment.isEmpty() && !exifComment.trimmed().isEmpty())
return exifComment;
}
if (readDescription)
{
Exiv2::ExifKey key2("Exif.Image.ImageDescription");
Exiv2::ExifData::const_iterator it2 = exifData.findKey(key2);
if (it2 != exifData.end())
{
QString exifComment = d->convertCommentValue(*it2);
// Some cameras fill in nonsense default values
QStringList blackList;
blackList << QLatin1String("SONY DSC"); // + whitespace
blackList << QLatin1String("OLYMPUS DIGITAL CAMERA");
blackList << QLatin1String("MINOLTA DIGITAL CAMERA");
QString trimmedComment = exifComment.trimmed();
// some cameras fill the UserComment with whitespace
if (!exifComment.isEmpty() && !trimmedComment.isEmpty() && !blackList.contains(trimmedComment))
return exifComment;
}
}
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot find Exif User Comment using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QString();
}
static bool is7BitAscii(const QByteArray& s)
{
const int size = s.size();
for (int i = 0 ; i < size ; i++)
{
if (!isascii(s[i]))
{
return false;
}
}
return true;
}
bool MetaEngine::setExifComment(const QString& comment, bool writeDescription) const
{
try
{
if (writeDescription)
removeExifTag("Exif.Image.ImageDescription");
removeExifTag("Exif.Photo.UserComment");
if (!comment.isNull())
{
if (writeDescription)
setExifTagString("Exif.Image.ImageDescription", comment);
// Write as Unicode only when necessary.
QTextCodec* latin1Codec = QTextCodec::codecForName("iso8859-1");
if (latin1Codec->canEncode(comment))
{
// We know it's in the ISO-8859-1 8bit range.
// Check if it's in the ASCII 7bit range
if (is7BitAscii(comment.toLatin1()))
{
// write as ASCII
std::string exifComment("charset=\"Ascii\" ");
exifComment += comment.toLatin1().constData();
d->exifMetadata()["Exif.Photo.UserComment"] = exifComment;
return true;
}
}
// write as Unicode (UCS-2)
std::string exifComment("charset=\"Unicode\" ");
exifComment += comment.toUtf8().constData();
d->exifMetadata()["Exif.Photo.UserComment"] = exifComment;
}
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Comment using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
QString MetaEngine::getExifTagTitle(const char* exifTagName)
{
try
{
std::string exifkey(exifTagName);
Exiv2::ExifKey ek(exifkey);
return QString::fromLocal8Bit( ek.tagLabel().c_str() );
}
catch (Exiv2::Error& e)
{
d->printExiv2ExceptionError(QLatin1String("Cannot get metadata tag title using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QString();
}
QString MetaEngine::getExifTagDescription(const char* exifTagName)
{
try
{
std::string exifkey(exifTagName);
Exiv2::ExifKey ek(exifkey);
return QString::fromLocal8Bit( ek.tagDesc().c_str() );
}
catch (Exiv2::Error& e)
{
d->printExiv2ExceptionError(QLatin1String("Cannot get metadata tag description using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QString();
}
bool MetaEngine::removeExifTag(const char* exifTagName) const
{
try
{
Exiv2::ExifKey exifKey(exifTagName);
Exiv2::ExifData::iterator it = d->exifMetadata().findKey(exifKey);
if (it != d->exifMetadata().end())
{
d->exifMetadata().erase(it);
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif tag using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::getExifTagRational(const char* exifTagName, long int& num, long int& den, int component) const
{
try
{
Exiv2::ExifKey exifKey(exifTagName);
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey);
if (it != exifData.end())
{
num = (*it).toRational(component).first;
den = (*it).toRational(component).second;
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif Rational value from key '%1' into image using Exiv2 ")
.arg(QLatin1String(exifTagName)), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setExifTagLong(const char* exifTagName, long val) const
{
try
{
d->exifMetadata()[exifTagName] = static_cast<int32_t>(val); // krazy:exclude=typedefs
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag long value into image using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setExifTagRational(const char* exifTagName, long int num, long int den) const
{
try
{
d->exifMetadata()[exifTagName] = Exiv2::Rational(num, den);
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag rational value into image using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setExifTagData(const char* exifTagName, const QByteArray& data) const
{
if (data.isEmpty())
return false;
try
{
Exiv2::DataValue val((Exiv2::byte*)data.data(), data.size());
d->exifMetadata()[exifTagName] = val;
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag data into image using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setExifTagVariant(const char* exifTagName, const QVariant& val,
bool rationalWantSmallDenominator) const
{
switch (val.type())
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::Bool:
case QVariant::LongLong:
case QVariant::ULongLong:
return setExifTagLong(exifTagName, val.toInt());
case QVariant::Double:
{
long num, den;
if (rationalWantSmallDenominator)
convertToRationalSmallDenominator(val.toDouble(), &num, &den);
else
convertToRational(val.toDouble(), &num, &den, 4);
return setExifTagRational(exifTagName, num, den);
}
case QVariant::List:
{
long num = 0, den = 1;
QList<QVariant> list = val.toList();
if (list.size() >= 1)
num = list[0].toInt();
if (list.size() >= 2)
den = list[1].toInt();
return setExifTagRational(exifTagName, num, den);
}
case QVariant::Date:
case QVariant::DateTime:
{
QDateTime dateTime = val.toDateTime();
if (!dateTime.isValid())
return false;
try
{
const std::string &exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData());
d->exifMetadata()[exifTagName] = exifdatetime;
}
catch( Exiv2::Error &e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Date & Time in image using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
case QVariant::String:
case QVariant::Char:
return setExifTagString(exifTagName, val.toString());
case QVariant::ByteArray:
return setExifTagData(exifTagName, val.toByteArray());
default:
break;
}
return false;
}
QString MetaEngine::createExifUserStringFromValue(const char* exifTagName, const QVariant& val, bool escapeCR)
{
try
{
Exiv2::ExifKey key(exifTagName);
Exiv2::Exifdatum datum(key);
switch (val.type())
{
case QVariant::Int:
case QVariant::Bool:
case QVariant::LongLong:
case QVariant::ULongLong:
datum = (int32_t)val.toInt();
break;
case QVariant::UInt:
datum = (uint32_t)val.toUInt();
break;
case QVariant::Double:
{
long num, den;
convertToRationalSmallDenominator(val.toDouble(), &num, &den);
Exiv2::Rational rational;
rational.first = num;
rational.second = den;
datum = rational;
break;
}
case QVariant::List:
{
long num = 0, den = 1;
QList<QVariant> list = val.toList();
if (list.size() >= 1)
num = list[0].toInt();
if (list.size() >= 2)
den = list[1].toInt();
Exiv2::Rational rational;
rational.first = num;
rational.second = den;
datum = rational;
break;
}
case QVariant::Date:
case QVariant::DateTime:
{
QDateTime dateTime = val.toDateTime();
if (!dateTime.isValid())
break;
const std::string &exifdatetime(dateTime.toString(QString::fromLatin1("yyyy:MM:dd hh:mm:ss")).toLatin1().constData());
datum = exifdatetime;
break;
}
case QVariant::ByteArray:
case QVariant::String:
case QVariant::Char:
datum = (std::string)val.toString().toLatin1().constData();
break;
default:
break;
}
std::ostringstream os;
os << datum;
QString tagValue = QString::fromLocal8Bit(os.str().c_str());
if (escapeCR)
tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
return tagValue;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Iptc tag string into image using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QString();
}
bool MetaEngine::getExifTagLong(const char* exifTagName, long& val) const
{
return getExifTagLong(exifTagName, val, 0);
}
bool MetaEngine::getExifTagLong(const char* exifTagName, long& val, int component) const
{
try
{
Exiv2::ExifKey exifKey(exifTagName);
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey);
if (it != exifData.end() && it->count() > 0)
{
val = it->toLong(component);
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ")
.arg(QLatin1String(exifTagName)), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
QByteArray MetaEngine::getExifTagData(const char* exifTagName) const
{
try
{
Exiv2::ExifKey exifKey(exifTagName);
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey);
if (it != exifData.end())
{
char* const s = new char[(*it).size()];
(*it).copy((Exiv2::byte*)s, Exiv2::bigEndian);
QByteArray data(s, (*it).size());
delete[] s;
return data;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ")
.arg(QLatin1String(exifTagName)), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QByteArray();
}
QVariant MetaEngine::getExifTagVariant(const char* exifTagName, bool rationalAsListOfInts, bool stringEscapeCR, int component) const
{
try
{
Exiv2::ExifKey exifKey(exifTagName);
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey);
if (it != exifData.end())
{
switch (it->typeId())
{
case Exiv2::unsignedByte:
case Exiv2::unsignedShort:
case Exiv2::unsignedLong:
case Exiv2::signedShort:
case Exiv2::signedLong:
if (it->count() > component)
return QVariant((int)it->toLong(component));
else
return QVariant(QVariant::Int);
case Exiv2::unsignedRational:
case Exiv2::signedRational:
if (rationalAsListOfInts)
{
if (it->count() <= component)
return QVariant(QVariant::List);
QList<QVariant> list;
list << (*it).toRational(component).first;
list << (*it).toRational(component).second;
return QVariant(list);
}
else
{
if (it->count() <= component)
return QVariant(QVariant::Double);
// prefer double precision
double num = (*it).toRational(component).first;
double den = (*it).toRational(component).second;
if (den == 0.0)
return QVariant(QVariant::Double);
return QVariant(num / den);
}
case Exiv2::date:
case Exiv2::time:
{
QDateTime dateTime = QDateTime::fromString(QLatin1String(it->toString().c_str()), Qt::ISODate);
return QVariant(dateTime);
}
case Exiv2::asciiString:
case Exiv2::comment:
case Exiv2::string:
{
std::ostringstream os;
it->write(os, &exifData);
QString tagValue = QString::fromLocal8Bit(os.str().c_str());
if (stringEscapeCR)
tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
return QVariant(tagValue);
}
default:
break;
}
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' in the image using Exiv2 ")
.arg(QLatin1String(exifTagName)), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QVariant();
}
QString MetaEngine::getExifTagString(const char* exifTagName, bool escapeCR) const
{
try
{
Exiv2::ExifKey exifKey(exifTagName);
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey);
if (it != exifData.end())
{
QString tagValue;
QString key = QLatin1String(it->key().c_str());
if (key == QLatin1String("Exif.CanonCs.LensType") && it->toLong() == 65535)
{
// FIXME: workaround for a possible crash in Exiv2 pretty-print function for the Exif.CanonCs.LensType.
tagValue = QString::fromLocal8Bit(it->toString().c_str());
}
else
{
// See BUG #184156 comment #13
std::string val = it->print(&exifData);
tagValue = QString::fromLocal8Bit(val.c_str());
}
if (escapeCR)
tagValue.replace(QLatin1Char('\n'), QLatin1String(" "));
return tagValue;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QString::fromLatin1("Cannot find Exif key '%1' into image using Exiv2 ")
.arg(QLatin1String(exifTagName)), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return QString();
}
bool MetaEngine::setExifTagString(const char* exifTagName, const QString& value) const
{
try
{
d->exifMetadata()[exifTagName] = std::string(value.toLatin1().constData());
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif tag string into image using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
QImage MetaEngine::getExifThumbnail(bool fixOrientation) const
{
QImage thumbnail;
if (d->exifMetadata().empty())
return thumbnail;
try
{
Exiv2::ExifThumbC thumb(d->exifMetadata());
Exiv2::DataBuf const c1 = thumb.copy();
thumbnail.loadFromData(c1.pData_, c1.size_);
if (!thumbnail.isNull())
{
if (fixOrientation)
{
Exiv2::ExifKey key1("Exif.Thumbnail.Orientation");
Exiv2::ExifKey key2("Exif.Image.Orientation");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(key1);
if (it == exifData.end())
{
it = exifData.findKey(key2);
}
if (it != exifData.end() && it->count())
{
long orientation = it->toLong();
qCDebug(DIGIKAM_METAENGINE_LOG) << "Exif Thumbnail Orientation: " << (int)orientation;
rotateExifQImage(thumbnail, (ImageOrientation)orientation);
}
return thumbnail;
}
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot get Exif Thumbnail using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return thumbnail;
}
bool MetaEngine::rotateExifQImage(QImage& image, ImageOrientation orientation) const
{
QMatrix matrix = MetaEngineRotation::toMatrix(orientation);
if ((orientation != ORIENTATION_NORMAL) && (orientation != ORIENTATION_UNSPECIFIED))
{
image = image.transformed(matrix);
return true;
}
return false;
}
bool MetaEngine::setExifThumbnail(const QImage& thumbImage) const
{
if (thumbImage.isNull())
{
return removeExifThumbnail();
}
try
{
QByteArray data;
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
thumbImage.save(&buffer, "JPEG");
Exiv2::ExifThumb thumb(d->exifMetadata());
thumb.setJpegThumbnail((Exiv2::byte *)data.data(), data.size());
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif Thumbnail using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setTiffThumbnail(const QImage& thumbImage) const
{
removeExifThumbnail();
try
{
// Make sure IFD0 is explicitly marked as a main image
Exiv2::ExifData::const_iterator pos = d->exifMetadata().findKey(Exiv2::ExifKey("Exif.Image.NewSubfileType"));
if (pos == d->exifMetadata().end() || pos->count() != 1 || pos->toLong() != 0)
{
throw Exiv2::Error(1, "Exif.Image.NewSubfileType missing or not set as main image");
}
// Remove sub-IFD tags
std::string subImage1("SubImage1");
for (Exiv2::ExifData::iterator md = d->exifMetadata().begin() ; md != d->exifMetadata().end() ;)
{
if (md->groupName() == subImage1)
{
md = d->exifMetadata().erase(md);
}
else
{
++md;
}
}
if (!thumbImage.isNull())
{
// Set thumbnail tags
QByteArray data;
QBuffer buffer(&data);
buffer.open(QIODevice::WriteOnly);
thumbImage.save(&buffer, "JPEG");
Exiv2::DataBuf buf((Exiv2::byte *)data.data(), data.size());
Exiv2::ULongValue val;
val.read("0");
val.setDataArea(buf.pData_, buf.size_);
d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormat"] = val;
d->exifMetadata()["Exif.SubImage1.JPEGInterchangeFormatLength"] = uint32_t(buf.size_);
d->exifMetadata()["Exif.SubImage1.Compression"] = uint16_t(6); // JPEG (old-style)
d->exifMetadata()["Exif.SubImage1.NewSubfileType"] = uint32_t(1); // Thumbnail image
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set TIFF Thumbnail using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::removeExifThumbnail() const
{
try
{
// Remove all IFD0 subimages.
Exiv2::ExifThumb thumb(d->exifMetadata());
thumb.erase();
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif Thumbnail using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
MetaEngine::TagsMap MetaEngine::getStdExifTagsList() const
{
try
{
QList<const Exiv2::TagInfo*> tags;
TagsMap tagsMap;
const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList();
while (gi->tagList_ != 0)
{
- // NOTE: See BUG #375809 : MPF tags = execption Exiv2 0.26
+ // NOTE: See BUG #375809 : MPF tags = exception Exiv2 0.26
if (QLatin1String(gi->ifdName_) != QLatin1String("Makernote"))
{
Exiv2::TagListFct tl = gi->tagList_;
const Exiv2::TagInfo* ti = tl();
while (ti->tag_ != 0xFFFF)
{
tags << ti;
++ti;
}
}
++gi;
}
for (QList<const Exiv2::TagInfo*>::iterator it = tags.begin() ; it != tags.end() ; ++it)
{
do
{
const Exiv2::TagInfo* const ti = *it;
if (ti)
{
QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str());
QStringList values;
values << QLatin1String(ti->name_) << QLatin1String(ti->title_) << QLatin1String(ti->desc_);
tagsMap.insert(key, values);
}
++(*it);
}
while((*it)->tag_ != 0xffff);
}
return tagsMap;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot get Exif Tags list using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return TagsMap();
}
MetaEngine::TagsMap MetaEngine::getMakernoteTagsList() const
{
try
{
QList<const Exiv2::TagInfo*> tags;
TagsMap tagsMap;
const Exiv2::GroupInfo* gi = Exiv2::ExifTags::groupList();
while (gi->tagList_ != 0)
{
if (QLatin1String(gi->ifdName_) == QLatin1String("Makernote"))
{
Exiv2::TagListFct tl = gi->tagList_;
const Exiv2::TagInfo* ti = tl();
while (ti->tag_ != 0xFFFF)
{
tags << ti;
++ti;
}
}
++gi;
}
for (QList<const Exiv2::TagInfo*>::iterator it = tags.begin() ; it != tags.end() ; ++it)
{
do
{
const Exiv2::TagInfo* const ti = *it;
if (ti)
{
QString key = QLatin1String(Exiv2::ExifKey(*ti).key().c_str());
QStringList values;
values << QLatin1String(ti->name_) << QLatin1String(ti->title_) << QLatin1String(ti->desc_);
tagsMap.insert(key, values);
}
++(*it);
}
while((*it)->tag_ != 0xffff);
}
return tagsMap;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot get Makernote Tags list using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return TagsMap();
}
} // namespace Digikam
diff --git a/core/libs/dmetadata/metaengine_gps.cpp b/core/libs/dmetadata/metaengine_gps.cpp
index 2452547ae6..de83d42a23 100644
--- a/core/libs/dmetadata/metaengine_gps.cpp
+++ b/core/libs/dmetadata/metaengine_gps.cpp
@@ -1,970 +1,970 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-09-15
* Description : Exiv2 library interface.
* GPS manipulation methods
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010-2012 by Michael G. Hansen <mike at mghansen dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "metaengine.h"
#include "metaengine_p.h"
// C ANSI includes
#include <math.h>
// C++ includes
#include <climits>
#include <cmath>
// Local includes
#include "digikam_debug.h"
namespace Digikam
{
bool MetaEngine::getGPSInfo(double& altitude, double& latitude, double& longitude) const
{
// Some GPS device do not set Altitude. So a valid GPS position can be with a zero value.
// No need to check return value.
getGPSAltitude(&altitude);
if (!getGPSLatitudeNumber(&latitude))
return false;
if (!getGPSLongitudeNumber(&longitude))
return false;
return true;
}
bool MetaEngine::getGPSLatitudeNumber(double* const latitude) const
{
try
{
*latitude = 0.0;
// Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
if (convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLatitude"), latitude))
return true;
// Now try to get the reference from Exif.
const QByteArray latRef = getExifTagData("Exif.GPSInfo.GPSLatitudeRef");
if (!latRef.isEmpty())
{
Exiv2::ExifKey exifKey("Exif.GPSInfo.GPSLatitude");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey);
if (it != exifData.end() && (*it).count() == 3)
{
double deg;
double min;
double sec;
deg = (double)((*it).toFloat(0));
if ((*it).toRational(0).second == 0 || deg == -1.0)
{
return false;
}
*latitude = deg;
min = (double)((*it).toFloat(1));
if ((*it).toRational(1).second == 0 || min == -1.0)
{
return false;
}
*latitude = *latitude + min/60.0;
sec = (double)((*it).toFloat(2));
if (sec != -1.0)
{
*latitude = *latitude + sec/3600.0;
}
}
else
{
return false;
}
if (latRef[0] == 'S')
{
*latitude *= -1.0;
}
if (*latitude < -90.0 || *latitude > 90.0)
{
return false;
}
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot get GPS tag using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::getGPSLongitudeNumber(double* const longitude) const
{
try
{
*longitude = 0.0;
// Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
if (convertFromGPSCoordinateString(getXmpTagString("Xmp.exif.GPSLongitude"), longitude))
return true;
// Now try to get the reference from Exif.
const QByteArray lngRef = getExifTagData("Exif.GPSInfo.GPSLongitudeRef");
if (!lngRef.isEmpty())
{
// Longitude decoding from Exif.
Exiv2::ExifKey exifKey2("Exif.GPSInfo.GPSLongitude");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey2);
if (it != exifData.end() && (*it).count() == 3)
{
/// @todo Decoding of latitude and longitude works in the same way,
/// code here can be put in a separate function
double deg;
double min;
double sec;
deg = (double)((*it).toFloat(0));
if ((*it).toRational(0).second == 0 || deg == -1.0)
{
return false;
}
*longitude = deg;
min = (double)((*it).toFloat(1));
if ((*it).toRational(1).second == 0 || min == -1.0)
{
return false;
}
*longitude = *longitude + min/60.0;
sec = (double)((*it).toFloat(2));
if (sec != -1.0)
{
*longitude = *longitude + sec/3600.0;
}
}
else
{
return false;
}
if (lngRef[0] == 'W')
{
*longitude *= -1.0;
}
if (*longitude < -180.0 || *longitude > 180.0)
{
return false;
}
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot get GPS tag using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::getGPSAltitude(double* const altitude) const
{
try
{
double num, den;
*altitude=0.0;
// Try XMP first. Reason: XMP in sidecar may be more up-to-date than EXIF in original image.
const QString altRefXmp = getXmpTagString("Xmp.exif.GPSAltitudeRef");
if (!altRefXmp.isEmpty())
{
const QString altXmp = getXmpTagString("Xmp.exif.GPSAltitude");
if (!altXmp.isEmpty())
{
num = altXmp.section(QLatin1Char('/'), 0, 0).toDouble();
den = altXmp.section(QLatin1Char('/'), 1, 1).toDouble();
if (den == 0)
return false;
*altitude = num/den;
if (altRefXmp == QLatin1String("1"))
*altitude *= -1.0;
return true;
}
}
// Get the reference from Exif (above/below sea level)
const QByteArray altRef = getExifTagData("Exif.GPSInfo.GPSAltitudeRef");
if (!altRef.isEmpty())
{
// Altitude decoding from Exif.
Exiv2::ExifKey exifKey3("Exif.GPSInfo.GPSAltitude");
Exiv2::ExifData exifData(d->exifMetadata());
Exiv2::ExifData::const_iterator it = exifData.findKey(exifKey3);
if (it != exifData.end() && (*it).count())
{
num = (double)((*it).toRational(0).first);
den = (double)((*it).toRational(0).second);
if (den == 0)
return false;
*altitude = num/den;
}
else
{
return false;
}
if (altRef[0] == '1')
*altitude *= -1.0;
return true;
}
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot get GPS tag using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
QString MetaEngine::getGPSLatitudeString() const
{
double latitude;
if (!getGPSLatitudeNumber(&latitude))
return QString();
return convertToGPSCoordinateString(true, latitude);
}
QString MetaEngine::getGPSLongitudeString() const
{
double longitude;
if (!getGPSLongitudeNumber(&longitude))
return QString();
return convertToGPSCoordinateString(false, longitude);
}
bool MetaEngine::initializeGPSInfo()
{
try
{
// TODO: what happens if these already exist?
// Do all the easy constant ones first.
// GPSVersionID tag: standard says is should be four bytes: 02 00 00 00
// (and, must be present).
Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
value->read("2 0 0 0");
d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSVersionID"), value.get());
// Datum: the datum of the measured data. If not given, we insert WGS-84.
d->exifMetadata()["Exif.GPSInfo.GPSMapDatum"] = "WGS-84";
#ifdef _XMP_SUPPORT_
setXmpTagString("Xmp.exif.GPSVersionID", QLatin1String("2.0.0.0"));
setXmpTagString("Xmp.exif.GPSMapDatum", QLatin1String("WGS-84"));
#endif // _XMP_SUPPORT_
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot initialize GPS data using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setGPSInfo(const double altitude, const double latitude, const double longitude)
{
return setGPSInfo(&altitude, latitude, longitude);
}
bool MetaEngine::setGPSInfo(const double* const altitude, const double latitude, const double longitude)
{
try
{
// In first, we need to clean up all existing GPS info.
removeGPSInfo();
// now re-initialize the GPS info:
if (!initializeGPSInfo())
return false;
char scratchBuf[100];
long int nom, denom;
long int deg, min;
// Now start adding data.
// ALTITUDE.
if (altitude)
{
- // Altitude reference: byte "00" meaning "above sea level", "01" mening "behing sea level".
+ // Altitude reference: byte "00" meaning "above sea level", "01" meaning "behind sea level".
Exiv2::Value::AutoPtr value = Exiv2::Value::create(Exiv2::unsignedByte);
if ((*altitude) >= 0) value->read("0");
else value->read("1");
d->exifMetadata().add(Exiv2::ExifKey("Exif.GPSInfo.GPSAltitudeRef"), value.get());
// And the actual altitude, as absolute value..
convertToRational(fabs(*altitude), &nom, &denom, 4);
snprintf(scratchBuf, 100, "%ld/%ld", nom, denom);
d->exifMetadata()["Exif.GPSInfo.GPSAltitude"] = scratchBuf;
#ifdef _XMP_SUPPORT_
setXmpTagString("Xmp.exif.GPSAltitudeRef", ((*altitude) >= 0) ? QLatin1String("0") : QLatin1String("1"));
setXmpTagString("Xmp.exif.GPSAltitude", QLatin1String(scratchBuf));
#endif // _XMP_SUPPORT_
}
// LATITUDE
// Latitude reference:
// latitude < 0 : "S"
// latitude > 0 : "N"
//
d->exifMetadata()["Exif.GPSInfo.GPSLatitudeRef"] = (latitude < 0 ) ? "S" : "N";
// Now the actual latitude itself.
// This is done as three rationals.
// I choose to do it as:
// dd/1 - degrees.
// mmmm/100 - minutes
// 0/1 - seconds
// Exif standard says you can do it with minutes
// as mm/1 and then seconds as ss/1, but its
// (slightly) more accurate to do it as
// mmmm/100 than to split it.
// We also absolute the value (with fabs())
// as the sign is encoded in LatRef.
// Further note: original code did not translate between
// dd.dddddd to dd mm.mm - that's why we now multiply
// by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
deg = (int)floor(fabs(latitude)); // Slice off after decimal.
min = (int)floor((fabs(latitude) - floor(fabs(latitude))) * 60000000);
snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
d->exifMetadata()["Exif.GPSInfo.GPSLatitude"] = scratchBuf;
#ifdef _XMP_SUPPORT_
/** @todo The XMP spec does not mention Xmp.exif.GPSLatitudeRef,
* because the reference is included in Xmp.exif.GPSLatitude.
* Is there a historic reason for writing it anyway?
*/
setXmpTagString("Xmp.exif.GPSLatitudeRef", (latitude < 0) ? QLatin1String("S") : QLatin1String("N"));
setXmpTagString("Xmp.exif.GPSLatitude", convertToGPSCoordinateString(true, latitude));
#endif // _XMP_SUPPORT_
// LONGITUDE
// Longitude reference:
// longitude < 0 : "W"
// longitude > 0 : "E"
d->exifMetadata()["Exif.GPSInfo.GPSLongitudeRef"] = (longitude < 0 ) ? "W" : "E";
// Now the actual longitude itself.
// This is done as three rationals.
// I choose to do it as:
// dd/1 - degrees.
// mmmm/100 - minutes
// 0/1 - seconds
// Exif standard says you can do it with minutes
// as mm/1 and then seconds as ss/1, but its
// (slightly) more accurate to do it as
// mmmm/100 than to split it.
// We also absolute the value (with fabs())
// as the sign is encoded in LongRef.
// Further note: original code did not translate between
// dd.dddddd to dd mm.mm - that's why we now multiply
// by 6000 - x60 to get minutes, x1000000 to get to mmmm/1000000.
deg = (int)floor(fabs(longitude)); // Slice off after decimal.
min = (int)floor((fabs(longitude) - floor(fabs(longitude))) * 60000000);
snprintf(scratchBuf, 100, "%ld/1 %ld/1000000 0/1", deg, min);
d->exifMetadata()["Exif.GPSInfo.GPSLongitude"] = scratchBuf;
#ifdef _XMP_SUPPORT_
/** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
* because the reference is included in Xmp.exif.GPSLongitude.
* Is there a historic reason for writing it anyway?
*/
setXmpTagString("Xmp.exif.GPSLongitudeRef", (longitude < 0) ? QLatin1String("W") : QLatin1String("E"));
setXmpTagString("Xmp.exif.GPSLongitude", convertToGPSCoordinateString(false, longitude));
#endif // _XMP_SUPPORT_
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot set Exif GPS tag using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
bool MetaEngine::setGPSInfo(const double altitude, const QString& latitude, const QString& longitude)
{
double longitudeValue, latitudeValue;
if (!convertFromGPSCoordinateString(latitude, &latitudeValue))
return false;
if (!convertFromGPSCoordinateString(longitude, &longitudeValue))
return false;
return setGPSInfo(&altitude, latitudeValue, longitudeValue);
}
bool MetaEngine::removeGPSInfo()
{
try
{
QStringList gpsTagsKeys;
for (Exiv2::ExifData::const_iterator it = d->exifMetadata().begin();
it != d->exifMetadata().end(); ++it)
{
QString key = QString::fromLocal8Bit(it->key().c_str());
if (key.section(QLatin1Char('.'), 1, 1) == QLatin1String("GPSInfo"))
gpsTagsKeys.append(key);
}
for(QStringList::const_iterator it2 = gpsTagsKeys.constBegin(); it2 != gpsTagsKeys.constEnd(); ++it2)
{
Exiv2::ExifKey gpsKey((*it2).toLatin1().constData());
Exiv2::ExifData::iterator it3 = d->exifMetadata().findKey(gpsKey);
if (it3 != d->exifMetadata().end())
d->exifMetadata().erase(it3);
}
#ifdef _XMP_SUPPORT_
/** @todo The XMP spec does not mention Xmp.exif.GPSLongitudeRef,
* and Xmp.exif.GPSLatitudeRef. But because we write them in setGPSInfo(),
* we should also remove them here.
*/
removeXmpTag("Xmp.exif.GPSLatitudeRef");
removeXmpTag("Xmp.exif.GPSLongitudeRef");
removeXmpTag("Xmp.exif.GPSVersionID");
removeXmpTag("Xmp.exif.GPSLatitude");
removeXmpTag("Xmp.exif.GPSLongitude");
removeXmpTag("Xmp.exif.GPSAltitudeRef");
removeXmpTag("Xmp.exif.GPSAltitude");
removeXmpTag("Xmp.exif.GPSTimeStamp");
removeXmpTag("Xmp.exif.GPSSatellites");
removeXmpTag("Xmp.exif.GPSStatus");
removeXmpTag("Xmp.exif.GPSMeasureMode");
removeXmpTag("Xmp.exif.GPSDOP");
removeXmpTag("Xmp.exif.GPSSpeedRef");
removeXmpTag("Xmp.exif.GPSSpeed");
removeXmpTag("Xmp.exif.GPSTrackRef");
removeXmpTag("Xmp.exif.GPSTrack");
removeXmpTag("Xmp.exif.GPSImgDirectionRef");
removeXmpTag("Xmp.exif.GPSImgDirection");
removeXmpTag("Xmp.exif.GPSMapDatum");
removeXmpTag("Xmp.exif.GPSDestLatitude");
removeXmpTag("Xmp.exif.GPSDestLongitude");
removeXmpTag("Xmp.exif.GPSDestBearingRef");
removeXmpTag("Xmp.exif.GPSDestBearing");
removeXmpTag("Xmp.exif.GPSDestDistanceRef");
removeXmpTag("Xmp.exif.GPSDestDistance");
removeXmpTag("Xmp.exif.GPSProcessingMethod");
removeXmpTag("Xmp.exif.GPSAreaInformation");
removeXmpTag("Xmp.exif.GPSDifferential");
#endif // _XMP_SUPPORT_
return true;
}
catch( Exiv2::Error& e )
{
d->printExiv2ExceptionError(QLatin1String("Cannot remove Exif GPS tag using Exiv2 "), e);
}
catch(...)
{
qCCritical(DIGIKAM_METAENGINE_LOG) << "Default exception from Exiv2";
}
return false;
}
void MetaEngine::convertToRational(const double number, long int* const numerator,
long int* const denominator, const int rounding)
{
// This function converts the given decimal number
// to a rational (fractional) number.
//
// Examples in comments use Number as 25.12345, Rounding as 4.
// Split up the number.
double whole = trunc(number);
double fractional = number - whole;
// Calculate the "number" used for rounding.
// This is 10^Digits - ie, 4 places gives us 10000.
double rounder = pow(10.0, rounding);
// Round the fractional part, and leave the number
// as greater than 1.
// To do this we: (for example)
// 0.12345 * 10000 = 1234.5
// floor(1234.5) = 1234 - now bigger than 1 - ready...
fractional = round(fractional * rounder);
// Convert the whole thing to a fraction.
// Fraction is:
// (25 * 10000) + 1234 251234
// ------------------- = ------ = 25.1234
// 10000 10000
double numTemp = (whole * rounder) + fractional;
double denTemp = rounder;
// Now we should reduce until we can reduce no more.
// Try simple reduction...
// if Num
// ----- = integer out then....
// Den
if (trunc(numTemp / denTemp) == (numTemp / denTemp))
{
// Divide both by Denominator.
numTemp /= denTemp;
// cppcheck-suppress duplicateExpression
denTemp /= denTemp;
}
// And, if that fails, brute force it.
while (1)
{
// Jump out if we can't integer divide one.
if ((numTemp / 2) != trunc(numTemp / 2)) break;
if ((denTemp / 2) != trunc(denTemp / 2)) break;
// Otherwise, divide away.
numTemp /= 2;
denTemp /= 2;
}
// Copy out the numbers.
*numerator = (int)numTemp;
*denominator = (int)denTemp;
}
void MetaEngine::convertToRationalSmallDenominator(const double number, long int* const numerator, long int* const denominator)
{
// This function converts the given decimal number
// to a rational (fractional) number.
//
// This method, in contrast to the method above, will retrieve the smallest possible
// denominator. It is tested to retrieve the correct value for 1/x, with 0 < x <= 1000000.
// Note: This requires double precision, storing in float breaks some numbers (49, 59, 86,...)
// Split up the number.
double whole = trunc(number);
double fractional = number - whole;
/*
* Find best rational approximation to a double
* by C.B. Falconer, 2006-09-07. Released to public domain.
*
* Newsgroups: comp.lang.c, comp.programming
* From: CBFalconer <cbfalconer@yahoo.com>
* Date: Thu, 07 Sep 2006 17:35:30 -0400
* Subject: Rational approximations
*/
int lastnum = 500; // this is _not_ the largest possible denominator
long int num, approx, bestnum=0, bestdenom=1;
double value, error, leasterr, criterion;
value = fractional;
if (value == 0.0)
{
*numerator = (long int)whole;
*denominator = 1;
return;
}
criterion = 2 * value * DBL_EPSILON;
for (leasterr = value, num = 1; num < lastnum; ++num)
{
approx = (int)(num / value + 0.5);
error = fabs((double)num / approx - value);
if (error < leasterr)
{
bestnum = num;
bestdenom = approx;
leasterr = error;
if (leasterr <= criterion) break;
}
}
// add whole number part
if (bestdenom * whole > (double)INT_MAX)
{
// In some cases, we would generate an integer overflow.
// Fall back to Gilles's code which is better suited for such numbers.
convertToRational(number, numerator, denominator, 5);
}
else
{
bestnum += bestdenom * (long int)whole;
*numerator = bestnum;
*denominator = bestdenom;
}
}
QString MetaEngine::convertToGPSCoordinateString(const long int numeratorDegrees, const long int denominatorDegrees,
const long int numeratorMinutes, const long int denominatorMinutes,
const long int numeratorSeconds, long int denominatorSeconds,
const char directionReference)
{
/**
* Precision:
* A second at sea level measures 30m for our purposes, a minute 1800m.
* (for more details, see http://en.wikipedia.org/wiki/Geographic_coordinate_system)
* This means with a decimal precision of 8 for minutes we get +/-0,018mm.
* (if I calculated correctly)
*/
QString coordinate;
// be relaxed with seconds of 0/0
if (denominatorSeconds == 0 && numeratorSeconds == 0)
denominatorSeconds = 1;
if (denominatorDegrees == 1 &&
denominatorMinutes == 1 &&
denominatorSeconds == 1)
{
// use form DDD,MM,SSk
coordinate = QLatin1String("%1,%2,%3%4");
coordinate = coordinate.arg(numeratorDegrees).arg(numeratorMinutes).arg(numeratorSeconds).arg(directionReference);
}
else if (denominatorDegrees == 1 &&
denominatorMinutes == 100 &&
denominatorSeconds == 1)
{
// use form DDD,MM.mmk
coordinate = QLatin1String("%1,%2%3");
double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
minutes += (double)numeratorSeconds / 60.0;
QString minutesString = QString::number(minutes, 'f', 8);
while (minutesString.endsWith(QLatin1String("0")) && !minutesString.endsWith(QLatin1String(".0")))
{
minutesString.chop(1);
}
coordinate = coordinate.arg(numeratorDegrees).arg(minutesString).arg(directionReference);
}
else if (denominatorDegrees == 0 ||
denominatorMinutes == 0 ||
denominatorSeconds == 0)
{
// Invalid. 1/0 is everything but 0. As is 0/0.
return QString();
}
else
{
// use form DDD,MM.mmk
coordinate = QLatin1String("%1,%2%3");
double degrees = (double)numeratorDegrees / (double)denominatorDegrees;
double wholeDegrees = trunc(degrees);
double minutes = (double)numeratorMinutes / (double)denominatorMinutes;
minutes += (degrees - wholeDegrees) * 60.0;
minutes += ((double)numeratorSeconds / (double)denominatorSeconds) / 60.0;
QString minutesString = QString::number(minutes, 'f', 8);
while (minutesString.endsWith(QLatin1String("0")) && !minutesString.endsWith(QLatin1String(".0")))
{
minutesString.chop(1);
}
coordinate = coordinate.arg((int)wholeDegrees).arg(minutesString).arg(directionReference);
}
return coordinate;
}
QString MetaEngine::convertToGPSCoordinateString(const bool isLatitude, double coordinate)
{
if (coordinate < -360.0 || coordinate > 360.0)
return QString();
QString coordinateString;
char directionReference;
if (isLatitude)
{
if (coordinate < 0)
directionReference = 'S';
else
directionReference = 'N';
}
else
{
if (coordinate < 0)
directionReference = 'W';
else
directionReference = 'E';
}
// remove sign
coordinate = fabs(coordinate);
int degrees = (int)floor(coordinate);
// get fractional part
coordinate = coordinate - (double)(degrees);
// To minutes
double minutes = coordinate * 60.0;
// use form DDD,MM.mmk
coordinateString = QLatin1String("%1,%2%3");
coordinateString = coordinateString.arg(degrees);
coordinateString = coordinateString.arg(minutes, 0, 'f', 8).arg(directionReference);
return coordinateString;
}
bool MetaEngine::convertFromGPSCoordinateString(const QString& gpsString,
long int* const numeratorDegrees, long int* const denominatorDegrees,
long int* const numeratorMinutes, long int* const denominatorMinutes,
long int* const numeratorSeconds, long int* const denominatorSeconds,
char* const directionReference)
{
if (gpsString.isEmpty())
return false;
*directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
QString coordinate = gpsString.left(gpsString.length() - 1);
QStringList parts = coordinate.split(QLatin1String(","));
if (parts.size() == 2)
{
// form DDD,MM.mmk
*denominatorDegrees = 1;
*denominatorMinutes = 1000000;
*denominatorSeconds = 1;
*numeratorDegrees = parts[0].toLong();
double minutes = parts[1].toDouble();
minutes *= 1000000;
*numeratorMinutes = (long)round(minutes);
*numeratorSeconds = 0;
return true;
}
else if (parts.size() == 3)
{
// use form DDD,MM,SSk
*denominatorDegrees = 1;
*denominatorMinutes = 1;
*denominatorSeconds = 1;
*numeratorDegrees = parts[0].toLong();
*numeratorMinutes = parts[1].toLong();
*numeratorSeconds = parts[2].toLong();
return true;
}
else
{
return false;
}
}
bool MetaEngine::convertFromGPSCoordinateString(const QString& gpsString, double* const degrees)
{
if (gpsString.isEmpty())
return false;
char directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
QString coordinate = gpsString.left(gpsString.length() - 1);
QStringList parts = coordinate.split(QLatin1String(","));
if (parts.size() == 2)
{
// form DDD,MM.mmk
*degrees = parts[0].toLong();
*degrees += parts[1].toDouble() / 60.0;
if (directionReference == 'W' || directionReference == 'S')
*degrees *= -1.0;
return true;
}
else if (parts.size() == 3)
{
// use form DDD,MM,SSk
*degrees = parts[0].toLong();
*degrees += parts[1].toLong() / 60.0;
*degrees += parts[2].toLong() / 3600.0;
if (directionReference == 'W' || directionReference == 'S')
*degrees *= -1.0;
return true;
}
else
{
return false;
}
}
bool MetaEngine::convertToUserPresentableNumbers(const QString& gpsString,
int* const degrees, int* const minutes,
double* const seconds, char* const directionReference)
{
if (gpsString.isEmpty())
return false;
*directionReference = gpsString.at(gpsString.length() - 1).toUpper().toLatin1();
QString coordinate = gpsString.left(gpsString.length() - 1);
QStringList parts = coordinate.split(QLatin1String(","));
if (parts.size() == 2)
{
// form DDD,MM.mmk
*degrees = parts[0].toInt();
double fractionalMinutes = parts[1].toDouble();
*minutes = (int)trunc(fractionalMinutes);
*seconds = (fractionalMinutes - (double)(*minutes)) * 60.0;
return true;
}
else if (parts.size() == 3)
{
// use form DDD,MM,SSk
*degrees = parts[0].toInt();
*minutes = parts[1].toInt();
*seconds = (double)parts[2].toInt();
return true;
}
else
{
return false;
}
}
void MetaEngine::convertToUserPresentableNumbers(const bool isLatitude, double coordinate,
int* const degrees, int* const minutes,
double* const seconds, char* const directionReference)
{
if (isLatitude)
{
if (coordinate < 0)
*directionReference = 'S';
else
*directionReference = 'N';
}
else
{
if (coordinate < 0)
*directionReference = 'W';
else
*directionReference = 'E';
}
// remove sign
coordinate = fabs(coordinate);
*degrees = (int)floor(coordinate);
// get fractional part
coordinate = coordinate - (double)(*degrees);
// To minutes
coordinate *= 60.0;
*minutes = (int)floor(coordinate);
// get fractional part
coordinate = coordinate - (double)(*minutes);
// To seconds
coordinate *= 60.0;
*seconds = coordinate;
}
} // namespace Digikam
diff --git a/core/libs/dmetadata/metaengine_rotation.h b/core/libs/dmetadata/metaengine_rotation.h
index efa4924e8f..3eedfa6c43 100644
--- a/core/libs/dmetadata/metaengine_rotation.h
+++ b/core/libs/dmetadata/metaengine_rotation.h
@@ -1,118 +1,118 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-09-15
* Description : Exiv2 library interface.
* Tools for combining rotation operations.
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef META_ENGINE_ROTATION_H
#define META_ENGINE_ROTATION_H
// Qt includes
#include <QMatrix>
// Local includes
#include "digikam_export.h"
#include "metaengine.h"
namespace Digikam
{
class DIGIKAM_EXPORT MetaEngineRotation
{
public:
/** This describes single transform primitives.
* Note some of the defined Exif rotation flags combine
* two of these actions.
* The enum values correspond to those defined
* as JXFORM_CODE in the often used the JPEG tool transupp.h.
*/
enum TransformationAction
{
NoTransformation = 0, /// no transformation
FlipHorizontal = 1, /// horizontal flip
FlipVertical = 2, /// vertical flip
Rotate90 = 5, /// 90-degree clockwise rotation
Rotate180 = 6, /// 180-degree rotation
Rotate270 = 7 /// 270-degree clockwise (or 90 ccw)
};
public:
/// Constructs the identity matrix (the matrix describing no transformation)
MetaEngineRotation();
/// Returns the matrix corresponding to the given TransformationAction
explicit MetaEngineRotation(TransformationAction action);
/// Returns the matrix corresponding to the given TransformationAction
explicit MetaEngineRotation(MetaEngine::ImageOrientation exifOrientation);
bool operator==(const MetaEngineRotation& ma) const;
bool operator!=(const MetaEngineRotation& ma) const;
/// Returns true of this matrix describes no transformation (is the identity matrix)
bool isNoTransform() const;
MetaEngineRotation& operator*=(const MetaEngineRotation& ma);
/// Applies the given transform to this matrix
MetaEngineRotation& operator*=(TransformationAction action);
/// Applies the given transform actions to this matrix
MetaEngineRotation& operator*=(QList<TransformationAction> actions);
/// Applies the given Exif orientation flag to this matrix
MetaEngineRotation& operator*=(MetaEngine::ImageOrientation exifOrientation);
/** Returns the actions described by this matrix. The order matters.
* Not all possible matrices are supported, but all those that can be combined
* by Exif rotation flags and the transform actions above.
* If isNoTransform() or the matrix is not supported returns an empty list. */
QList<TransformationAction> transformations() const;
- /** Returns the Exif orienation flag describing this matrix.
+ /** Returns the Exif orientation flag describing this matrix.
* Returns ORIENTATION_UNSPECIFIED if no flag matches this matrix.
*/
MetaEngine::ImageOrientation exifOrientation() const;
/// Returns a QMatrix representing this matrix
QMatrix toMatrix() const;
/// Returns a QMatrix for the given Exif orientation
static QMatrix toMatrix(MetaEngine::ImageOrientation orientation);
MetaEngineRotation(int m11, int m12, int m21, int m22);
protected:
void set(int m11, int m12, int m21, int m22);
protected:
int m[2][2];
};
} // namespace Digikam
#endif // META_ENGINE_ROTATION_H
diff --git a/core/libs/dmetadata/template.h b/core/libs/dmetadata/template.h
index ad566abf5c..ac84860261 100644
--- a/core/libs/dmetadata/template.h
+++ b/core/libs/dmetadata/template.h
@@ -1,162 +1,162 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-06-20
* Description : Template information container.
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_TEMPLATE_H
#define DIGIKAM_TEMPLATE_H
// Qt includes
#include <QMetaType>
#include <QString>
#include <QStringList>
#include <QDebug>
// Local includes
#include "metadatainfo.h"
#include "digikam_export.h"
#include "metaengine.h"
namespace Digikam
{
class TemplatePrivate;
class DIGIKAM_EXPORT Template
{
public:
explicit Template();
~Template();
/**
* Return true if Template title is null
*/
bool isNull() const;
/**
* Return true if Template contents is empty
*/
bool isEmpty() const;
/**
* Compare for metadata equality, not including "templateTitle" value.
*/
bool operator==(const Template& t) const;
void setTemplateTitle(const QString& title);
QString templateTitle() const;
void setAuthors(const QStringList& authors);
void setAuthorsPosition(const QString& authorPosition);
void setCredit(const QString& credit);
void setCopyright(const MetaEngine::AltLangMap& copyright);
void setRightUsageTerms(const MetaEngine::AltLangMap& rightUsageTerms);
void setSource(const QString& source);
void setInstructions(const QString& instructions);
void setLocationInfo(const IptcCoreLocationInfo& inf);
void setContactInfo(const IptcCoreContactInfo& inf);
void setIptcSubjects(const QStringList& subjects);
QStringList authors() const;
QString authorsPosition() const;
QString credit() const;
MetaEngine::AltLangMap copyright() const;
MetaEngine::AltLangMap rightUsageTerms() const;
QString source() const;
QString instructions() const;
IptcCoreLocationInfo locationInfo() const;
IptcCoreContactInfo contactInfo() const;
QStringList IptcSubjects() const;
static QString removeTemplateTitle()
{
return QLatin1String("_REMOVE_TEMPLATE_");
};
protected:
/**
- * Template title used internaly. This value always exist and cannot be empty.
+ * Template title used internally. This value always exist and cannot be empty.
*/
QString m_templateTitle;
/**
* List of author names.
*/
QStringList m_authors;
/**
* Description of authors position.
*/
QString m_authorsPosition;
/**
* Credit description.
*/
QString m_credit;
/**
* Language alternative copyright notices.
*/
MetaEngine::AltLangMap m_copyright;
/**
* Language alternative right term usages.
*/
MetaEngine::AltLangMap m_rightUsageTerms;
/**
* Descriptions of contents source.
*/
QString m_source;
/**
* Special instructions to process with contents.
*/
QString m_instructions;
/**
* IPTC Location Information.
*/
IptcCoreLocationInfo m_locationInfo;
/**
* IPTC Contact Information.
*/
IptcCoreContactInfo m_contactInfo;
/**
* IPTC Subjects Information.
*/
QStringList m_subjects;
};
//! qDebug() stream operator. Writes property @a t to the debug output in a nicely formatted way.
DIGIKAM_EXPORT QDebug operator<<(QDebug dbg, const Template& t);
} // namespace Digikam
Q_DECLARE_METATYPE(Digikam::Template)
#endif // DIGIKAM_TEMPLATE_H
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_1d_table.h b/core/libs/dngwriter/extra/dng_sdk/dng_1d_table.h
index 1e0240fe90..da869f7c16 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_1d_table.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_1d_table.h
@@ -1,122 +1,122 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_1d_table.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Definition of a lookup table based 1D floating-point to floating-point function abstraction using linear interpolation.
*/
/*****************************************************************************/
#ifndef __dng_1d_table__
#define __dng_1d_table__
/*****************************************************************************/
#include "dng_assertions.h"
#include "dng_auto_ptr.h"
#include "dng_classes.h"
#include "dng_types.h"
/*****************************************************************************/
/// \brief A 1D floating-point lookup table using linear interpolation.
class dng_1d_table
{
public:
/// Constants denoting size of table.
enum
{
kTableBits = 12, //< Table is always a power of 2 in size. This is log2(kTableSize).
kTableSize = (1 << kTableBits) //< Number of entries in table.
};
protected:
AutoPtr<dng_memory_block> fBuffer;
real32 *fTable;
public:
dng_1d_table ();
virtual ~dng_1d_table ();
- /// Set up table, initialize entries using functiion.
+ /// Set up table, initialize entries using function.
/// This method can throw an exception, e.g. if there is not enough memory.
/// \param allocator Memory allocator from which table memory is allocated.
- /// \param function Table is initialized with values of finction.Evalluate(0.0) to function.Evaluate(1.0).
+ /// \param function Table is initialized with values of function.Evaluate(0.0) to function.Evaluate(1.0).
/// \param subSample If true, only sample the function a limited number of times and interpolate.
void Initialize (dng_memory_allocator &allocator,
const dng_1d_function &function,
bool subSample = false);
/// Lookup and interpolate mapping for an input.
/// \param x value from 0.0 to 1.0 used as input for mapping
/// \retval Approximation of function.Evaluate(x)
real32 Interpolate (real32 x) const
{
real32 y = x * (real32) kTableSize;
int32 index = (int32) y;
DNG_ASSERT (index >= 0 && index <= kTableSize,
"dng_1d_table::Interpolate parameter out of range");
real32 z = (real32) index;
real32 fract = y - z;
return fTable [index ] * (1.0f - fract) +
fTable [index + 1] * ( fract);
}
/// Direct access function for table data.
const real32 * Table () const
{
return fTable;
}
/// Expand the table to a 16-bit to 16-bit table.
void Expand16 (uint16 *table16) const;
private:
void SubDivide (const dng_1d_function &function,
uint32 lower,
uint32 upper,
real32 maxDelta);
// Hidden copy constructor and assignment operator.
dng_1d_table (const dng_1d_table &table);
dng_1d_table & operator= (const dng_1d_table &table);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_area_task.h b/core/libs/dngwriter/extra/dng_sdk/dng_area_task.h
index 7b54f83638..0be7026fa9 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_area_task.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_area_task.h
@@ -1,198 +1,198 @@
/*****************************************************************************/
// Copyright 2006 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_area_task.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Class to handle partitioning a rectangular image processing operation taking into account multiple processing resources and memory constraints.
*/
/*****************************************************************************/
#ifndef __dng_area_task__
#define __dng_area_task__
/*****************************************************************************/
#include "dng_classes.h"
#include "dng_point.h"
#include "dng_types.h"
/*****************************************************************************/
/// \brief Abstract class for rectangular processing operations with support for partitioning across multiple processing resources and observing memory constraints.
class dng_area_task
{
protected:
uint32 fMaxThreads;
uint32 fMinTaskArea;
dng_point fUnitCell;
dng_point fMaxTileSize;
public:
dng_area_task ();
virtual ~dng_area_task ();
/// Getter for the maximum number of threads (resources) that can be used for processing
///
/// \retval Number of threads, minimum of 1, that can be used for this task.
virtual uint32 MaxThreads () const
{
return fMaxThreads;
}
/// Getter for minimum area of a partitioned rectangle.
/// Often it is not profitable to use more resources if it requires partitioning the input into chunks that are too small,
- /// as the overhead increases more than the speedup. This method can be ovreridden for a specific task to indicate the smallest
+ /// as the overhead increases more than the speedup. This method can be overridden for a specific task to indicate the smallest
/// area for partitioning. Default is 256x256 pixels.
///
- /// \retval Minimum area for a partitoned tile in order to give performant operation. (Partitions can be smaller due to small inputs and edge cases.)
+ /// \retval Minimum area for a partitioned tile in order to give performant operation. (Partitions can be smaller due to small inputs and edge cases.)
virtual uint32 MinTaskArea () const
{
return fMinTaskArea;
}
/// Getter for dimensions of which partitioned tiles should be a multiple.
/// Various methods of processing prefer certain alignments. The partitioning attempts to construct tiles such that the
/// sizes are a multiple of the dimensions of this point.
///
/// \retval a point giving preferred alignment in x and y
virtual dng_point UnitCell () const
{
return fUnitCell;
}
/// Getter for maximum size of a tile for processing.
/// Often processing will need to allocate temporary buffers or use other resources that are either fixed or in limited supply.
/// The maximum tile size forces further partitioning if the tile is bigger than this size.
///
/// \retval Maximum tile size allowed for this area task.
virtual dng_point MaxTileSize () const
{
return fMaxTileSize;
}
/// Getter for RepeatingTile1.
/// RepeatingTile1, RepeatingTile2, and RepeatingTile3 are used to establish a set of 0 to 3 tile patterns for which
/// the resulting partitions that the final Process method is called on will not cross tile boundaries in any of the
/// tile patterns. This can be used for a processing routine that needs to read from two tiles and write to a third
/// such that all the tiles are aligned and sized in a certain way. A RepeatingTile value is valid if it is non-empty.
/// Higher numbered RepeatingTile patterns are only used if all lower ones are non-empty. A RepeatingTile pattern must
/// be a multiple of UnitCell in size for all constraints of the partitionerr to be met.
virtual dng_rect RepeatingTile1 () const;
/// Getter for RepeatingTile2.
/// RepeatingTile1, RepeatingTile2, and RepeatingTile3 are used to establish a set of 0 to 3 tile patterns for which
/// the resulting partitions that the final Process method is called on will not cross tile boundaries in any of the
/// tile patterns. This can be used for a processing routine that needs to read from two tiles and write to a third
/// such that all the tiles are aligned and sized in a certain way. A RepeatingTile value is valid if it is non-empty.
/// Higher numbered RepeatingTile patterns are only used if all lower ones are non-empty. A RepeatingTile pattern must
/// be a multiple of UnitCell in size for all constraints of the partitionerr to be met.
virtual dng_rect RepeatingTile2 () const;
/// Getter for RepeatingTile3.
/// RepeatingTile1, RepeatingTile2, and RepeatingTile3 are used to establish a set of 0 to 3 tile patterns for which
/// the resulting partitions that the final Process method is called on will not cross tile boundaries in any of the
/// tile patterns. This can be used for a processing routine that needs to read from two tiles and write to a third
/// such that all the tiles are aligned and sized in a certain way. A RepeatingTile value is valid if it is non-empty.
/// Higher numbered RepeatingTile patterns are only used if all lower ones are non-empty. A RepeatingTile pattern must
/// be a multiple of UnitCell in size for all constraints of the partitionerr to be met.
virtual dng_rect RepeatingTile3 () const;
/// Task startup method called before any processing is done on partitions.
/// The Start method is called before any processing is done and can be overridden to allocate temporary buffers, etc.
///
/// \param threadCount Total number of threads that will be used for processing. Less than or equal to MaxThreads.
/// \param tileSize Size of source tiles which will be processed. (Not all tiles will be this size due to edge conditions.)
/// \param allocator dng_memory_allocator to use for allocating temporary buffers, etc.
/// \param sniffer Sniffer to test for user cancellation and to set up progress.
virtual void Start (uint32 threadCount,
const dng_point &tileSize,
dng_memory_allocator *allocator,
dng_abort_sniffer *sniffer);
/// Process one tile or fully partitioned area.
/// This method is overridden by derived classes to implement the actual image processing. Note that the sniffer can be ignored if it is certain that a
/// processing task will complete very quickly.
/// This method should never be called directly but rather accessed via Process.
/// There is no allocator parameter as all allocation should be done in Start.
///
/// \param threadIndex 0 to threadCount - 1 index indicating which thread this is. (Can be used to get a thread-specific buffer allocated in the Start method.)
/// \param tile Area to process.
/// \param sniffer dng_abort_sniffer to use to check for user cancellation and progress updates.
virtual void Process (uint32 threadIndex,
const dng_rect &tile,
dng_abort_sniffer *sniffer) = 0;
/// Task computation finalization and teardown method.
/// Called after all resources have completed processing. Can be overridden to accumulate results and free resources allocated in Start.
///
/// \param threadCount Number of threads used for processing. Same as value passed to Start.
virtual void Finish (uint32 threadCount);
/// Find tile size taking into account repeating tiles, unit cell, and maximum tile size.
/// \param area Computation area for which to find tile size.
/// \retval Tile size as height and width in point.
dng_point FindTileSize (const dng_rect &area) const;
/// Handle one resource's worth of partitioned tiles.
/// Called after thread partitioning has already been done. Area may be further subdivided to handle maximum tile size, etc.
/// It will be rare to override this method.
///
/// \param threadIndex 0 to threadCount - 1 index indicating which thread this is.
/// \param area Tile area partitioned to this resource.
/// \param tileSize
/// \param sniffer dng_abort_sniffer to use to check for user cancellation and progress updates.
void ProcessOnThread (uint32 threadIndex,
const dng_rect &area,
const dng_point &tileSize,
dng_abort_sniffer *sniffer);
/// Default resource partitioner that assumes a single resource to be used for processing.
/// Implementations that are aware of multiple processing resources should override (replace) this method.
/// This is usually done in dng_host::PerformAreaTask .
/// \param task The task to perform.
/// \param area The area on which mage processing should be performed.
/// \param allocator dng_memory_allocator to use for allocating temporary buffers, etc.
/// \param sniffer dng_abort_sniffer to use to check for user cancellation and progress updates.
static void Perform (dng_area_task &task,
const dng_rect &area,
dng_memory_allocator *allocator,
dng_abort_sniffer *sniffer);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.cpp
index d4b5602fe6..87f0b1a514 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.cpp
@@ -1,1261 +1,1261 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_camera_profile.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
#include "dng_camera_profile.h"
#include "dng_assertions.h"
#include "dng_host.h"
#include "dng_exceptions.h"
#include "dng_image_writer.h"
#include "dng_info.h"
#include "dng_parse_utils.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_temperature.h"
#include "dng_xy_coord.h"
/*****************************************************************************/
const char * kProfileName_Embedded = "Embedded";
const char * kAdobeCalibrationSignature = "com.adobe";
/*****************************************************************************/
dng_camera_profile::dng_camera_profile ()
: fName ()
, fCalibrationIlluminant1 (lsUnknown)
, fCalibrationIlluminant2 (lsUnknown)
, fColorMatrix1 ()
, fColorMatrix2 ()
, fForwardMatrix1 ()
, fForwardMatrix2 ()
, fReductionMatrix1 ()
, fReductionMatrix2 ()
, fFingerprint ()
, fCopyright ()
, fEmbedPolicy (pepAllowCopying)
, fHueSatDeltas1 ()
, fHueSatDeltas2 ()
, fLookTable ()
, fToneCurve ()
, fProfileCalibrationSignature ()
, fUniqueCameraModelRestriction ()
, fWasReadFromDNG (false)
, fWasStubbed (false)
{
fToneCurve.SetInvalid ();
}
/*****************************************************************************/
dng_camera_profile::~dng_camera_profile ()
{
}
/*****************************************************************************/
real64 dng_camera_profile::IlluminantToTemperature (uint32 light)
{
switch (light)
{
case lsStandardLightA:
case lsTungsten:
{
return 2850.0;
}
case lsISOStudioTungsten:
{
return 3200.0;
}
case lsD50:
{
return 5000.0;
}
case lsD55:
case lsDaylight:
case lsFineWeather:
case lsFlash:
case lsStandardLightB:
{
return 5500.0;
}
case lsD65:
case lsStandardLightC:
case lsCloudyWeather:
{
return 6500.0;
}
case lsD75:
case lsShade:
{
return 7500.0;
}
case lsDaylightFluorescent:
{
return (5700.0 + 7100.0) * 0.5;
}
case lsDayWhiteFluorescent:
{
return (4600.0 + 5400.0) * 0.5;
}
case lsCoolWhiteFluorescent:
case lsFluorescent:
{
return (3900.0 + 4500.0) * 0.5;
}
case lsWhiteFluorescent:
{
return (3200.0 + 3700.0) * 0.5;
}
default:
{
return 0.0;
}
}
}
/******************************************************************************/
void dng_camera_profile::NormalizeColorMatrix (dng_matrix &m)
{
if (m.NotEmpty ())
{
// Find scale factor to normalize the matrix.
dng_vector coord = m * PCStoXYZ ();
real64 maxCoord = coord.MaxEntry ();
if (maxCoord > 0.0 && (maxCoord < 0.99 || maxCoord > 1.01))
{
m.Scale (1.0 / maxCoord);
}
// Round to four decimal places.
m.Round (10000);
}
}
/******************************************************************************/
void dng_camera_profile::SetColorMatrix1 (const dng_matrix &m)
{
fColorMatrix1 = m;
NormalizeColorMatrix (fColorMatrix1);
ClearFingerprint ();
}
/******************************************************************************/
void dng_camera_profile::SetColorMatrix2 (const dng_matrix &m)
{
fColorMatrix2 = m;
NormalizeColorMatrix (fColorMatrix2);
ClearFingerprint ();
}
/******************************************************************************/
// Make sure the forward matrix maps to exactly the PCS.
void dng_camera_profile::NormalizeForwardMatrix (dng_matrix &m)
{
if (m.NotEmpty ())
{
dng_vector cameraOne;
cameraOne.SetIdentity (m.Cols ());
dng_vector xyz = m * cameraOne;
m = PCStoXYZ ().AsDiagonal () *
Invert (xyz.AsDiagonal ()) *
m;
}
}
/******************************************************************************/
void dng_camera_profile::SetForwardMatrix1 (const dng_matrix &m)
{
fForwardMatrix1 = m;
fForwardMatrix1.Round (10000);
ClearFingerprint ();
}
/******************************************************************************/
void dng_camera_profile::SetForwardMatrix2 (const dng_matrix &m)
{
fForwardMatrix2 = m;
fForwardMatrix2.Round (10000);
ClearFingerprint ();
}
/*****************************************************************************/
void dng_camera_profile::SetReductionMatrix1 (const dng_matrix &m)
{
fReductionMatrix1 = m;
fReductionMatrix1.Round (10000);
ClearFingerprint ();
}
/******************************************************************************/
void dng_camera_profile::SetReductionMatrix2 (const dng_matrix &m)
{
fReductionMatrix2 = m;
fReductionMatrix2.Round (10000);
ClearFingerprint ();
}
/*****************************************************************************/
bool dng_camera_profile::HasColorMatrix1 () const
{
return fColorMatrix1.Cols () == 3 &&
fColorMatrix1.Rows () > 1;
}
/*****************************************************************************/
bool dng_camera_profile::HasColorMatrix2 () const
{
return fColorMatrix2.Cols () == 3 &&
fColorMatrix2.Rows () == fColorMatrix1.Rows ();
}
/*****************************************************************************/
void dng_camera_profile::SetHueSatDeltas1 (const dng_hue_sat_map &deltas1)
{
fHueSatDeltas1 = deltas1;
ClearFingerprint ();
}
/*****************************************************************************/
void dng_camera_profile::SetHueSatDeltas2 (const dng_hue_sat_map &deltas2)
{
fHueSatDeltas2 = deltas2;
ClearFingerprint ();
}
/*****************************************************************************/
void dng_camera_profile::SetLookTable (const dng_hue_sat_map &table)
{
fLookTable = table;
ClearFingerprint ();
}
/*****************************************************************************/
static void FingerprintMatrix (dng_md5_printer_stream &printer,
const dng_matrix &matrix)
{
tag_matrix tag (0, matrix);
// Tag's Put routine doesn't write the header, only the data
tag.Put (printer);
}
/*****************************************************************************/
static void FingerprintHueSatMap (dng_md5_printer_stream &printer,
const dng_hue_sat_map &map)
{
if (map.IsNull ())
return;
uint32 hues;
uint32 sats;
uint32 vals;
map.GetDivisions (hues, sats, vals);
printer.Put_uint32 (hues);
printer.Put_uint32 (sats);
printer.Put_uint32 (vals);
for (uint32 val = 0; val < vals; val++)
for (uint32 hue = 0; hue < hues; hue++)
for (uint32 sat = 0; sat < sats; sat++)
{
dng_hue_sat_map::HSBModify modify;
map.GetDelta (hue, sat, val, modify);
printer.Put_real32 (modify.fHueShift);
printer.Put_real32 (modify.fSatScale);
printer.Put_real32 (modify.fValScale);
}
}
/*****************************************************************************/
void dng_camera_profile::CalculateFingerprint () const
{
DNG_ASSERT (!fWasStubbed, "CalculateFingerprint on stubbed profile");
dng_md5_printer_stream printer;
// MD5 hash is always calculated on little endian data.
printer.SetLittleEndian ();
// The data that we fingerprint closely matches that saved
// by the profile_tag_set class in dng_image_writer.cpp, with
// the exception of the fingerprint itself.
if (HasColorMatrix1 ())
{
uint32 colorChannels = ColorMatrix1 ().Rows ();
printer.Put_uint16 ((uint16) fCalibrationIlluminant1);
FingerprintMatrix (printer, fColorMatrix1);
if (fForwardMatrix1.Rows () == fColorMatrix1.Cols () &&
fForwardMatrix1.Cols () == fColorMatrix1.Rows ())
{
FingerprintMatrix (printer, fForwardMatrix1);
}
if (colorChannels > 3 && fReductionMatrix1.Rows () *
fReductionMatrix1.Cols () == colorChannels * 3)
{
FingerprintMatrix (printer, fReductionMatrix1);
}
if (HasColorMatrix2 ())
{
printer.Put_uint16 ((uint16) fCalibrationIlluminant2);
FingerprintMatrix (printer, fColorMatrix2);
if (fForwardMatrix2.Rows () == fColorMatrix2.Cols () &&
fForwardMatrix2.Cols () == fColorMatrix2.Rows ())
{
FingerprintMatrix (printer, fForwardMatrix2);
}
if (colorChannels > 3 && fReductionMatrix2.Rows () *
fReductionMatrix2.Cols () == colorChannels * 3)
{
FingerprintMatrix (printer, fReductionMatrix2);
}
}
printer.Put (fName.Get (),
fName.Length ());
printer.Put (fProfileCalibrationSignature.Get (),
fProfileCalibrationSignature.Length ());
printer.Put_uint32 (fEmbedPolicy);
printer.Put (fCopyright.Get (),
fCopyright.Length ());
bool haveHueSat1 = HueSatDeltas1 ().IsValid ();
bool haveHueSat2 = HueSatDeltas2 ().IsValid () &&
HasColorMatrix2 ();
if (haveHueSat1)
{
FingerprintHueSatMap (printer, fHueSatDeltas1);
}
if (haveHueSat2)
{
FingerprintHueSatMap (printer, fHueSatDeltas2);
}
if (fLookTable.IsValid ())
{
FingerprintHueSatMap (printer, fLookTable);
}
if (fToneCurve.IsValid ())
{
for (uint32 i = 0; i < fToneCurve.fCoord.size (); i++)
{
printer.Put_real32 ((real32) fToneCurve.fCoord [i].h);
printer.Put_real32 ((real32) fToneCurve.fCoord [i].v);
}
}
}
fFingerprint = printer.Result ();
}
/******************************************************************************/
bool dng_camera_profile::ValidForwardMatrix (const dng_matrix &m)
{
const real64 kThreshold = 0.01;
if (m.NotEmpty ())
{
dng_vector cameraOne;
cameraOne.SetIdentity (m.Cols ());
dng_vector xyz = m * cameraOne;
dng_vector pcs = PCStoXYZ ();
if (Abs_real64 (xyz [0] - pcs [0]) > kThreshold ||
Abs_real64 (xyz [1] - pcs [1]) > kThreshold ||
Abs_real64 (xyz [2] - pcs [2]) > kThreshold)
{
return false;
}
}
return true;
}
/******************************************************************************/
bool dng_camera_profile::IsValid (uint32 channels) const
{
// For Monochrome images, we ignore the camera profile.
if (channels == 1)
{
return true;
}
// ColorMatrix1 is required for all color images.
if (fColorMatrix1.Cols () != 3 ||
fColorMatrix1.Rows () != channels)
{
#if qDNGValidate
ReportError ("ColorMatrix1 is wrong size");
#endif
return false;
}
// ColorMatrix2 is optional, but it must be valid if present.
if (fColorMatrix2.Cols () != 0 ||
fColorMatrix2.Rows () != 0)
{
if (fColorMatrix2.Cols () != 3 ||
fColorMatrix2.Rows () != channels)
{
#if qDNGValidate
ReportError ("ColorMatrix2 is wrong size");
#endif
return false;
}
}
// ForwardMatrix1 is optional, but it must be valid if present.
if (fForwardMatrix1.Cols () != 0 ||
fForwardMatrix1.Rows () != 0)
{
if (fForwardMatrix1.Rows () != 3 ||
fForwardMatrix1.Cols () != channels)
{
#if qDNGValidate
ReportError ("ForwardMatrix1 is wrong size");
#endif
return false;
}
// Make sure ForwardMatrix1 does a valid mapping.
if (!ValidForwardMatrix (fForwardMatrix1))
{
#if qDNGValidate
ReportError ("ForwardMatrix1 does not map equal camera values to XYZ D50");
#endif
return false;
}
}
// ForwardMatrix2 is optional, but it must be valid if present.
if (fForwardMatrix2.Cols () != 0 ||
fForwardMatrix2.Rows () != 0)
{
if (fForwardMatrix2.Rows () != 3 ||
fForwardMatrix2.Cols () != channels)
{
#if qDNGValidate
ReportError ("ForwardMatrix2 is wrong size");
#endif
return false;
}
// Make sure ForwardMatrix2 does a valid mapping.
if (!ValidForwardMatrix (fForwardMatrix2))
{
#if qDNGValidate
ReportError ("ForwardMatrix2 does not map equal camera values to XYZ D50");
#endif
return false;
}
}
// ReductionMatrix1 is optional, but it must be valid if present.
if (fReductionMatrix1.Cols () != 0 ||
fReductionMatrix1.Rows () != 0)
{
if (fReductionMatrix1.Cols () != channels ||
fReductionMatrix1.Rows () != 3)
{
#if qDNGValidate
ReportError ("ReductionMatrix1 is wrong size");
#endif
return false;
}
}
// ReductionMatrix2 is optional, but it must be valid if present.
if (fReductionMatrix2.Cols () != 0 ||
fReductionMatrix2.Rows () != 0)
{
if (fReductionMatrix2.Cols () != channels ||
fReductionMatrix2.Rows () != 3)
{
#if qDNGValidate
ReportError ("ReductionMatrix2 is wrong size");
#endif
return false;
}
}
- // Make sure ColorMatrix1 is invertable.
+ // Make sure ColorMatrix1 is invertible.
try
{
if (fReductionMatrix1.NotEmpty ())
{
(void) Invert (fColorMatrix1,
fReductionMatrix1);
}
else
{
(void) Invert (fColorMatrix1);
}
}
catch (...)
{
#if qDNGValidate
- ReportError ("ColorMatrix1 is not invertable");
+ ReportError ("ColorMatrix1 is not invertible");
#endif
return false;
}
- // Make sure ColorMatrix2 is invertable.
+ // Make sure ColorMatrix2 is invertible.
if (fColorMatrix2.NotEmpty ())
{
try
{
if (fReductionMatrix2.NotEmpty ())
{
(void) Invert (fColorMatrix2,
fReductionMatrix2);
}
else
{
(void) Invert (fColorMatrix2);
}
}
catch (...)
{
#if qDNGValidate
- ReportError ("ColorMatrix2 is not invertable");
+ ReportError ("ColorMatrix2 is not invertible");
#endif
return false;
}
}
return true;
}
/*****************************************************************************/
bool dng_camera_profile::EqualData (const dng_camera_profile &profile) const
{
return fCalibrationIlluminant1 == profile.fCalibrationIlluminant1 &&
fCalibrationIlluminant2 == profile.fCalibrationIlluminant2 &&
fColorMatrix1 == profile.fColorMatrix1 &&
fColorMatrix2 == profile.fColorMatrix2 &&
fForwardMatrix1 == profile.fForwardMatrix1 &&
fForwardMatrix2 == profile.fForwardMatrix2 &&
fReductionMatrix1 == profile.fReductionMatrix1 &&
fReductionMatrix2 == profile.fReductionMatrix2 &&
fHueSatDeltas1 == profile.fHueSatDeltas1 &&
fHueSatDeltas2 == profile.fHueSatDeltas2 &&
fLookTable == profile.fLookTable &&
fToneCurve == profile.fToneCurve &&
fProfileCalibrationSignature == profile.fProfileCalibrationSignature;
}
/*****************************************************************************/
void dng_camera_profile::ReadHueSatMap (dng_stream &stream,
dng_hue_sat_map &hueSatMap,
uint32 hues,
uint32 sats,
uint32 vals,
bool skipSat0)
{
hueSatMap.SetDivisions (hues, sats, vals);
for (uint32 val = 0; val < vals; val++)
{
for (uint32 hue = 0; hue < hues; hue++)
{
for (uint32 sat = skipSat0 ? 1 : 0; sat < sats; sat++)
{
dng_hue_sat_map::HSBModify modify;
modify.fHueShift = stream.Get_real32 ();
modify.fSatScale = stream.Get_real32 ();
modify.fValScale = stream.Get_real32 ();
hueSatMap.SetDelta (hue, sat, val, modify);
}
}
}
}
/*****************************************************************************/
void dng_camera_profile::Parse (dng_stream &stream,
dng_camera_profile_info &profileInfo)
{
SetUniqueCameraModelRestriction (profileInfo.fUniqueCameraModel.Get ());
if (profileInfo.fProfileName.NotEmpty ())
{
SetName (profileInfo.fProfileName.Get ());
}
SetCopyright (profileInfo.fProfileCopyright.Get ());
SetEmbedPolicy (profileInfo.fEmbedPolicy);
SetCalibrationIlluminant1 (profileInfo.fCalibrationIlluminant1);
SetColorMatrix1 (profileInfo.fColorMatrix1);
if (profileInfo.fForwardMatrix1.NotEmpty ())
{
SetForwardMatrix1 (profileInfo.fForwardMatrix1);
}
if (profileInfo.fReductionMatrix1.NotEmpty ())
{
SetReductionMatrix1 (profileInfo.fReductionMatrix1);
}
if (profileInfo.fColorMatrix2.NotEmpty ())
{
SetCalibrationIlluminant2 (profileInfo.fCalibrationIlluminant2);
SetColorMatrix2 (profileInfo.fColorMatrix2);
if (profileInfo.fForwardMatrix2.NotEmpty ())
{
SetForwardMatrix2 (profileInfo.fForwardMatrix2);
}
if (profileInfo.fReductionMatrix2.NotEmpty ())
{
SetReductionMatrix2 (profileInfo.fReductionMatrix2);
}
}
SetProfileCalibrationSignature (profileInfo.fProfileCalibrationSignature.Get ());
if (profileInfo.fHueSatDeltas1Offset != 0 &&
profileInfo.fHueSatDeltas1Count != 0)
{
TempBigEndian setEndianness (stream, profileInfo.fBigEndian);
stream.SetReadPosition (profileInfo.fHueSatDeltas1Offset);
bool skipSat0 = (profileInfo.fHueSatDeltas1Count == profileInfo.fProfileHues *
(profileInfo.fProfileSats - 1) *
profileInfo.fProfileVals * 3);
ReadHueSatMap (stream,
fHueSatDeltas1,
profileInfo.fProfileHues,
profileInfo.fProfileSats,
profileInfo.fProfileVals,
skipSat0);
}
if (profileInfo.fHueSatDeltas2Offset != 0 &&
profileInfo.fHueSatDeltas2Count != 0)
{
TempBigEndian setEndianness (stream, profileInfo.fBigEndian);
stream.SetReadPosition (profileInfo.fHueSatDeltas2Offset);
bool skipSat0 = (profileInfo.fHueSatDeltas2Count == profileInfo.fProfileHues *
(profileInfo.fProfileSats - 1) *
profileInfo.fProfileVals * 3);
ReadHueSatMap (stream,
fHueSatDeltas2,
profileInfo.fProfileHues,
profileInfo.fProfileSats,
profileInfo.fProfileVals,
skipSat0);
}
if (profileInfo.fLookTableOffset != 0 &&
profileInfo.fLookTableCount != 0)
{
TempBigEndian setEndianness (stream, profileInfo.fBigEndian);
stream.SetReadPosition (profileInfo.fLookTableOffset);
bool skipSat0 = (profileInfo.fLookTableCount == profileInfo.fLookTableHues *
(profileInfo.fLookTableSats - 1) *
profileInfo.fLookTableVals * 3);
ReadHueSatMap (stream,
fLookTable,
profileInfo.fLookTableHues,
profileInfo.fLookTableSats,
profileInfo.fLookTableVals,
skipSat0);
}
if ((profileInfo.fToneCurveCount & 1) == 0)
{
TempBigEndian setEndianness (stream, profileInfo.fBigEndian);
stream.SetReadPosition (profileInfo.fToneCurveOffset);
uint32 points = profileInfo.fToneCurveCount / 2;
fToneCurve.fCoord.resize (points);
for (size_t i = 0; i < points; i++)
{
dng_point_real64 point;
point.h = stream.Get_real32 ();
point.v = stream.Get_real32 ();
fToneCurve.fCoord [i] = point;
}
}
}
/*****************************************************************************/
bool dng_camera_profile::ParseExtended (dng_stream &stream)
{
try
{
dng_camera_profile_info profileInfo;
if (!profileInfo.ParseExtended (stream))
{
return false;
}
Parse (stream, profileInfo);
return true;
}
catch (...)
{
// Eat parsing errors.
}
return false;
}
/*****************************************************************************/
void dng_camera_profile::SetFourColorBayer ()
{
uint32 j;
if (!IsValid (3))
{
ThrowProgramError ();
}
if (fColorMatrix1.NotEmpty ())
{
dng_matrix m (4, 3);
for (j = 0; j < 3; j++)
{
m [0] [j] = fColorMatrix1 [0] [j];
m [1] [j] = fColorMatrix1 [1] [j];
m [2] [j] = fColorMatrix1 [2] [j];
m [3] [j] = fColorMatrix1 [1] [j];
}
fColorMatrix1 = m;
}
if (fColorMatrix2.NotEmpty ())
{
dng_matrix m (4, 3);
for (j = 0; j < 3; j++)
{
m [0] [j] = fColorMatrix2 [0] [j];
m [1] [j] = fColorMatrix2 [1] [j];
m [2] [j] = fColorMatrix2 [2] [j];
m [3] [j] = fColorMatrix2 [1] [j];
}
fColorMatrix2 = m;
}
fReductionMatrix1.Clear ();
fReductionMatrix2.Clear ();
fForwardMatrix1.Clear ();
fForwardMatrix2.Clear ();
}
/*****************************************************************************/
dng_hue_sat_map * dng_camera_profile::HueSatMapForWhite (const dng_xy_coord &white) const
{
if (fHueSatDeltas1.IsValid ())
{
// If we only have the first table, just use it for any color temperature.
if (!fHueSatDeltas2.IsValid ())
{
return new dng_hue_sat_map (fHueSatDeltas1);
}
// Else we need to interpolate based on color temperature.
real64 temperature1 = CalibrationTemperature1 ();
real64 temperature2 = CalibrationTemperature2 ();
if (temperature1 <= 0.0 ||
temperature2 <= 0.0 ||
temperature1 == temperature2)
{
return new dng_hue_sat_map (fHueSatDeltas1);
}
bool reverseOrder = temperature1 > temperature2;
if (reverseOrder)
{
real64 temp = temperature1;
temperature1 = temperature2;
temperature2 = temp;
}
// Convert to temperature/offset space.
dng_temperature td (white);
// Find fraction to weight the first calibration.
real64 g;
if (td.Temperature () <= temperature1)
g = 1.0;
else if (td.Temperature () >= temperature2)
g = 0.0;
else
{
real64 invT = 1.0 / td.Temperature ();
g = (invT - (1.0 / temperature2)) /
((1.0 / temperature1) - (1.0 / temperature2));
}
// Fix up if we swapped the order.
if (reverseOrder)
{
g = 1.0 - g;
}
// Do the interpolation.
return dng_hue_sat_map::Interpolate (HueSatDeltas1 (),
HueSatDeltas2 (),
g);
}
return NULL;
}
/*****************************************************************************/
void dng_camera_profile::Stub ()
{
(void) Fingerprint ();
dng_hue_sat_map nullTable;
fHueSatDeltas1 = nullTable;
fHueSatDeltas2 = nullTable;
fLookTable = nullTable;
fToneCurve.SetInvalid ();
fWasStubbed = true;
}
/*****************************************************************************/
void SplitCameraProfileName (const dng_string &name,
dng_string &baseName,
int32 &version)
{
baseName = name;
version = 0;
uint32 len = baseName.Length ();
if (len > 5 && baseName.EndsWith (" beta"))
{
baseName.Truncate (len - 5);
version += -10;
}
else if (len > 7)
{
char lastChar = name.Get () [len - 1];
if (lastChar >= '0' && lastChar <= '9')
{
dng_string temp = name;
temp.Truncate (len - 1);
if (temp.EndsWith (" beta "))
{
baseName.Truncate (len - 7);
version += ((int32) (lastChar - '0')) - 10;
}
}
}
len = baseName.Length ();
if (len > 3)
{
char lastChar = name.Get () [len - 1];
if (lastChar >= '0' && lastChar <= '9')
{
dng_string temp = name;
temp.Truncate (len - 1);
if (temp.EndsWith (" v"))
{
baseName.Truncate (len - 3);
version += ((int32) (lastChar - '0')) * 100;
}
}
}
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.h b/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.h
index 57e7ff7661..4157e05379 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_camera_profile.h
@@ -1,656 +1,656 @@
/******************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/******************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_camera_profile.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Support for DNG camera color profile information.
* Per the \ref spec_dng "DNG 1.1.0 specification", a DNG file can store up to
* two sets of color profile information for a camera in the DNG file from that
* camera. The second set is optional and when there are two sets, they represent
* profiles made under different illumination.
*
* Profiling information is optionally separated into two parts. One part represents
* a profile for a reference camera. (ColorMatrix1 and ColorMatrix2 here.) The
* second is a per-camera calibration that takes into account unit-to-unit variation.
* This is designed to allow replacing the reference color matrix with one of one's
* own construction while maintaining any unit-specific calibration the camera
* manufacturer may have provided.
*
* See Appendix 6 of the \ref spec_dng "DNG 1.1.0 specification" for more information.
*/
#ifndef __dng_camera_profile__
#define __dng_camera_profile__
/******************************************************************************/
#include "dng_assertions.h"
#include "dng_classes.h"
#include "dng_fingerprint.h"
#include "dng_hue_sat_map.h"
#include "dng_matrix.h"
#include "dng_string.h"
#include "dng_tag_values.h"
#include "dng_tone_curve.h"
/******************************************************************************/
extern const char * kProfileName_Embedded;
extern const char * kAdobeCalibrationSignature;
/******************************************************************************/
class dng_camera_profile_id
{
private:
dng_string fName;
dng_fingerprint fFingerprint;
public:
dng_camera_profile_id ()
: fName ()
, fFingerprint ()
{
}
dng_camera_profile_id (const char *name)
: fName ()
, fFingerprint ()
{
fName.Set (name);
}
dng_camera_profile_id (const dng_string &name)
: fName (name)
, fFingerprint ()
{
}
dng_camera_profile_id (const char *name,
const dng_fingerprint &fingerprint)
: fName ()
, fFingerprint (fingerprint)
{
fName.Set (name);
DNG_ASSERT (!fFingerprint.IsValid () || fName.NotEmpty (),
"Cannot have profile fingerprint without name");
}
dng_camera_profile_id (const dng_string &name,
const dng_fingerprint &fingerprint)
: fName (name)
, fFingerprint (fingerprint)
{
DNG_ASSERT (!fFingerprint.IsValid () || fName.NotEmpty (),
"Cannot have profile fingerprint without name");
}
const dng_string & Name () const
{
return fName;
}
const dng_fingerprint & Fingerprint () const
{
return fFingerprint;
}
bool operator== (const dng_camera_profile_id &id) const
{
return fName == id.fName &&
fFingerprint == id.fFingerprint;
}
bool operator!= (const dng_camera_profile_id &id) const
{
return !(*this == id);
}
bool IsValid () const
{
return fName.NotEmpty (); // Fingerprint is optional.
}
void Clear ()
{
*this = dng_camera_profile_id ();
}
};
/******************************************************************************/
/// \brief Container for DNG camera color profile and calibration data.
class dng_camera_profile
{
protected:
// Name of this camera profile.
dng_string fName;
// Light sources for up to two calibrations. These use the EXIF
// encodings for illuminant and are used to distinguish which
// matrix to use.
uint32 fCalibrationIlluminant1;
uint32 fCalibrationIlluminant2;
// Color matrices for up to two calibrations.
// These matrices map XYZ values to non-white balanced camera values.
// Adobe needs to go that direction in order to determine the clipping
// points for highlight recovery logic based on the white point. If
// cameras were all 3-color, the matrix could be stored as a forward matrix,
// but we need the backwards matrix to deal with 4-color cameras.
dng_matrix fColorMatrix1;
dng_matrix fColorMatrix2;
// These matrices map white balanced camera values to XYZ chromatically
// adapted to D50 (the ICC profile PCS white point). If the matrices
// exist, then this implies that white balancing should be done by scaling
// camera values with a diagonal matrix.
dng_matrix fForwardMatrix1;
dng_matrix fForwardMatrix2;
// Dimensionality reduction hints for more than three color cameras.
// This is an optional matrix that maps the camera's color components
// to 3 components. These are only used if the forward matrices don't
// exist, and are used invert the color matrices.
dng_matrix fReductionMatrix1;
dng_matrix fReductionMatrix2;
// MD5 hash for all data bits of the profile.
mutable dng_fingerprint fFingerprint;
// Copyright notice from creator of profile.
dng_string fCopyright;
// Rules for how this profile can be embedded and/or copied.
uint32 fEmbedPolicy;
// 2-D (or 3-D) hue/sat tables to modify colors.
dng_hue_sat_map fHueSatDeltas1;
dng_hue_sat_map fHueSatDeltas2;
// 3-D hue/sat table to apply a "look".
dng_hue_sat_map fLookTable;
// The "as shot" tone curve for this profile. Check IsValid method
// to tell if one exists in profile.
dng_tone_curve fToneCurve;
// If this string matches the fCameraCalibrationSignature of the
// negative, then use the calibration matrix values from the negative.
dng_string fProfileCalibrationSignature;
// If non-empty, only allow use of this profile with camera having
// same unique model name.
dng_string fUniqueCameraModelRestriction;
// Was this profile read from inside a DNG file? (If so, we wnat
// to be sure to include it again when writing out an updated
// DNG file)
bool fWasReadFromDNG;
// Was this profile stubbed to save memory (and no longer valid
// for building color conversion tables)?
bool fWasStubbed;
public:
dng_camera_profile ();
virtual ~dng_camera_profile ();
// API for profile name:
/// Setter for camera profile name.
/// \param name Name to use for this camera profile.
void SetName (const char *name)
{
fName.Set (name);
ClearFingerprint ();
}
/// Getter for camera profile name.
/// \retval Name of profile.
const dng_string & Name () const
{
return fName;
}
/// Test if this name is embedded.
/// \retval true if the name matches the name of the embedded camera profile.
bool NameIsEmbedded () const
{
return fName.Matches (kProfileName_Embedded, true);
}
// API for calibration illuminants:
/// Setter for first of up to two light sources used for calibration.
/// Uses the EXIF encodings for illuminant and is used to distinguish which
/// matrix to use.
/// Corresponds to the DNG CalibrationIlluminant1 tag.
void SetCalibrationIlluminant1 (uint32 light)
{
fCalibrationIlluminant1 = light;
ClearFingerprint ();
}
/// Setter for second of up to two light sources used for calibration.
/// Uses the EXIF encodings for illuminant and is used to distinguish which
/// matrix to use.
/// Corresponds to the DNG CalibrationIlluminant2 tag.
void SetCalibrationIlluminant2 (uint32 light)
{
fCalibrationIlluminant2 = light;
ClearFingerprint ();
}
/// Getter for first of up to two light sources used for calibration.
/// Uses the EXIF encodings for illuminant and is used to distinguish which
/// matrix to use.
/// Corresponds to the DNG CalibrationIlluminant1 tag.
uint32 CalibrationIlluminant1 () const
{
return fCalibrationIlluminant1;
}
/// Getter for second of up to two light sources used for calibration.
/// Uses the EXIF encodings for illuminant and is used to distinguish which
/// matrix to use.
/// Corresponds to the DNG CalibrationIlluminant2 tag.
uint32 CalibrationIlluminant2 () const
{
return fCalibrationIlluminant2;
}
/// Getter for first of up to two light sources used for calibration, returning
/// result as color temperature.
real64 CalibrationTemperature1 () const
{
return IlluminantToTemperature (CalibrationIlluminant1 ());
}
/// Getter for second of up to two light sources used for calibration, returning
/// result as color temperature.
real64 CalibrationTemperature2 () const
{
return IlluminantToTemperature (CalibrationIlluminant2 ());
}
// API for color matrices:
/// Utility function to normalize the scale of the color matrix.
static void NormalizeColorMatrix (dng_matrix &m);
/// Setter for first of up to two color matrices used for reference camera calibrations.
/// These matrices map XYZ values to camera values. The DNG SDK needs to map colors
/// that direction in order to determine the clipping points for
/// highlight recovery logic based on the white point. If cameras
/// were all three-color, the matrix could be stored as a forward matrix.
- /// The inverse matrix is requried to support four-color cameras.
+ /// The inverse matrix is required to support four-color cameras.
void SetColorMatrix1 (const dng_matrix &m);
/// Setter for second of up to two color matrices used for reference camera calibrations.
/// These matrices map XYZ values to camera values. The DNG SDK needs to map colors
/// that direction in order to determine the clipping points for
/// highlight recovery logic based on the white point. If cameras
/// were all three-color, the matrix could be stored as a forward matrix.
- /// The inverse matrix is requried to support four-color cameras.
+ /// The inverse matrix is required to support four-color cameras.
void SetColorMatrix2 (const dng_matrix &m);
/// Predicate to test if first camera matrix is set
bool HasColorMatrix1 () const;
/// Predicate to test if second camera matrix is set
bool HasColorMatrix2 () const;
/// Getter for first of up to two color matrices used for calibrations.
const dng_matrix & ColorMatrix1 () const
{
return fColorMatrix1;
}
/// Getter for second of up to two color matrices used for calibrations.
const dng_matrix & ColorMatrix2 () const
{
return fColorMatrix2;
}
// API for forward matrices:
/// Utility function to normalize the scale of the forward matrix.
static void NormalizeForwardMatrix (dng_matrix &m);
/// Setter for first of up to two forward matrices used for calibrations.
void SetForwardMatrix1 (const dng_matrix &m);
/// Setter for second of up to two forward matrices used for calibrations.
void SetForwardMatrix2 (const dng_matrix &m);
/// Getter for first of up to two forward matrices used for calibrations.
const dng_matrix & ForwardMatrix1 () const
{
return fForwardMatrix1;
}
/// Getter for second of up to two forward matrices used for calibrations.
const dng_matrix & ForwardMatrix2 () const
{
return fForwardMatrix2;
}
// API for reduction matrices:
/// Setter for first of up to two dimensionality reduction hints for four-color cameras.
/// This is an optional matrix that maps four components to three.
/// See Appendix 6 of the \ref spec_dng "DNG 1.1.0 specification."
void SetReductionMatrix1 (const dng_matrix &m);
/// Setter for second of up to two dimensionality reduction hints for four-color cameras.
/// This is an optional matrix that maps four components to three.
/// See Appendix 6 of the \ref spec_dng "DNG 1.1.0 specification."
void SetReductionMatrix2 (const dng_matrix &m);
/// Getter for first of up to two dimensionality reduction hints for four color cameras.
const dng_matrix & ReductionMatrix1 () const
{
return fReductionMatrix1;
}
/// Getter for second of up to two dimensionality reduction hints for four color cameras.
const dng_matrix & ReductionMatrix2 () const
{
return fReductionMatrix2;
}
/// Getter function from profile fingerprint.
const dng_fingerprint &Fingerprint () const
{
if (!fFingerprint.IsValid ())
CalculateFingerprint ();
return fFingerprint;
}
/// Getter for camera profile id.
/// \retval ID of profile.
dng_camera_profile_id ProfileID () const
{
return dng_camera_profile_id (Name (), Fingerprint ());
}
/// Setter for camera profile copyright.
/// \param copyright Copyright string to use for this camera profile.
void SetCopyright (const char *copyright)
{
fCopyright.Set (copyright);
ClearFingerprint ();
}
/// Getter for camera profile copyright.
/// \retval Copyright string for profile.
const dng_string & Copyright () const
{
return fCopyright;
}
// Accessors for embed policy.
void SetEmbedPolicy (uint32 policy)
{
fEmbedPolicy = policy;
ClearFingerprint ();
}
uint32 EmbedPolicy () const
{
return fEmbedPolicy;
}
bool IsLegalToEmbed () const
{
return WasReadFromDNG () ||
EmbedPolicy () == pepAllowCopying ||
EmbedPolicy () == pepEmbedIfUsed ||
EmbedPolicy () == pepNoRestrictions;
}
// Accessors for hue sat maps.
bool HasHueSatDeltas () const
{
return fHueSatDeltas1.IsValid ();
}
const dng_hue_sat_map & HueSatDeltas1 () const
{
return fHueSatDeltas1;
}
void SetHueSatDeltas1 (const dng_hue_sat_map &deltas1);
const dng_hue_sat_map & HueSatDeltas2 () const
{
return fHueSatDeltas2;
}
void SetHueSatDeltas2 (const dng_hue_sat_map &deltas2);
// Accessors for look table.
bool HasLookTable () const
{
return fLookTable.IsValid ();
}
const dng_hue_sat_map & LookTable () const
{
return fLookTable;
}
void SetLookTable (const dng_hue_sat_map &table);
// Accessors for tone curve.
const dng_tone_curve & ToneCurve () const
{
return fToneCurve;
}
void SetToneCurve (const dng_tone_curve &curve)
{
fToneCurve = curve;
ClearFingerprint ();
}
// Accessors for profile calibration signature.
void SetProfileCalibrationSignature (const char *signature)
{
fProfileCalibrationSignature.Set (signature);
}
const dng_string & ProfileCalibrationSignature () const
{
return fProfileCalibrationSignature;
}
/// Setter for camera unique model name to restrict use of this profile.
/// \param camera Camera unique model name designating only camera this
/// profile can be used with. (Empty string for no restriction.)
void SetUniqueCameraModelRestriction (const char *camera)
{
fUniqueCameraModelRestriction.Set (camera);
// Not included in fingerprint, so don't need ClearFingerprint ().
}
/// Getter for camera unique model name to restrict use of this profile.
/// \retval Unique model name of only camera this profile can be used with
/// or empty if no restriction.
const dng_string & UniqueCameraModelRestriction () const
{
return fUniqueCameraModelRestriction;
}
// Accessors for was read from DNG flag.
void SetWasReadFromDNG (bool state = true)
{
fWasReadFromDNG = state;
}
bool WasReadFromDNG () const
{
return fWasReadFromDNG;
}
/// Determines if this a valid profile for this number of color channels?
/// \retval true if the profile is valid.
bool IsValid (uint32 channels) const;
/// Predicate to check if two camera profiles are colorwise equal, thus ignores
/// the profile name.
/// \param profile Camera profile to compare to.
bool EqualData (const dng_camera_profile &profile) const;
/// Parse profile from dng_camera_profile_info data.
void Parse (dng_stream &stream,
dng_camera_profile_info &profileInfo);
/// Parse from an extended profile stream, which is similar to stand alone
/// TIFF file.
bool ParseExtended (dng_stream &stream);
/// Convert from a three-color to a four-color Bayer profile.
virtual void SetFourColorBayer ();
/// Find the hue/sat table to use for a given white point, if any.
/// The calling routine owns the resulting table.
dng_hue_sat_map * HueSatMapForWhite (const dng_xy_coord &white) const;
/// Stub out the profile (free memory used by large tables).
void Stub ();
/// Was this profile stubbed?
bool WasStubbed () const
{
return fWasStubbed;
}
protected:
static real64 IlluminantToTemperature (uint32 light);
void ClearFingerprint ()
{
fFingerprint.Clear ();
}
void CalculateFingerprint () const;
static bool ValidForwardMatrix (const dng_matrix &m);
static void ReadHueSatMap (dng_stream &stream,
dng_hue_sat_map &hueSatMap,
uint32 hues,
uint32 sats,
uint32 vals,
bool skipSat0);
};
/******************************************************************************/
void SplitCameraProfileName (const dng_string &name,
dng_string &baseName,
int32 &version);
/******************************************************************************/
#endif
/******************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_date_time.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_date_time.cpp
index 26d655729e..0482cbb34a 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_date_time.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_date_time.cpp
@@ -1,1136 +1,1136 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_date_time.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_date_time.h"
#include "dng_exceptions.h"
#include "dng_mutex.h"
#include "dng_stream.h"
#include "dng_string.h"
#include "dng_utils.h"
#include <time.h>
#if qMacOS
#include <CoreServices/CoreServices.h>
#endif
#if qWinOS
#include <windows.h>
#if defined(__GNUC__)
#include <winbase.h>
/*
* Win32 kernel time functions from Wine API.
*
* Copyright 1995 Martin von Loewis and Cameron Heide
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#define FILETIME2LL( pft, ll) ll = (((LONGLONG)((pft)->dwHighDateTime))<<32) + (pft)-> dwLowDateTime;
#define LL2FILETIME( ll, pft ) (pft)->dwLowDateTime = (UINT)(ll); (pft)->dwHighDateTime = (UINT)((ll) >> 32);
const int MonthLengths[2][12] =
{
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
inline int IsLeapYear(int Year)
{
return Year % 4 == 0 && (Year % 100 != 0 || Year % 400 == 0) ? 1 : 0;
};
BOOL TIME_GetTimezoneBias(const TIME_ZONE_INFORMATION *pTZinfo,
FILETIME *lpFileTime, BOOL islocal, LONG *pBias);
BOOL TIME_CompTimeZoneID(const TIME_ZONE_INFORMATION *pTZinfo,
FILETIME *lpFileTime, BOOL islocal);
int TIME_DayLightCompareDate(const SYSTEMTIME *date,
const SYSTEMTIME *compareDate);
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
LPSYSTEMTIME lpLocalTime,
LPSYSTEMTIME lpUniversalTime);
#endif /* defined(__GNUC__) */
#endif
/******************************************************************************/
dng_date_time::dng_date_time ()
: fYear (0)
, fMonth (0)
, fDay (0)
, fHour (0)
, fMinute (0)
, fSecond (0)
{
}
/******************************************************************************/
dng_date_time::dng_date_time (uint32 year,
uint32 month,
uint32 day,
uint32 hour,
uint32 minute,
uint32 second)
: fYear (year)
, fMonth (month)
, fDay (day)
, fHour (hour)
, fMinute (minute)
, fSecond (second)
{
}
/******************************************************************************/
bool dng_date_time::IsValid () const
{
return fYear >= 1 && fYear <= 9999 &&
fMonth >= 1 && fMonth <= 12 &&
fDay >= 1 && fDay <= 31 &&
fHour >= 0 && fHour <= 23 &&
fMinute >= 0 && fMinute <= 59 &&
fSecond >= 0 && fSecond <= 59;
}
/*****************************************************************************/
void dng_date_time::Clear ()
{
*this = dng_date_time ();
}
/*****************************************************************************/
static uint32 DateTimeParseU32 (const char *&s)
{
uint32 x = 0;
while (*s == ' ' || *s == ':')
s++;
while (*s >= '0' && *s <= '9')
{
x = x * 10 + (uint32) (*(s++) - '0');
}
return x;
}
/*****************************************************************************/
bool dng_date_time::Parse (const char *s)
{
fYear = DateTimeParseU32 (s);
fMonth = DateTimeParseU32 (s);
fDay = DateTimeParseU32 (s);
fHour = DateTimeParseU32 (s);
fMinute = DateTimeParseU32 (s);
fSecond = DateTimeParseU32 (s);
return IsValid ();
}
/*****************************************************************************/
dng_string dng_time_zone::Encode_ISO_8601 () const
{
dng_string result;
if (IsValid ())
{
if (OffsetMinutes () == 0)
{
result.Set ("Z");
}
else
{
char s [64];
int offset = OffsetMinutes ();
if (offset > 0)
{
sprintf (s, "+%02d:%02d", offset / 60, offset % 60);
}
else
{
offset = -offset;
sprintf (s, "-%02d:%02d", offset / 60, offset % 60);
}
result.Set (s);
}
}
return result;
}
/*****************************************************************************/
dng_date_time_info::dng_date_time_info ()
: fDateOnly (true)
, fDateTime ()
, fSubseconds ()
, fTimeZone ()
{
}
/*****************************************************************************/
bool dng_date_time_info::IsValid () const
{
return fDateTime.IsValid ();
}
/*****************************************************************************/
void dng_date_time_info::SetDate (uint32 year,
uint32 month,
uint32 day)
{
fDateTime.fYear = year;
fDateTime.fMonth = month;
fDateTime.fDay = day;
}
/*****************************************************************************/
void dng_date_time_info::SetTime (uint32 hour,
uint32 minute,
uint32 second)
{
fDateOnly = false;
fDateTime.fHour = hour;
fDateTime.fMinute = minute;
fDateTime.fSecond = second;
}
/*****************************************************************************/
void dng_date_time_info::Decode_ISO_8601 (const char *s)
{
Clear ();
uint32 len = (uint32) strlen (s);
if (!len)
{
return;
}
unsigned year = 0;
unsigned month = 0;
unsigned day = 0;
if (sscanf (s,
"%u-%u-%u",
&year,
&month,
&day) != 3)
{
return;
}
SetDate ((uint32) year,
(uint32) month,
(uint32) day);
if (fDateTime.NotValid ())
{
Clear ();
return;
}
for (uint32 j = 0; j < len; j++)
{
if (s [j] == 'T')
{
unsigned hour = 0;
unsigned minute = 0;
unsigned second = 0;
if (sscanf (s + j + 1,
"%u:%u:%u",
&hour,
&minute,
&second) == 3)
{
SetTime ((uint32) hour,
(uint32) minute,
(uint32) second);
if (fDateTime.NotValid ())
{
Clear ();
return;
}
for (uint32 k = j + 1; k < len; k++)
{
if (s [k] == '.')
{
while (++k < len && s [k] >= '0' && s [k] <= '9')
{
char ss [2];
ss [0] = s [k];
ss [1] = 0;
fSubseconds.Append (ss);
}
break;
}
}
for (uint32 k = j + 1; k < len; k++)
{
if (s [k] == 'Z')
{
fTimeZone.SetOffsetMinutes (0);
break;
}
if (s [k] == '+' || s [k] == '-')
{
int32 sign = (s [k] == '-' ? -1 : 1);
unsigned tzhour = 0;
unsigned tzmin = 0;
if (sscanf (s + k + 1,
"%u:%u",
&tzhour,
&tzmin) > 0)
{
fTimeZone.SetOffsetMinutes (sign * (tzhour * 60 + tzmin));
}
break;
}
}
}
break;
}
}
}
/*****************************************************************************/
dng_string dng_date_time_info::Encode_ISO_8601 () const
{
dng_string result;
if (IsValid ())
{
char s [256];
sprintf (s,
"%04u-%02u-%02u",
(unsigned) fDateTime.fYear,
(unsigned) fDateTime.fMonth,
(unsigned) fDateTime.fDay);
result.Set (s);
if (!fDateOnly)
{
sprintf (s,
"T%02u:%02u:%02u",
(unsigned) fDateTime.fHour,
(unsigned) fDateTime.fMinute,
(unsigned) fDateTime.fSecond);
result.Append (s);
if (fSubseconds.NotEmpty ())
{
bool subsecondsValid = true;
uint32 len = fSubseconds.Length ();
for (uint32 index = 0; index < len; index++)
{
if (fSubseconds.Get () [index] < '0' ||
fSubseconds.Get () [index] > '9')
{
subsecondsValid = false;
break;
}
}
if (subsecondsValid)
{
result.Append (".");
result.Append (fSubseconds.Get ());
}
}
// Kludge: Early versions of the XMP toolkit assume Zulu time
// if the time zone is missing. It is safer for fill in the
// local time zone.
dng_time_zone tempZone = fTimeZone;
if (tempZone.NotValid ())
{
tempZone = LocalTimeZone (fDateTime);
}
result.Append (tempZone.Encode_ISO_8601 ().Get ());
}
}
return result;
}
/*****************************************************************************/
void dng_date_time_info::Decode_IPTC_Date (const char *s)
{
if (strlen (s) == 8)
{
unsigned year = 0;
unsigned month = 0;
unsigned day = 0;
if (sscanf (s,
"%4u%2u%2u",
&year,
&month,
&day) == 3)
{
SetDate ((uint32) year,
(uint32) month,
(uint32) day);
}
}
}
/*****************************************************************************/
dng_string dng_date_time_info::Encode_IPTC_Date () const
{
dng_string result;
if (IsValid ())
{
char s [64];
sprintf (s,
"%04u%02u%02u",
fDateTime.fYear,
fDateTime.fMonth,
fDateTime.fDay);
result.Set (s);
}
return result;
}
/*****************************************************************************/
void dng_date_time_info::Decode_IPTC_Time (const char *s)
{
if (strlen (s) == 11)
{
char time [12];
memcpy (time, s, sizeof (time));
if (time [6] == '+' ||
time [6] == '-')
{
int tzsign = (time [6] == '-') ? -1 : 1;
time [6] = 0;
unsigned hour = 0;
unsigned minute = 0;
unsigned second = 0;
unsigned tzhour = 0;
unsigned tzmin = 0;
if (sscanf (time,
"%2u%2u%2u",
&hour,
&minute,
&second) == 3 &&
sscanf (time + 7,
"%2u%2u",
&tzhour,
&tzmin) == 2)
{
dng_time_zone zone;
zone.SetOffsetMinutes (tzsign * (tzhour * 60 + tzmin));
if (zone.IsValid ())
{
SetTime ((uint32) hour,
(uint32) minute,
(uint32) second);
SetZone (zone);
}
}
}
}
}
/*****************************************************************************/
dng_string dng_date_time_info::Encode_IPTC_Time () const
{
dng_string result;
if (IsValid () && !fDateOnly && fTimeZone.IsValid ())
{
char s [64];
sprintf (s,
"%02u%02u%02u%c%02u%02u",
fDateTime.fHour,
fDateTime.fMinute,
fDateTime.fSecond,
fTimeZone.OffsetMinutes () >= 0 ? '+' : '-',
Abs_int32 (fTimeZone.OffsetMinutes ()) / 60,
Abs_int32 (fTimeZone.OffsetMinutes ()) % 60);
result.Set (s);
}
return result;
}
/*****************************************************************************/
static dng_mutex gDateTimeMutex ("gDateTimeMutex");
/*****************************************************************************/
void CurrentDateTimeAndZone (dng_date_time_info &info)
{
time_t sec;
time (&sec);
tm t;
tm zt;
{
dng_lock_mutex lock (&gDateTimeMutex);
t = *localtime (&sec);
zt = *gmtime (&sec);
}
dng_date_time dt;
dt.fYear = t.tm_year + 1900;
dt.fMonth = t.tm_mon + 1;
dt.fDay = t.tm_mday;
dt.fHour = t.tm_hour;
dt.fMinute = t.tm_min;
dt.fSecond = t.tm_sec;
info.SetDateTime (dt);
int tzHour = t.tm_hour - zt.tm_hour;
int tzMin = t.tm_min - zt.tm_min;
bool zonePositive = (t.tm_year > zt.tm_year) ||
(t.tm_year == zt.tm_year && t.tm_yday > zt.tm_yday) ||
(t.tm_year == zt.tm_year && t.tm_yday == zt.tm_yday && tzHour > 0) ||
(t.tm_year == zt.tm_year && t.tm_yday == zt.tm_yday && tzHour == 0 && tzMin >= 0);
tzMin += tzHour * 60;
if (zonePositive)
{
while (tzMin < 0)
tzMin += 24 * 60;
}
else
{
while (tzMin > 0)
tzMin -= 24 * 60;
}
dng_time_zone zone;
zone.SetOffsetMinutes (tzMin);
info.SetZone (zone);
}
/*****************************************************************************/
void DecodeUnixTime (uint32 unixTime, dng_date_time &dt)
{
time_t sec = (time_t) unixTime;
tm t;
{
dng_lock_mutex lock (&gDateTimeMutex);
#if qMacOS && !defined(__MACH__)
// Macintosh CFM stores time in local time zone.
tm *tp = localtime (&sec);
#else
// Macintosh Mach-O and Windows stores time in Zulu time.
tm *tp = gmtime (&sec);
#endif
if (!tp)
{
dt.Clear ();
return;
}
t = *tp;
}
dt.fYear = t.tm_year + 1900;
dt.fMonth = t.tm_mon + 1;
dt.fDay = t.tm_mday;
dt.fHour = t.tm_hour;
dt.fMinute = t.tm_min;
dt.fSecond = t.tm_sec;
}
/*****************************************************************************/
dng_time_zone LocalTimeZone (const dng_date_time &dt)
{
dng_time_zone result;
if (dt.IsValid ())
{
#if qMacOS
CFTimeZoneRef zoneRef = CFTimeZoneCopyDefault ();
if (zoneRef)
{
CFGregorianDate gregDate;
gregDate.year = dt.fYear;
gregDate.month = dt.fMonth;
gregDate.day = dt.fDay;
gregDate.hour = dt.fHour;
gregDate.minute = dt.fMinute;
gregDate.second = dt.fSecond;
CFAbsoluteTime absTime = CFGregorianDateGetAbsoluteTime (gregDate, zoneRef);
CFTimeInterval secondsDelta = CFTimeZoneGetSecondsFromGMT (zoneRef, absTime);
CFRelease (zoneRef);
result.SetOffsetSeconds (secondsDelta);
if (result.IsValid ())
{
return result;
}
}
#endif
#if qWinOS
if (GetTimeZoneInformation != NULL &&
SystemTimeToTzSpecificLocalTime != NULL &&
SystemTimeToFileTime != NULL)
{
TIME_ZONE_INFORMATION tzInfo;
DWORD x = GetTimeZoneInformation (&tzInfo);
SYSTEMTIME localST;
memset (&localST, 0, sizeof (localST));
localST.wYear = (WORD) dt.fYear;
localST.wMonth = (WORD) dt.fMonth;
localST.wDay = (WORD) dt.fDay;
localST.wHour = (WORD) dt.fHour;
localST.wMinute = (WORD) dt.fMinute;
localST.wSecond = (WORD) dt.fSecond;
SYSTEMTIME utcST;
if (TzSpecificLocalTimeToSystemTime (&tzInfo, &localST, &utcST))
{
FILETIME localFT;
FILETIME utcFT;
(void) SystemTimeToFileTime (&localST, &localFT);
(void) SystemTimeToFileTime (&utcST , &utcFT );
uint64 time1 = (((uint64) localFT.dwHighDateTime) << 32) + localFT.dwLowDateTime;
uint64 time2 = (((uint64) utcFT .dwHighDateTime) << 32) + utcFT .dwLowDateTime;
// FILETIMEs are in units to 100 ns. Convert to seconds.
int64 time1Sec = time1 / 10000000;
int64 time2Sec = time2 / 10000000;
int32 delta = (int32) (time1Sec - time2Sec);
result.SetOffsetSeconds (delta);
if (result.IsValid ())
{
return result;
}
}
}
#endif
}
// Figure out local time zone.
dng_date_time_info current_info;
CurrentDateTimeAndZone (current_info);
result = current_info.TimeZone ();
return result;
}
/*****************************************************************************/
dng_date_time_storage_info::dng_date_time_storage_info ()
: fOffset (kDNGStreamInvalidOffset )
, fFormat (dng_date_time_format_unknown)
{
}
/*****************************************************************************/
dng_date_time_storage_info::dng_date_time_storage_info (uint64 offset,
dng_date_time_format format)
: fOffset (offset)
, fFormat (format)
{
}
/*****************************************************************************/
bool dng_date_time_storage_info::IsValid () const
{
return fOffset != kDNGStreamInvalidOffset;
}
/*****************************************************************************/
uint64 dng_date_time_storage_info::Offset () const
{
if (!IsValid ())
ThrowProgramError ();
return fOffset;
}
/*****************************************************************************/
dng_date_time_format dng_date_time_storage_info::Format () const
{
if (!IsValid ())
ThrowProgramError ();
return fFormat;
}
/*****************************************************************************
* Win32 kernel time functions from Wine API used to implement
- * TzSpecificLocalTimeToSystemTime() from win32 which is unavialable with MinGW.
+ * TzSpecificLocalTimeToSystemTime() from win32 which is unavailable with MinGW.
*/
#if qWinOS && defined(__GNUC__)
/***********************************************************************
* TIME_GetTimezoneBias
*
* Calculates the local time bias for a given time zone.
*
* PARAMS
* pTZinfo [in] The time zone data.
* lpFileTime [in] The system or local time.
* islocal [in] It is local time.
* pBias [out] The calculated bias in minutes.
*
* RETURNS
* TRUE when the time zone bias was calculated.
*/
BOOL TIME_GetTimezoneBias(const TIME_ZONE_INFORMATION *pTZinfo,
FILETIME *lpFileTime, BOOL islocal, LONG *pBias)
{
LONG bias = pTZinfo->Bias;
DWORD tzid = TIME_CompTimeZoneID( pTZinfo, lpFileTime, islocal);
if( tzid == TIME_ZONE_ID_INVALID)
return FALSE;
if (tzid == TIME_ZONE_ID_DAYLIGHT)
bias += pTZinfo->DaylightBias;
else if (tzid == TIME_ZONE_ID_STANDARD)
bias += pTZinfo->StandardBias;
*pBias = bias;
return TRUE;
}
/***********************************************************************
* TIME_CompTimeZoneID
*
* Computes the local time bias for a given time and time zone.
*
* PARAMS
* pTZinfo [in] The time zone data.
* lpFileTime [in] The system or local time.
* islocal [in] it is local time.
*
* RETURNS
* TIME_ZONE_ID_INVALID An error occurred
* TIME_ZONE_ID_UNKNOWN There are no transition time known
* TIME_ZONE_ID_STANDARD Current time is standard time
* TIME_ZONE_ID_DAYLIGHT Current time is dayligh saving time
*/
BOOL TIME_CompTimeZoneID(const TIME_ZONE_INFORMATION *pTZinfo,
FILETIME *lpFileTime, BOOL islocal)
{
int ret;
BOOL beforeStandardDate, afterDaylightDate;
DWORD retval = TIME_ZONE_ID_INVALID;
LONGLONG llTime = 0; /* initialized to prevent gcc complaining */
SYSTEMTIME SysTime;
FILETIME ftTemp;
if (pTZinfo->DaylightDate.wMonth != 0)
{
if (pTZinfo->StandardDate.wMonth == 0 ||
pTZinfo->StandardDate.wDay<1 ||
pTZinfo->StandardDate.wDay>5 ||
pTZinfo->DaylightDate.wDay<1 ||
pTZinfo->DaylightDate.wDay>5)
{
SetLastError(ERROR_INVALID_PARAMETER);
return TIME_ZONE_ID_INVALID;
}
if (!islocal) {
FILETIME2LL( lpFileTime, llTime );
llTime -= ( pTZinfo->Bias + pTZinfo->DaylightBias )
* (LONGLONG)600000000;
LL2FILETIME( llTime, &ftTemp)
lpFileTime = &ftTemp;
}
FileTimeToSystemTime(lpFileTime, &SysTime);
/* check for daylight saving */
ret = TIME_DayLightCompareDate( &SysTime, &pTZinfo->StandardDate);
if (ret == -2)
return TIME_ZONE_ID_INVALID;
beforeStandardDate = ret < 0;
if (!islocal) {
llTime -= ( pTZinfo->StandardBias - pTZinfo->DaylightBias )
* (LONGLONG)600000000;
LL2FILETIME( llTime, &ftTemp)
FileTimeToSystemTime(lpFileTime, &SysTime);
}
ret = TIME_DayLightCompareDate( &SysTime, &pTZinfo->DaylightDate);
if (ret == -2)
return TIME_ZONE_ID_INVALID;
afterDaylightDate = ret >= 0;
retval = TIME_ZONE_ID_STANDARD;
if( pTZinfo->DaylightDate.wMonth < pTZinfo->StandardDate.wMonth ) {
/* Northern hemisphere */
if( beforeStandardDate && afterDaylightDate )
retval = TIME_ZONE_ID_DAYLIGHT;
} else /* Down south */
if( beforeStandardDate || afterDaylightDate )
retval = TIME_ZONE_ID_DAYLIGHT;
} else
/* No transition date */
retval = TIME_ZONE_ID_UNKNOWN;
return retval;
}
/***********************************************************************
* TIME_DayLightCompareDate
*
* Compares two dates without looking at the year.
*
* PARAMS
* date [in] The local time to compare.
* compareDate [in] The daylight saving begin or end date.
*
* RETURNS
*
* -1 if date < compareDate
* 0 if date == compareDate
* 1 if date > compareDate
* -2 if an error occurs
*/
int TIME_DayLightCompareDate(const SYSTEMTIME *date,
const SYSTEMTIME *compareDate)
{
int limit_day, dayinsecs;
if (date->wMonth < compareDate->wMonth)
return -1; /* We are in a month before the date limit. */
if (date->wMonth > compareDate->wMonth)
return 1; /* We are in a month after the date limit. */
if (compareDate->wDayOfWeek <= 6)
{
WORD First;
/* compareDate->wDay is interpreted as number of the week in the month
* 5 means: the last week in the month */
int weekofmonth = compareDate->wDay;
/* calculate the day of the first DayOfWeek in the month */
First = ( 6 + compareDate->wDayOfWeek - date->wDayOfWeek + date->wDay
) % 7 + 1;
limit_day = First + 7 * (weekofmonth - 1);
/* check needed for the 5th weekday of the month */
if(limit_day > MonthLengths[date->wMonth==2 && IsLeapYear(date->wYear)]
[date->wMonth - 1])
limit_day -= 7;
}
else
{
limit_day = compareDate->wDay;
}
/* convert to seconds */
limit_day = ((limit_day * 24 + compareDate->wHour) * 60 +
compareDate->wMinute ) * 60;
dayinsecs = ((date->wDay * 24 + date->wHour) * 60 +
date->wMinute ) * 60 + date->wSecond;
/* and compare */
return dayinsecs < limit_day ? -1 :
dayinsecs > limit_day ? 1 :
0; /* date is equal to the date limit. */
}
/***********************************************************************
* TzSpecificLocalTimeToSystemTime
*
* Converts a local time to a time in utc.
*
* PARAMS
* lpTimeZoneInformation [in] The desired time zone.
* lpLocalTime [in] The local time.
* lpUniversalTime [out] The calculated utc time.
*
* RETURNS
* Success: TRUE. lpUniversalTime contains the converted time.
* Failure: FALSE.
*/
BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformation,
LPSYSTEMTIME lpLocalTime,
LPSYSTEMTIME lpUniversalTime)
{
FILETIME ft;
LONG lBias;
LONGLONG t;
TIME_ZONE_INFORMATION tzinfo;
if (lpTimeZoneInformation != NULL)
{
memcpy(&tzinfo, lpTimeZoneInformation, sizeof(TIME_ZONE_INFORMATION));
}
else
{
if (GetTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_INVALID)
return FALSE;
}
if (!SystemTimeToFileTime(lpLocalTime, &ft))
return FALSE;
FILETIME2LL( &ft, t)
if (!TIME_GetTimezoneBias(&tzinfo, &ft, TRUE, &lBias))
return FALSE;
/* convert minutes to 100-nanoseconds-ticks */
t += (LONGLONG)lBias * 600000000;
LL2FILETIME( t, &ft)
return FileTimeToSystemTime(&ft, lpUniversalTime);
}
#endif /* qWinOS && defined(__GNUC__) */
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_date_time.h b/core/libs/dngwriter/extra/dng_sdk/dng_date_time.h
index 3cece89380..6ffe27ee04 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_date_time.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_date_time.h
@@ -1,379 +1,379 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_date_time.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Functions and classes for working with dates and times in DNG files.
*/
/*****************************************************************************/
#ifndef __dng_date_time__
#define __dng_date_time__
/*****************************************************************************/
#include "dng_classes.h"
#include "dng_string.h"
#include "dng_types.h"
/*****************************************************************************/
/// \brief Class for holding a date/time and converting to and from relevant
/// date/time formats
class dng_date_time
{
public:
uint32 fYear;
uint32 fMonth;
uint32 fDay;
uint32 fHour;
uint32 fMinute;
uint32 fSecond;
public:
/// Construct an invalid date/time
dng_date_time ();
/// Construct a date/time with specific values.
/// \param year Year to use as actual integer value, such as 2006.
/// \param month Month to use from 1 - 12, where 1 is January.
/// \param day Day of month to use from 1 -31, where 1 is the first.
/// \param hour Hour of day to use from 0 - 23, where 0 is midnight.
/// \param minute Minute of hour to use from 0 - 59.
/// \param second Second of minute to use from 0 - 59.
dng_date_time (uint32 year,
uint32 month,
uint32 day,
uint32 hour,
uint32 minute,
uint32 second);
/// Predicate to determine if a date is valid.
/// \retval true if all fields are within range.
bool IsValid () const;
/// Predicate to determine if a date is invalid.
/// \retval true if any field is out of range.
bool NotValid () const
{
return !IsValid ();
}
/// Equal operator.
bool operator== (const dng_date_time &dt) const
{
return fYear == dt.fYear &&
fMonth == dt.fMonth &&
fDay == dt.fDay &&
fHour == dt.fHour &&
fMinute == dt.fMinute &&
fSecond == dt.fSecond;
}
// Not-equal operator.
bool operator!= (const dng_date_time &dt) const
{
return !(*this == dt);
}
/// Set date to an invalid value.
void Clear ();
/// Parse an EXIF format date string.
/// \param s Input date string to parse.
/// \retval true if date was parsed successfully and date is valid.
bool Parse (const char *s);
};
/*****************************************************************************/
/// \brief Class for holding a time zone.
class dng_time_zone
{
private:
enum
{
kMaxOffsetHours = 15,
kMinOffsetHours = -kMaxOffsetHours,
kMaxOffsetMinutes = kMaxOffsetHours * 60,
kMinOffsetMinutes = kMinOffsetHours * 60,
kInvalidOffset = kMinOffsetMinutes - 1
};
// Offset from GMT in minutes. Positive numbers are
// ahead of GMT, negative number are behind GMT.
int32 fOffsetMinutes;
public:
dng_time_zone ()
: fOffsetMinutes (kInvalidOffset)
{
}
void Clear ()
{
fOffsetMinutes = kInvalidOffset;
}
void SetOffsetHours (int32 offset)
{
fOffsetMinutes = offset * 60;
}
void SetOffsetMinutes (int32 offset)
{
fOffsetMinutes = offset;
}
void SetOffsetSeconds (int32 offset)
{
fOffsetMinutes = (offset > 0) ? ((offset + 30) / 60)
: ((offset - 30) / 60);
}
bool IsValid () const
{
return fOffsetMinutes >= kMinOffsetMinutes &&
fOffsetMinutes <= kMaxOffsetMinutes;
}
bool NotValid () const
{
return !IsValid ();
}
int32 OffsetMinutes () const
{
return fOffsetMinutes;
}
bool IsExactHourOffset () const
{
return IsValid () && ((fOffsetMinutes % 60) == 0);
}
int32 ExactHourOffset () const
{
return fOffsetMinutes / 60;
}
dng_string Encode_ISO_8601 () const;
};
/*****************************************************************************/
/// \brief Class for holding complete data/time/zone information.
class dng_date_time_info
{
private:
// Is only the date valid and not the time?
bool fDateOnly;
// Date and time.
dng_date_time fDateTime;
// Subseconds string (stored in a separate tag in EXIF).
dng_string fSubseconds;
// Time zone, if known.
dng_time_zone fTimeZone;
public:
dng_date_time_info ();
bool IsValid () const;
bool NotValid () const
{
return !IsValid ();
}
void Clear ()
{
*this = dng_date_time_info ();
}
const dng_date_time & DateTime () const
{
return fDateTime;
}
void SetDateTime (const dng_date_time &dt)
{
fDateOnly = false;
fDateTime = dt;
}
const dng_string & Subseconds () const
{
return fSubseconds;
}
void SetSubseconds (const dng_string &s)
{
fSubseconds = s;
}
const dng_time_zone & TimeZone () const
{
return fTimeZone;
}
void SetZone (const dng_time_zone &zone)
{
fTimeZone = zone;
}
void Decode_ISO_8601 (const char *s);
dng_string Encode_ISO_8601 () const;
void Decode_IPTC_Date (const char *s);
dng_string Encode_IPTC_Date () const;
void Decode_IPTC_Time (const char *s);
dng_string Encode_IPTC_Time () const;
private:
void SetDate (uint32 year,
uint32 month,
uint32 day);
void SetTime (uint32 hour,
uint32 minute,
uint32 second);
};
/*****************************************************************************/
/// Get the current date/time and timezone.
/// \param info Receives current data/time/zone.
void CurrentDateTimeAndZone (dng_date_time_info &info);
/*****************************************************************************/
/// Convert UNIX "seconds since Jan 1, 1970" time to a dng_date_time
void DecodeUnixTime (uint32 unixTime, dng_date_time &dt);
/*****************************************************************************/
/// Return timezone of current location at a given date.
/// \param dt Date at which to compute timezone difference. (For example, used
/// to determine Daylight Savings, etc.)
/// \retval Time zone for date/time dt.
dng_time_zone LocalTimeZone (const dng_date_time &dt);
/*****************************************************************************/
-/// Tag to encode date represenation format
+/// Tag to encode date representation format
enum dng_date_time_format
{
dng_date_time_format_unknown = 0, /// Date format not known
dng_date_time_format_exif = 1, /// EXIF date string
dng_date_time_format_unix_little_endian = 2, /// 32-bit UNIX time as 4-byte little endian
dng_date_time_format_unix_big_endian = 3 /// 32-bit UNIX time as 4-byte big endian
};
/*****************************************************************************/
/// \brief Store file offset from which date was read.
///
/// Used internally by Adobe to update date in original file.
/// \warning Use at your own risk.
class dng_date_time_storage_info
{
private:
uint64 fOffset;
dng_date_time_format fFormat;
public:
/// The default constructor initializes to an invalid state.
dng_date_time_storage_info ();
/// Construct with file offset and date format.
dng_date_time_storage_info (uint64 offset,
dng_date_time_format format);
/// Predicate to determine if an offset is valid.
/// \retval true if offset is valid.
bool IsValid () const;
// The accessors throw if the data is not valid.
/// Getter for offset in file.
/// \exception dng_exception with fErrorCode equal to dng_error_unknown
/// if offset is not valid.
uint64 Offset () const;
/// Get for format date was originally stored in file. Throws a
/// dng_error_unknown exception if offset is invalid.
/// \exception dng_exception with fErrorCode equal to dng_error_unknown
/// if offset is not valid.
dng_date_time_format Format () const;
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_fast_module.h b/core/libs/dngwriter/extra/dng_sdk/dng_fast_module.h
index d0feff7330..21e58df51e 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_fast_module.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_fast_module.h
@@ -1,31 +1,31 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_fast_module.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Include file to set optimization to highest level for performance-critical routines.
- * Normal files should have otpimization set to normal level to save code size as there is less
+ * Normal files should have optimization set to normal level to save code size as there is less
* cache pollution this way.
*/
/*****************************************************************************/
// Include this file in modules that contain routines that should be as fast
// as possible, even at the expense of slight code size increases.
/*****************************************************************************/
#ifdef _MSC_VER
#pragma optimize ("t", on)
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_host.h b/core/libs/dngwriter/extra/dng_sdk/dng_host.h
index 55c04fdd95..f638f71914 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_host.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_host.h
@@ -1,390 +1,390 @@
/*****************************************************************************/
// Copyright 2006-2009 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_host.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Class definition for dng_host, initial point of contact and control between
* host application and DNG SDK.
*/
/*****************************************************************************/
#ifndef __dng_host__
#define __dng_host__
/*****************************************************************************/
#include "dng_auto_ptr.h"
#include "dng_classes.h"
#include "dng_errors.h"
#include "dng_types.h"
/*****************************************************************************/
/// \brief The main class for communication between the application and the
/// DNG SDK. Used to customize memory allocation and other behaviors.
///
/// dng_host allows setting parameters for the DNG conversion, mediates callback
/// style interactions between the host application and the DNG SDK, and allows
/// controlling certain internal behavior of the SDK such as memory allocation.
/// Many applications will be able to use the default implementation of dng_host
/// by just setting the dng_memory_allocator and dng_abort_sniffer in the
/// constructor. More complex interactions will require deriving a class from
/// dng_host.
///
/// Multiple dng_host objects can be allocated in a single process. This may
/// be useful for DNG processing on separate threads. (Distinct dng_host objects
/// are completely threadsafe for read/write. The application is responsible for
/// establishing mutual exclusion for read/write access to a single dng_host
/// object if it is used in multiple threads.)
class dng_host
{
private:
dng_memory_allocator *fAllocator;
dng_abort_sniffer *fSniffer;
// Does the host require all the image metadata (vs. just checking
// to see if the file is readable)?
bool fNeedsMeta;
// Does the host require actual image data (vs. just getting metadata
// or just checking to see if the file is readable)?
bool fNeedsImage;
// If we need the image data, can it be read at preview quality?
bool fForPreview;
// If non-zero, the minimum size (longer of the two pixel dimensions)
// image to read. If zero, or if the full size image is smaller than
// this, read the full size image.
uint32 fMinimumSize;
// What is the preferred size for a preview image? This can
// be slightly larger than the minimum size. Zero if we want
// the full resolution image.
uint32 fPreferredSize;
// What is the maximum size for a preview image? Zero if there
// is no maximum size limit.
uint32 fMaximumSize;
// The fraction of the image kept after a crop. This is used to
// adjust the sizes to take into account the cropping that
- // will be peformed.
+ // will be performed.
real64 fCropFactor;
// What DNG version should we keep enough data to save?
uint32 fSaveDNGVersion;
// Do we want to force saving to a linear DNG?
bool fSaveLinearDNG;
// Keep the original raw file data block?
bool fKeepOriginalFile;
public:
- /// Allocate a dng_host object, possiblly with custom allocator and sniffer.
+ /// Allocate a dng_host object, possibly with custom allocator and sniffer.
/// \param allocator Allows controlling all memory allocation done via this
/// dng_host. Defaults to singleton global dng_memory_allocator, which calls
/// new/delete dng_malloc_block for appropriate size.
/// \param sniffer Used to periodically check if pending DNG conversions
/// should be aborted and to communicate progress updates. Defaults to singleton
/// global dng_abort_sniffer, which never aborts and ignores progress updated.
dng_host (dng_memory_allocator *allocator = NULL,
dng_abort_sniffer *sniffer = NULL);
/// Clean up direct memory for dng_host. Memory allocator and abort sniffer
/// are not deleted. Objects such as dng_image and others returned from
/// host can still be used after host is deleted.
virtual ~dng_host ();
/// Getter for host's memory allocator.
dng_memory_allocator & Allocator ();
/// Alocate a new dng_memory_block using the host's memory allocator.
/// Uses the Allocator() property of host to allocate a new block of memory.
/// Will call ThrowMemoryFull if block cannot be allocated.
/// \param logicalSize Number of usable bytes returned dng_memory_block
/// must contain.
virtual dng_memory_block * Allocate (uint32 logicalSize);
/// Setter for host's abort sniffer.
void SetSniffer (dng_abort_sniffer *sniffer)
{
fSniffer = sniffer;
}
/// Getter for host's abort sniffer.
dng_abort_sniffer * Sniffer ()
{
return fSniffer;
}
/// Check for pending abort. Should call ThrowUserCanceled if an abort
/// is pending.
virtual void SniffForAbort ();
/// Setter for flag determining whether all XMP metadata should be parsed.
/// Defaults to true. One might not want metadata when doing a quick check
/// to see if a file is readable.
/// \param needs If true, metadata is needed.
void SetNeedsMeta (bool needs)
{
fNeedsMeta = needs;
}
/// Getter for flag determining whether all XMP metadata should be parsed.
bool NeedsMeta () const
{
return fNeedsMeta;
}
/// Setter for flag determining whether DNG image data is needed. Defaults
/// to true. Image data might not be needed for applications which only
/// manipulate metadata.
/// \param needs If true, image data is needed.
void SetNeedsImage (bool needs)
{
fNeedsImage = needs;
}
/// Setter for flag determining whether DNG image data is needed.
bool NeedsImage () const
{
return fNeedsImage;
}
/// Setter for flag determining whether image should be preview quality,
/// or full quality.
/// \param preview If true, rendered images are for preview.
void SetForPreview (bool preview)
{
fForPreview = preview;
}
/// Getter for flag determining whether image should be preview quality.
/// Preview quality images may be rendered more quickly. Current DNG SDK
/// does not change rendering behavior based on this flag, but derived
/// versions may use this getter to choose between a slower more accurate path
/// and a faster "good enough for preview" one. Data produce with ForPreview set
/// to true should not be written back to a DNG file, except as a preview image.
bool ForPreview () const
{
return fForPreview;
}
/// Setter for the minimum preview size.
/// \param size Minimum pixel size (long side of image).
void SetMinimumSize (uint32 size)
{
fMinimumSize = size;
}
/// Getter for the minimum preview size.
uint32 MinimumSize () const
{
return fMinimumSize;
}
/// Setter for the preferred preview size.
/// \param size Preferred pixel size (long side of image).
void SetPreferredSize (uint32 size)
{
fPreferredSize = size;
}
/// Getter for the preferred preview size.
uint32 PreferredSize () const
{
return fPreferredSize;
}
/// Setter for the maximum preview size.
/// \param size Maximum pixel size (long side of image).
void SetMaximumSize (uint32 size)
{
fMaximumSize = size;
}
/// Getter for the maximum preview size.
uint32 MaximumSize () const
{
return fMaximumSize;
}
/// Setter for the cropping factor.
/// \param cropFactor Fraction of image to be used after crop.
void SetCropFactor (real64 cropFactor)
{
fCropFactor = cropFactor;
}
/// Getter for the cropping factor.
real64 CropFactor () const
{
return fCropFactor;
}
/// Makes sures minimum, preferred, and maximum sizes are reasonable.
void ValidateSizes ();
/// Setter for what version to save DNG file compatible with.
/// \param version What version to save DNG file compatible with.
void SetSaveDNGVersion (uint32 version)
{
fSaveDNGVersion = version;
}
/// Getter for what version to save DNG file compatible with.
virtual uint32 SaveDNGVersion () const;
/// Setter for flag determining whether to force saving a linear DNG file.
/// \param linear If true, we should force saving a linear DNG file.
void SetSaveLinearDNG (bool linear)
{
fSaveLinearDNG = linear;
}
/// Getter for flag determining whether to save a linear DNG file.
virtual bool SaveLinearDNG (const dng_negative &negative) const;
/// Setter for flag determining whether to keep original RAW file data.
- /// \param keep If true, origianl RAW data will be kept.
+ /// \param keep If true, original RAW data will be kept.
void SetKeepOriginalFile (bool keep)
{
fKeepOriginalFile = keep;
}
/// Getter for flag determining whether to keep original RAW file data.
bool KeepOriginalFile ()
{
return fKeepOriginalFile;
}
/// Determine if an error is the result of a temporary, but planned-for
- /// occurence such as user cancellation or memory exhaustion. This method is
+ /// occurrence such as user cancellation or memory exhaustion. This method is
/// sometimes used to determine whether to try and continue processing a DNG
/// file despite errors in the file format, etc. In such cases, processing will
/// be continued if IsTransientError returns false. This is so that user cancellation
/// and memory exhaustion always terminate processing.
/// \param code Error to test for transience.
virtual bool IsTransientError (dng_error_code code);
- /// General top-level botttleneck for image processing tasks.
+ /// General top-level bottleneck for image processing tasks.
/// Default implementation calls dng_area_task::PerformAreaTask method on
/// task. Can be overridden in derived classes to support multiprocessing,
/// for example.
/// \param task Image processing task to perform on area.
/// \param area Rectangle over which to perform image processing task.
virtual void PerformAreaTask (dng_area_task &task,
const dng_rect &area);
/// Factory method for dng_exif class. Can be used to customize allocation or
/// to ensure a derived class is used instead of dng_exif.
virtual dng_exif * Make_dng_exif ();
/// Factory method for dng_shared class. Can be used to customize allocation
/// or to ensure a derived class is used instead of dng_shared.
virtual dng_shared * Make_dng_shared ();
/// Factory method for dng_ifd class. Can be used to customize allocation or
/// to ensure a derived class is used instead of dng_ifd.
virtual dng_ifd * Make_dng_ifd ();
/// Factory method for dng_negative class. Can be used to customize allocation
/// or to ensure a derived class is used instead of dng_negative.
virtual dng_negative * Make_dng_negative ();
/// Factory method for dng_image class. Can be used to customize allocation
/// or to ensure a derived class is used instead of dng_simple_image.
virtual dng_image * Make_dng_image (const dng_rect &bounds,
uint32 planes,
uint32 pixelType);
- /// Factory method for parsing dng_opcode based classs. Can be used to
+ /// Factory method for parsing dng_opcode based class. Can be used to
/// override opcode implementations.
virtual dng_opcode * Make_dng_opcode (uint32 opcodeID,
dng_stream &stream);
/// Factory method to apply a dng_opcode_list. Can be used to override
/// opcode list applications.
virtual void ApplyOpcodeList (dng_opcode_list &list,
dng_negative &negative,
AutoPtr<dng_image> &image);
private:
// Hidden copy constructor and assignment operator.
dng_host (const dng_host &host);
dng_host & operator= (const dng_host &host);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_ifd.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_ifd.cpp
index 71518371e8..3ef91b8c62 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_ifd.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_ifd.cpp
@@ -1,3837 +1,3837 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_ifd.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_ifd.h"
#include "dng_exceptions.h"
#include "dng_globals.h"
#include "dng_ifd.h"
#include "dng_types.h"
#include "dng_parse_utils.h"
#include "dng_read_image.h"
#include "dng_stream.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_utils.h"
/*****************************************************************************/
dng_preview_info::dng_preview_info ()
: fIsPrimary (true)
, fApplicationName ()
, fApplicationVersion ()
, fSettingsName ()
, fSettingsDigest ()
, fColorSpace (previewColorSpace_MaxEnum)
, fDateTime ()
{
}
/*****************************************************************************/
dng_preview_info::~dng_preview_info ()
{
}
/*****************************************************************************/
dng_ifd::dng_ifd ()
: fUsesNewSubFileType (false)
, fNewSubFileType (0)
, fImageWidth (0)
, fImageLength (0)
, fCompression (ccUncompressed)
, fPredictor (cpNullPredictor)
, fPhotometricInterpretation (0xFFFFFFFF)
, fFillOrder (1)
, fOrientation (0)
, fOrientationType (0)
, fOrientationOffset (kDNGStreamInvalidOffset)
, fOrientationBigEndian (false)
, fSamplesPerPixel (1)
, fPlanarConfiguration (pcInterleaved)
, fXResolution (0.0)
, fYResolution (0.0)
, fResolutionUnit (0)
, fUsesStrips (false)
, fUsesTiles (false)
, fTileWidth (0)
, fTileLength (0)
, fTileOffsetsType (0)
, fTileOffsetsCount (0)
, fTileOffsetsOffset (0)
, fTileByteCountsType (0)
, fTileByteCountsCount (0)
, fTileByteCountsOffset (0)
, fSubIFDsCount (0)
, fSubIFDsOffset (0)
, fExtraSamplesCount (0)
, fJPEGTablesCount (0)
, fJPEGTablesOffset (0)
, fJPEGInterchangeFormat (0)
, fJPEGInterchangeFormatLength (0)
, fYCbCrCoefficientR (0.0)
, fYCbCrCoefficientG (0.0)
, fYCbCrCoefficientB (0.0)
, fYCbCrSubSampleH (0)
, fYCbCrSubSampleV (0)
, fYCbCrPositioning (0)
, fCFARepeatPatternRows (0)
, fCFARepeatPatternCols (0)
, fCFALayout (1)
, fLinearizationTableType (0)
, fLinearizationTableCount (0)
, fLinearizationTableOffset (0)
, fBlackLevelRepeatRows (1)
, fBlackLevelRepeatCols (1)
, fBlackLevelDeltaHType (0)
, fBlackLevelDeltaHCount (0)
, fBlackLevelDeltaHOffset (0)
, fBlackLevelDeltaVType (0)
, fBlackLevelDeltaVCount (0)
, fBlackLevelDeltaVOffset (0)
, fDefaultScaleH (1, 1)
, fDefaultScaleV (1, 1)
, fBestQualityScale (1, 1)
, fDefaultCropOriginH (0, 1)
, fDefaultCropOriginV (0, 1)
, fDefaultCropSizeH ()
, fDefaultCropSizeV ()
, fBayerGreenSplit (0)
, fChromaBlurRadius ()
, fAntiAliasStrength (1, 1)
, fActiveArea ()
, fMaskedAreaCount (0)
, fRowInterleaveFactor (1)
, fSubTileBlockRows (1)
, fSubTileBlockCols (1)
, fPreviewInfo ()
, fOpcodeList1Count (0)
, fOpcodeList1Offset (0)
, fOpcodeList2Count (0)
, fOpcodeList2Offset (0)
, fOpcodeList3Count (0)
, fOpcodeList3Offset (0)
, fLosslessJPEGBug16 (false)
, fSampleBitShift (0)
, fThisIFD (0)
, fNextIFD (0)
{
uint32 j;
uint32 k;
uint32 n;
for (j = 0; j < kMaxSamplesPerPixel; j++)
{
fBitsPerSample [j] = 0;
}
for (j = 0; j < kMaxTileInfo; j++)
{
fTileOffset [j] = 0;
fTileByteCount [j] = 0;
}
for (j = 0; j < kMaxSamplesPerPixel; j++)
{
fExtraSamples [j] = esUnspecified;
}
for (j = 0; j < kMaxSamplesPerPixel; j++)
{
fSampleFormat [j] = sfUnsignedInteger;
}
for (j = 0; j < 6; j++)
{
fReferenceBlackWhite [j] = 0.0;
}
for (j = 0; j < kMaxCFAPattern; j++)
for (k = 0; k < kMaxCFAPattern; k++)
{
fCFAPattern [j] [k] = 255;
}
for (j = 0; j < kMaxColorPlanes; j++)
{
fCFAPlaneColor [j] = (uint8) (j < 3 ? j : 255);
}
for (j = 0; j < kMaxBlackPattern; j++)
for (k = 0; k < kMaxBlackPattern; k++)
for (n = 0; n < kMaxSamplesPerPixel; n++)
{
fBlackLevel [j] [k] [n] = 0.0;
}
for (j = 0; j < kMaxSamplesPerPixel; j++)
{
fWhiteLevel [j] = -1.0; // Don't know real default yet.
}
}
/*****************************************************************************/
dng_ifd::~dng_ifd ()
{
}
/*****************************************************************************/
// Parses tags that should only appear in IFDs that contain images.
bool dng_ifd::ParseTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint64 tagOffset)
{
uint32 j;
uint32 k;
uint32 n;
switch (tagCode)
{
case tcNewSubFileType:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fUsesNewSubFileType = true;
fNewSubFileType = stream.TagValue_uint32 (tagType);
fPreviewInfo.fIsPrimary = (fNewSubFileType == sfPreviewImage);
#if qDNGValidate
if (gVerbose)
{
printf ("NewSubFileType: %s\n",
LookupNewSubFileType (fNewSubFileType));
}
#endif
break;
}
case tcImageWidth:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fImageWidth = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ImageWidth: %u\n", (unsigned) fImageWidth);
}
#endif
break;
}
case tcImageLength:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fImageLength = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ImageLength: %u\n", (unsigned) fImageLength);
}
#endif
break;
}
case tcBitsPerSample:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1, 0x0FFFF);
#if qDNGValidate
if (gVerbose)
{
printf ("BitsPerSample:");
}
#endif
bool extrasMatch = true;
for (j = 0; j < tagCount; j++)
{
uint32 x = stream.TagValue_uint32 (tagType);
if (j < kMaxSamplesPerPixel)
{
fBitsPerSample [j] = x;
}
else if (x != fBitsPerSample [kMaxSamplesPerPixel - 1])
{
extrasMatch = false;
}
#if qDNGValidate
if (gVerbose)
{
printf (" %u", (unsigned) x);
}
#endif
}
#if qDNGValidate
if (gVerbose)
{
printf ("\n");
}
#endif
if (!extrasMatch)
{
#if qDNGValidate
ReportError ("BitsPerSample not constant");
#endif
ThrowBadFormat ();
}
break;
}
case tcCompression:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fCompression = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("Compression: %s\n",
LookupCompression (fCompression));
}
#endif
// Correct a common TIFF writer mistake.
if (fCompression == 0)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s has invalid zero compression code",
LookupParentCode (parentCode));
ReportWarning (message);
}
#endif
fCompression = ccUncompressed;
}
break;
}
case tcPhotometricInterpretation:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fPhotometricInterpretation = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("PhotometricInterpretation: %s\n",
LookupPhotometricInterpretation (fPhotometricInterpretation));
}
#endif
break;
}
case tcFillOrder:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fFillOrder = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("FillOrder: %u\n", (unsigned) fFillOrder);
}
#endif
break;
}
case tcStripOffsets:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
fUsesStrips = true;
fTileOffsetsType = tagType;
fTileOffsetsCount = tagCount;
fTileOffsetsOffset = tagOffset;
if (tagCount <= kMaxTileInfo)
{
for (j = 0; j < tagCount; j++)
{
fTileOffset [j] = stream.TagValue_uint32 (tagType);
}
}
#if qDNGValidate
if (gVerbose)
{
stream.SetReadPosition (tagOffset);
DumpTagValues (stream,
"Offset",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcOrientation:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fOrientationType = tagType;
fOrientationOffset = stream.PositionInOriginalFile ();
fOrientationBigEndian = stream.BigEndian ();
fOrientation = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("Orientation: %s\n",
LookupOrientation (fOrientation));
}
#endif
break;
}
case tcSamplesPerPixel:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fSamplesPerPixel = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("SamplesPerPixel: %u\n", (unsigned) fSamplesPerPixel);
}
#endif
break;
}
case tcRowsPerStrip:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fUsesStrips = true;
fTileLength = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("RowsPerStrip: %u\n", (unsigned) fTileLength);
}
#endif
break;
}
case tcStripByteCounts:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
fUsesStrips = true;
fTileByteCountsType = tagType;
fTileByteCountsCount = tagCount;
fTileByteCountsOffset = tagOffset;
if (tagCount <= kMaxTileInfo)
{
for (j = 0; j < tagCount; j++)
{
fTileByteCount [j] = stream.TagValue_uint32 (tagType);
}
}
#if qDNGValidate
if (gVerbose)
{
stream.SetReadPosition (tagOffset);
DumpTagValues (stream,
"Count",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcXResolution:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fXResolution = stream.TagValue_real64 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("XResolution: %0.2f\n", fXResolution);
}
#endif
break;
}
case tcYResolution:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fYResolution = stream.TagValue_real64 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("YResolution: %0.2f\n", fYResolution);
}
#endif
break;
}
case tcPlanarConfiguration:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fPlanarConfiguration = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("PlanarConfiguration: %u\n", (unsigned) fPlanarConfiguration);
}
#endif
break;
}
case tcResolutionUnit:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fResolutionUnit = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ResolutionUnit: %s\n",
LookupResolutionUnit (fResolutionUnit));
}
#endif
break;
}
case tcPredictor:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fPredictor = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("Predictor: %u\n", (unsigned) fPredictor);
}
#endif
break;
}
case tcTileWidth:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fUsesTiles = true;
fTileWidth = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("TileWidth: %u\n", (unsigned) fTileWidth);
}
#endif
break;
}
case tcTileLength:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fUsesTiles = true;
fTileLength = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("TileLength: %u\n", (unsigned) fTileLength);
}
#endif
break;
}
case tcTileOffsets:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
fUsesTiles = true;
fTileOffsetsType = tagType;
fTileOffsetsCount = tagCount;
fTileOffsetsOffset = tagOffset;
if (tagCount <= kMaxTileInfo)
{
for (j = 0; j < tagCount; j++)
{
fTileOffset [j] = stream.TagValue_uint32 (tagType);
}
}
#if qDNGValidate
if (gVerbose)
{
stream.SetReadPosition (tagOffset);
DumpTagValues (stream,
"Offset",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcTileByteCounts:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
fUsesTiles = true;
fTileByteCountsType = tagType;
fTileByteCountsCount = tagCount;
fTileByteCountsOffset = tagOffset;
if (tagCount <= kMaxTileInfo)
{
for (j = 0; j < tagCount; j++)
{
fTileByteCount [j] = stream.TagValue_uint32 (tagType);
}
}
#if qDNGValidate
if (gVerbose)
{
stream.SetReadPosition (tagOffset);
DumpTagValues (stream,
"Count",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcSubIFDs:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
fSubIFDsCount = tagCount;
fSubIFDsOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
DumpTagValues (stream,
"IFD",
parentCode,
tagCode,
ttLong,
tagCount);
}
#endif
break;
}
case tcExtraSamples:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1, fSamplesPerPixel);
#if qDNGValidate
if (gVerbose)
{
printf ("ExtraSamples:");
}
#endif
fExtraSamplesCount = tagCount;
for (j = 0; j < tagCount; j++)
{
uint32 x = stream.TagValue_uint32 (tagType);
if (j < kMaxSamplesPerPixel)
{
fExtraSamples [j] = x;
}
#if qDNGValidate
if (gVerbose)
{
printf (" %u", (unsigned) x);
}
#endif
}
#if qDNGValidate
if (gVerbose)
{
printf ("\n");
}
#endif
break;
}
case tcSampleFormat:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, fSamplesPerPixel);
#if qDNGValidate
if (gVerbose)
{
printf ("SampleFormat:");
}
#endif
bool extrasMatch = true;
for (j = 0; j < tagCount; j++)
{
uint32 x = stream.TagValue_uint32 (tagType);
if (j < kMaxSamplesPerPixel)
{
fSampleFormat [j] = x;
}
else if (x != fSampleFormat [kMaxSamplesPerPixel - 1])
{
extrasMatch = false;
}
#if qDNGValidate
if (gVerbose)
{
printf (" %u", (unsigned) x);
}
#endif
}
#if qDNGValidate
if (gVerbose)
{
printf ("\n");
}
#endif
if (!extrasMatch)
{
#if qDNGValidate
ReportError ("SampleFormat not constant");
#endif
ThrowBadFormat ();
}
break;
}
case tcJPEGTables:
{
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fJPEGTablesCount = tagCount;
fJPEGTablesOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("JPEGTables: count = %u, offset = %u\n",
(unsigned) fJPEGTablesCount,
(unsigned) fJPEGTablesOffset);
}
#endif
break;
}
case tcJPEGInterchangeFormat:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fJPEGInterchangeFormat = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("JPEGInterchangeFormat: %u\n",
(unsigned) fJPEGInterchangeFormat);
}
#endif
break;
}
case tcJPEGInterchangeFormatLength:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fJPEGInterchangeFormatLength = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("JPEGInterchangeFormatLength: %u\n",
(unsigned) fJPEGInterchangeFormatLength);
}
#endif
break;
}
case tcYCbCrCoefficients:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
if (!CheckTagCount (parentCode, tagCode, tagCount, 3))
{
return false;
}
fYCbCrCoefficientR = stream.TagValue_real64 (tagType);
fYCbCrCoefficientG = stream.TagValue_real64 (tagType);
fYCbCrCoefficientB = stream.TagValue_real64 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("YCbCrCoefficients: R = %0.3f, G = %0.3f, B = %0.3f\n",
fYCbCrCoefficientR,
fYCbCrCoefficientG,
fYCbCrCoefficientB);
}
#endif
break;
}
case tcYCbCrSubSampling:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
{
return false;
}
fYCbCrSubSampleH = stream.TagValue_uint32 (tagType);
fYCbCrSubSampleV = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("YCbCrSubSampling: H = %u, V = %u\n",
(unsigned) fYCbCrSubSampleH,
(unsigned) fYCbCrSubSampleV);
}
#endif
break;
}
case tcYCbCrPositioning:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fYCbCrPositioning = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("YCbCrPositioning: %u\n",
(unsigned) fYCbCrPositioning);
}
#endif
break;
}
case tcReferenceBlackWhite:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
if (!CheckTagCount (parentCode, tagCode, tagCount, 6))
{
return false;
}
for (j = 0; j < 6; j++)
{
fReferenceBlackWhite [j] = stream.TagValue_real64 (tagType);
}
#if qDNGValidate
if (gVerbose)
{
printf ("ReferenceBlackWhite: %0.1f %0.1f %0.1f %0.1f %0.1f %0.1f\n",
fReferenceBlackWhite [0],
fReferenceBlackWhite [1],
fReferenceBlackWhite [2],
fReferenceBlackWhite [3],
fReferenceBlackWhite [4],
fReferenceBlackWhite [5]);
}
#endif
break;
}
case tcCFARepeatPatternDim:
{
CheckCFA (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttShort);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
{
return false;
}
fCFARepeatPatternRows = stream.TagValue_uint32 (tagType);
fCFARepeatPatternCols = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("CFARepeatPatternDim: Rows = %u, Cols = %u\n",
(unsigned) fCFARepeatPatternRows,
(unsigned) fCFARepeatPatternCols);
}
#endif
break;
}
case tcCFAPattern:
{
CheckCFA (parentCode, tagCode, fPhotometricInterpretation);
if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
{
return false;
}
if (!CheckTagCount (parentCode, tagCode, tagCount, fCFARepeatPatternRows *
fCFARepeatPatternCols))
{
return false;
}
if (fCFARepeatPatternRows < 1 || fCFARepeatPatternRows > kMaxCFAPattern ||
fCFARepeatPatternCols < 1 || fCFARepeatPatternCols > kMaxCFAPattern)
{
return false;
}
// Note that the Exif spec stores this array in a different
// scan order than the TIFF-EP spec.
for (j = 0; j < fCFARepeatPatternRows; j++)
for (k = 0; k < fCFARepeatPatternCols; k++)
{
fCFAPattern [j] [k] = stream.Get_uint8 ();
}
#if qDNGValidate
if (gVerbose)
{
printf ("CFAPattern:\n");
for (j = 0; j < fCFARepeatPatternRows; j++)
{
int32 spaces = 4;
for (k = 0; k < fCFARepeatPatternCols; k++)
{
while (spaces-- > 0)
{
printf (" ");
}
const char *name = LookupCFAColor (fCFAPattern [j] [k]);
spaces = 9 - (int32) strlen (name);
printf ("%s", name);
}
printf ("\n");
}
}
#endif
break;
}
case tcCFAPlaneColor:
{
CheckCFA (parentCode, tagCode, fPhotometricInterpretation);
if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
{
return false;
}
if (!CheckTagCount (parentCode, tagCode, tagCount, 3, kMaxColorPlanes))
{
return false;
}
for (j = 0; j < kMaxColorPlanes; j++)
{
if (j < tagCount)
fCFAPlaneColor [j] = stream.Get_uint8 ();
else
fCFAPlaneColor [j] = 255;
}
#if qDNGValidate
if (gVerbose)
{
printf ("CFAPlaneColor:");
for (j = 0; j < tagCount; j++)
{
printf (" %s", LookupCFAColor (fCFAPlaneColor [j]));
}
printf ("\n");
}
#endif
break;
}
case tcCFALayout:
{
CheckCFA (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fCFALayout = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("CFALayout: %s\n",
LookupCFALayout (fCFALayout));
}
#endif
break;
}
case tcLinearizationTable:
{
CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttShort);
fLinearizationTableType = tagType;
fLinearizationTableCount = tagCount;
fLinearizationTableOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
DumpTagValues (stream,
"Table",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcBlackLevelRepeatDim:
{
CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttShort);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
{
return false;
}
fBlackLevelRepeatRows = stream.TagValue_uint32 (tagType);
fBlackLevelRepeatCols = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("BlackLevelRepeatDim: Rows = %u, Cols = %u\n",
(unsigned) fBlackLevelRepeatRows,
(unsigned) fBlackLevelRepeatCols);
}
#endif
break;
}
case tcBlackLevel:
{
CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);
if (!CheckTagCount (parentCode, tagCode, tagCount, fBlackLevelRepeatRows *
fBlackLevelRepeatCols *
fSamplesPerPixel))
{
return false;
}
if (fBlackLevelRepeatRows < 1 || fBlackLevelRepeatRows > kMaxBlackPattern ||
fBlackLevelRepeatCols < 1 || fBlackLevelRepeatCols > kMaxBlackPattern ||
fSamplesPerPixel < 1 || fSamplesPerPixel > kMaxSamplesPerPixel)
{
return false;
}
for (j = 0; j < fBlackLevelRepeatRows; j++)
for (k = 0; k < fBlackLevelRepeatCols; k++)
for (n = 0; n < fSamplesPerPixel; n++)
{
fBlackLevel [j] [k] [n] = stream.TagValue_real64 (tagType);
}
#if qDNGValidate
if (gVerbose)
{
printf ("BlackLevel:");
if (fBlackLevelRepeatRows == 1 &&
fBlackLevelRepeatCols == 1)
{
for (n = 0; n < fSamplesPerPixel; n++)
{
printf (" %0.2f", fBlackLevel [0] [0] [n]);
}
printf ("\n");
}
else
{
printf ("\n");
for (n = 0; n < fSamplesPerPixel; n++)
{
if (fSamplesPerPixel > 1)
{
printf (" Sample: %u\n", (unsigned) n);
}
for (j = 0; j < fBlackLevelRepeatRows; j++)
{
printf (" ");
for (k = 0; k < fBlackLevelRepeatCols; k++)
{
printf (" %8.2f", fBlackLevel [j] [k] [n]);
}
printf ("\n");
}
}
}
}
#endif
break;
}
case tcBlackLevelDeltaH:
{
CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttSRational);
fBlackLevelDeltaHType = tagType;
fBlackLevelDeltaHCount = tagCount;
fBlackLevelDeltaHOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
DumpTagValues (stream,
"Delta",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcBlackLevelDeltaV:
{
CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttSRational);
fBlackLevelDeltaVType = tagType;
fBlackLevelDeltaVCount = tagCount;
fBlackLevelDeltaVOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
DumpTagValues (stream,
"Delta",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcWhiteLevel:
{
CheckRawIFD (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
if (!CheckTagCount (parentCode, tagCode, tagCount, fSamplesPerPixel))
return false;
for (j = 0; j < tagCount && j < kMaxSamplesPerPixel; j++)
{
fWhiteLevel [j] = stream.TagValue_real64 (tagType);
}
#if qDNGValidate
if (gVerbose)
{
printf ("WhiteLevel:");
for (j = 0; j < tagCount && j < kMaxSamplesPerPixel; j++)
{
printf (" %0.0f", fWhiteLevel [j]);
}
printf ("\n");
}
#endif
break;
}
case tcDefaultScale:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttRational);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
return false;
fDefaultScaleH = stream.TagValue_urational (tagType);
fDefaultScaleV = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("DefaultScale: H = %0.4f V = %0.4f\n",
fDefaultScaleH.As_real64 (),
fDefaultScaleV.As_real64 ());
}
#endif
break;
}
case tcDefaultCropOrigin:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
return false;
fDefaultCropOriginH = stream.TagValue_urational (tagType);
fDefaultCropOriginV = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("DefaultCropOrigin: H = %0.2f V = %0.2f\n",
fDefaultCropOriginH.As_real64 (),
fDefaultCropOriginV.As_real64 ());
}
#endif
break;
}
case tcDefaultCropSize:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
return false;
fDefaultCropSizeH = stream.TagValue_urational (tagType);
fDefaultCropSizeV = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("DefaultCropSize: H = %0.2f V = %0.2f\n",
fDefaultCropSizeH.As_real64 (),
fDefaultCropSizeV.As_real64 ());
}
#endif
break;
}
case tcBayerGreenSplit:
{
CheckCFA (parentCode, tagCode, fPhotometricInterpretation);
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fBayerGreenSplit = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("BayerGreenSplit: %u\n", (unsigned) fBayerGreenSplit);
}
#endif
break;
}
case tcChromaBlurRadius:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fChromaBlurRadius = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ChromaBlurRadius: %0.2f\n",
fChromaBlurRadius.As_real64 ());
}
#endif
break;
}
case tcAntiAliasStrength:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fAntiAliasStrength = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("AntiAliasStrength: %0.2f\n",
fAntiAliasStrength.As_real64 ());
}
#endif
break;
}
case tcBestQualityScale:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fBestQualityScale = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("BestQualityScale: %0.4f\n",
fBestQualityScale.As_real64 ());
}
#endif
break;
}
case tcActiveArea:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
if (!CheckTagCount (parentCode, tagCode, tagCount, 4))
return false;
fActiveArea.t = stream.TagValue_int32 (tagType);
fActiveArea.l = stream.TagValue_int32 (tagType);
fActiveArea.b = stream.TagValue_int32 (tagType);
fActiveArea.r = stream.TagValue_int32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ActiveArea: T = %d L = %d B = %d R = %d\n",
(int) fActiveArea.t,
(int) fActiveArea.l,
(int) fActiveArea.b,
(int) fActiveArea.r);
}
#endif
break;
}
case tcMaskedAreas:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
uint32 rect_count = tagCount / 4;
if (!CheckTagCount (parentCode, tagCode, tagCount, rect_count * 4))
return false;
fMaskedAreaCount = rect_count;
if (fMaskedAreaCount > kMaxMaskedAreas)
fMaskedAreaCount = kMaxMaskedAreas;
for (j = 0; j < fMaskedAreaCount; j++)
{
fMaskedArea [j].t = stream.TagValue_int32 (tagType);
fMaskedArea [j].l = stream.TagValue_int32 (tagType);
fMaskedArea [j].b = stream.TagValue_int32 (tagType);
fMaskedArea [j].r = stream.TagValue_int32 (tagType);
}
#if qDNGValidate
if (gVerbose)
{
printf ("MaskedAreas: %u\n", (unsigned) fMaskedAreaCount);
for (j = 0; j < fMaskedAreaCount; j++)
{
printf (" Area [%u]: T = %d L = %d B = %d R = %d\n",
(unsigned) j,
(int) fMaskedArea [j].t,
(int) fMaskedArea [j].l,
(int) fMaskedArea [j].b,
(int) fMaskedArea [j].r);
}
}
#endif
break;
}
case tcPreviewApplicationName:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fPreviewInfo.fApplicationName,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("PreviewApplicationName: ");
DumpString (fPreviewInfo.fApplicationName);
printf ("\n");
}
#endif
break;
}
case tcPreviewApplicationVersion:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fPreviewInfo.fApplicationVersion,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("PreviewApplicationVersion: ");
DumpString (fPreviewInfo.fApplicationVersion);
printf ("\n");
}
#endif
break;
}
case tcPreviewSettingsName:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fPreviewInfo.fSettingsName,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("PreviewSettingsName: ");
DumpString (fPreviewInfo.fSettingsName);
printf ("\n");
}
#endif
break;
}
case tcPreviewSettingsDigest:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
return false;
stream.Get (fPreviewInfo.fSettingsDigest.data, 16);
#if qDNGValidate
if (gVerbose)
{
printf ("PreviewSettingsDigest: ");
DumpFingerprint (fPreviewInfo.fSettingsDigest);
printf ("\n");
}
#endif
break;
}
case tcPreviewColorSpace:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fPreviewInfo.fColorSpace = (PreviewColorSpaceEnum)
stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("PreviewColorSpace: %s\n",
LookupPreviewColorSpace ((uint32) fPreviewInfo.fColorSpace));
}
#endif
break;
}
case tcPreviewDateTime:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fPreviewInfo.fDateTime,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("PreviewDateTime: ");
DumpString (fPreviewInfo.fDateTime);
printf ("\n");
}
#endif
break;
}
case tcRowInterleaveFactor:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
if (!CheckTagCount (parentCode, tagCode, tagCount, 1))
return false;
fRowInterleaveFactor = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("RowInterleaveFactor: %u\n",
(unsigned) fRowInterleaveFactor);
}
#endif
break;
}
case tcSubTileBlockSize:
{
CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
return false;
fSubTileBlockRows = stream.TagValue_uint32 (tagType);
fSubTileBlockCols = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("SubTileBlockSize: rows = %u, cols = %u\n",
(unsigned) fSubTileBlockRows,
(unsigned) fSubTileBlockCols);
}
#endif
break;
}
case tcOpcodeList1:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fOpcodeList1Count = tagCount;
fOpcodeList1Offset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("OpcodeList1: count = %u, offset = %u\n",
(unsigned) fOpcodeList1Count,
(unsigned) fOpcodeList1Offset);
}
#endif
break;
}
case tcOpcodeList2:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fOpcodeList2Count = tagCount;
fOpcodeList2Offset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("OpcodeList2: count = %u, offset = %u\n",
(unsigned) fOpcodeList2Count,
(unsigned) fOpcodeList2Offset);
}
#endif
break;
}
case tcOpcodeList3:
{
CheckMainIFD (parentCode, tagCode, fNewSubFileType);
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fOpcodeList3Count = tagCount;
fOpcodeList3Offset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("OpcodeList3: count = %u, offset = %u\n",
(unsigned) fOpcodeList3Count,
(unsigned) fOpcodeList3Offset);
}
#endif
break;
}
default:
{
return false;
}
}
return true;
}
/*****************************************************************************/
void dng_ifd::PostParse ()
{
uint32 j;
uint32 k;
// There is only one PlanarConfiguration for single sample imaages.
if (fSamplesPerPixel == 1)
{
fPlanarConfiguration = pcInterleaved;
}
// Default tile size.
if (fTileWidth == 0)
{
fTileWidth = fImageWidth;
}
if (fTileLength == 0)
{
fTileLength = fImageLength;
}
// Default ActiveArea.
dng_rect imageArea (0, 0, fImageLength, fImageWidth);
if (fActiveArea.IsZero ())
{
fActiveArea = imageArea;
}
// Default crop size.
if (fDefaultCropSizeH.d == 0)
{
fDefaultCropSizeH = dng_urational (fActiveArea.W (), 1);
}
if (fDefaultCropSizeV.d == 0)
{
fDefaultCropSizeV = dng_urational (fActiveArea.H (), 1);
}
// Default white level.
uint32 defaultWhite = (1 << fBitsPerSample [0]) - 1;
for (j = 0; j < kMaxSamplesPerPixel; j++)
{
if (fWhiteLevel [j] < 0.0)
{
fWhiteLevel [j] = (real64) defaultWhite;
}
}
// Check AntiAliasStrength.
if (fAntiAliasStrength.As_real64 () < 0.0 ||
fAntiAliasStrength.As_real64 () > 1.0)
{
#if qDNGValidate
ReportWarning ("Invalid AntiAliasStrength");
#endif
fAntiAliasStrength = dng_urational (1, 1);
}
// Check MaskedAreas.
for (j = 0; j < fMaskedAreaCount; j++)
{
const dng_rect &r = fMaskedArea [j];
if (r.IsEmpty () || ((r & imageArea) != r))
{
#if qDNGValidate
ReportWarning ("Invalid MaskedArea");
#endif
fMaskedAreaCount = 0;
break;
}
if ((r & fActiveArea).NotEmpty ())
{
#if qDNGValidate
ReportWarning ("MaskedArea overlaps ActiveArea");
#endif
fMaskedAreaCount = 0;
break;
}
for (k = 0; k < j; k++)
{
if ((r & fMaskedArea [k]).NotEmpty ())
{
#if qDNGValidate
ReportWarning ("MaskedAreas overlap each other");
#endif
fMaskedAreaCount = 0;
break;
}
}
}
}
/*****************************************************************************/
bool dng_ifd::IsValidCFA (dng_shared &shared,
uint32 parentCode)
{
uint32 j;
uint32 k;
uint32 n;
#if !qDNGValidate
(void) parentCode; // Unused
#endif
if (fCFARepeatPatternRows < 1 || fCFARepeatPatternRows > kMaxCFAPattern ||
fCFARepeatPatternCols < 1 || fCFARepeatPatternCols > kMaxCFAPattern)
{
#if qDNGValidate
ReportError ("Missing or invalid CFAPatternRepeatDim",
LookupParentCode (parentCode));
#endif
return false;
}
uint32 count [kMaxColorPlanes];
for (n = 0; n < shared.fCameraProfile.fColorPlanes; n++)
{
count [n] = 0;
}
for (j = 0; j < fCFARepeatPatternRows; j++)
{
for (k = 0; k < fCFARepeatPatternCols; k++)
{
bool found = false;
for (n = 0; n < shared.fCameraProfile.fColorPlanes; n++)
{
if (fCFAPattern [j] [k] == fCFAPlaneColor [n])
{
found = true;
count [n] ++;
break;
}
}
if (!found)
{
#if qDNGValidate
ReportError ("CFAPattern contains colors not included in the CFAPlaneColor tag",
LookupParentCode (parentCode));
#endif
return false;
}
}
}
for (n = 0; n < shared.fCameraProfile.fColorPlanes; n++)
{
if (count [n] == 0)
{
#if qDNGValidate
ReportError ("CFAPattern does not contain all the colors in the CFAPlaneColor tag",
LookupParentCode (parentCode));
#endif
return false;
}
}
if (fCFALayout < 1 || fCFALayout > 9)
{
#if qDNGValidate
ReportError ("Invalid CFALayout",
LookupParentCode (parentCode));
#endif
return false;
}
return true;
}
/*****************************************************************************/
bool dng_ifd::IsValidDNG (dng_shared &shared,
uint32 parentCode)
{
uint32 j;
dng_rect imageArea (0, 0, fImageLength, fImageWidth);
uint32 defaultWhite = (1 << fBitsPerSample [0]) - 1;
bool isMonochrome = (shared.fCameraProfile.fColorPlanes == 1);
bool isColor = !isMonochrome;
bool isMainIFD = (fNewSubFileType == sfMainImage);
// Check NewSubFileType.
if (!fUsesNewSubFileType)
{
#if qDNGValidate
ReportError ("Missing NewSubFileType",
LookupParentCode (parentCode));
#endif
return false;
}
if (fNewSubFileType != sfMainImage &&
fNewSubFileType != sfPreviewImage &&
fNewSubFileType != sfAltPreviewImage)
{
#if qDNGValidate
ReportError ("Unexpected NewSubFileType",
LookupParentCode (parentCode));
#endif
return false;
}
// Check ImageWidth and ImageLength.
if (fImageWidth < 1)
{
#if qDNGValidate
ReportError ("Missing or invalid ImageWidth",
LookupParentCode (parentCode));
#endif
return false;
}
if (fImageLength < 1)
{
#if qDNGValidate
ReportError ("Missing or invalid ImageLength",
LookupParentCode (parentCode));
#endif
return false;
}
if (fImageWidth > kMaxImageSide ||
fImageLength > kMaxImageSide)
{
#if qDNGValidate
ReportWarning ("Image size is larger than supported");
#endif
return false;
}
// Check PhotometricInterpretation.
switch (fPhotometricInterpretation)
{
case piBlackIsZero:
case piRGB:
case piYCbCr:
{
if (isMainIFD)
{
#if qDNGValidate
ReportError ("PhotometricInterpretation requires NewSubFileType = 1",
LookupParentCode (parentCode));
#endif
return false;
}
break;
}
case piCFA:
{
if (!isMainIFD)
{
#if qDNGValidate
ReportError ("PhotometricInterpretation requires NewSubFileType = 0",
LookupParentCode (parentCode));
#endif
return false;
}
break;
}
case piLinearRaw:
break;
default:
{
#if qDNGValidate
ReportError ("Missing or invalid PhotometricInterpretation",
LookupParentCode (parentCode));
#endif
return false;
}
}
switch (fPhotometricInterpretation)
{
case piBlackIsZero:
{
// Allow black in white previews even in color images since the
// raw processing software may be converting to grayscale.
if (isColor && isMainIFD)
{
#if qDNGValidate
ReportError ("PhotometricInterpretation forbids use of ColorMatrix1 tag",
LookupParentCode (parentCode));
#endif
return false;
}
break;
}
case piRGB:
case piYCbCr:
{
// Allow color previews even in monochrome DNG files, since the
- // raw procesing software may be adding color effects.
+ // raw processing software may be adding color effects.
break;
}
case piCFA:
{
if (isMonochrome)
{
#if qDNGValidate
ReportError ("PhotometricInterpretation requires use of ColorMatrix1 tag",
LookupParentCode (parentCode));
#endif
return false;
}
break;
}
}
// Check SamplesPerPixel and BitsPerSample.
uint32 minSamplesPerPixel = 1;
uint32 maxSamplesPerPixel = 1;
uint32 minBitsPerSample = 8;
uint32 maxBitsPerSample = 16;
switch (fPhotometricInterpretation)
{
case piBlackIsZero:
break;
case piRGB:
case piYCbCr:
{
minSamplesPerPixel = 3;
maxSamplesPerPixel = 3;
break;
}
case piCFA:
{
maxSamplesPerPixel = kMaxSamplesPerPixel;
maxBitsPerSample = 32;
break;
}
case piLinearRaw:
{
minSamplesPerPixel = shared.fCameraProfile.fColorPlanes;
maxSamplesPerPixel = shared.fCameraProfile.fColorPlanes;
maxBitsPerSample = 32;
break;
}
}
if (fSamplesPerPixel < minSamplesPerPixel ||
fSamplesPerPixel > maxSamplesPerPixel)
{
#if qDNGValidate
ReportError ("Missing or invalid SamplesPerPixel",
LookupParentCode (parentCode));
#endif
return false;
}
for (j = 0; j < kMaxSamplesPerPixel; j++)
{
if (j < fSamplesPerPixel)
{
if (fBitsPerSample [j] < minBitsPerSample ||
fBitsPerSample [j] > maxBitsPerSample)
{
#if qDNGValidate
ReportError ("Missing or invalid BitsPerSample",
LookupParentCode (parentCode));
#endif
return false;
}
if (minBitsPerSample == 8 &&
maxBitsPerSample == 16 &&
fBitsPerSample [j] != 8 &&
fBitsPerSample [j] != 16)
{
#if qDNGValidate
ReportError ("Rendered previews require 8 or 16 bits per sample",
LookupParentCode (parentCode));
#endif
return false;
}
if (j > 0 && fBitsPerSample [j] != fBitsPerSample [0])
{
#if qDNGValidate
ReportError ("BitsPerSample not equal for all samples",
LookupParentCode (parentCode));
#endif
return false;
}
}
else
{
if (fBitsPerSample [j] != 0)
{
#if qDNGValidate
ReportError ("Too many values specified in BitsPerSample",
LookupParentCode (parentCode));
#endif
return false;
}
}
}
// Check Compression.
switch (fCompression)
{
case ccUncompressed:
break;
case ccJPEG:
{
if (fPhotometricInterpretation == piRGB)
{
#if qDNGValidate
ReportError ("JPEG previews should use PhotometricInterpretation = YCbYb",
LookupParentCode (parentCode));
#endif
return false;
}
if (fBitsPerSample [0] > 16)
{
#if qDNGValidate
ReportError ("JPEG compression is limited to 16 bits/sample",
LookupParentCode (parentCode));
#endif
return false;
}
break;
}
default:
{
#if qDNGValidate
ReportError ("Unsupported Compression",
LookupParentCode (parentCode));
#endif
return false;
}
}
// Check Predictor.
if (fPredictor != cpNullPredictor)
{
#if qDNGValidate
ReportError ("Unsupported Predictor",
LookupParentCode (parentCode));
#endif
return false;
}
// Check FillOrder.
if (fFillOrder != 1)
{
#if qDNGValidate
ReportError ("Unsupported FillOrder",
LookupParentCode (parentCode));
#endif
return false;
}
// Check PlanarConfiguration.
if (fPlanarConfiguration != pcInterleaved)
{
#if qDNGValidate
ReportError ("Unsupported PlanarConfiguration",
LookupParentCode (parentCode));
#endif
return false;
}
// Check ExtraSamples.
if (fExtraSamplesCount != 0)
{
#if qDNGValidate
ReportError ("Unsupported ExtraSamples",
LookupParentCode (parentCode));
#endif
return false;
}
// Check SampleFormat.
for (j = 0; j < fSamplesPerPixel; j++)
{
if (fSampleFormat [j] != sfUnsignedInteger)
{
#if qDNGValidate
ReportError ("Unsupported SampleFormat",
LookupParentCode (parentCode));
#endif
return false;
}
}
// Check Orientation.
if (fOrientation > 9)
{
#if qDNGValidate
ReportError ("Unknown Orientation",
LookupParentCode (parentCode));
#endif
return false;
}
#if qDNGValidate
if (fOrientation != 0 && parentCode != 0)
{
ReportWarning ("Unexpected Orientation tag",
LookupParentCode (parentCode));
}
if (fOrientation == 0 && parentCode == 0)
{
ReportWarning ("Missing Orientation tag",
LookupParentCode (parentCode));
}
#endif
// Check Strips vs. Tiles.
if (!fUsesStrips && !fUsesTiles)
{
#if qDNGValidate
ReportError ("IFD uses neither strips nor tiles",
LookupParentCode (parentCode));
#endif
return false;
}
if (fUsesStrips && fUsesTiles)
{
#if qDNGValidate
ReportError ("IFD uses both strips and tiles",
LookupParentCode (parentCode));
#endif
return false;
}
// Check tile info.
uint32 tilesWide = (fImageWidth + fTileWidth - 1) / fTileWidth;
uint32 tilesHigh = (fImageLength + fTileLength - 1) / fTileLength;
uint32 tileCount = tilesWide * tilesHigh;
if (fTileOffsetsCount != tileCount)
{
#if qDNGValidate
ReportError ("Missing or invalid Strip/TileOffsets",
LookupParentCode (parentCode));
#endif
return false;
}
if (fTileByteCountsCount != tileCount)
{
#if qDNGValidate
ReportError ("Missing or invalid Strip/TileByteCounts",
LookupParentCode (parentCode));
#endif
return false;
}
// Check CFA pattern.
if (fPhotometricInterpretation == piCFA)
{
if (!IsValidCFA (shared, parentCode))
{
return false;
}
}
// Check ActiveArea.
if (((fActiveArea & imageArea) != fActiveArea) || fActiveArea.IsEmpty ())
{
#if qDNGValidate
ReportError ("Invalid ActiveArea",
LookupParentCode (parentCode));
#endif
return false;
}
if (fActiveArea != imageArea)
{
if (shared.fDNGBackwardVersion < dngVersion_1_1_0_0)
{
#if qDNGValidate
ReportError ("Non-default ActiveArea tag not allowed in this DNG version",
LookupParentCode (parentCode));
#endif
return false;
}
}
// Check LinearizationTable.
if (fLinearizationTableCount)
{
if (fLinearizationTableType != ttShort)
{
#if qDNGValidate
ReportError ("Invalidate LinearizationTable type",
LookupParentCode (parentCode));
#endif
return false;
}
if (fLinearizationTableCount < 2 ||
fLinearizationTableCount > 65536)
{
#if qDNGValidate
ReportError ("Invalidate LinearizationTable count",
LookupParentCode (parentCode));
#endif
return false;
}
}
// Check BlackLevelRepeatDim.
if (fBlackLevelRepeatRows < 1 || fBlackLevelRepeatRows > kMaxBlackPattern ||
fBlackLevelRepeatCols < 1 || fBlackLevelRepeatCols > kMaxBlackPattern)
{
#if qDNGValidate
ReportError ("Invalid BlackLevelRepeatDim",
LookupParentCode (parentCode));
#endif
return false;
}
// Check BlackLevelDeltaH.
if (fBlackLevelDeltaHCount != 0 &&
fBlackLevelDeltaHCount != fActiveArea.W ())
{
#if qDNGValidate
ReportError ("Invalid BlackLevelDeltaH count",
LookupParentCode (parentCode));
#endif
return false;
}
// Check BlackLevelDeltaV.
if (fBlackLevelDeltaVCount != 0 &&
fBlackLevelDeltaVCount != fActiveArea.H ())
{
#if qDNGValidate
ReportError ("Invalid BlackLevelDeltaV count",
LookupParentCode (parentCode));
#endif
return false;
}
// Check WhiteLevel.
real64 maxWhite = fLinearizationTableCount ? 65535.0
: (real64) defaultWhite;
for (j = 0; j < fSamplesPerPixel; j++)
{
if (fWhiteLevel [j] < 1.0 ||
fWhiteLevel [j] > maxWhite)
{
#if qDNGValidate
ReportError ("Invalid WhiteLevel",
LookupParentCode (parentCode));
#endif
return false;
}
}
// Check DefaultScale.
if (fDefaultScaleH.As_real64 () <= 0.0 ||
fDefaultScaleV.As_real64 () <= 0.0)
{
#if qDNGValidate
ReportError ("Invalid DefaultScale");
#endif
return false;
}
// Check BestQualityScale.
if (fBestQualityScale.As_real64 () < 1.0)
{
#if qDNGValidate
ReportError ("Invalid BestQualityScale");
#endif
return false;
}
// Check DefaultCropOrigin.
if (fDefaultCropOriginH.As_real64 () < 0.0 ||
fDefaultCropOriginV.As_real64 () < 0.0 ||
fDefaultCropOriginH.As_real64 () >= (real64) fActiveArea.W () ||
fDefaultCropOriginV.As_real64 () >= (real64) fActiveArea.H ())
{
#if qDNGValidate
ReportError ("Invalid DefaultCropOrigin");
#endif
return false;
}
// Check DefaultCropSize.
if (fDefaultCropSizeH.As_real64 () <= 0.0 ||
fDefaultCropSizeV.As_real64 () <= 0.0 ||
fDefaultCropSizeH.As_real64 () > (real64) fActiveArea.W () ||
fDefaultCropSizeV.As_real64 () > (real64) fActiveArea.H ())
{
#if qDNGValidate
ReportError ("Invalid DefaultCropSize");
#endif
return false;
}
// Check DefaultCrop area.
if (fDefaultCropOriginH.As_real64 () +
fDefaultCropSizeH .As_real64 () > (real64) fActiveArea.W () ||
fDefaultCropOriginV.As_real64 () +
fDefaultCropSizeV .As_real64 () > (real64) fActiveArea.H ())
{
#if qDNGValidate
ReportError ("Default crop extends outside ActiveArea");
#endif
return false;
}
// Warning if too little padding on CFA image.
#if qDNGValidate
if (fPhotometricInterpretation == piCFA)
{
const real64 kMinPad = 1.9;
if (fDefaultCropOriginH.As_real64 () < kMinPad)
{
ReportWarning ("Too little padding on left edge of CFA image",
"possible interpolation artifacts");
}
if (fDefaultCropOriginV.As_real64 () < kMinPad)
{
ReportWarning ("Too little padding on top edge of CFA image",
"possible interpolation artifacts");
}
if (fDefaultCropOriginH.As_real64 () +
fDefaultCropSizeH .As_real64 () > (real64) fActiveArea.W () - kMinPad)
{
ReportWarning ("Too little padding on right edge of CFA image",
"possible interpolation artifacts");
}
if (fDefaultCropOriginV.As_real64 () +
fDefaultCropSizeV .As_real64 () > (real64) fActiveArea.H () - kMinPad)
{
ReportWarning ("Too little padding on bottom edge of CFA image",
"possible interpolation artifacts");
}
}
#endif
// Check RowInterleaveFactor
if (fRowInterleaveFactor != 1)
{
if (fRowInterleaveFactor < 1 ||
fRowInterleaveFactor > fImageLength)
{
#if qDNGValidate
ReportError ("RowInterleaveFactor out of valid range",
LookupParentCode (parentCode));
#endif
return false;
}
if (shared.fDNGBackwardVersion < dngVersion_1_2_0_0)
{
#if qDNGValidate
ReportError ("Non-default RowInterleaveFactor tag not allowed in this DNG version",
LookupParentCode (parentCode));
#endif
return false;
}
}
// Check SubTileBlockSize
if (fSubTileBlockRows != 1 || fSubTileBlockCols != 1)
{
if (fSubTileBlockRows < 2 || fSubTileBlockRows > fTileLength ||
fSubTileBlockCols < 1 || fSubTileBlockCols > fTileWidth)
{
#if qDNGValidate
ReportError ("SubTileBlockSize out of valid range",
LookupParentCode (parentCode));
#endif
return false;
}
if ((fTileLength % fSubTileBlockRows) != 0 ||
(fTileWidth % fSubTileBlockCols) != 0)
{
#if qDNGValidate
ReportError ("TileSize not exact multiple of SubTileBlockSize",
LookupParentCode (parentCode));
#endif
return false;
}
if (shared.fDNGBackwardVersion < dngVersion_1_2_0_0)
{
#if qDNGValidate
ReportError ("Non-default SubTileBlockSize tag not allowed in this DNG version",
LookupParentCode (parentCode));
#endif
return false;
}
}
return true;
}
/*****************************************************************************/
uint32 dng_ifd::TilesAcross () const
{
if (fTileWidth)
{
return (fImageWidth + fTileWidth - 1) / fTileWidth;
}
return 0;
}
/*****************************************************************************/
uint32 dng_ifd::TilesDown () const
{
if (fTileLength)
{
return (fImageLength + fTileLength - 1) / fTileLength;
}
return 0;
}
/*****************************************************************************/
uint32 dng_ifd::TilesPerImage () const
{
uint32 total = TilesAcross () * TilesDown ();
if (fPlanarConfiguration == pcPlanar)
{
total *= fSamplesPerPixel;
}
return total;
}
/*****************************************************************************/
dng_rect dng_ifd::TileArea (uint32 rowIndex,
uint32 colIndex) const
{
dng_rect r;
r.t = rowIndex * fTileLength;
r.b = r.t + fTileLength;
r.l = colIndex * fTileWidth;
r.r = r.l + fTileWidth;
// If this IFD is using strips rather than tiles, the last strip
// is trimmed so it does not extend beyond the end of the image.
if (fUsesStrips)
{
r.b = Min_uint32 (r.b, fImageLength);
}
return r;
}
/*****************************************************************************/
uint32 dng_ifd::TileByteCount (const dng_rect &tile) const
{
if (fCompression == ccUncompressed)
{
uint32 bitsPerRow = tile.W () *
fBitsPerSample [0];
if (fPlanarConfiguration == pcInterleaved)
{
bitsPerRow *= fSamplesPerPixel;
}
uint32 bytesPerRow = (bitsPerRow + 7) >> 3;
if (fPlanarConfiguration == pcRowInterleaved)
{
bytesPerRow *= fSamplesPerPixel;
}
return bytesPerRow * tile.H ();
}
return 0;
}
/*****************************************************************************/
void dng_ifd::SetSingleStrip ()
{
fTileWidth = fImageWidth;
fTileLength = fImageLength;
fUsesTiles = false;
fUsesStrips = true;
}
/*****************************************************************************/
void dng_ifd::FindTileSize (uint32 bytesPerTile,
uint32 cellH,
uint32 cellV)
{
uint32 bytesPerSample = fSamplesPerPixel *
((fBitsPerSample [0] + 7) >> 3);
uint32 samplesPerTile = bytesPerTile / bytesPerSample;
uint32 tileSide = Round_uint32 (sqrt ((real64) samplesPerTile));
fTileWidth = Min_uint32 (fImageWidth, tileSide);
uint32 across = TilesAcross ();
fTileWidth = (fImageWidth + across - 1) / across;
fTileWidth = ((fTileWidth + cellH - 1) / cellH) * cellH;
fTileLength = Pin_uint32 (1,
samplesPerTile / fTileWidth,
fImageLength);
uint32 down = TilesDown ();
fTileLength = (fImageLength + down - 1) / down;
fTileLength = ((fTileLength + cellV - 1) / cellV) * cellV;
fUsesTiles = true;
fUsesStrips = false;
}
/*****************************************************************************/
void dng_ifd::FindStripSize (uint32 bytesPerStrip,
uint32 cellV)
{
uint32 bytesPerSample = fSamplesPerPixel *
((fBitsPerSample [0] + 7) >> 3);
uint32 samplesPerStrip = bytesPerStrip / bytesPerSample;
fTileWidth = fImageWidth;
fTileLength = Pin_uint32 (1,
samplesPerStrip / fTileWidth,
fImageLength);
uint32 down = TilesDown ();
fTileLength = (fImageLength + down - 1) / down;
fTileLength = ((fTileLength + cellV - 1) / cellV) * cellV;
fUsesTiles = false;
fUsesStrips = true;
}
/*****************************************************************************/
uint32 dng_ifd::PixelType () const
{
if (fSampleFormat [0] == sfFloatingPoint)
{
return ttFloat;
}
if (fBitsPerSample [0] <= 8)
{
return ttByte;
}
else if (fBitsPerSample [0] <= 16)
{
return ttShort;
}
return ttLong;
}
/*****************************************************************************/
bool dng_ifd::IsBaselineJPEG () const
{
if (fCompression != ccJPEG)
{
return false;
}
if (fBitsPerSample [0] != 8)
{
return false;
}
if (fSampleFormat [0] != sfUnsignedInteger)
{
return false;
}
switch (fPhotometricInterpretation)
{
case piBlackIsZero:
{
return (fSamplesPerPixel == 1);
}
case piYCbCr:
{
return (fSamplesPerPixel == 3 ) &&
(fPlanarConfiguration == pcInterleaved);
}
default:
break;
}
return false;
}
/*****************************************************************************/
bool dng_ifd::CanRead () const
{
dng_read_image reader;
return reader.CanRead (*this);
}
/*****************************************************************************/
void dng_ifd::ReadImage (dng_host &host,
dng_stream &stream,
dng_image &image) const
{
dng_read_image reader;
reader.Read (host,
*this,
stream,
image);
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_image.h b/core/libs/dngwriter/extra/dng_sdk/dng_image.h
index a9033bc551..72f7460237 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_image.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_image.h
@@ -1,433 +1,433 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_image.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Support for working with image data in DNG SDK.
*/
/*****************************************************************************/
#ifndef __dng_image__
#define __dng_image__
/*****************************************************************************/
#include "dng_assertions.h"
#include "dng_classes.h"
#include "dng_pixel_buffer.h"
#include "dng_point.h"
#include "dng_rect.h"
#include "dng_tag_types.h"
#include "dng_types.h"
/*****************************************************************************/
/// \brief Class to get resource acquisition is instantiation behavior for tile
/// buffers. Can be dirty or constant tile access.
class dng_tile_buffer: public dng_pixel_buffer
{
protected:
const dng_image &fImage;
void *fRefData;
protected:
/// Obtain a tile from an image.
/// \param image Image tile will come from.
/// \param tile Rectangle denoting extent of tile.
- /// \param dirty Flag indicating whether this is read-only or read-write acesss.
+ /// \param dirty Flag indicating whether this is read-only or read-write access.
dng_tile_buffer (const dng_image &image,
const dng_rect &tile,
bool dirty);
virtual ~dng_tile_buffer ();
public:
void SetRefData (void *refData)
{
fRefData = refData;
}
void * GetRefData () const
{
return fRefData;
}
private:
// Hidden copy constructor and assignment operator.
dng_tile_buffer (const dng_tile_buffer &buffer);
dng_tile_buffer & operator= (const dng_tile_buffer &buffer);
};
/*****************************************************************************/
/// \brief Class to get resource acquisition is instantiation behavior for
/// constant (read-only) tile buffers.
class dng_const_tile_buffer: public dng_tile_buffer
{
public:
/// Obtain a read-only tile from an image.
/// \param image Image tile will come from.
/// \param tile Rectangle denoting extent of tile.
dng_const_tile_buffer (const dng_image &image,
const dng_rect &tile);
virtual ~dng_const_tile_buffer ();
};
/*****************************************************************************/
/// \brief Class to get resource acquisition is instantiation behavior for
/// dirty (writable) tile buffers.
class dng_dirty_tile_buffer: public dng_tile_buffer
{
public:
/// Obtain a writable tile from an image.
/// \param image Image tile will come from.
/// \param tile Rectangle denoting extent of tile.
dng_dirty_tile_buffer (dng_image &image,
const dng_rect &tile);
virtual ~dng_dirty_tile_buffer ();
};
/*****************************************************************************/
/// \brief Base class for holding image data in DNG SDK. See dng_simple_image
/// for derived class most often used in DNG SDK.
class dng_image
{
friend class dng_tile_buffer;
protected:
// Bounds for this image.
dng_rect fBounds;
// Number of image planes.
uint32 fPlanes;
// Basic pixel type (TIFF tag type code).
uint32 fPixelType;
public:
/// How to handle requests to get image areas outside the image bounds.
enum edge_option
{
/// Leave edge pixels unchanged.
edge_none,
/// Pad with zeros.
edge_zero,
/// Repeat edge pixels.
edge_repeat,
/// Repeat edge pixels, except for last plane which is zero padded.
edge_repeat_zero_last
};
protected:
dng_image (const dng_rect &bounds,
uint32 planes,
uint32 pixelType);
public:
virtual ~dng_image ();
virtual dng_image * Clone () const;
/// Getter method for bounds of an image.
const dng_rect & Bounds () const
{
return fBounds;
}
/// Getter method for size of an image.
dng_point Size () const
{
return Bounds ().Size ();
}
/// Getter method for width of an image.
uint32 Width () const
{
return Bounds ().W ();
}
/// Getter method for height of an image.
uint32 Height () const
{
return Bounds ().H ();
}
/// Getter method for number of planes in an image.
uint32 Planes () const
{
return fPlanes;
}
/// Getter for pixel type.
/// \retval See dng_tagtypes.h . Valid values are ttByte, ttShort, ttSShort,
/// ttLong, ttFloat .
uint32 PixelType () const
{
return fPixelType;
}
/// Setter for pixel type.
/// \param pixelType The new pixel type .
virtual void SetPixelType (uint32 pixelType);
/// Getter for pixel size.
/// \retval Size, in bytes, of pixel type for this image .
uint32 PixelSize () const;
/// Getter for pixel range.
/// For unsigned types, range is 0 to return value.
/// For signed types, range is return value - 0x8000U.
/// For ttFloat type, pixel range is 0.0 to 1.0 and this routine returns 1.
uint32 PixelRange () const;
/// Getter for best "tile stride" for accessing image.
virtual dng_rect RepeatingTile () const;
/// Get a pixel buffer of data on image with proper edge padding.
/// \param buffer Receives resulting pixel buffer.
/// \param edgeOption edge_option describing how to pad edges.
/// \param repeatV Amount of repeated padding needed in vertical for
/// edge_repeat and edge_repeat_zero_last edgeOption cases.
/// \param repeatH Amount of repeated padding needed in horizontal for
/// edge_repeat and edge_repeat_zero_last edgeOption cases.
void Get (dng_pixel_buffer &buffer,
edge_option edgeOption = edge_none,
uint32 repeatV = 1,
uint32 repeatH = 1) const;
/// Put a pixel buffer into image.
/// \param buffer Pixel buffer to copy from.
void Put (const dng_pixel_buffer &buffer);
/// Shrink bounds of image to given rectangle.
/// \param r Rectangle to crop to.
virtual void Trim (const dng_rect &r);
/// Rotate image to reflect given orientation change.
/// \param orientation Directive to rotate image in a certain way.
virtual void Rotate (const dng_orientation &orientation);
/// Copy image data from an area of one image to same area of another.
/// \param src Image to copy from.
/// \param area Rectangle of images to copy.
/// \param srcPlane Plane to start copying in src.
/// \param dstPlane Plane to start copying in this.
/// \param planes Number of planes to copy.
void CopyArea (const dng_image &src,
const dng_rect &area,
uint32 srcPlane,
uint32 dstPlane,
uint32 planes);
/// Copy image data from an area of one image to same area of another.
/// \param src Image to copy from.
/// \param area Rectangle of images to copy.
/// \param plane Plane to start copying in src and this.
/// \param planes Number of planes to copy.
void CopyArea (const dng_image &src,
const dng_rect &area,
uint32 plane,
uint32 planes)
{
CopyArea (src, area, plane, plane, planes);
}
/// Return true if the contents of an area of the image are the same as those of another.
/// \param rhs Image to compare against.
/// \param area Rectangle of image to test.
/// \param plane Plane to start comparing.
/// \param planes Number of planes to compare.
bool EqualArea (const dng_image &rhs,
const dng_rect &area,
uint32 plane,
uint32 planes) const;
// Routines to set the entire image to a constant value.
void SetConstant_uint8 (uint8 value,
const dng_rect &area)
{
DNG_ASSERT (fPixelType == ttByte, "Mismatched pixel type");
SetConstant ((uint32) value, area);
}
void SetConstant_uint8 (uint8 value)
{
SetConstant (value, Bounds ());
}
void SetConstant_uint16 (uint16 value,
const dng_rect &area)
{
DNG_ASSERT (fPixelType == ttShort, "Mismatched pixel type");
SetConstant ((uint32) value, area);
}
void SetConstant_uint16 (uint16 value)
{
SetConstant_uint16 (value, Bounds ());
}
void SetConstant_int16 (int16 value,
const dng_rect &area)
{
DNG_ASSERT (fPixelType == ttSShort, "Mismatched pixel type");
SetConstant ((uint32) (uint16) value, area);
}
void SetConstant_int16 (int16 value)
{
SetConstant_int16 (value, Bounds ());
}
void SetConstant_uint32 (uint32 value,
const dng_rect &area)
{
DNG_ASSERT (fPixelType == ttLong, "Mismatched pixel type");
SetConstant (value, area);
}
void SetConstant_uint32 (uint32 value)
{
SetConstant_uint32 (value, Bounds ());
}
void SetConstant_real32 (real32 value,
const dng_rect &area)
{
DNG_ASSERT (fPixelType == ttFloat, "Mismatched pixel type");
union
{
uint32 i;
real32 f;
} x;
x.f = value;
SetConstant (x.i, area);
}
void SetConstant_real32 (real32 value)
{
SetConstant_real32 (value, Bounds ());
}
virtual void GetRepeat (dng_pixel_buffer &buffer,
const dng_rect &srcArea,
const dng_rect &dstArea) const;
protected:
virtual void AcquireTileBuffer (dng_tile_buffer &buffer,
const dng_rect &area,
bool dirty) const;
virtual void ReleaseTileBuffer (dng_tile_buffer &buffer) const;
virtual void DoGet (dng_pixel_buffer &buffer) const;
virtual void DoPut (const dng_pixel_buffer &buffer);
void GetEdge (dng_pixel_buffer &buffer,
edge_option edgeOption,
const dng_rect &srcArea,
const dng_rect &dstArea) const;
virtual void SetConstant (uint32 value,
const dng_rect &area);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_image_writer.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_image_writer.cpp
index 666e47fcc2..a4b07a7344 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_image_writer.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_image_writer.cpp
@@ -1,4022 +1,4022 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_image_writer.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_image_writer.h"
#include "dng_bottlenecks.h"
#include "dng_camera_profile.h"
#include "dng_color_space.h"
#include "dng_exif.h"
#include "dng_flags.h"
#include "dng_exceptions.h"
#include "dng_host.h"
#include "dng_ifd.h"
#include "dng_image.h"
#include "dng_lossless_jpeg.h"
#include "dng_memory_stream.h"
#include "dng_negative.h"
#include "dng_pixel_buffer.h"
#include "dng_preview.h"
#include "dng_read_image.h"
#include "dng_stream.h"
#include "dng_tag_codes.h"
#include "dng_tag_values.h"
#include "dng_utils.h"
#include "dng_xmp.h"
/*****************************************************************************/
// Defines for testing DNG 1.2 features.
//#define qTestRowInterleave 2
//#define qTestSubTileBlockRows 2
//#define qTestSubTileBlockCols 2
/*****************************************************************************/
dng_resolution::dng_resolution ()
: fXResolution ()
, fYResolution ()
, fResolutionUnit (0)
{
}
/******************************************************************************/
static void SpoolAdobeData (dng_stream &stream,
const dng_negative *negative,
const dng_jpeg_preview *preview,
const dng_memory_block *imageResources)
{
TempBigEndian tempEndian (stream);
if (negative && negative->GetXMP ())
{
bool marked = false;
if (negative->GetXMP ()->GetBoolean ("http://ns.adobe.com/xap/1.0/rights/",
"Marked",
marked))
{
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
stream.Put_uint16 (1034);
stream.Put_uint16 (0);
stream.Put_uint32 (1);
stream.Put_uint8 (marked ? 1 : 0);
stream.Put_uint8 (0);
}
dng_string webStatement;
if (negative->GetXMP ()->GetString ("http://ns.adobe.com/xap/1.0/rights/",
"WebStatement",
webStatement))
{
dng_memory_data buffer;
uint32 size = webStatement.Get_SystemEncoding (buffer);
if (size > 0)
{
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
stream.Put_uint16 (1035);
stream.Put_uint16 (0);
stream.Put_uint32 (size);
stream.Put (buffer.Buffer (), size);
if (size & 1)
stream.Put_uint8 (0);
}
}
}
if (preview)
{
preview->SpoolAdobeThumbnail (stream);
}
if (negative)
{
dng_fingerprint iptcDigest = negative->IPTCDigest ();
if (iptcDigest.IsValid ())
{
stream.Put_uint32 (DNG_CHAR4 ('8','B','I','M'));
stream.Put_uint16 (1061);
stream.Put_uint16 (0);
stream.Put_uint32 (16);
stream.Put (iptcDigest.data, 16);
}
}
if (imageResources)
{
uint32 size = imageResources->LogicalSize ();
stream.Put (imageResources->Buffer (), size);
if (size & 1)
stream.Put_uint8 (0);
}
}
/******************************************************************************/
static dng_memory_block * BuildAdobeData (dng_host &host,
const dng_negative *negative,
const dng_jpeg_preview *preview,
const dng_memory_block *imageResources)
{
dng_memory_stream stream (host.Allocator ());
SpoolAdobeData (stream,
negative,
preview,
imageResources);
return stream.AsMemoryBlock (host.Allocator ());
}
/*****************************************************************************/
tag_string::tag_string (uint16 code,
const dng_string &s,
bool forceASCII)
: tiff_tag (code, ttAscii, 0)
, fString (s)
{
if (forceASCII)
{
fString.ForceASCII ();
}
else if (!fString.IsASCII ())
{
fType = ttByte;
}
fCount = fString.Length () + 1;
}
/*****************************************************************************/
void tag_string::Put (dng_stream &stream) const
{
stream.Put (fString.Get (), Size ());
}
/*****************************************************************************/
tag_encoded_text::tag_encoded_text (uint16 code,
const dng_string &text)
: tiff_tag (code, ttUndefined, 0)
, fText (text)
, fUTF16 ()
{
if (fText.IsASCII ())
{
fCount = 8 + fText.Length ();
}
else
{
fCount = 8 + fText.Get_UTF16 (fUTF16) * 2;
}
}
/*****************************************************************************/
void tag_encoded_text::Put (dng_stream &stream) const
{
if (fUTF16.Buffer ())
{
stream.Put ("UNICODE\000", 8);
uint32 chars = (fCount - 8) >> 1;
const uint16 *buf = fUTF16.Buffer_uint16 ();
for (uint32 j = 0; j < chars; j++)
{
stream.Put_uint16 (buf [j]);
}
}
else
{
stream.Put ("ASCII\000\000\000", 8);
stream.Put (fText.Get (), fCount - 8);
}
}
/*****************************************************************************/
void tag_data_ptr::Put (dng_stream &stream) const
{
// If we are swapping bytes, we need to swap with the right size
// entries.
if (stream.SwapBytes ())
{
switch (Type ())
{
// Two byte entries.
case ttShort:
case ttSShort:
case ttUnicode:
{
const uint16 *p = (const uint16 *) fData;
uint32 entries = (Size () >> 1);
for (uint32 j = 0; j < entries; j++)
{
stream.Put_uint16 (p [j]);
}
return;
}
// Four byte entries.
case ttLong:
case ttSLong:
case ttRational:
case ttSRational:
case ttIFD:
case ttFloat:
case ttComplex:
{
const uint32 *p = (const uint32 *) fData;
uint32 entries = (Size () >> 2);
for (uint32 j = 0; j < entries; j++)
{
stream.Put_uint32 (p [j]);
}
return;
}
// Eight byte entries.
case ttDouble:
{
const real64 *p = (const real64 *) fData;
uint32 entries = (Size () >> 3);
for (uint32 j = 0; j < entries; j++)
{
stream.Put_real64 (p [j]);
}
return;
}
// Entries don't need to be byte swapped. Fall through
// to non-byte swapped case.
default:
{
break;
}
}
}
// Non-byte swapped case.
stream.Put (fData, Size ());
}
/******************************************************************************/
tag_matrix::tag_matrix (uint16 code,
const dng_matrix &m)
: tag_srational_ptr (code, fEntry, m.Rows () * m.Cols ())
{
uint32 index = 0;
for (uint32 r = 0; r < m.Rows (); r++)
for (uint32 c = 0; c < m.Cols (); c++)
{
fEntry [index].Set_real64 (m [r] [c], 10000);
index++;
}
}
/******************************************************************************/
tag_icc_profile::tag_icc_profile (const void *profileData,
uint32 profileSize)
: tag_data_ptr (tcICCProfile,
ttUndefined,
0,
NULL)
{
if (profileData && profileSize)
{
SetCount (profileSize);
SetData (profileData);
}
}
/******************************************************************************/
void tag_cfa_pattern::Put (dng_stream &stream) const
{
stream.Put_uint16 ((uint16) fCols);
stream.Put_uint16 ((uint16) fRows);
for (uint32 col = 0; col < fCols; col++)
for (uint32 row = 0; row < fRows; row++)
{
stream.Put_uint8 (fPattern [row * kMaxCFAPattern + col]);
}
}
/******************************************************************************/
tag_exif_date_time::tag_exif_date_time (uint16 code,
const dng_date_time &dt)
: tag_data_ptr (code, ttAscii, 20, fData)
{
if (dt.IsValid ())
{
sprintf (fData,
"%04d:%02d:%02d %02d:%02d:%02d",
(int) dt.fYear,
(int) dt.fMonth,
(int) dt.fDay,
(int) dt.fHour,
(int) dt.fMinute,
(int) dt.fSecond);
}
}
/******************************************************************************/
tag_iptc::tag_iptc (const void *data,
uint32 length)
: tiff_tag (tcIPTC_NAA, ttLong, (length + 3) >> 2)
, fData (data )
, fLength (length)
{
}
/******************************************************************************/
void tag_iptc::Put (dng_stream &stream) const
{
- // Note: For historical compatiblity reasons, the standard TIFF data
+ // Note: For historical compatibility reasons, the standard TIFF data
// type for IPTC data is ttLong, but without byte swapping. This really
// should be ttUndefined, but doing the right thing would break some
// existing readers.
stream.Put (fData, fLength);
// Pad with zeros to get to long word boundary.
uint32 extra = fCount * 4 - fLength;
while (extra--)
{
stream.Put_uint8 (0);
}
}
/******************************************************************************/
tag_xmp::tag_xmp (const dng_xmp *xmp)
: tag_uint8_ptr (tcXMP, NULL, 0)
, fBuffer ()
{
if (xmp)
{
fBuffer.Reset (xmp->Serialize (true));
if (fBuffer.Get ())
{
SetData (fBuffer->Buffer_uint8 ());
SetCount (fBuffer->LogicalSize ());
}
}
}
/******************************************************************************/
void dng_tiff_directory::Add (const tiff_tag *tag)
{
if (fEntries >= kMaxEntries)
{
ThrowProgramError ();
}
// Tags must be sorted in increasing order of tag code.
uint32 index = fEntries;
for (uint32 j = 0; j < fEntries; j++)
{
if (tag->Code () < fTag [j]->Code ())
{
index = j;
break;
}
}
for (uint32 k = fEntries; k > index; k--)
{
fTag [k] = fTag [k - 1];
}
fTag [index] = tag;
fEntries++;
}
/******************************************************************************/
uint32 dng_tiff_directory::Size () const
{
if (!fEntries) return 0;
uint32 size = fEntries * 12 + 6;
for (uint32 index = 0; index < fEntries; index++)
{
uint32 tagSize = fTag [index]->Size ();
if (tagSize > 4)
{
size += (tagSize + 1) & ~1;
}
}
return size;
}
/******************************************************************************/
void dng_tiff_directory::Put (dng_stream &stream,
OffsetsBase offsetsBase,
uint32 explicitBase) const
{
if (!fEntries) return;
uint32 index;
uint32 bigData = fEntries * 12 + 6;
if (offsetsBase == offsetsRelativeToStream)
bigData += (uint32) stream.Position ();
else if (offsetsBase == offsetsRelativeToExplicitBase)
bigData += explicitBase;
stream.Put_uint16 ((uint16) fEntries);
for (index = 0; index < fEntries; index++)
{
const tiff_tag &tag = *fTag [index];
stream.Put_uint16 (tag.Code ());
stream.Put_uint16 (tag.Type ());
stream.Put_uint32 (tag.Count ());
uint32 size = tag.Size ();
if (size <= 4)
{
tag.Put (stream);
while (size < 4)
{
stream.Put_uint8 (0);
size++;
}
}
else
{
stream.Put_uint32 (bigData);
bigData += (size + 1) & ~1;
}
}
stream.Put_uint32 (fChained); // Next IFD offset
for (index = 0; index < fEntries; index++)
{
const tiff_tag &tag = *fTag [index];
uint32 size = tag.Size ();
if (size > 4)
{
tag.Put (stream);
if (size & 1)
stream.Put_uint8 (0);
}
}
}
/******************************************************************************/
dng_basic_tag_set::dng_basic_tag_set (dng_tiff_directory &directory,
const dng_ifd &info)
: fNewSubFileType (tcNewSubFileType, info.fNewSubFileType)
, fImageWidth (tcImageWidth , info.fImageWidth )
, fImageLength (tcImageLength, info.fImageLength)
, fPhotoInterpretation (tcPhotometricInterpretation,
(uint16) info.fPhotometricInterpretation)
, fFillOrder (tcFillOrder, 1)
, fSamplesPerPixel (tcSamplesPerPixel, (uint16) info.fSamplesPerPixel)
, fBitsPerSample (tcBitsPerSample,
fBitsPerSampleData,
info.fSamplesPerPixel)
, fStrips (info.fUsesStrips)
, fTileWidth (tcTileWidth, info.fTileWidth)
, fTileLength (fStrips ? tcRowsPerStrip : tcTileLength,
info.fTileLength)
, fTileInfoBuffer (info.TilesPerImage () * 8)
, fTileOffsetData (fTileInfoBuffer.Buffer_uint32 ())
, fTileOffsets (fStrips ? tcStripOffsets : tcTileOffsets,
fTileOffsetData,
info.TilesPerImage ())
, fTileByteCountData (fTileOffsetData + info.TilesPerImage ())
, fTileByteCounts (fStrips ? tcStripByteCounts : tcTileByteCounts,
fTileByteCountData,
info.TilesPerImage ())
, fPlanarConfiguration (tcPlanarConfiguration, pcInterleaved)
, fCompression (tcCompression, (uint16) info.fCompression)
, fPredictor (tcPredictor , (uint16) info.fPredictor )
, fExtraSamples (tcExtraSamples,
fExtraSamplesData,
info.fExtraSamplesCount)
, fSampleFormat (tcSampleFormat,
fSampleFormatData,
info.fSamplesPerPixel)
, fRowInterleaveFactor (tcRowInterleaveFactor,
(uint16) info.fRowInterleaveFactor)
, fSubTileBlockSize (tcSubTileBlockSize,
fSubTileBlockSizeData,
2)
{
uint32 j;
for (j = 0; j < info.fSamplesPerPixel; j++)
{
fBitsPerSampleData [j] = (uint16) info.fBitsPerSample [0];
}
directory.Add (&fNewSubFileType);
directory.Add (&fImageWidth);
directory.Add (&fImageLength);
directory.Add (&fPhotoInterpretation);
directory.Add (&fSamplesPerPixel);
directory.Add (&fBitsPerSample);
if (info.fBitsPerSample [0] != 8 &&
info.fBitsPerSample [0] != 16 &&
info.fBitsPerSample [0] != 32)
{
directory.Add (&fFillOrder);
}
if (!fStrips)
{
directory.Add (&fTileWidth);
}
directory.Add (&fTileLength);
directory.Add (&fTileOffsets);
directory.Add (&fTileByteCounts);
directory.Add (&fPlanarConfiguration);
directory.Add (&fCompression);
if (info.fPredictor != cpNullPredictor)
{
directory.Add (&fPredictor);
}
if (info.fExtraSamplesCount != 0)
{
for (j = 0; j < info.fExtraSamplesCount; j++)
{
fExtraSamplesData [j] = (uint16) info.fExtraSamples [j];
}
directory.Add (&fExtraSamples);
}
if (info.fSampleFormat [0] != sfUnsignedInteger)
{
for (j = 0; j < info.fSamplesPerPixel; j++)
{
fSampleFormatData [j] = (uint16) info.fSampleFormat [j];
}
directory.Add (&fSampleFormat);
}
if (info.fRowInterleaveFactor != 1)
{
directory.Add (&fRowInterleaveFactor);
}
if (info.fSubTileBlockRows != 1 ||
info.fSubTileBlockCols != 1)
{
fSubTileBlockSizeData [0] = (uint16) info.fSubTileBlockRows;
fSubTileBlockSizeData [1] = (uint16) info.fSubTileBlockCols;
directory.Add (&fSubTileBlockSize);
}
}
/******************************************************************************/
exif_tag_set::exif_tag_set (dng_tiff_directory &directory,
const dng_exif &exif,
bool makerNoteSafe,
const void *makerNoteData,
uint32 makerNoteLength,
bool insideDNG)
: fExifIFD ()
, fGPSIFD ()
, fExifLink (tcExifIFD, 0)
, fGPSLink (tcGPSInfo, 0)
, fAddedExifLink (false)
, fAddedGPSLink (false)
, fExifVersion (tcExifVersion, ttUndefined, 4, fExifVersionData)
, fExposureTime (tcExposureTime , exif.fExposureTime )
, fShutterSpeedValue (tcShutterSpeedValue, exif.fShutterSpeedValue)
, fFNumber (tcFNumber , exif.fFNumber )
, fApertureValue (tcApertureValue, exif.fApertureValue)
, fBrightnessValue (tcBrightnessValue, exif.fBrightnessValue)
, fExposureBiasValue (tcExposureBiasValue, exif.fExposureBiasValue)
, fMaxApertureValue (tcMaxApertureValue , exif.fMaxApertureValue)
, fSubjectDistance (tcSubjectDistance, exif.fSubjectDistance)
, fFocalLength (tcFocalLength, exif.fFocalLength)
, fISOSpeedRatings (tcISOSpeedRatings, (uint16) exif.fISOSpeedRatings [0])
, fFlash (tcFlash, (uint16) exif.fFlash)
, fExposureProgram (tcExposureProgram, (uint16) exif.fExposureProgram)
, fMeteringMode (tcMeteringMode, (uint16) exif.fMeteringMode)
, fLightSource (tcLightSource, (uint16) exif.fLightSource)
, fSensingMethod (tcSensingMethodExif, (uint16) exif.fSensingMethod)
, fFocalLength35mm (tcFocalLengthIn35mmFilm, (uint16) exif.fFocalLengthIn35mmFilm)
, fFileSourceData ((uint8) exif.fFileSource)
, fFileSource (tcFileSource, ttUndefined, 1, &fFileSourceData)
, fSceneTypeData ((uint8) exif.fSceneType)
, fSceneType (tcSceneType, ttUndefined, 1, &fSceneTypeData)
, fCFAPattern (tcCFAPatternExif,
exif.fCFARepeatPatternRows,
exif.fCFARepeatPatternCols,
&exif.fCFAPattern [0] [0])
, fCustomRendered (tcCustomRendered , (uint16) exif.fCustomRendered )
, fExposureMode (tcExposureMode , (uint16) exif.fExposureMode )
, fWhiteBalance (tcWhiteBalance , (uint16) exif.fWhiteBalance )
, fSceneCaptureType (tcSceneCaptureType , (uint16) exif.fSceneCaptureType )
, fGainControl (tcGainControl , (uint16) exif.fGainControl )
, fContrast (tcContrast , (uint16) exif.fContrast )
, fSaturation (tcSaturation , (uint16) exif.fSaturation )
, fSharpness (tcSharpness , (uint16) exif.fSharpness )
, fSubjectDistanceRange (tcSubjectDistanceRange, (uint16) exif.fSubjectDistanceRange)
, fDigitalZoomRatio (tcDigitalZoomRatio, exif.fDigitalZoomRatio)
, fExposureIndex (tcExposureIndexExif, exif.fExposureIndex)
, fImageNumber (tcImageNumber, exif.fImageNumber)
, fSelfTimerMode (tcSelfTimerMode, (uint16) exif.fSelfTimerMode)
, fBatteryLevelA (tcBatteryLevel, exif.fBatteryLevelA)
, fBatteryLevelR (tcBatteryLevel, exif.fBatteryLevelR)
, fFocalPlaneXResolution (tcFocalPlaneXResolutionExif, exif.fFocalPlaneXResolution)
, fFocalPlaneYResolution (tcFocalPlaneYResolutionExif, exif.fFocalPlaneYResolution)
, fFocalPlaneResolutionUnit (tcFocalPlaneResolutionUnitExif, (uint16) exif.fFocalPlaneResolutionUnit)
, fSubjectArea (tcSubjectArea, fSubjectAreaData, exif.fSubjectAreaCount)
, fLensInfo (tcLensInfo, fLensInfoData, 4)
, fDateTime (tcDateTime , exif.fDateTime .DateTime ())
, fDateTimeOriginal (tcDateTimeOriginal , exif.fDateTimeOriginal .DateTime ())
, fDateTimeDigitized (tcDateTimeDigitized, exif.fDateTimeDigitized.DateTime ())
, fSubsecTime (tcSubsecTime, exif.fDateTime .Subseconds ())
, fSubsecTimeOriginal (tcSubsecTimeOriginal, exif.fDateTimeOriginal .Subseconds ())
, fSubsecTimeDigitized (tcSubsecTimeDigitized, exif.fDateTimeDigitized.Subseconds ())
, fTimeZoneOffset (tcTimeZoneOffset, fTimeZoneOffsetData, 2)
, fMake (tcMake, exif.fMake)
, fModel (tcModel, exif.fModel)
, fArtist (tcArtist, exif.fArtist)
, fSoftware (tcSoftware, exif.fSoftware)
, fCopyright (tcCopyright, exif.fCopyright)
, fMakerNoteSafety (tcMakerNoteSafety, makerNoteSafe ? 1 : 0)
, fMakerNote (tcMakerNote, ttUndefined, makerNoteLength, makerNoteData)
, fImageDescription (tcImageDescription, exif.fImageDescription)
, fSerialNumber (tcCameraSerialNumber, exif.fCameraSerialNumber)
, fUserComment (tcUserComment, exif.fUserComment)
, fImageUniqueID (tcImageUniqueID, ttAscii, 33, fImageUniqueIDData)
, fGPSVersionID (tcGPSVersionID, fGPSVersionData, 4)
, fGPSLatitudeRef (tcGPSLatitudeRef, exif.fGPSLatitudeRef)
, fGPSLatitude (tcGPSLatitude, exif.fGPSLatitude, 3)
, fGPSLongitudeRef (tcGPSLongitudeRef, exif.fGPSLongitudeRef)
, fGPSLongitude (tcGPSLongitude, exif.fGPSLongitude, 3)
, fGPSAltitudeRef (tcGPSAltitudeRef, (uint8) exif.fGPSAltitudeRef)
, fGPSAltitude (tcGPSAltitude, exif.fGPSAltitude )
, fGPSTimeStamp (tcGPSTimeStamp, exif.fGPSTimeStamp, 3)
, fGPSSatellites (tcGPSSatellites , exif.fGPSSatellites )
, fGPSStatus (tcGPSStatus , exif.fGPSStatus )
, fGPSMeasureMode (tcGPSMeasureMode, exif.fGPSMeasureMode)
, fGPSDOP (tcGPSDOP, exif.fGPSDOP)
, fGPSSpeedRef (tcGPSSpeedRef, exif.fGPSSpeedRef)
, fGPSSpeed (tcGPSSpeed , exif.fGPSSpeed )
, fGPSTrackRef (tcGPSTrackRef, exif.fGPSTrackRef)
, fGPSTrack (tcGPSTrack , exif.fGPSTrack )
, fGPSImgDirectionRef (tcGPSImgDirectionRef, exif.fGPSImgDirectionRef)
, fGPSImgDirection (tcGPSImgDirection , exif.fGPSImgDirection )
, fGPSMapDatum (tcGPSMapDatum, exif.fGPSMapDatum)
, fGPSDestLatitudeRef (tcGPSDestLatitudeRef, exif.fGPSDestLatitudeRef)
, fGPSDestLatitude (tcGPSDestLatitude, exif.fGPSDestLatitude, 3)
, fGPSDestLongitudeRef (tcGPSDestLongitudeRef, exif.fGPSDestLongitudeRef)
, fGPSDestLongitude (tcGPSDestLongitude, exif.fGPSDestLongitude, 3)
, fGPSDestBearingRef (tcGPSDestBearingRef, exif.fGPSDestBearingRef)
, fGPSDestBearing (tcGPSDestBearing , exif.fGPSDestBearing )
, fGPSDestDistanceRef (tcGPSDestDistanceRef, exif.fGPSDestDistanceRef)
, fGPSDestDistance (tcGPSDestDistance , exif.fGPSDestDistance )
, fGPSProcessingMethod (tcGPSProcessingMethod, exif.fGPSProcessingMethod)
, fGPSAreaInformation (tcGPSAreaInformation , exif.fGPSAreaInformation )
, fGPSDateStamp (tcGPSDateStamp, exif.fGPSDateStamp)
, fGPSDifferential (tcGPSDifferential, (uint16) exif.fGPSDifferential)
{
if (exif.fExifVersion)
{
fExifVersionData [0] = (uint8) (exif.fExifVersion >> 24);
fExifVersionData [1] = (uint8) (exif.fExifVersion >> 16);
fExifVersionData [2] = (uint8) (exif.fExifVersion >> 8);
fExifVersionData [3] = (uint8) (exif.fExifVersion );
fExifIFD.Add (&fExifVersion);
}
if (exif.fExposureTime.IsValid ())
{
fExifIFD.Add (&fExposureTime);
}
if (exif.fShutterSpeedValue.IsValid ())
{
fExifIFD.Add (&fShutterSpeedValue);
}
if (exif.fFNumber.IsValid ())
{
fExifIFD.Add (&fFNumber);
}
if (exif.fApertureValue.IsValid ())
{
fExifIFD.Add (&fApertureValue);
}
if (exif.fBrightnessValue.IsValid ())
{
fExifIFD.Add (&fBrightnessValue);
}
if (exif.fExposureBiasValue.IsValid ())
{
fExifIFD.Add (&fExposureBiasValue);
}
if (exif.fMaxApertureValue.IsValid ())
{
fExifIFD.Add (&fMaxApertureValue);
}
if (exif.fSubjectDistance.IsValid ())
{
fExifIFD.Add (&fSubjectDistance);
}
if (exif.fFocalLength.IsValid ())
{
fExifIFD.Add (&fFocalLength);
}
if (exif.fISOSpeedRatings [0] != 0)
{
fExifIFD.Add (&fISOSpeedRatings);
}
if (exif.fFlash <= 0x0FFFF)
{
fExifIFD.Add (&fFlash);
}
if (exif.fExposureProgram <= 0x0FFFF)
{
fExifIFD.Add (&fExposureProgram);
}
if (exif.fMeteringMode <= 0x0FFFF)
{
fExifIFD.Add (&fMeteringMode);
}
if (exif.fLightSource <= 0x0FFFF)
{
fExifIFD.Add (&fLightSource);
}
if (exif.fSensingMethod <= 0x0FFFF)
{
fExifIFD.Add (&fSensingMethod);
}
if (exif.fFocalLengthIn35mmFilm != 0)
{
fExifIFD.Add (&fFocalLength35mm);
}
if (exif.fFileSource <= 0x0FF)
{
fExifIFD.Add (&fFileSource);
}
if (exif.fSceneType <= 0x0FF)
{
fExifIFD.Add (&fSceneType);
}
if (exif.fCFARepeatPatternRows &&
exif.fCFARepeatPatternCols)
{
fExifIFD.Add (&fCFAPattern);
}
if (exif.fCustomRendered <= 0x0FFFF)
{
fExifIFD.Add (&fCustomRendered);
}
if (exif.fExposureMode <= 0x0FFFF)
{
fExifIFD.Add (&fExposureMode);
}
if (exif.fWhiteBalance <= 0x0FFFF)
{
fExifIFD.Add (&fWhiteBalance);
}
if (exif.fSceneCaptureType <= 0x0FFFF)
{
fExifIFD.Add (&fSceneCaptureType);
}
if (exif.fGainControl <= 0x0FFFF)
{
fExifIFD.Add (&fGainControl);
}
if (exif.fContrast <= 0x0FFFF)
{
fExifIFD.Add (&fContrast);
}
if (exif.fSaturation <= 0x0FFFF)
{
fExifIFD.Add (&fSaturation);
}
if (exif.fSharpness <= 0x0FFFF)
{
fExifIFD.Add (&fSharpness);
}
if (exif.fSubjectDistanceRange <= 0x0FFFF)
{
fExifIFD.Add (&fSubjectDistanceRange);
}
if (exif.fDigitalZoomRatio.IsValid ())
{
fExifIFD.Add (&fDigitalZoomRatio);
}
if (exif.fExposureIndex.IsValid ())
{
fExifIFD.Add (&fExposureIndex);
}
if (insideDNG) // TIFF-EP only tags
{
if (exif.fImageNumber != 0xFFFFFFFF)
{
directory.Add (&fImageNumber);
}
if (exif.fSelfTimerMode <= 0x0FFFF)
{
directory.Add (&fSelfTimerMode);
}
if (exif.fBatteryLevelA.NotEmpty ())
{
directory.Add (&fBatteryLevelA);
}
else if (exif.fBatteryLevelR.IsValid ())
{
directory.Add (&fBatteryLevelR);
}
}
if (exif.fFocalPlaneXResolution.IsValid ())
{
fExifIFD.Add (&fFocalPlaneXResolution);
}
if (exif.fFocalPlaneYResolution.IsValid ())
{
fExifIFD.Add (&fFocalPlaneYResolution);
}
if (exif.fFocalPlaneResolutionUnit <= 0x0FFFF)
{
fExifIFD.Add (&fFocalPlaneResolutionUnit);
}
if (exif.fSubjectAreaCount)
{
fSubjectAreaData [0] = (uint16) exif.fSubjectArea [0];
fSubjectAreaData [1] = (uint16) exif.fSubjectArea [1];
fSubjectAreaData [2] = (uint16) exif.fSubjectArea [2];
fSubjectAreaData [3] = (uint16) exif.fSubjectArea [3];
fExifIFD.Add (&fSubjectArea);
}
if (exif.fLensInfo [0].IsValid () &&
exif.fLensInfo [1].IsValid () && insideDNG)
{
fLensInfoData [0] = exif.fLensInfo [0];
fLensInfoData [1] = exif.fLensInfo [1];
fLensInfoData [2] = exif.fLensInfo [2];
fLensInfoData [3] = exif.fLensInfo [3];
directory.Add (&fLensInfo);
}
if (exif.fDateTime.IsValid ())
{
directory.Add (&fDateTime);
if (exif.fDateTime.Subseconds ().NotEmpty ())
{
fExifIFD.Add (&fSubsecTime);
}
}
if (exif.fDateTimeOriginal.IsValid ())
{
fExifIFD.Add (&fDateTimeOriginal);
if (exif.fDateTimeOriginal.Subseconds ().NotEmpty ())
{
fExifIFD.Add (&fSubsecTimeOriginal);
}
}
if (exif.fDateTimeDigitized.IsValid ())
{
fExifIFD.Add (&fDateTimeDigitized);
if (exif.fDateTimeDigitized.Subseconds ().NotEmpty ())
{
fExifIFD.Add (&fSubsecTimeDigitized);
}
}
if (insideDNG) // TIFF-EP only tags
{
if (exif.fDateTimeOriginal.IsValid () &&
exif.fDateTimeOriginal.TimeZone ().IsExactHourOffset ())
{
fTimeZoneOffsetData [0] = (int16) exif.fDateTimeOriginal.TimeZone ().ExactHourOffset ();
fTimeZoneOffsetData [1] = (int16) exif.fDateTime .TimeZone ().ExactHourOffset ();
if (!exif.fDateTime.IsValid () ||
!exif.fDateTime.TimeZone ().IsExactHourOffset ())
{
fTimeZoneOffset.SetCount (1);
}
directory.Add (&fTimeZoneOffset);
}
}
if (exif.fMake.NotEmpty ())
{
directory.Add (&fMake);
}
if (exif.fModel.NotEmpty ())
{
directory.Add (&fModel);
}
if (exif.fArtist.NotEmpty ())
{
directory.Add (&fArtist);
}
if (exif.fSoftware.NotEmpty ())
{
directory.Add (&fSoftware);
}
if (exif.fCopyright.NotEmpty ())
{
directory.Add (&fCopyright);
}
if (exif.fImageDescription.NotEmpty ())
{
directory.Add (&fImageDescription);
}
if (exif.fCameraSerialNumber.NotEmpty () && insideDNG)
{
directory.Add (&fSerialNumber);
}
if (makerNoteSafe && makerNoteData)
{
directory.Add (&fMakerNoteSafety);
fExifIFD.Add (&fMakerNote);
}
if (exif.fUserComment.NotEmpty ())
{
fExifIFD.Add (&fUserComment);
}
if (exif.fImageUniqueID.IsValid ())
{
for (uint32 j = 0; j < 16; j++)
{
sprintf (fImageUniqueIDData + j * 2,
"%02X",
(unsigned) exif.fImageUniqueID.data [j]);
}
fExifIFD.Add (&fImageUniqueID);
}
if (exif.fGPSVersionID)
{
fGPSVersionData [0] = (uint8) (exif.fGPSVersionID >> 24);
fGPSVersionData [1] = (uint8) (exif.fGPSVersionID >> 16);
fGPSVersionData [2] = (uint8) (exif.fGPSVersionID >> 8);
fGPSVersionData [3] = (uint8) (exif.fGPSVersionID );
fGPSIFD.Add (&fGPSVersionID);
}
if (exif.fGPSLatitudeRef.NotEmpty () &&
exif.fGPSLatitude [0].IsValid ())
{
fGPSIFD.Add (&fGPSLatitudeRef);
fGPSIFD.Add (&fGPSLatitude );
}
if (exif.fGPSLongitudeRef.NotEmpty () &&
exif.fGPSLongitude [0].IsValid ())
{
fGPSIFD.Add (&fGPSLongitudeRef);
fGPSIFD.Add (&fGPSLongitude );
}
if (exif.fGPSAltitudeRef <= 0x0FF)
{
fGPSIFD.Add (&fGPSAltitudeRef);
}
if (exif.fGPSAltitude.IsValid ())
{
fGPSIFD.Add (&fGPSAltitude);
}
if (exif.fGPSTimeStamp [0].IsValid ())
{
fGPSIFD.Add (&fGPSTimeStamp);
}
if (exif.fGPSSatellites.NotEmpty ())
{
fGPSIFD.Add (&fGPSSatellites);
}
if (exif.fGPSStatus.NotEmpty ())
{
fGPSIFD.Add (&fGPSStatus);
}
if (exif.fGPSMeasureMode.NotEmpty ())
{
fGPSIFD.Add (&fGPSMeasureMode);
}
if (exif.fGPSDOP.IsValid ())
{
fGPSIFD.Add (&fGPSDOP);
}
if (exif.fGPSSpeedRef.NotEmpty ())
{
fGPSIFD.Add (&fGPSSpeedRef);
}
if (exif.fGPSSpeed.IsValid ())
{
fGPSIFD.Add (&fGPSSpeed);
}
if (exif.fGPSTrackRef.NotEmpty ())
{
fGPSIFD.Add (&fGPSTrackRef);
}
if (exif.fGPSTrack.IsValid ())
{
fGPSIFD.Add (&fGPSTrack);
}
if (exif.fGPSImgDirectionRef.NotEmpty ())
{
fGPSIFD.Add (&fGPSImgDirectionRef);
}
if (exif.fGPSImgDirection.IsValid ())
{
fGPSIFD.Add (&fGPSImgDirection);
}
if (exif.fGPSMapDatum.NotEmpty ())
{
fGPSIFD.Add (&fGPSMapDatum);
}
if (exif.fGPSDestLatitudeRef.NotEmpty () &&
exif.fGPSDestLatitude [0].IsValid ())
{
fGPSIFD.Add (&fGPSDestLatitudeRef);
fGPSIFD.Add (&fGPSDestLatitude );
}
if (exif.fGPSDestLongitudeRef.NotEmpty () &&
exif.fGPSDestLongitude [0].IsValid ())
{
fGPSIFD.Add (&fGPSDestLongitudeRef);
fGPSIFD.Add (&fGPSDestLongitude );
}
if (exif.fGPSDestBearingRef.NotEmpty ())
{
fGPSIFD.Add (&fGPSDestBearingRef);
}
if (exif.fGPSDestBearing.IsValid ())
{
fGPSIFD.Add (&fGPSDestBearing);
}
if (exif.fGPSDestDistanceRef.NotEmpty ())
{
fGPSIFD.Add (&fGPSDestDistanceRef);
}
if (exif.fGPSDestDistance.IsValid ())
{
fGPSIFD.Add (&fGPSDestDistance);
}
if (exif.fGPSProcessingMethod.NotEmpty ())
{
fGPSIFD.Add (&fGPSProcessingMethod);
}
if (exif.fGPSAreaInformation.NotEmpty ())
{
fGPSIFD.Add (&fGPSAreaInformation);
}
if (exif.fGPSDateStamp.NotEmpty ())
{
fGPSIFD.Add (&fGPSDateStamp);
}
if (exif.fGPSDifferential <= 0x0FFFF)
{
fGPSIFD.Add (&fGPSDifferential);
}
AddLinks (directory);
}
/******************************************************************************/
void exif_tag_set::AddLinks (dng_tiff_directory &directory)
{
if (fExifIFD.Size () != 0 && !fAddedExifLink)
{
directory.Add (&fExifLink);
fAddedExifLink = true;
}
if (fGPSIFD.Size () != 0 && !fAddedGPSLink)
{
directory.Add (&fGPSLink);
fAddedGPSLink = true;
}
}
/******************************************************************************/
class range_tag_set
{
private:
uint32 fActiveAreaData [4];
tag_uint32_ptr fActiveArea;
uint32 fMaskedAreaData [kMaxMaskedAreas * 4];
tag_uint32_ptr fMaskedAreas;
tag_uint16_ptr fLinearizationTable;
uint16 fBlackLevelRepeatDimData [2];
tag_uint16_ptr fBlackLevelRepeatDim;
dng_urational fBlackLevelData [kMaxBlackPattern *
kMaxBlackPattern *
kMaxSamplesPerPixel];
tag_urational_ptr fBlackLevel;
dng_memory_data fBlackLevelDeltaHData;
dng_memory_data fBlackLevelDeltaVData;
tag_srational_ptr fBlackLevelDeltaH;
tag_srational_ptr fBlackLevelDeltaV;
uint16 fWhiteLevelData16 [kMaxSamplesPerPixel];
uint32 fWhiteLevelData32 [kMaxSamplesPerPixel];
tag_uint16_ptr fWhiteLevel16;
tag_uint32_ptr fWhiteLevel32;
public:
range_tag_set (dng_tiff_directory &directory,
const dng_negative &negative);
};
/******************************************************************************/
range_tag_set::range_tag_set (dng_tiff_directory &directory,
const dng_negative &negative)
: fActiveArea (tcActiveArea,
fActiveAreaData,
4)
, fMaskedAreas (tcMaskedAreas,
fMaskedAreaData,
0)
, fLinearizationTable (tcLinearizationTable,
NULL,
0)
, fBlackLevelRepeatDim (tcBlackLevelRepeatDim,
fBlackLevelRepeatDimData,
2)
, fBlackLevel (tcBlackLevel,
fBlackLevelData)
, fBlackLevelDeltaHData ()
, fBlackLevelDeltaVData ()
, fBlackLevelDeltaH (tcBlackLevelDeltaH)
, fBlackLevelDeltaV (tcBlackLevelDeltaV)
, fWhiteLevel16 (tcWhiteLevel,
fWhiteLevelData16)
, fWhiteLevel32 (tcWhiteLevel,
fWhiteLevelData32)
{
const dng_image &rawImage (negative.RawImage ());
const dng_linearization_info *rangeInfo = negative.GetLinearizationInfo ();
if (rangeInfo)
{
// ActiveArea:
{
const dng_rect &r = rangeInfo->fActiveArea;
if (r.NotEmpty ())
{
fActiveAreaData [0] = r.t;
fActiveAreaData [1] = r.l;
fActiveAreaData [2] = r.b;
fActiveAreaData [3] = r.r;
directory.Add (&fActiveArea);
}
}
// MaskedAreas:
if (rangeInfo->fMaskedAreaCount)
{
fMaskedAreas.SetCount (rangeInfo->fMaskedAreaCount * 4);
for (uint32 index = 0; index < rangeInfo->fMaskedAreaCount; index++)
{
const dng_rect &r = rangeInfo->fMaskedArea [index];
fMaskedAreaData [index * 4 + 0] = r.t;
fMaskedAreaData [index * 4 + 1] = r.l;
fMaskedAreaData [index * 4 + 2] = r.b;
fMaskedAreaData [index * 4 + 3] = r.r;
}
directory.Add (&fMaskedAreas);
}
// LinearizationTable:
if (rangeInfo->fLinearizationTable.Get ())
{
fLinearizationTable.SetData (rangeInfo->fLinearizationTable->Buffer_uint16 () );
fLinearizationTable.SetCount (rangeInfo->fLinearizationTable->LogicalSize () >> 1);
directory.Add (&fLinearizationTable);
}
// BlackLevelRepeatDim:
{
fBlackLevelRepeatDimData [0] = (uint16) rangeInfo->fBlackLevelRepeatRows;
fBlackLevelRepeatDimData [1] = (uint16) rangeInfo->fBlackLevelRepeatCols;
directory.Add (&fBlackLevelRepeatDim);
}
// BlackLevel:
{
uint32 index = 0;
for (uint16 v = 0; v < rangeInfo->fBlackLevelRepeatRows; v++)
{
for (uint32 h = 0; h < rangeInfo->fBlackLevelRepeatCols; h++)
{
for (uint32 c = 0; c < rawImage.Planes (); c++)
{
fBlackLevelData [index++] = rangeInfo->BlackLevel (v, h, c);
}
}
}
fBlackLevel.SetCount (rangeInfo->fBlackLevelRepeatRows *
rangeInfo->fBlackLevelRepeatCols * rawImage.Planes ());
directory.Add (&fBlackLevel);
}
// BlackLevelDeltaH:
if (rangeInfo->ColumnBlackCount ())
{
uint32 count = rangeInfo->ColumnBlackCount ();
fBlackLevelDeltaHData.Allocate (count * sizeof (dng_srational));
dng_srational *blacks = (dng_srational *) fBlackLevelDeltaHData.Buffer ();
for (uint32 col = 0; col < count; col++)
{
blacks [col] = rangeInfo->ColumnBlack (col);
}
fBlackLevelDeltaH.SetData (blacks);
fBlackLevelDeltaH.SetCount (count );
directory.Add (&fBlackLevelDeltaH);
}
// BlackLevelDeltaV:
if (rangeInfo->RowBlackCount ())
{
uint32 count = rangeInfo->RowBlackCount ();
fBlackLevelDeltaVData.Allocate (count * sizeof (dng_srational));
dng_srational *blacks = (dng_srational *) fBlackLevelDeltaVData.Buffer ();
for (uint32 row = 0; row < count; row++)
{
blacks [row] = rangeInfo->RowBlack (row);
}
fBlackLevelDeltaV.SetData (blacks);
fBlackLevelDeltaV.SetCount (count );
directory.Add (&fBlackLevelDeltaV);
}
}
// WhiteLevel:
// Only use the 32-bit data type if we must use it since there
// are some lazy (non-Adobe) DNG readers out there.
bool needs32 = false;
fWhiteLevel16.SetCount (rawImage.Planes ());
fWhiteLevel32.SetCount (rawImage.Planes ());
for (uint32 c = 0; c < fWhiteLevel16.Count (); c++)
{
fWhiteLevelData32 [c] = negative.WhiteLevel (c);
if (fWhiteLevelData32 [c] > 0x0FFFF)
{
needs32 = true;
}
fWhiteLevelData16 [c] = (uint16) fWhiteLevelData32 [c];
}
if (needs32)
{
directory.Add (&fWhiteLevel32);
}
else
{
directory.Add (&fWhiteLevel16);
}
}
/******************************************************************************/
class mosaic_tag_set
{
private:
uint16 fCFARepeatPatternDimData [2];
tag_uint16_ptr fCFARepeatPatternDim;
uint8 fCFAPatternData [kMaxCFAPattern *
kMaxCFAPattern];
tag_uint8_ptr fCFAPattern;
uint8 fCFAPlaneColorData [kMaxColorPlanes];
tag_uint8_ptr fCFAPlaneColor;
tag_uint16 fCFALayout;
tag_uint32 fGreenSplit;
public:
mosaic_tag_set (dng_tiff_directory &directory,
const dng_mosaic_info &info);
};
/******************************************************************************/
mosaic_tag_set::mosaic_tag_set (dng_tiff_directory &directory,
const dng_mosaic_info &info)
: fCFARepeatPatternDim (tcCFARepeatPatternDim,
fCFARepeatPatternDimData,
2)
, fCFAPattern (tcCFAPattern,
fCFAPatternData)
, fCFAPlaneColor (tcCFAPlaneColor,
fCFAPlaneColorData)
, fCFALayout (tcCFALayout,
(uint16) info.fCFALayout)
, fGreenSplit (tcBayerGreenSplit,
info.fBayerGreenSplit)
{
if (info.IsColorFilterArray ())
{
// CFARepeatPatternDim:
fCFARepeatPatternDimData [0] = (uint16) info.fCFAPatternSize.v;
fCFARepeatPatternDimData [1] = (uint16) info.fCFAPatternSize.h;
directory.Add (&fCFARepeatPatternDim);
// CFAPattern:
fCFAPattern.SetCount (info.fCFAPatternSize.v *
info.fCFAPatternSize.h);
for (int32 r = 0; r < info.fCFAPatternSize.v; r++)
{
for (int32 c = 0; c < info.fCFAPatternSize.h; c++)
{
fCFAPatternData [r * info.fCFAPatternSize.h + c] = info.fCFAPattern [r] [c];
}
}
directory.Add (&fCFAPattern);
// CFAPlaneColor:
fCFAPlaneColor.SetCount (info.fColorPlanes);
for (uint32 j = 0; j < info.fColorPlanes; j++)
{
fCFAPlaneColorData [j] = info.fCFAPlaneColor [j];
}
directory.Add (&fCFAPlaneColor);
// CFALayout:
fCFALayout.Set ((uint16) info.fCFALayout);
directory.Add (&fCFALayout);
// BayerGreenSplit: (only include if the pattern is a Bayer pattern)
if (info.fCFAPatternSize == dng_point (2, 2) &&
info.fColorPlanes == 3)
{
directory.Add (&fGreenSplit);
}
}
}
/******************************************************************************/
class color_tag_set
{
private:
uint32 fColorChannels;
tag_matrix fCameraCalibration1;
tag_matrix fCameraCalibration2;
tag_string fCameraCalibrationSignature;
tag_string fAsShotProfileName;
dng_urational fAnalogBalanceData [4];
tag_urational_ptr fAnalogBalance;
dng_urational fAsShotNeutralData [4];
tag_urational_ptr fAsShotNeutral;
dng_urational fAsShotWhiteXYData [2];
tag_urational_ptr fAsShotWhiteXY;
tag_urational fLinearResponseLimit;
public:
color_tag_set (dng_tiff_directory &directory,
const dng_negative &negative);
};
/******************************************************************************/
color_tag_set::color_tag_set (dng_tiff_directory &directory,
const dng_negative &negative)
: fColorChannels (negative.ColorChannels ())
, fCameraCalibration1 (tcCameraCalibration1,
negative.CameraCalibration1 ())
, fCameraCalibration2 (tcCameraCalibration2,
negative.CameraCalibration2 ())
, fCameraCalibrationSignature (tcCameraCalibrationSignature,
negative.CameraCalibrationSignature ())
, fAsShotProfileName (tcAsShotProfileName,
negative.AsShotProfileName ())
, fAnalogBalance (tcAnalogBalance,
fAnalogBalanceData,
fColorChannels)
, fAsShotNeutral (tcAsShotNeutral,
fAsShotNeutralData,
fColorChannels)
, fAsShotWhiteXY (tcAsShotWhiteXY,
fAsShotWhiteXYData,
2)
, fLinearResponseLimit (tcLinearResponseLimit,
negative.LinearResponseLimitR ())
{
if (fColorChannels > 1)
{
uint32 channels2 = fColorChannels * fColorChannels;
if (fCameraCalibration1.Count () == channels2)
{
directory.Add (&fCameraCalibration1);
}
if (fCameraCalibration2.Count () == channels2)
{
directory.Add (&fCameraCalibration2);
}
if (fCameraCalibration1.Count () == channels2 ||
fCameraCalibration2.Count () == channels2)
{
if (negative.CameraCalibrationSignature ().NotEmpty ())
{
directory.Add (&fCameraCalibrationSignature);
}
}
if (negative.AsShotProfileName ().NotEmpty ())
{
directory.Add (&fAsShotProfileName);
}
for (uint32 j = 0; j < fColorChannels; j++)
{
fAnalogBalanceData [j] = negative.AnalogBalanceR (j);
}
directory.Add (&fAnalogBalance);
if (negative.HasCameraNeutral ())
{
for (uint32 k = 0; k < fColorChannels; k++)
{
fAsShotNeutralData [k] = negative.CameraNeutralR (k);
}
directory.Add (&fAsShotNeutral);
}
else if (negative.HasCameraWhiteXY ())
{
negative.GetCameraWhiteXY (fAsShotWhiteXYData [0],
fAsShotWhiteXYData [1]);
directory.Add (&fAsShotWhiteXY);
}
directory.Add (&fLinearResponseLimit);
}
}
/******************************************************************************/
class profile_tag_set
{
private:
tag_uint16 fCalibrationIlluminant1;
tag_uint16 fCalibrationIlluminant2;
tag_matrix fColorMatrix1;
tag_matrix fColorMatrix2;
tag_matrix fForwardMatrix1;
tag_matrix fForwardMatrix2;
tag_matrix fReductionMatrix1;
tag_matrix fReductionMatrix2;
tag_string fProfileName;
tag_string fProfileCalibrationSignature;
tag_uint32 fEmbedPolicyTag;
tag_string fCopyrightTag;
uint32 fHueSatMapDimData [3];
tag_uint32_ptr fHueSatMapDims;
tag_data_ptr fHueSatData1;
tag_data_ptr fHueSatData2;
uint32 fLookTableDimData [3];
tag_uint32_ptr fLookTableDims;
tag_data_ptr fLookTableData;
dng_memory_data fToneCurveBuffer;
tag_data_ptr fToneCurveTag;
public:
profile_tag_set (dng_tiff_directory &directory,
const dng_camera_profile &profile);
};
/******************************************************************************/
profile_tag_set::profile_tag_set (dng_tiff_directory &directory,
const dng_camera_profile &profile)
: fCalibrationIlluminant1 (tcCalibrationIlluminant1,
(uint16) profile.CalibrationIlluminant1 ())
, fCalibrationIlluminant2 (tcCalibrationIlluminant2,
(uint16) profile.CalibrationIlluminant2 ())
, fColorMatrix1 (tcColorMatrix1,
profile.ColorMatrix1 ())
, fColorMatrix2 (tcColorMatrix2,
profile.ColorMatrix2 ())
, fForwardMatrix1 (tcForwardMatrix1,
profile.ForwardMatrix1 ())
, fForwardMatrix2 (tcForwardMatrix2,
profile.ForwardMatrix2 ())
, fReductionMatrix1 (tcReductionMatrix1,
profile.ReductionMatrix1 ())
, fReductionMatrix2 (tcReductionMatrix2,
profile.ReductionMatrix2 ())
, fProfileName (tcProfileName,
profile.Name (),
false)
, fProfileCalibrationSignature (tcProfileCalibrationSignature,
profile.ProfileCalibrationSignature (),
false)
, fEmbedPolicyTag (tcProfileEmbedPolicy,
profile.EmbedPolicy ())
, fCopyrightTag (tcProfileCopyright,
profile.Copyright (),
false)
, fHueSatMapDims (tcProfileHueSatMapDims,
fHueSatMapDimData,
3)
, fHueSatData1 (tcProfileHueSatMapData1,
ttFloat,
profile.HueSatDeltas1 ().DeltasCount () * 3,
profile.HueSatDeltas1 ().GetDeltas ())
, fHueSatData2 (tcProfileHueSatMapData2,
ttFloat,
profile.HueSatDeltas2 ().DeltasCount () * 3,
profile.HueSatDeltas2 ().GetDeltas ())
, fLookTableDims (tcProfileLookTableDims,
fLookTableDimData,
3)
, fLookTableData (tcProfileLookTableData,
ttFloat,
profile.LookTable ().DeltasCount () * 3,
profile.LookTable ().GetDeltas ())
, fToneCurveBuffer ()
, fToneCurveTag (tcProfileToneCurve,
ttFloat,
0,
NULL)
{
if (profile.HasColorMatrix1 ())
{
uint32 colorChannels = profile.ColorMatrix1 ().Rows ();
directory.Add (&fCalibrationIlluminant1);
directory.Add (&fColorMatrix1);
if (fForwardMatrix1.Count () == colorChannels * 3)
{
directory.Add (&fForwardMatrix1);
}
if (colorChannels > 3 && fReductionMatrix1.Count () == colorChannels * 3)
{
directory.Add (&fReductionMatrix1);
}
if (profile.HasColorMatrix2 ())
{
directory.Add (&fCalibrationIlluminant2);
directory.Add (&fColorMatrix2);
if (fForwardMatrix2.Count () == colorChannels * 3)
{
directory.Add (&fForwardMatrix2);
}
if (colorChannels > 3 && fReductionMatrix2.Count () == colorChannels * 3)
{
directory.Add (&fReductionMatrix2);
}
}
if (profile.Name ().NotEmpty ())
{
directory.Add (&fProfileName);
}
if (profile.ProfileCalibrationSignature ().NotEmpty ())
{
directory.Add (&fProfileCalibrationSignature);
}
directory.Add (&fEmbedPolicyTag);
if (profile.Copyright ().NotEmpty ())
{
directory.Add (&fCopyrightTag);
}
bool haveHueSat1 = profile.HueSatDeltas1 ().IsValid ();
bool haveHueSat2 = profile.HueSatDeltas2 ().IsValid () &&
profile.HasColorMatrix2 ();
if (haveHueSat1 || haveHueSat2)
{
uint32 hueDivs = 0;
uint32 satDivs = 0;
uint32 valDivs = 0;
if (haveHueSat1)
{
profile.HueSatDeltas1 ().GetDivisions (hueDivs,
satDivs,
valDivs);
}
else
{
profile.HueSatDeltas2 ().GetDivisions (hueDivs,
satDivs,
valDivs);
}
fHueSatMapDimData [0] = hueDivs;
fHueSatMapDimData [1] = satDivs;
fHueSatMapDimData [2] = valDivs;
directory.Add (&fHueSatMapDims);
}
if (haveHueSat1)
{
directory.Add (&fHueSatData1);
}
if (haveHueSat2)
{
directory.Add (&fHueSatData2);
}
if (profile.HasLookTable ())
{
uint32 hueDivs = 0;
uint32 satDivs = 0;
uint32 valDivs = 0;
profile.LookTable ().GetDivisions (hueDivs,
satDivs,
valDivs);
fLookTableDimData [0] = hueDivs;
fLookTableDimData [1] = satDivs;
fLookTableDimData [2] = valDivs;
directory.Add (&fLookTableDims);
directory.Add (&fLookTableData);
}
if (profile.ToneCurve ().IsValid ())
{
// Tone curve stored as pairs of 32-bit coordinates. Probably could do with
// 16-bits here, but should be small number of points so...
uint32 toneCurvePoints = (uint32) (profile.ToneCurve ().fCoord.size ());
fToneCurveBuffer.Allocate (toneCurvePoints * 2 * sizeof (real32));
real32 *points = fToneCurveBuffer.Buffer_real32 ();
fToneCurveTag.SetCount (toneCurvePoints * 2);
fToneCurveTag.SetData (points);
for (uint32 i = 0; i < toneCurvePoints; i++)
{
// Transpose coordinates so they are in a more expected
// order (domain -> range).
points [i * 2 ] = (real32) profile.ToneCurve ().fCoord [i].h;
points [i * 2 + 1] = (real32) profile.ToneCurve ().fCoord [i].v;
}
directory.Add (&fToneCurveTag);
}
}
}
/******************************************************************************/
tiff_dng_extended_color_profile::tiff_dng_extended_color_profile
(const dng_camera_profile &profile)
: fProfile (profile)
{
}
/******************************************************************************/
void tiff_dng_extended_color_profile::Put (dng_stream &stream,
bool includeModelRestriction)
{
// Profile header.
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
stream.Put_uint16 (magicExtendedProfile);
stream.Put_uint32 (8);
// Profile tags.
profile_tag_set tagSet (*this, fProfile);
// Camera this profile is for.
tag_string cameraModelTag (tcUniqueCameraModel,
fProfile.UniqueCameraModelRestriction ());
if (includeModelRestriction)
{
if (fProfile.UniqueCameraModelRestriction ().NotEmpty ())
{
Add (&cameraModelTag);
}
}
// Write it all out.
dng_tiff_directory::Put (stream, offsetsRelativeToExplicitBase, 8);
}
/*****************************************************************************/
tag_dng_noise_profile::tag_dng_noise_profile (const dng_noise_profile &profile)
: tag_data_ptr (tcNoiseProfile,
ttDouble,
2 * profile.NumFunctions (),
fValues)
{
DNG_REQUIRE (profile.NumFunctions () <= kMaxColorPlanes,
"Too many noise functions in tag_dng_noise_profile.");
for (uint32 i = 0; i < profile.NumFunctions (); i++)
{
fValues [(2 * i) ] = profile.NoiseFunction (i).Scale ();
fValues [(2 * i) + 1] = profile.NoiseFunction (i).Offset ();
}
}
/*****************************************************************************/
dng_image_writer::dng_image_writer ()
: fCompressedBuffer ()
, fUncompressedBuffer ()
, fSubTileBlockBuffer ()
{
}
/*****************************************************************************/
dng_image_writer::~dng_image_writer ()
{
}
/*****************************************************************************/
uint32 dng_image_writer::CompressedBufferSize (const dng_ifd &ifd,
uint32 uncompressedSize)
{
// If we are saving lossless JPEG from an 8-bit image, reserve
// space to pad the data out to 16-bits.
if (ifd.fCompression == ccJPEG && ifd.fBitsPerSample [0] <= 8)
{
return uncompressedSize * 2;
}
return 0;
}
/*****************************************************************************/
void dng_image_writer::EncodePredictor (dng_host & /* host */,
const dng_ifd &ifd,
dng_pixel_buffer & /* buffer */)
{
if (ifd.fPredictor != cpNullPredictor)
{
ThrowProgramError ();
}
}
/*****************************************************************************/
void dng_image_writer::ByteSwapBuffer (dng_host & /* host */,
dng_pixel_buffer &buffer)
{
uint32 pixels = buffer.fRowStep * buffer.fArea.H ();
switch (buffer.fPixelSize)
{
case 2:
{
DoSwapBytes16 ((uint16 *) buffer.fData,
pixels);
break;
}
case 4:
{
DoSwapBytes32 ((uint32 *) buffer.fData,
pixels);
break;
}
default:
break;
}
}
/*****************************************************************************/
void dng_image_writer::ReorderSubTileBlocks (const dng_ifd &ifd,
dng_pixel_buffer &buffer)
{
uint32 blockRows = ifd.fSubTileBlockRows;
uint32 blockCols = ifd.fSubTileBlockCols;
uint32 rowBlocks = buffer.fArea.H () / blockRows;
uint32 colBlocks = buffer.fArea.W () / blockCols;
int32 rowStep = buffer.fRowStep * buffer.fPixelSize;
int32 colStep = buffer.fColStep * buffer.fPixelSize;
int32 rowBlockStep = rowStep * blockRows;
int32 colBlockStep = colStep * blockCols;
uint32 blockColBytes = blockCols * buffer.fPlanes * buffer.fPixelSize;
const uint8 *s0 = fUncompressedBuffer->Buffer_uint8 ();
uint8 *d0 = fSubTileBlockBuffer->Buffer_uint8 ();
for (uint32 rowBlock = 0; rowBlock < rowBlocks; rowBlock++)
{
const uint8 *s1 = s0;
for (uint32 colBlock = 0; colBlock < colBlocks; colBlock++)
{
const uint8 *s2 = s1;
for (uint32 blockRow = 0; blockRow < blockRows; blockRow++)
{
for (uint32 j = 0; j < blockColBytes; j++)
{
d0 [j] = s2 [j];
}
d0 += blockColBytes;
s2 += rowStep;
}
s1 += colBlockStep;
}
s0 += rowBlockStep;
}
// Copy back reordered pixels.
DoCopyBytes (fSubTileBlockBuffer->Buffer (),
fUncompressedBuffer->Buffer (),
fUncompressedBuffer->LogicalSize ());
}
/*****************************************************************************/
void dng_image_writer::WriteData (dng_host &host,
const dng_ifd &ifd,
dng_stream &stream,
dng_pixel_buffer &buffer)
{
switch (ifd.fCompression)
{
case ccUncompressed:
{
// Special case support for when we save to 8-bits from
// 16-bit data.
if (ifd.fBitsPerSample [0] == 8 && buffer.fPixelType == ttShort)
{
uint32 count = buffer.fRowStep *
buffer.fArea.H ();
const uint16 *sPtr = (const uint16 *) buffer.fData;
for (uint32 j = 0; j < count; j++)
{
stream.Put_uint8 ((uint8) sPtr [j]);
}
}
else
{
// Swap bytes if required.
if (stream.SwapBytes ())
{
ByteSwapBuffer (host, buffer);
}
// Write the bytes.
stream.Put (buffer.fData, buffer.fRowStep *
buffer.fArea.H () *
buffer.fPixelSize);
}
break;
}
case ccJPEG:
{
dng_pixel_buffer temp (buffer);
if (buffer.fPixelType == ttByte)
{
// The lossless JPEG encoder needs 16-bit data, so if we are
// are saving 8 bit data, we need to pad it out to 16-bits.
temp.fData = fCompressedBuffer->Buffer ();
temp.fPixelType = ttShort;
temp.fPixelSize = 2;
temp.CopyArea (buffer,
buffer.fArea,
buffer.fPlane,
buffer.fPlanes);
}
EncodeLosslessJPEG ((const uint16 *) temp.fData,
temp.fArea.H (),
temp.fArea.W (),
temp.fPlanes,
ifd.fBitsPerSample [0],
temp.fRowStep,
temp.fColStep,
stream);
break;
}
default:
{
ThrowProgramError ();
break;
}
}
}
/*****************************************************************************/
void dng_image_writer::WriteTile (dng_host &host,
const dng_ifd &ifd,
dng_stream &stream,
const dng_image &image,
const dng_rect &tileArea,
uint32 fakeChannels)
{
// Create pixel buffer to hold uncompressed tile.
dng_pixel_buffer buffer;
buffer.fArea = tileArea;
buffer.fPlane = 0;
buffer.fPlanes = ifd.fSamplesPerPixel;
buffer.fRowStep = buffer.fPlanes * tileArea.W ();
buffer.fColStep = buffer.fPlanes;
buffer.fPlaneStep = 1;
buffer.fPixelType = image.PixelType ();
buffer.fPixelSize = image.PixelSize ();
buffer.fData = fUncompressedBuffer->Buffer ();
// Get the uncompressed data.
image.Get (buffer, dng_image::edge_zero);
// Deal with sub-tile blocks.
if (ifd.fSubTileBlockRows > 1)
{
ReorderSubTileBlocks (ifd, buffer);
}
// Run predictor.
EncodePredictor (host,
ifd,
buffer);
// Adjust pixel buffer for fake channels.
if (fakeChannels > 1)
{
buffer.fPlanes *= fakeChannels;
buffer.fColStep *= fakeChannels;
buffer.fArea.r = buffer.fArea.l + (buffer.fArea.W () / fakeChannels);
}
// Compress (if required) and write out the data.
WriteData (host,
ifd,
stream,
buffer);
}
/*****************************************************************************/
void dng_image_writer::WriteImage (dng_host &host,
const dng_ifd &ifd,
dng_basic_tag_set &basic,
dng_stream &stream,
const dng_image &image,
uint32 fakeChannels)
{
// Deal with row interleaved images.
if (ifd.fRowInterleaveFactor > 1 &&
ifd.fRowInterleaveFactor < ifd.fImageLength)
{
dng_ifd tempIFD (ifd);
tempIFD.fRowInterleaveFactor = 1;
dng_row_interleaved_image tempImage (*((dng_image *) &image),
ifd.fRowInterleaveFactor);
WriteImage (host,
tempIFD,
basic,
stream,
tempImage,
fakeChannels);
return;
}
// Compute basic information.
uint32 bytesPerSample = TagTypeSize (image.PixelType ());
uint32 bytesPerPixel = ifd.fSamplesPerPixel * bytesPerSample;
uint32 tileRowBytes = ifd.fTileWidth * bytesPerPixel;
// If we can compute the number of bytes needed to store the
// data, we can split the write for each tile into sub-tiles.
uint32 subTileLength = ifd.fTileLength;
if (ifd.TileByteCount (ifd.TileArea (0, 0)) != 0)
{
subTileLength = Pin_uint32 (ifd.fSubTileBlockRows,
kImageBufferSize / tileRowBytes,
ifd.fTileLength);
// Don't split sub-tiles across subTileBlocks.
subTileLength = subTileLength / ifd.fSubTileBlockRows
* ifd.fSubTileBlockRows;
}
// Allocate buffer to hold one sub-tile of uncompressed data.
uint32 uncompressedSize = subTileLength * tileRowBytes;
fUncompressedBuffer.Reset (host.Allocate (uncompressedSize));
// Buffer to repack tiles order.
if (ifd.fSubTileBlockRows > 1)
{
fSubTileBlockBuffer.Reset (host.Allocate (uncompressedSize));
}
// Allocate compressed buffer, if required.
uint32 compressedSize = CompressedBufferSize (ifd, uncompressedSize);
if (compressedSize)
{
fCompressedBuffer.Reset (host.Allocate (compressedSize));
}
// Write out each tile.
uint32 tileIndex = 0;
uint32 tilesAcross = ifd.TilesAcross ();
uint32 tilesDown = ifd.TilesDown ();
for (uint32 rowIndex = 0; rowIndex < tilesDown; rowIndex++)
{
for (uint32 colIndex = 0; colIndex < tilesAcross; colIndex++)
{
// Remember this offset.
uint32 tileOffset = (uint32) stream.Position ();
basic.SetTileOffset (tileIndex, tileOffset);
// Split tile into sub-tiles if possible.
dng_rect tileArea = ifd.TileArea (rowIndex, colIndex);
uint32 subTileCount = (tileArea.H () + subTileLength - 1) /
subTileLength;
for (uint32 subIndex = 0; subIndex < subTileCount; subIndex++)
{
host.SniffForAbort ();
dng_rect subArea (tileArea);
subArea.t = tileArea.t + subIndex * subTileLength;
subArea.b = Min_int32 (subArea.t + subTileLength,
tileArea.b);
// Write the sub-tile.
WriteTile (host,
ifd,
stream,
image,
subArea,
fakeChannels);
}
// Update tile count.
uint32 tileByteCount = (uint32) stream.Position () - tileOffset;
basic.SetTileByteCount (tileIndex, tileByteCount);
tileIndex++;
// Keep the tiles on even byte offsets.
if (tileByteCount & 1)
{
stream.Put_uint8 (0);
}
}
}
// We are done with the compression buffers.
fCompressedBuffer .Reset ();
fUncompressedBuffer.Reset ();
}
/*****************************************************************************/
void dng_image_writer::WriteTIFF (dng_host &host,
dng_stream &stream,
const dng_image &image,
uint32 photometricInterpretation,
uint32 compression,
dng_negative *negative,
const dng_color_space *space,
const dng_resolution *resolution,
const dng_jpeg_preview *thumbnail,
const dng_memory_block *imageResources)
{
const void *profileData = NULL;
uint32 profileSize = 0;
const uint8 *data = NULL;
uint32 size = 0;
if (space && space->ICCProfile (size, data))
{
profileData = data;
profileSize = size;
}
WriteTIFFWithProfile (host,
stream,
image,
photometricInterpretation,
compression,
negative,
profileData,
profileSize,
resolution,
thumbnail,
imageResources);
}
/*****************************************************************************/
void dng_image_writer::WriteTIFFWithProfile (dng_host &host,
dng_stream &stream,
const dng_image &image,
uint32 photometricInterpretation,
uint32 compression,
dng_negative *negative,
const void *profileData,
uint32 profileSize,
const dng_resolution *resolution,
const dng_jpeg_preview *thumbnail,
const dng_memory_block *imageResources)
{
uint32 j;
dng_ifd ifd;
ifd.fNewSubFileType = sfMainImage;
ifd.fImageWidth = image.Bounds ().W ();
ifd.fImageLength = image.Bounds ().H ();
ifd.fSamplesPerPixel = image.Planes ();
ifd.fBitsPerSample [0] = TagTypeSize (image.PixelType ()) * 8;
for (j = 1; j < ifd.fSamplesPerPixel; j++)
{
ifd.fBitsPerSample [j] = ifd.fBitsPerSample [0];
}
ifd.fPhotometricInterpretation = photometricInterpretation;
ifd.fCompression = compression;
if (ifd.fCompression == ccUncompressed)
{
ifd.SetSingleStrip ();
}
else
{
ifd.FindStripSize (128 * 1024);
ifd.fPredictor = cpHorizontalDifference;
}
uint32 extraSamples = 0;
switch (photometricInterpretation)
{
case piBlackIsZero:
{
extraSamples = image.Planes () - 1;
break;
}
case piRGB:
{
extraSamples = image.Planes () - 3;
break;
}
default:
break;
}
ifd.fExtraSamplesCount = extraSamples;
if (image.PixelType () == ttFloat)
{
for (j = 0; j < ifd.fSamplesPerPixel; j++)
{
ifd.fSampleFormat [j] = sfFloatingPoint;
}
}
dng_tiff_directory mainIFD;
dng_basic_tag_set basic (mainIFD, ifd);
// Resolution.
dng_resolution res;
if (resolution)
{
res = *resolution;
}
tag_urational tagXResolution (tcXResolution, res.fXResolution);
tag_urational tagYResolution (tcYResolution, res.fYResolution);
tag_uint16 tagResolutionUnit (tcResolutionUnit, res.fResolutionUnit);
if (resolution)
{
mainIFD.Add (&tagXResolution );
mainIFD.Add (&tagYResolution );
mainIFD.Add (&tagResolutionUnit);
}
// ICC Profile.
tag_icc_profile iccProfileTag (profileData, profileSize);
if (iccProfileTag.Count ())
{
mainIFD.Add (&iccProfileTag);
}
// Rebuild IPTC with TIFF padding bytes.
if (negative && negative->GetXMP ())
{
negative->RebuildIPTC (true, false);
}
// XMP metadata.
AutoPtr<dng_xmp> xmp;
if (negative && negative->GetXMP ())
{
xmp.Reset (new dng_xmp (*negative->GetXMP ()));
xmp->ClearOrientation ();
xmp->ClearImageInfo ();
xmp->SetImageSize (image.Size ());
xmp->SetSampleInfo (ifd.fSamplesPerPixel,
ifd.fBitsPerSample [0]);
xmp->SetPhotometricInterpretation (ifd.fPhotometricInterpretation);
if (resolution)
{
xmp->SetResolution (*resolution);
}
}
tag_xmp tagXMP (xmp.Get ());
if (tagXMP.Count ())
{
mainIFD.Add (&tagXMP);
}
xmp.Reset ();
// IPTC metadata.
tag_iptc tagIPTC (negative ? negative->IPTCData () : NULL,
negative ? negative->IPTCLength () : 0);
if (tagIPTC.Count ())
{
mainIFD.Add (&tagIPTC);
}
// Adobe data (thumbnail and IPTC digest)
AutoPtr<dng_memory_block> adobeData (BuildAdobeData (host,
negative,
thumbnail,
imageResources));
tag_uint8_ptr tagAdobe (tcAdobeData,
adobeData->Buffer_uint8 (),
adobeData->LogicalSize ());
if (tagAdobe.Count ())
{
mainIFD.Add (&tagAdobe);
}
// Exif metadata.
exif_tag_set exifSet (mainIFD,
negative && negative->GetExif () ? *negative->GetExif ()
: dng_exif (),
negative ? negative->IsMakerNoteSafe () : false,
negative ? negative->MakerNoteData () : NULL,
negative ? negative->MakerNoteLength () : 0,
false);
// Find offset to main image data.
uint32 offsetMainIFD = 8;
uint32 offsetExifData = offsetMainIFD + mainIFD.Size ();
exifSet.Locate (offsetExifData);
uint32 offsetMainData = offsetExifData + exifSet.Size ();
stream.SetWritePosition (offsetMainData);
// Write the main image data.
WriteImage (host,
ifd,
basic,
stream,
image);
// Trim the file to this length.
stream.SetLength (stream.Position ());
// TIFF has a 4G size limit.
if (stream.Length () > 0x0FFFFFFFFL)
{
ThrowImageTooBigTIFF ();
}
// Write TIFF Header.
stream.SetWritePosition (0);
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
stream.Put_uint16 (42);
stream.Put_uint32 (offsetMainIFD);
// Write the IFDs.
mainIFD.Put (stream);
exifSet.Put (stream);
stream.Flush ();
}
/*****************************************************************************/
void dng_image_writer::WriteDNG (dng_host &host,
dng_stream &stream,
const dng_negative &negative,
const dng_image_preview &thumbnail,
uint32 compression,
const dng_preview_list *previewList)
{
uint32 j;
// Figure out what main version to use.
uint32 dngVersion = dngVersion_Current;
// Figure out what backward version to use.
uint32 dngBackwardVersion = dngVersion_1_1_0_0;
#if defined(qTestRowInterleave) || defined(qTestSubTileBlockRows) || defined(qTestSubTileBlockCols)
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_2_0_0);
#endif
dngBackwardVersion = Max_uint32 (dngBackwardVersion,
negative.OpcodeList1 ().MinVersion (false));
dngBackwardVersion = Max_uint32 (dngBackwardVersion,
negative.OpcodeList2 ().MinVersion (false));
dngBackwardVersion = Max_uint32 (dngBackwardVersion,
negative.OpcodeList3 ().MinVersion (false));
if (negative.GetMosaicInfo () &&
negative.GetMosaicInfo ()->fCFALayout >= 6)
{
dngBackwardVersion = Max_uint32 (dngBackwardVersion, dngVersion_1_3_0_0);
}
if (dngBackwardVersion > dngVersion)
{
ThrowProgramError ();
}
// Create the main IFD
dng_tiff_directory mainIFD;
// Include DNG version tags.
uint8 dngVersionData [4];
dngVersionData [0] = (uint8) (dngVersion >> 24);
dngVersionData [1] = (uint8) (dngVersion >> 16);
dngVersionData [2] = (uint8) (dngVersion >> 8);
dngVersionData [3] = (uint8) (dngVersion );
tag_uint8_ptr tagDNGVersion (tcDNGVersion, dngVersionData, 4);
mainIFD.Add (&tagDNGVersion);
uint8 dngBackwardVersionData [4];
dngBackwardVersionData [0] = (uint8) (dngBackwardVersion >> 24);
dngBackwardVersionData [1] = (uint8) (dngBackwardVersion >> 16);
dngBackwardVersionData [2] = (uint8) (dngBackwardVersion >> 8);
dngBackwardVersionData [3] = (uint8) (dngBackwardVersion );
tag_uint8_ptr tagDNGBackwardVersion (tcDNGBackwardVersion, dngBackwardVersionData, 4);
mainIFD.Add (&tagDNGBackwardVersion);
// The main IFD contains the thumbnail.
AutoPtr<dng_basic_tag_set> thmBasic (thumbnail.AddTagSet (mainIFD));
// Get the raw image we are writing.
const dng_image &rawImage (negative.RawImage ());
// We currently don't support compression for deeper
// than 16-bit images.
if (rawImage.PixelType () == ttLong)
{
compression = ccUncompressed;
}
// Get a copy of the mosaic info.
dng_mosaic_info mosaicInfo;
if (negative.GetMosaicInfo ())
{
mosaicInfo = *(negative.GetMosaicInfo ());
}
// Create the IFD for the raw data.
dng_tiff_directory rawIFD;
// Create a dng_ifd record for the raw image.
dng_ifd info;
info.fImageWidth = rawImage.Width ();
info.fImageLength = rawImage.Height ();
info.fSamplesPerPixel = rawImage.Planes ();
info.fPhotometricInterpretation = mosaicInfo.IsColorFilterArray () ? piCFA
: piLinearRaw;
info.fCompression = compression;
uint32 rawPixelType = rawImage.PixelType ();
if (rawPixelType == ttShort)
{
// See if we are using a linearization table with <= 256 entries, in which
// case the useful data will all fit within 8-bits.
const dng_linearization_info *rangeInfo = negative.GetLinearizationInfo ();
if (rangeInfo)
{
if (rangeInfo->fLinearizationTable.Get ())
{
uint32 entries = rangeInfo->fLinearizationTable->LogicalSize () >> 1;
if (entries <= 256)
{
rawPixelType = ttByte;
}
}
}
}
switch (rawPixelType)
{
case ttByte:
{
info.fBitsPerSample [0] = 8;
break;
}
case ttShort:
{
info.fBitsPerSample [0] = 16;
break;
}
case ttLong:
{
info.fBitsPerSample [0] = 32;
break;
}
default:
{
ThrowProgramError ();
break;
}
}
// For lossless JPEG compression, we often lie about the
// actual channel count to get the predictors to work across
// same color mosaic pixels.
uint32 fakeChannels = 1;
if (info.fCompression == ccJPEG)
{
if (mosaicInfo.IsColorFilterArray ())
{
if (mosaicInfo.fCFAPatternSize.h == 4)
{
fakeChannels = 4;
}
else if (mosaicInfo.fCFAPatternSize.h == 2)
{
fakeChannels = 2;
}
// However, lossless JEPG is limited to four channels,
// so compromise might be required.
while (fakeChannels * info.fSamplesPerPixel > 4 &&
fakeChannels > 1)
{
fakeChannels >>= 1;
}
}
}
// Figure out tile sizes.
if (info.fCompression == ccJPEG)
{
info.FindTileSize (128 * 1024);
}
// Don't use tiles for uncompressed images.
else
{
info.SetSingleStrip ();
}
#ifdef qTestRowInterleave
info.fRowInterleaveFactor = qTestRowInterleave;
#endif
#if defined(qTestSubTileBlockRows) && defined(qTestSubTileBlockCols)
info.fSubTileBlockRows = qTestSubTileBlockRows;
info.fSubTileBlockCols = qTestSubTileBlockCols;
if (fakeChannels == 2)
fakeChannels = 4;
#endif
// Basic information.
dng_basic_tag_set rawBasic (rawIFD, info);
// DefaultScale tag.
dng_urational defaultScaleData [2];
defaultScaleData [0] = negative.DefaultScaleH ();
defaultScaleData [1] = negative.DefaultScaleV ();
tag_urational_ptr tagDefaultScale (tcDefaultScale,
defaultScaleData,
2);
rawIFD.Add (&tagDefaultScale);
// Best quality scale tag.
tag_urational tagBestQualityScale (tcBestQualityScale,
negative.BestQualityScale ());
rawIFD.Add (&tagBestQualityScale);
// DefaultCropOrigin tag.
dng_urational defaultCropOriginData [2];
defaultCropOriginData [0] = negative.DefaultCropOriginH ();
defaultCropOriginData [1] = negative.DefaultCropOriginV ();
tag_urational_ptr tagDefaultCropOrigin (tcDefaultCropOrigin,
defaultCropOriginData,
2);
rawIFD.Add (&tagDefaultCropOrigin);
// DefaultCropSize tag.
dng_urational defaultCropSizeData [2];
defaultCropSizeData [0] = negative.DefaultCropSizeH ();
defaultCropSizeData [1] = negative.DefaultCropSizeV ();
tag_urational_ptr tagDefaultCropSize (tcDefaultCropSize,
defaultCropSizeData,
2);
rawIFD.Add (&tagDefaultCropSize);
// Range mapping tag set.
range_tag_set rangeSet (rawIFD, negative);
// Mosaic pattern information.
mosaic_tag_set mosaicSet (rawIFD, mosaicInfo);
// Chroma blur radius.
tag_urational tagChromaBlurRadius (tcChromaBlurRadius,
negative.ChromaBlurRadius ());
if (negative.ChromaBlurRadius ().IsValid ())
{
rawIFD.Add (&tagChromaBlurRadius);
}
// Anti-alias filter strength.
tag_urational tagAntiAliasStrength (tcAntiAliasStrength,
negative.AntiAliasStrength ());
if (negative.AntiAliasStrength ().IsValid ())
{
rawIFD.Add (&tagAntiAliasStrength);
}
// Profile and other color related tags.
AutoPtr<profile_tag_set> profileSet;
AutoPtr<color_tag_set> colorSet;
std::vector<uint32> extraProfileIndex;
if (!negative.IsMonochrome ())
{
const dng_camera_profile &mainProfile (*negative.CameraProfileToEmbed ());
profileSet.Reset (new profile_tag_set (mainIFD,
mainProfile));
colorSet.Reset (new color_tag_set (mainIFD,
negative));
// Build list of profile indices to include in extra profiles tag.
uint32 profileCount = negative.ProfileCount ();
for (uint32 index = 0; index < profileCount; index++)
{
const dng_camera_profile &profile (negative.ProfileByIndex (index));
if (&profile != &mainProfile)
{
if (profile.WasReadFromDNG ())
{
extraProfileIndex.push_back (index);
}
}
}
}
// Extra camera profiles tag.
uint32 extraProfileCount = (uint32) extraProfileIndex.size ();
dng_memory_data extraProfileOffsets (extraProfileCount * sizeof (uint32));
tag_uint32_ptr extraProfileTag (tcExtraCameraProfiles,
extraProfileOffsets.Buffer_uint32 (),
extraProfileCount);
if (extraProfileCount)
{
mainIFD.Add (&extraProfileTag);
}
// Other tags.
tag_uint16 tagOrientation (tcOrientation,
(uint16) negative.Orientation ().GetTIFF ());
mainIFD.Add (&tagOrientation);
tag_srational tagBaselineExposure (tcBaselineExposure,
negative.BaselineExposureR ());
mainIFD.Add (&tagBaselineExposure);
tag_urational tagBaselineNoise (tcBaselineNoise,
negative.BaselineNoiseR ());
mainIFD.Add (&tagBaselineNoise);
tag_urational tagNoiseReductionApplied (tcNoiseReductionApplied,
negative.NoiseReductionApplied ());
if (negative.NoiseReductionApplied ().IsValid ())
{
mainIFD.Add (&tagNoiseReductionApplied);
}
tag_dng_noise_profile tagNoiseProfile (negative.NoiseProfile ());
if (negative.NoiseProfile ().IsValidForNegative (negative))
{
mainIFD.Add (&tagNoiseProfile);
}
tag_urational tagBaselineSharpness (tcBaselineSharpness,
negative.BaselineSharpnessR ());
mainIFD.Add (&tagBaselineSharpness);
tag_string tagUniqueName (tcUniqueCameraModel,
negative.ModelName (),
true);
mainIFD.Add (&tagUniqueName);
tag_string tagLocalName (tcLocalizedCameraModel,
negative.LocalName (),
false);
if (negative.LocalName ().NotEmpty ())
{
mainIFD.Add (&tagLocalName);
}
tag_urational tagShadowScale (tcShadowScale,
negative.ShadowScaleR ());
mainIFD.Add (&tagShadowScale);
tag_uint16 tagColorimetricReference (tcColorimetricReference,
(uint16) negative.ColorimetricReference ());
if (negative.ColorimetricReference () != crSceneReferred)
{
mainIFD.Add (&tagColorimetricReference);
}
negative.FindRawImageDigest (host);
tag_uint8_ptr tagRawImageDigest (tcRawImageDigest,
negative.RawImageDigest ().data,
16);
if (negative.RawImageDigest ().IsValid ())
{
mainIFD.Add (&tagRawImageDigest);
}
negative.FindRawDataUniqueID (host);
tag_uint8_ptr tagRawDataUniqueID (tcRawDataUniqueID,
negative.RawDataUniqueID ().data,
16);
if (negative.RawDataUniqueID ().IsValid ())
{
mainIFD.Add (&tagRawDataUniqueID);
}
tag_string tagOriginalRawFileName (tcOriginalRawFileName,
negative.OriginalRawFileName (),
false);
if (negative.HasOriginalRawFileName ())
{
mainIFD.Add (&tagOriginalRawFileName);
}
negative.FindOriginalRawFileDigest ();
tag_data_ptr tagOriginalRawFileData (tcOriginalRawFileData,
ttUndefined,
negative.OriginalRawFileDataLength (),
negative.OriginalRawFileData ());
tag_uint8_ptr tagOriginalRawFileDigest (tcOriginalRawFileDigest,
negative.OriginalRawFileDigest ().data,
16);
if (negative.OriginalRawFileData ())
{
mainIFD.Add (&tagOriginalRawFileData);
mainIFD.Add (&tagOriginalRawFileDigest);
}
// XMP metadata.
AutoPtr<dng_xmp> xmp;
if (negative.GetXMP ())
{
xmp.Reset (new dng_xmp (*negative.GetXMP ()));
// Make sure the XMP orientation always matches the
// tag orientation.
xmp->SetOrientation (negative.Orientation ());
}
tag_xmp tagXMP (xmp.Get ());
if (tagXMP.Count ())
{
mainIFD.Add (&tagXMP);
}
xmp.Reset ();
// Exif tags.
exif_tag_set exifSet (mainIFD,
*negative.GetExif (),
negative.IsMakerNoteSafe (),
negative.MakerNoteData (),
negative.MakerNoteLength (),
true);
// Private data.
tag_uint8_ptr tagPrivateData (tcDNGPrivateData,
negative.PrivateData (),
negative.PrivateLength ());
if (negative.PrivateLength ())
{
mainIFD.Add (&tagPrivateData);
}
// Opcode list 1.
AutoPtr<dng_memory_block> opcodeList1Data (negative.OpcodeList1 ().Spool (host));
tag_data_ptr tagOpcodeList1 (tcOpcodeList1,
ttUndefined,
opcodeList1Data.Get () ? opcodeList1Data->LogicalSize () : 0,
opcodeList1Data.Get () ? opcodeList1Data->Buffer () : NULL);
if (opcodeList1Data.Get ())
{
rawIFD.Add (&tagOpcodeList1);
}
// Opcode list 2.
AutoPtr<dng_memory_block> opcodeList2Data (negative.OpcodeList2 ().Spool (host));
tag_data_ptr tagOpcodeList2 (tcOpcodeList2,
ttUndefined,
opcodeList2Data.Get () ? opcodeList2Data->LogicalSize () : 0,
opcodeList2Data.Get () ? opcodeList2Data->Buffer () : NULL);
if (opcodeList2Data.Get ())
{
rawIFD.Add (&tagOpcodeList2);
}
// Opcode list 3.
AutoPtr<dng_memory_block> opcodeList3Data (negative.OpcodeList3 ().Spool (host));
tag_data_ptr tagOpcodeList3 (tcOpcodeList3,
ttUndefined,
opcodeList3Data.Get () ? opcodeList3Data->LogicalSize () : 0,
opcodeList3Data.Get () ? opcodeList3Data->Buffer () : NULL);
if (opcodeList3Data.Get ())
{
rawIFD.Add (&tagOpcodeList3);
}
// Add other subfiles.
uint32 subFileCount = 1;
// Add previews.
uint32 previewCount = previewList ? previewList->Count () : 0;
AutoPtr<dng_tiff_directory> previewIFD [kMaxDNGPreviews];
AutoPtr<dng_basic_tag_set> previewBasic [kMaxDNGPreviews];
for (j = 0; j < previewCount; j++)
{
previewIFD [j] . Reset (new dng_tiff_directory);
previewBasic [j] . Reset (previewList->Preview (j).AddTagSet (*previewIFD [j]));
subFileCount++;
}
// And a link to the raw and JPEG image IFDs.
uint32 subFileData [kMaxDNGPreviews + 1];
tag_uint32_ptr tagSubFile (tcSubIFDs,
subFileData,
subFileCount);
mainIFD.Add (&tagSubFile);
// Skip past the header and IFDs for now.
uint32 currentOffset = 8;
currentOffset += mainIFD.Size ();
subFileData [0] = currentOffset;
currentOffset += rawIFD.Size ();
for (j = 0; j < previewCount; j++)
{
subFileData [j + 1] = currentOffset;
currentOffset += previewIFD [j]->Size ();
}
exifSet.Locate (currentOffset);
currentOffset += exifSet.Size ();
stream.SetWritePosition (currentOffset);
// Write the extra profiles.
if (extraProfileCount)
{
for (j = 0; j < extraProfileCount; j++)
{
extraProfileOffsets.Buffer_uint32 () [j] = (uint32) stream.Position ();
uint32 index = extraProfileIndex [j];
const dng_camera_profile &profile (negative.ProfileByIndex (index));
tiff_dng_extended_color_profile extraWriter (profile);
extraWriter.Put (stream, false);
}
}
// Write the thumbnail data.
thumbnail.WriteData (host,
*this,
*thmBasic,
stream);
// Write the preview data.
for (j = 0; j < previewCount; j++)
{
previewList->Preview (j).WriteData (host,
*this,
*previewBasic [j],
stream);
}
// Write the raw data.
WriteImage (host,
info,
rawBasic,
stream,
rawImage,
fakeChannels);
// Trim the file to this length.
stream.SetLength (stream.Position ());
// DNG has a 4G size limit.
if (stream.Length () > 0x0FFFFFFFFL)
{
ThrowImageTooBigDNG ();
}
// Write TIFF Header.
stream.SetWritePosition (0);
stream.Put_uint16 (stream.BigEndian () ? byteOrderMM : byteOrderII);
stream.Put_uint16 (42);
stream.Put_uint32 (8);
// Write the IFDs.
mainIFD.Put (stream);
rawIFD.Put (stream);
for (j = 0; j < previewCount; j++)
{
previewIFD [j]->Put (stream);
}
exifSet.Put (stream);
stream.Flush ();
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_lossless_jpeg.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_lossless_jpeg.cpp
index 409bb49b53..220de476b4 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_lossless_jpeg.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_lossless_jpeg.cpp
@@ -1,3747 +1,3747 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_lossless_jpeg.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
// Lossless JPEG code adapted from:
/* Copyright (C) 1991, 1992, Thomas G. Lane.
* Part of the Independent JPEG Group's software.
* See the file Copyright for more details.
*
* Copyright (c) 1993 Brian C. Smith, The Regents of the University
* of California
* All rights reserved.
*
* Copyright (c) 1994 Kongji Huang and Brian C. Smith.
* Cornell University
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* IN NO EVENT SHALL CORNELL UNIVERSITY BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF CORNELL
* UNIVERSITY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* CORNELL UNIVERSITY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND CORNELL UNIVERSITY HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/
/*****************************************************************************/
#include "dng_lossless_jpeg.h"
#include "dng_assertions.h"
#include "dng_exceptions.h"
#include "dng_memory.h"
#include "dng_stream.h"
#include "dng_tag_codes.h"
/*****************************************************************************/
// This module contains routines that should be as fast as possible, even
// at the expense of slight code size increases.
#include "dng_fast_module.h"
/*****************************************************************************/
// The qSupportCanon_sRAW stuff not actually required for DNG support, but
// only included to allow this code to be used on Canon sRAW files.
#ifndef qSupportCanon_sRAW
#define qSupportCanon_sRAW 1
#endif
// The qSupportHasselblad_3FR stuff not actually required for DNG support, but
// only included to allow this code to be used on Hasselblad 3FR files.
#ifndef qSupportHasselblad_3FR
#define qSupportHasselblad_3FR 1
#endif
/*****************************************************************************/
/*
* One of the following structures is created for each huffman coding
* table. We use the same structure for encoding and decoding, so there
* may be some extra fields for encoding that aren't used in the decoding
* and vice-versa.
*/
struct HuffmanTable
{
/*
* These two fields directly represent the contents of a JPEG DHT
* marker
*/
uint8 bits[17];
uint8 huffval[256];
/*
* The remaining fields are computed from the above to allow more
* efficient coding and decoding. These fields should be considered
* private to the Huffman compression & decompression modules.
*/
uint16 mincode[17];
int32 maxcode[18];
int16 valptr[17];
int32 numbits[256];
int32 value[256];
uint16 ehufco[256];
int8 ehufsi[256];
};
/*****************************************************************************/
// Computes the derived fields in the Huffman table structure.
static void FixHuffTbl (HuffmanTable *htbl)
{
int32 l;
int32 i;
const uint32 bitMask [] =
{
0xffffffff, 0x7fffffff, 0x3fffffff, 0x1fffffff,
0x0fffffff, 0x07ffffff, 0x03ffffff, 0x01ffffff,
0x00ffffff, 0x007fffff, 0x003fffff, 0x001fffff,
0x000fffff, 0x0007ffff, 0x0003ffff, 0x0001ffff,
0x0000ffff, 0x00007fff, 0x00003fff, 0x00001fff,
0x00000fff, 0x000007ff, 0x000003ff, 0x000001ff,
0x000000ff, 0x0000007f, 0x0000003f, 0x0000001f,
0x0000000f, 0x00000007, 0x00000003, 0x00000001
};
// Figure C.1: make table of Huffman code length for each symbol
// Note that this is in code-length order.
int8 huffsize [257];
int32 p = 0;
for (l = 1; l <= 16; l++)
{
for (i = 1; i <= (int32) htbl->bits [l]; i++)
huffsize [p++] = (int8) l;
}
huffsize [p] = 0;
int32 lastp = p;
// Figure C.2: generate the codes themselves
// Note that this is in code-length order.
uint16 huffcode [257];
uint16 code = 0;
int32 si = huffsize [0];
p = 0;
while (huffsize [p])
{
while (((int32) huffsize [p]) == si)
{
huffcode [p++] = code;
code++;
}
code <<= 1;
si++;
}
// Figure C.3: generate encoding tables
// These are code and size indexed by symbol value
// Set any codeless symbols to have code length 0; this allows
// EmitBits to detect any attempt to emit such symbols.
memset (htbl->ehufsi, 0, sizeof (htbl->ehufsi));
for (p = 0; p < lastp; p++)
{
htbl->ehufco [htbl->huffval [p]] = huffcode [p];
htbl->ehufsi [htbl->huffval [p]] = huffsize [p];
}
// Figure F.15: generate decoding tables
p = 0;
for (l = 1; l <= 16; l++)
{
if (htbl->bits [l])
{
htbl->valptr [l] = (int16) p;
htbl->mincode [l] = huffcode [p];
p += htbl->bits [l];
htbl->maxcode [l] = huffcode [p - 1];
}
else
{
htbl->maxcode [l] = -1;
}
}
// We put in this value to ensure HuffDecode terminates.
htbl->maxcode[17] = 0xFFFFFL;
// Build the numbits, value lookup tables.
// These table allow us to gather 8 bits from the bits stream,
// and immediately lookup the size and value of the huffman codes.
// If size is zero, it means that more than 8 bits are in the huffman
// code (this happens about 3-4% of the time).
memset (htbl->numbits, 0, sizeof (htbl->numbits));
for (p = 0; p < lastp; p++)
{
int32 size = huffsize [p];
if (size <= 8)
{
int32 value = htbl->huffval [p];
code = huffcode [p];
int32 ll = code << (8 -size);
int32 ul = (size < 8 ? ll | bitMask [24 + size]
: ll);
for (i = ll; i <= ul; i++)
{
htbl->numbits [i] = size;
htbl->value [i] = value;
}
}
}
}
/*****************************************************************************/
/*
* The following structure stores basic information about one component.
*/
struct JpegComponentInfo
{
/*
* These values are fixed over the whole image.
* They are read from the SOF marker.
*/
int16 componentId; /* identifier for this component (0..255) */
int16 componentIndex; /* its index in SOF or cPtr->compInfo[] */
/*
* Downsampling is not normally used in lossless JPEG, although
* it is permitted by the JPEG standard (DIS). We set all sampling
* factors to 1 in this program.
*/
int16 hSampFactor; /* horizontal sampling factor */
int16 vSampFactor; /* vertical sampling factor */
/*
* Huffman table selector (0..3). The value may vary
* between scans. It is read from the SOS marker.
*/
int16 dcTblNo;
};
/*
* One of the following structures is used to pass around the
* decompression information.
*/
struct DecompressInfo
{
/*
* Image width, height, and image data precision (bits/sample)
* These fields are set by ReadFileHeader or ReadScanHeader
*/
int32 imageWidth;
int32 imageHeight;
int32 dataPrecision;
/*
* compInfo[i] describes component that appears i'th in SOF
* numComponents is the # of color components in JPEG image.
*/
JpegComponentInfo *compInfo;
int16 numComponents;
/*
* *curCompInfo[i] describes component that appears i'th in SOS.
* compsInScan is the # of color components in current scan.
*/
JpegComponentInfo *curCompInfo[4];
int16 compsInScan;
/*
* MCUmembership[i] indexes the i'th component of MCU into the
* curCompInfo array.
*/
int16 MCUmembership[10];
/*
* ptrs to Huffman coding tables, or NULL if not defined
*/
HuffmanTable *dcHuffTblPtrs[4];
/*
* prediction selection value (PSV) and point transform parameter (Pt)
*/
int32 Ss;
int32 Pt;
/*
* In lossless JPEG, restart interval shall be an integer
* multiple of the number of MCU in a MCU row.
*/
int32 restartInterval;/* MCUs per restart interval, 0 = no restart */
int32 restartInRows; /*if > 0, MCU rows per restart interval; 0 = no restart*/
/*
* these fields are private data for the entropy decoder
*/
int32 restartRowsToGo; /* MCUs rows left in this restart interval */
int16 nextRestartNum; /* # of next RSTn marker (0..7) */
};
/*****************************************************************************/
// An MCU (minimum coding unit) is an array of samples.
typedef uint16 ComponentType; // the type of image components
typedef ComponentType *MCU; // MCU - array of samples
/*****************************************************************************/
class dng_lossless_decoder
{
private:
dng_stream *fStream; // Input data.
dng_spooler *fSpooler; // Output data.
bool fBug16; // Decode data with the "16-bit" bug.
dng_memory_data huffmanBuffer [4];
dng_memory_data compInfoBuffer;
DecompressInfo info;
dng_memory_data mcuBuffer1;
dng_memory_data mcuBuffer2;
dng_memory_data mcuBuffer3;
dng_memory_data mcuBuffer4;
MCU *mcuROW1;
MCU *mcuROW2;
uint64 getBuffer; // current bit-extraction buffer
int32 bitsLeft; // # of unused bits in it
#if qSupportHasselblad_3FR
bool fHasselblad3FR;
#endif
public:
dng_lossless_decoder (dng_stream *stream,
dng_spooler *spooler,
bool bug16);
void StartRead (uint32 &imageWidth,
uint32 &imageHeight,
uint32 &imageChannels);
void FinishRead ();
private:
uint8 GetJpegChar ()
{
return fStream->Get_uint8 ();
}
void UnGetJpegChar ()
{
fStream->SetReadPosition (fStream->Position () - 1);
}
uint16 Get2bytes ();
void SkipVariable ();
void GetDht ();
void GetDri ();
void GetApp0 ();
void GetSof (int32 code);
void GetSos ();
void GetSoi ();
int32 NextMarker ();
JpegMarker ProcessTables ();
void ReadFileHeader ();
int32 ReadScanHeader ();
void DecoderStructInit ();
void HuffDecoderInit ();
void ProcessRestart ();
int32 QuickPredict (int32 col,
int32 curComp,
MCU *curRowBuf,
MCU *prevRowBuf);
void FillBitBuffer (int32 nbits);
int32 show_bits8 ();
void flush_bits (int32 nbits);
int32 get_bits (int32 nbits);
int32 get_bit ();
int32 HuffDecode (HuffmanTable *htbl);
void HuffExtend (int32 &x, int32 s);
void PmPutRow (MCU *buf,
int32 numComp,
int32 numCol,
int32 row);
void DecodeFirstRow (MCU *curRowBuf);
void DecodeImage ();
// Hidden copy constructor and assignment operator.
dng_lossless_decoder (const dng_lossless_decoder &decoder);
dng_lossless_decoder & operator= (const dng_lossless_decoder &decoder);
};
/*****************************************************************************/
dng_lossless_decoder::dng_lossless_decoder (dng_stream *stream,
dng_spooler *spooler,
bool bug16)
: fStream (stream )
, fSpooler (spooler)
, fBug16 (bug16 )
, compInfoBuffer ()
, info ()
, mcuBuffer1 ()
, mcuBuffer2 ()
, mcuBuffer3 ()
, mcuBuffer4 ()
, mcuROW1 (NULL)
, mcuROW2 (NULL)
, getBuffer (0)
, bitsLeft (0)
#if qSupportHasselblad_3FR
, fHasselblad3FR (false)
#endif
{
memset (&info, 0, sizeof (info));
}
/*****************************************************************************/
uint16 dng_lossless_decoder::Get2bytes ()
{
uint16 a = GetJpegChar ();
return (a << 8) + GetJpegChar ();
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* SkipVariable --
*
* Skip over an unknown or uninteresting variable-length marker
*
* Results:
* None.
*
* Side effects:
* Bitstream is parsed over marker.
*
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::SkipVariable ()
{
uint32 length = Get2bytes () - 2;
fStream->Skip (length);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GetDht --
*
* Process a DHT marker
*
* Results:
* None
*
* Side effects:
* A huffman table is read.
* Exits on error.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::GetDht ()
{
int32 length = Get2bytes () - 2;
while (length > 0)
{
int32 index = GetJpegChar ();
if (index < 0 || index >= 4)
{
ThrowBadFormat ();
}
HuffmanTable *&htblptr = info.dcHuffTblPtrs [index];
if (htblptr == NULL)
{
huffmanBuffer [index] . Allocate (sizeof (HuffmanTable));
htblptr = (HuffmanTable *) huffmanBuffer [index] . Buffer ();
}
htblptr->bits [0] = 0;
int32 count = 0;
for (int32 i = 1; i <= 16; i++)
{
htblptr->bits [i] = GetJpegChar ();
count += htblptr->bits [i];
}
if (count > 256)
{
ThrowBadFormat ();
}
for (int32 j = 0; j < count; j++)
{
htblptr->huffval [j] = GetJpegChar ();
}
length -= 1 + 16 + count;
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GetDri --
*
* Process a DRI marker
*
* Results:
* None
*
* Side effects:
* Exits on error.
* Bitstream is parsed.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::GetDri ()
{
if (Get2bytes () != 4)
{
ThrowBadFormat ();
}
info.restartInterval = Get2bytes ();
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GetApp0 --
*
* Process an APP0 marker.
*
* Results:
* None
*
* Side effects:
* Bitstream is parsed
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::GetApp0 ()
{
SkipVariable ();
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GetSof --
*
* Process a SOFn marker
*
* Results:
* None.
*
* Side effects:
* Bitstream is parsed
* Exits on error
* info structure is filled in
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::GetSof (int32 /*code*/)
{
int32 length = Get2bytes ();
info.dataPrecision = GetJpegChar ();
info.imageHeight = Get2bytes ();
info.imageWidth = Get2bytes ();
info.numComponents = GetJpegChar ();
// We don't support files in which the image height is initially
// specified as 0 and is later redefined by DNL. As long as we
// have to check that, might as well have a general sanity check.
if ((info.imageHeight <= 0) ||
(info.imageWidth <= 0) ||
(info.numComponents <= 0))
{
ThrowBadFormat ();
}
// Lossless JPEG specifies data precision to be from 2 to 16 bits/sample.
const int32 MinPrecisionBits = 2;
const int32 MaxPrecisionBits = 16;
if ((info.dataPrecision < MinPrecisionBits) ||
(info.dataPrecision > MaxPrecisionBits))
{
ThrowBadFormat ();
}
// Check length of tag.
if (length != (info.numComponents * 3 + 8))
{
ThrowBadFormat ();
}
// Allocate per component info.
compInfoBuffer.Allocate (info.numComponents *
sizeof (JpegComponentInfo));
info.compInfo = (JpegComponentInfo *) compInfoBuffer.Buffer ();
// Read in the per compent info.
for (int32 ci = 0; ci < info.numComponents; ci++)
{
JpegComponentInfo *compptr = &info.compInfo [ci];
compptr->componentIndex = (int16) ci;
compptr->componentId = GetJpegChar ();
int32 c = GetJpegChar ();
compptr->hSampFactor = (int16) ((c >> 4) & 15);
compptr->vSampFactor = (int16) ((c ) & 15);
(void) GetJpegChar (); /* skip Tq */
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GetSos --
*
* Process a SOS marker
*
* Results:
* None.
*
* Side effects:
* Bitstream is parsed.
* Exits on error.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::GetSos ()
{
int32 length = Get2bytes ();
// Get the number of image components.
int32 n = GetJpegChar ();
info.compsInScan = (int16) n;
// Check length.
length -= 3;
if (length != (n * 2 + 3) || n < 1 || n > 4)
{
ThrowBadFormat ();
}
// Find index and huffman table for each component.
for (int32 i = 0; i < n; i++)
{
int32 cc = GetJpegChar ();
int32 c = GetJpegChar ();
int32 ci;
for (ci = 0; ci < info.numComponents; ci++)
{
if (cc == info.compInfo[ci].componentId)
{
break;
}
}
if (ci >= info.numComponents)
{
ThrowBadFormat ();
}
JpegComponentInfo *compptr = &info.compInfo [ci];
info.curCompInfo [i] = compptr;
compptr->dcTblNo = (int16) ((c >> 4) & 15);
}
// Get the PSV, skip Se, and get the point transform parameter.
info.Ss = GetJpegChar ();
(void) GetJpegChar ();
info.Pt = GetJpegChar () & 0x0F;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GetSoi --
*
* Process an SOI marker
*
* Results:
* None.
*
* Side effects:
* Bitstream is parsed.
* Exits on error.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::GetSoi ()
{
// Reset all parameters that are defined to be reset by SOI
info.restartInterval = 0;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* NextMarker --
*
* Find the next JPEG marker Note that the output might not
* be a valid marker code but it will never be 0 or FF
*
* Results:
* The marker found.
*
* Side effects:
* Bitstream is parsed.
*
*--------------------------------------------------------------
*/
int32 dng_lossless_decoder::NextMarker ()
{
int32 c;
do
{
// skip any non-FF bytes
do
{
c = GetJpegChar ();
}
while (c != 0xFF);
// skip any duplicate FFs, since extra FFs are legal
do
{
c = GetJpegChar();
}
while (c == 0xFF);
}
while (c == 0); // repeat if it was a stuffed FF/00
return c;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* ProcessTables --
*
* Scan and process JPEG markers that can appear in any order
* Return when an SOI, EOI, SOFn, or SOS is found
*
* Results:
* The marker found.
*
* Side effects:
* Bitstream is parsed.
*
*--------------------------------------------------------------
*/
JpegMarker dng_lossless_decoder::ProcessTables ()
{
while (true)
{
int32 c = NextMarker ();
switch (c)
{
case M_SOF0:
case M_SOF1:
case M_SOF2:
case M_SOF3:
case M_SOF5:
case M_SOF6:
case M_SOF7:
case M_JPG:
case M_SOF9:
case M_SOF10:
case M_SOF11:
case M_SOF13:
case M_SOF14:
case M_SOF15:
case M_SOI:
case M_EOI:
case M_SOS:
return (JpegMarker) c;
case M_DHT:
GetDht ();
break;
case M_DQT:
break;
case M_DRI:
GetDri ();
break;
case M_APP0:
GetApp0 ();
break;
case M_RST0: // these are all parameterless
case M_RST1:
case M_RST2:
case M_RST3:
case M_RST4:
case M_RST5:
case M_RST6:
case M_RST7:
case M_TEM:
break;
default: // must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn
SkipVariable ();
break;
}
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* ReadFileHeader --
*
* Initialize and read the stream header (everything through
* the SOF marker).
*
* Results:
* None
*
* Side effects:
* Exit on error.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::ReadFileHeader ()
{
// Demand an SOI marker at the start of the stream --- otherwise it's
// probably not a JPEG stream at all.
int32 c = GetJpegChar ();
int32 c2 = GetJpegChar ();
if ((c != 0xFF) || (c2 != M_SOI))
{
ThrowBadFormat ();
}
// OK, process SOI
GetSoi ();
// Process markers until SOF
c = ProcessTables ();
switch (c)
{
case M_SOF0:
case M_SOF1:
case M_SOF3:
GetSof (c);
break;
default:
ThrowBadFormat ();
break;
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* ReadScanHeader --
*
* Read the start of a scan (everything through the SOS marker).
*
* Results:
* 1 if find SOS, 0 if find EOI
*
* Side effects:
* Bitstream is parsed, may exit on errors.
*
*--------------------------------------------------------------
*/
int32 dng_lossless_decoder::ReadScanHeader ()
{
// Process markers until SOS or EOI
int32 c = ProcessTables ();
switch (c)
{
case M_SOS:
GetSos ();
return 1;
case M_EOI:
return 0;
default:
ThrowBadFormat ();
break;
}
return 0;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* DecoderStructInit --
*
* Initialize the rest of the fields in the decompression
* structure.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::DecoderStructInit ()
{
int32 ci;
#if qSupportCanon_sRAW
bool canon_sRAW = (info.numComponents == 3) &&
(info.compInfo [0].hSampFactor == 2) &&
(info.compInfo [1].hSampFactor == 1) &&
(info.compInfo [2].hSampFactor == 1) &&
(info.compInfo [0].vSampFactor == 1) &&
(info.compInfo [1].vSampFactor == 1) &&
(info.compInfo [2].vSampFactor == 1) &&
(info.dataPrecision == 15) &&
(info.Ss == 1) &&
((info.imageWidth & 1) == 0);
bool canon_sRAW2 = (info.numComponents == 3) &&
(info.compInfo [0].hSampFactor == 2) &&
(info.compInfo [1].hSampFactor == 1) &&
(info.compInfo [2].hSampFactor == 1) &&
(info.compInfo [0].vSampFactor == 2) &&
(info.compInfo [1].vSampFactor == 1) &&
(info.compInfo [2].vSampFactor == 1) &&
(info.dataPrecision == 15) &&
(info.Ss == 1) &&
((info.imageWidth & 1) == 0) &&
((info.imageHeight & 1) == 0);
if (!canon_sRAW && !canon_sRAW2)
#endif
{
// Check sampling factor validity.
for (ci = 0; ci < info.numComponents; ci++)
{
JpegComponentInfo *compPtr = &info.compInfo [ci];
if (compPtr->hSampFactor != 1 ||
compPtr->vSampFactor != 1)
{
ThrowBadFormat ();
}
}
}
// Prepare array describing MCU composition.
if (info.compsInScan > 4)
{
ThrowBadFormat ();
}
for (ci = 0; ci < info.compsInScan; ci++)
{
info.MCUmembership [ci] = (int16) ci;
}
// Initialize mucROW1 and mcuROW2 which buffer two rows of
// pixels for predictor calculation.
int32 mcuSize = info.compsInScan * sizeof (ComponentType);
mcuBuffer1.Allocate (info.imageWidth * sizeof (MCU));
mcuBuffer2.Allocate (info.imageWidth * sizeof (MCU));
mcuROW1 = (MCU *) mcuBuffer1.Buffer ();
mcuROW2 = (MCU *) mcuBuffer2.Buffer ();
mcuBuffer3.Allocate (info.imageWidth * mcuSize);
mcuBuffer4.Allocate (info.imageWidth * mcuSize);
mcuROW1 [0] = (ComponentType *) mcuBuffer3.Buffer ();
mcuROW2 [0] = (ComponentType *) mcuBuffer4.Buffer ();
for (int32 j = 1; j < info.imageWidth; j++)
{
mcuROW1 [j] = mcuROW1 [j - 1] + info.compsInScan;
mcuROW2 [j] = mcuROW2 [j - 1] + info.compsInScan;
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* HuffDecoderInit --
*
* Initialize for a Huffman-compressed scan.
* This is invoked after reading the SOS marker.
*
* Results:
* None
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::HuffDecoderInit ()
{
// Initialize bit parser state
getBuffer = 0;
bitsLeft = 0;
// Prepare Huffman tables.
for (int16 ci = 0; ci < info.compsInScan; ci++)
{
JpegComponentInfo *compptr = info.curCompInfo [ci];
// Make sure requested tables are present
if (compptr->dcTblNo < 0 || compptr->dcTblNo > 3)
{
ThrowBadFormat ();
}
if (info.dcHuffTblPtrs [compptr->dcTblNo] == NULL)
{
ThrowBadFormat ();
}
// Compute derived values for Huffman tables.
// We may do this more than once for same table, but it's not a
// big deal
FixHuffTbl (info.dcHuffTblPtrs [compptr->dcTblNo]);
}
// Initialize restart stuff
info.restartInRows = info.restartInterval / info.imageWidth;
info.restartRowsToGo = info.restartInRows;
info.nextRestartNum = 0;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* ProcessRestart --
*
* Check for a restart marker & resynchronize decoder.
*
* Results:
* None.
*
* Side effects:
* BitStream is parsed, bit buffer is reset, etc.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::ProcessRestart ()
{
// Throw away and unused odd bits in the bit buffer.
fStream->SetReadPosition (fStream->Position () - bitsLeft / 8);
bitsLeft = 0;
getBuffer = 0;
// Scan for next JPEG marker
int32 c;
do
{
// skip any non-FF bytes
do
{
c = GetJpegChar ();
}
while (c != 0xFF);
// skip any duplicate FFs
do
{
c = GetJpegChar ();
}
while (c == 0xFF);
}
while (c == 0); // repeat if it was a stuffed FF/00
// Verify correct restart code.
if (c != (M_RST0 + info.nextRestartNum))
{
ThrowBadFormat ();
}
// Update restart state.
info.restartRowsToGo = info.restartInRows;
info.nextRestartNum = (info.nextRestartNum + 1) & 7;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* QuickPredict --
*
* Calculate the predictor for sample curRowBuf[col][curComp].
* It does not handle the special cases at image edges, such
* as first row and first column of a scan. We put the special
* case checkings outside so that the computations in main
* loop can be simpler. This has enhenced the performance
* significantly.
*
* Results:
* predictor is passed out.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
inline int32 dng_lossless_decoder::QuickPredict (int32 col,
int32 curComp,
MCU *curRowBuf,
MCU *prevRowBuf)
{
int32 diag = prevRowBuf [col - 1] [curComp];
int32 upper = prevRowBuf [col ] [curComp];
int32 left = curRowBuf [col - 1] [curComp];
switch (info.Ss)
{
case 0:
return 0;
case 1:
return left;
case 2:
return upper;
case 3:
return diag;
case 4:
return left + upper - diag;
case 5:
return left + ((upper - diag) >> 1);
case 6:
return upper + ((left - diag) >> 1);
case 7:
return (left + upper) >> 1;
default:
{
ThrowBadFormat ();
return 0;
}
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* FillBitBuffer --
*
* Load up the bit buffer with at least nbits
* Process any stuffed bytes at this time.
*
* Results:
* None
*
* Side effects:
* The bitwise global variables are updated.
*
*--------------------------------------------------------------
*/
inline void dng_lossless_decoder::FillBitBuffer (int32 nbits)
{
const int32 kMinGetBits = sizeof (uint32) * 8 - 7;
#if qSupportHasselblad_3FR
if (fHasselblad3FR)
{
while (bitsLeft < kMinGetBits)
{
int32 c0 = GetJpegChar ();
int32 c1 = GetJpegChar ();
int32 c2 = GetJpegChar ();
int32 c3 = GetJpegChar ();
getBuffer = (getBuffer << 8) | c3;
getBuffer = (getBuffer << 8) | c2;
getBuffer = (getBuffer << 8) | c1;
getBuffer = (getBuffer << 8) | c0;
bitsLeft += 32;
}
return;
}
#endif
while (bitsLeft < kMinGetBits)
{
int32 c = GetJpegChar ();
// If it's 0xFF, check and discard stuffed zero byte
if (c == 0xFF)
{
int32 c2 = GetJpegChar ();
if (c2 != 0)
{
// Oops, it's actually a marker indicating end of
// compressed data. Better put it back for use later.
UnGetJpegChar ();
UnGetJpegChar ();
// There should be enough bits still left in the data
// segment; if so, just break out of the while loop.
if (bitsLeft >= nbits)
break;
// Uh-oh. Corrupted data: stuff zeroes into the data
// stream, since this sometimes occurs when we are on the
// last show_bits8 during decoding of the Huffman
// segment.
c = 0;
}
}
getBuffer = (getBuffer << 8) | c;
bitsLeft += 8;
}
}
/*****************************************************************************/
inline int32 dng_lossless_decoder::show_bits8 ()
{
if (bitsLeft < 8)
FillBitBuffer (8);
return (int32) ((getBuffer >> (bitsLeft - 8)) & 0xff);
}
/*****************************************************************************/
inline void dng_lossless_decoder::flush_bits (int32 nbits)
{
bitsLeft -= nbits;
}
/*****************************************************************************/
inline int32 dng_lossless_decoder::get_bits (int32 nbits)
{
if (bitsLeft < nbits)
FillBitBuffer (nbits);
return (int32) ((getBuffer >> (bitsLeft -= nbits)) & (0x0FFFF >> (16 - nbits)));
}
/*****************************************************************************/
inline int32 dng_lossless_decoder::get_bit ()
{
if (!bitsLeft)
FillBitBuffer (1);
return (int32) ((getBuffer >> (--bitsLeft)) & 1);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* HuffDecode --
*
* Taken from Figure F.16: extract next coded symbol from
* input stream. This should becode a macro.
*
* Results:
* Next coded symbol
*
* Side effects:
* Bitstream is parsed.
*
*--------------------------------------------------------------
*/
inline int32 dng_lossless_decoder::HuffDecode (HuffmanTable *htbl)
{
// If the huffman code is less than 8 bits, we can use the fast
// table lookup to get its value. It's more than 8 bits about
// 3-4% of the time.
int32 code = show_bits8 ();
if (htbl->numbits [code])
{
flush_bits (htbl->numbits [code]);
return htbl->value [code];
}
else
{
flush_bits (8);
int32 l = 8;
while (code > htbl->maxcode [l])
{
code = (code << 1) | get_bit ();
l++;
}
// With garbage input we may reach the sentinel value l = 17.
if (l > 16)
{
return 0; // fake a zero as the safest result
}
else
{
return htbl->huffval [htbl->valptr [l] +
((int32) (code - htbl->mincode [l]))];
}
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* HuffExtend --
*
* Code and table for Figure F.12: extend sign bit
*
* Results:
* The extended value.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
inline void dng_lossless_decoder::HuffExtend (int32 &x, int32 s)
{
if (x < (0x08000 >> (16 - s)))
{
x += (-1 << s) + 1;
}
}
/*****************************************************************************/
// Called from DecodeImage () to write one row.
void dng_lossless_decoder::PmPutRow (MCU *buf,
int32 numComp,
int32 numCol,
int32 /* row */)
{
uint16 *sPtr = &buf [0] [0];
uint32 pixels = numCol * numComp;
fSpooler->Spool (sPtr, pixels * sizeof (uint16));
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* DecodeFirstRow --
*
* Decode the first raster line of samples at the start of
* the scan and at the beginning of each restart interval.
* This includes modifying the component value so the real
* value, not the difference is returned.
*
* Results:
* None.
*
* Side effects:
* Bitstream is parsed.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::DecodeFirstRow (MCU *curRowBuf)
{
int32 compsInScan = info.compsInScan;
// Process the first column in the row.
for (int32 curComp = 0; curComp < compsInScan; curComp++)
{
int32 ci = info.MCUmembership [curComp];
JpegComponentInfo *compptr = info.curCompInfo [ci];
HuffmanTable *dctbl = info.dcHuffTblPtrs [compptr->dcTblNo];
// Section F.2.2.1: decode the difference
int32 d = 0;
int32 s = HuffDecode (dctbl);
if (s)
{
if (s == 16 && !fBug16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
// Add the predictor to the difference.
int32 Pr = info.dataPrecision;
int32 Pt = info.Pt;
curRowBuf [0] [curComp] = (ComponentType) (d + (1 << (Pr-Pt-1)));
}
// Process the rest of the row.
int32 numCOL = info.imageWidth;
for (int32 col = 1; col < numCOL; col++)
{
for (int32 curComp = 0; curComp < compsInScan; curComp++)
{
int32 ci = info.MCUmembership [curComp];
JpegComponentInfo *compptr = info.curCompInfo [ci];
HuffmanTable *dctbl = info.dcHuffTblPtrs [compptr->dcTblNo];
// Section F.2.2.1: decode the difference
int32 d = 0;
int32 s = HuffDecode (dctbl);
if (s)
{
if (s == 16 && !fBug16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
// Add the predictor to the difference.
curRowBuf [col] [curComp] = (ComponentType) (d + curRowBuf [col-1] [curComp]);
}
}
// Update the restart counter
if (info.restartInRows)
{
info.restartRowsToGo--;
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* DecodeImage --
*
* Decode the input stream. This includes modifying
* the component value so the real value, not the
* difference is returned.
*
* Results:
* None.
*
* Side effects:
* Bitstream is parsed.
*
*--------------------------------------------------------------
*/
void dng_lossless_decoder::DecodeImage ()
{
#define swap(type,a,b) {type c; c=(a); (a)=(b); (b)=c;}
int32 numCOL = info.imageWidth;
int32 numROW = info.imageHeight;
int32 compsInScan = info.compsInScan;
// Precompute the decoding table for each table.
HuffmanTable *ht [4];
for (int32 curComp = 0; curComp < compsInScan; curComp++)
{
int32 ci = info.MCUmembership [curComp];
JpegComponentInfo *compptr = info.curCompInfo [ci];
ht [curComp] = info.dcHuffTblPtrs [compptr->dcTblNo];
}
MCU *prevRowBuf = mcuROW1;
MCU *curRowBuf = mcuROW2;
#if qSupportCanon_sRAW
// Canon sRAW support
if (info.compInfo [0].hSampFactor == 2 &&
info.compInfo [0].vSampFactor == 1)
{
for (int32 row = 0; row < numROW; row++)
{
// Initialize predictors.
int32 p0;
int32 p1;
int32 p2;
if (row == 0)
{
p0 = 1 << 14;
p1 = 1 << 14;
p2 = 1 << 14;
}
else
{
p0 = prevRowBuf [0] [0];
p1 = prevRowBuf [0] [1];
p2 = prevRowBuf [0] [2];
}
for (int32 col = 0; col < numCOL; col += 2)
{
// Read first luminance component.
{
int32 d = 0;
int32 s = HuffDecode (ht [0]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p0 += d;
curRowBuf [col] [0] = (ComponentType) p0;
}
// Read second luminance component.
{
int32 d = 0;
int32 s = HuffDecode (ht [0]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p0 += d;
curRowBuf [col + 1] [0] = (ComponentType) p0;
}
// Read first chroma component.
{
int32 d = 0;
int32 s = HuffDecode (ht [1]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p1 += d;
curRowBuf [col ] [1] = (ComponentType) p1;
curRowBuf [col + 1] [1] = (ComponentType) p1;
}
// Read second chroma component.
{
int32 d = 0;
int32 s = HuffDecode (ht [2]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p2 += d;
curRowBuf [col ] [2] = (ComponentType) p2;
curRowBuf [col + 1] [2] = (ComponentType) p2;
}
}
PmPutRow (curRowBuf, compsInScan, numCOL, row);
swap (MCU *, prevRowBuf, curRowBuf);
}
return;
}
if (info.compInfo [0].hSampFactor == 2 &&
info.compInfo [0].vSampFactor == 2)
{
for (int32 row = 0; row < numROW; row += 2)
{
// Initialize predictors.
int32 p0;
int32 p1;
int32 p2;
if (row == 0)
{
p0 = 1 << 14;
p1 = 1 << 14;
p2 = 1 << 14;
}
else
{
p0 = prevRowBuf [0] [0];
p1 = prevRowBuf [0] [1];
p2 = prevRowBuf [0] [2];
}
for (int32 col = 0; col < numCOL; col += 2)
{
// Read first luminance component.
{
int32 d = 0;
int32 s = HuffDecode (ht [0]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p0 += d;
prevRowBuf [col] [0] = (ComponentType) p0;
}
// Read second luminance component.
{
int32 d = 0;
int32 s = HuffDecode (ht [0]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p0 += d;
prevRowBuf [col + 1] [0] = (ComponentType) p0;
}
// Read third luminance component.
{
int32 d = 0;
int32 s = HuffDecode (ht [0]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p0 += d;
curRowBuf [col] [0] = (ComponentType) p0;
}
// Read fourth luminance component.
{
int32 d = 0;
int32 s = HuffDecode (ht [0]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p0 += d;
curRowBuf [col + 1] [0] = (ComponentType) p0;
}
// Read first chroma component.
{
int32 d = 0;
int32 s = HuffDecode (ht [1]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p1 += d;
prevRowBuf [col ] [1] = (ComponentType) p1;
prevRowBuf [col + 1] [1] = (ComponentType) p1;
curRowBuf [col ] [1] = (ComponentType) p1;
curRowBuf [col + 1] [1] = (ComponentType) p1;
}
// Read second chroma component.
{
int32 d = 0;
int32 s = HuffDecode (ht [2]);
if (s)
{
if (s == 16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
p2 += d;
prevRowBuf [col ] [2] = (ComponentType) p2;
prevRowBuf [col + 1] [2] = (ComponentType) p2;
curRowBuf [col ] [2] = (ComponentType) p2;
curRowBuf [col + 1] [2] = (ComponentType) p2;
}
}
PmPutRow (prevRowBuf, compsInScan, numCOL, row);
PmPutRow (curRowBuf, compsInScan, numCOL, row);
}
return;
}
#endif
#if qSupportHasselblad_3FR
if (info.Ss == 8)
{
fHasselblad3FR = true;
for (int32 row = 0; row < numROW; row++)
{
int32 p0 = 32768;
int32 p1 = 32768;
for (int32 col = 0; col < numCOL; col += 2)
{
int32 s0 = HuffDecode (ht [0]);
int32 s1 = HuffDecode (ht [0]);
if (s0)
{
int32 d = get_bits (s0);
HuffExtend (d, s0);
p0 += d;
}
if (s1)
{
int32 d = get_bits (s1);
HuffExtend (d, s1);
p1 += d;
}
curRowBuf [col ] [0] = (ComponentType) p0;
curRowBuf [col + 1] [0] = (ComponentType) p1;
}
PmPutRow (curRowBuf, compsInScan, numCOL, row);
}
return;
}
#endif
// Decode the first row of image. Output the row and
// turn this row into a previous row for later predictor
// calculation.
DecodeFirstRow (mcuROW1);
PmPutRow (mcuROW1, compsInScan, numCOL, 0);
// Process each row.
for (int32 row = 1; row < numROW; row++)
{
// Account for restart interval, process restart marker if needed.
if (info.restartInRows)
{
if (info.restartRowsToGo == 0)
{
ProcessRestart ();
// Reset predictors at restart.
DecodeFirstRow (curRowBuf);
PmPutRow (curRowBuf, compsInScan, numCOL, row);
swap (MCU *, prevRowBuf, curRowBuf);
continue;
}
info.restartRowsToGo--;
}
// The upper neighbors are predictors for the first column.
for (int32 curComp = 0; curComp < compsInScan; curComp++)
{
// Section F.2.2.1: decode the difference
int32 d = 0;
int32 s = HuffDecode (ht [curComp]);
if (s)
{
if (s == 16 && !fBug16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
// First column of row above is predictor for first column.
curRowBuf [0] [curComp] = (ComponentType) (d + prevRowBuf [0] [curComp]);
}
// For the rest of the column on this row, predictor
// calculations are based on PSV.
if (compsInScan == 2 && info.Ss == 1)
{
// This is the combination used by both the Canon and Kodak raw formats.
// Unrolling the general case logic results in a significant speed increase.
uint16 *dPtr = &curRowBuf [1] [0];
int32 prev0 = dPtr [-2];
int32 prev1 = dPtr [-1];
for (int32 col = 1; col < numCOL; col++)
{
int32 s = HuffDecode (ht [0]);
if (s)
{
int32 d;
if (s == 16 && !fBug16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
prev0 += d;
}
s = HuffDecode (ht [1]);
if (s)
{
int32 d;
if (s == 16 && !fBug16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
prev1 += d;
}
dPtr [0] = (uint16) prev0;
dPtr [1] = (uint16) prev1;
dPtr += 2;
}
}
else
{
for (int32 col = 1; col < numCOL; col++)
{
for (int32 curComp = 0; curComp < compsInScan; curComp++)
{
// Section F.2.2.1: decode the difference
int32 d = 0;
int32 s = HuffDecode (ht [curComp]);
if (s)
{
if (s == 16 && !fBug16)
{
d = -32768;
}
else
{
d = get_bits (s);
HuffExtend (d, s);
}
}
// Predict the pixel value.
int32 predictor = QuickPredict (col,
curComp,
curRowBuf,
prevRowBuf);
// Save the difference.
curRowBuf [col] [curComp] = (ComponentType) (d + predictor);
}
}
}
PmPutRow (curRowBuf, compsInScan, numCOL, row);
swap (MCU *, prevRowBuf, curRowBuf);
}
#undef swap
}
/*****************************************************************************/
void dng_lossless_decoder::StartRead (uint32 &imageWidth,
uint32 &imageHeight,
uint32 &imageChannels)
{
ReadFileHeader ();
ReadScanHeader ();
DecoderStructInit ();
HuffDecoderInit ();
imageWidth = info.imageWidth;
imageHeight = info.imageHeight;
imageChannels = info.compsInScan;
}
/*****************************************************************************/
void dng_lossless_decoder::FinishRead ()
{
DecodeImage ();
}
/*****************************************************************************/
void DecodeLosslessJPEG (dng_stream &stream,
dng_spooler &spooler,
uint32 minDecodedSize,
uint32 maxDecodedSize,
bool bug16)
{
dng_lossless_decoder decoder (&stream,
&spooler,
bug16);
uint32 imageWidth;
uint32 imageHeight;
uint32 imageChannels;
decoder.StartRead (imageWidth,
imageHeight,
imageChannels);
uint32 decodedSize = imageWidth *
imageHeight *
imageChannels *
sizeof (uint16);
if (decodedSize < minDecodedSize ||
decodedSize > maxDecodedSize)
{
ThrowBadFormat ();
}
decoder.FinishRead ();
}
/*****************************************************************************/
class dng_lossless_encoder
{
private:
const uint16 *fSrcData;
uint32 fSrcRows;
uint32 fSrcCols;
uint32 fSrcChannels;
uint32 fSrcBitDepth;
int32 fSrcRowStep;
int32 fSrcColStep;
dng_stream &fStream;
HuffmanTable huffTable [4];
uint32 freqCount [4] [257];
// Current bit-accumulation buffer
int32 huffPutBuffer;
int32 huffPutBits;
// Lookup table for number of bits in an 8 bit value.
int numBitsTable [256];
public:
dng_lossless_encoder (const uint16 *srcData,
uint32 srcRows,
uint32 srcCols,
uint32 srcChannels,
uint32 srcBitDepth,
int32 srcRowStep,
int32 srcColStep,
dng_stream &stream);
void Encode ();
private:
void EmitByte (uint8 value);
void EmitBits (int code, int size);
void FlushBits ();
void CountOneDiff (int diff, uint32 *countTable);
void EncodeOneDiff (int diff, HuffmanTable *dctbl);
void FreqCountSet ();
void HuffEncode ();
void GenHuffCoding (HuffmanTable *htbl, uint32 *freq);
void HuffOptimize ();
void EmitMarker (JpegMarker mark);
void Emit2bytes (int value);
void EmitDht (int index);
void EmitSof (JpegMarker code);
void EmitSos ();
void WriteFileHeader ();
void WriteScanHeader ();
void WriteFileTrailer ();
};
/*****************************************************************************/
dng_lossless_encoder::dng_lossless_encoder (const uint16 *srcData,
uint32 srcRows,
uint32 srcCols,
uint32 srcChannels,
uint32 srcBitDepth,
int32 srcRowStep,
int32 srcColStep,
dng_stream &stream)
: fSrcData (srcData )
, fSrcRows (srcRows )
, fSrcCols (srcCols )
, fSrcChannels (srcChannels)
, fSrcBitDepth (srcBitDepth)
, fSrcRowStep (srcRowStep )
, fSrcColStep (srcColStep )
, fStream (stream )
, huffPutBuffer (0)
, huffPutBits (0)
{
// Initialize number of bits lookup table.
numBitsTable [0] = 0;
for (int i = 1; i < 256; i++)
{
int temp = i;
int nbits = 1;
while (temp >>= 1)
{
nbits++;
}
numBitsTable [i] = nbits;
}
}
/*****************************************************************************/
inline void dng_lossless_encoder::EmitByte (uint8 value)
{
fStream.Put_uint8 (value);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* EmitBits --
*
* Code for outputting bits to the file
*
* Only the right 24 bits of huffPutBuffer are used; the valid
* bits are left-justified in this part. At most 16 bits can be
* passed to EmitBits in one call, and we never retain more than 7
* bits in huffPutBuffer between calls, so 24 bits are
* sufficient.
*
* Results:
* None.
*
* Side effects:
* huffPutBuffer and huffPutBits are updated.
*
*--------------------------------------------------------------
*/
inline void dng_lossless_encoder::EmitBits (int code, int size)
{
DNG_ASSERT (size != 0, "Bad Huffman table entry");
int putBits = size;
int putBuffer = code;
putBits += huffPutBits;
putBuffer <<= 24 - putBits;
putBuffer |= huffPutBuffer;
while (putBits >= 8)
{
uint8 c = putBuffer >> 16;
// Output whole bytes we've accumulated with byte stuffing
EmitByte (c);
if (c == 0xFF)
{
EmitByte (0);
}
putBuffer <<= 8;
putBits -= 8;
}
huffPutBuffer = putBuffer;
huffPutBits = putBits;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* FlushBits --
*
* Flush any remaining bits in the bit buffer. Used before emitting
* a marker.
*
* Results:
* None.
*
* Side effects:
* huffPutBuffer and huffPutBits are reset
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::FlushBits ()
{
// The first call forces output of any partial bytes.
EmitBits (0x007F, 7);
// We can then zero the buffer.
huffPutBuffer = 0;
huffPutBits = 0;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* CountOneDiff --
*
* Count the difference value in countTable.
*
* Results:
* diff is counted in countTable.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
inline void dng_lossless_encoder::CountOneDiff (int diff, uint32 *countTable)
{
// Encode the DC coefficient difference per section F.1.2.1
int temp = diff;
if (temp < 0)
{
temp = -temp;
}
// Find the number of bits needed for the magnitude of the coefficient
int nbits = temp >= 256 ? numBitsTable [temp >> 8 ] + 8
: numBitsTable [temp & 0xFF];
// Update count for this bit length
countTable [nbits] ++;
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* EncodeOneDiff --
*
* Encode a single difference value.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
inline void dng_lossless_encoder::EncodeOneDiff (int diff, HuffmanTable *dctbl)
{
// Encode the DC coefficient difference per section F.1.2.1
int temp = diff;
int temp2 = diff;
if (temp < 0)
{
temp = -temp;
// For a negative input, want temp2 = bitwise complement of
// abs (input). This code assumes we are on a two's complement
// machine.
temp2--;
}
// Find the number of bits needed for the magnitude of the coefficient
int nbits = temp >= 256 ? numBitsTable [temp >> 8 ] + 8
: numBitsTable [temp & 0xFF];
// Emit the Huffman-coded symbol for the number of bits
EmitBits (dctbl->ehufco [nbits],
dctbl->ehufsi [nbits]);
// Emit that number of bits of the value, if positive,
// or the complement of its magnitude, if negative.
// If the number of bits is 16, there is only one possible difference
// value (-32786), so the lossless JPEG spec says not to output anything
- // in that case. So we only need to output the diference value if
+ // in that case. So we only need to output the difference value if
// the number of bits is between 1 and 15.
if (nbits & 15)
{
EmitBits (temp2 & (0x0FFFF >> (16 - nbits)),
nbits);
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* FreqCountSet --
*
* Count the times each category symbol occurs in this image.
*
* Results:
* None.
*
* Side effects:
* The freqCount has counted all category
* symbols appeared in the image.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::FreqCountSet ()
{
memset (freqCount, 0, sizeof (freqCount));
DNG_ASSERT ((int32)fSrcRows >= 0, "dng_lossless_encoder::FreqCountSet: fSrcRpws too large.");
for (int32 row = 0; row < (int32)fSrcRows; row++)
{
const uint16 *sPtr = fSrcData + row * fSrcRowStep;
// Initialize predictors for this row.
int32 predictor [4];
for (int32 channel = 0; channel < (int32)fSrcChannels; channel++)
{
if (row == 0)
predictor [channel] = 1 << (fSrcBitDepth - 1);
else
predictor [channel] = sPtr [channel - fSrcRowStep];
}
// Unroll most common case of two channels
if (fSrcChannels == 2)
{
int32 pred0 = predictor [0];
int32 pred1 = predictor [1];
uint32 srcCols = fSrcCols;
int32 srcColStep = fSrcColStep;
for (uint32 col = 0; col < srcCols; col++)
{
int32 pixel0 = sPtr [0];
int32 pixel1 = sPtr [1];
int16 diff0 = (int16) (pixel0 - pred0);
int16 diff1 = (int16) (pixel1 - pred1);
CountOneDiff (diff0, freqCount [0]);
CountOneDiff (diff1, freqCount [1]);
pred0 = pixel0;
pred1 = pixel1;
sPtr += srcColStep;
}
}
// General case.
else
{
for (uint32 col = 0; col < fSrcCols; col++)
{
for (uint32 channel = 0; channel < fSrcChannels; channel++)
{
int32 pixel = sPtr [channel];
int16 diff = (int16) (pixel - predictor [channel]);
CountOneDiff (diff, freqCount [channel]);
predictor [channel] = pixel;
}
sPtr += fSrcColStep;
}
}
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* HuffEncode --
*
* Encode and output Huffman-compressed image data.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::HuffEncode ()
{
DNG_ASSERT ((int32)fSrcRows >= 0, "dng_lossless_encoder::HuffEncode: fSrcRows too large.");
for (int32 row = 0; row < (int32)fSrcRows; row++)
{
const uint16 *sPtr = fSrcData + row * fSrcRowStep;
// Initialize predictors for this row.
int32 predictor [4];
for (int32 channel = 0; channel < (int32)fSrcChannels; channel++)
{
if (row == 0)
predictor [channel] = 1 << (fSrcBitDepth - 1);
else
predictor [channel] = sPtr [channel - fSrcRowStep];
}
// Unroll most common case of two channels
if (fSrcChannels == 2)
{
int32 pred0 = predictor [0];
int32 pred1 = predictor [1];
uint32 srcCols = fSrcCols;
int32 srcColStep = fSrcColStep;
for (uint32 col = 0; col < srcCols; col++)
{
int32 pixel0 = sPtr [0];
int32 pixel1 = sPtr [1];
int16 diff0 = (int16) (pixel0 - pred0);
int16 diff1 = (int16) (pixel1 - pred1);
EncodeOneDiff (diff0, &huffTable [0]);
EncodeOneDiff (diff1, &huffTable [1]);
pred0 = pixel0;
pred1 = pixel1;
sPtr += srcColStep;
}
}
// General case.
else
{
for (uint32 col = 0; col < fSrcCols; col++)
{
for (uint32 channel = 0; channel < fSrcChannels; channel++)
{
int32 pixel = sPtr [channel];
int16 diff = (int16) (pixel - predictor [channel]);
EncodeOneDiff (diff, &huffTable [channel]);
predictor [channel] = pixel;
}
sPtr += fSrcColStep;
}
}
}
FlushBits ();
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* GenHuffCoding --
*
* Generate the optimal coding for the given counts.
* This algorithm is explained in section K.2 of the
* JPEG standard.
*
* Results:
* htbl->bits and htbl->huffval are constructed.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::GenHuffCoding (HuffmanTable *htbl, uint32 *freq)
{
int i;
int j;
const int MAX_CLEN = 32; // assumed maximum initial code length
uint8 bits [MAX_CLEN + 1]; // bits [k] = # of symbols with code length k
short codesize [257]; // codesize [k] = code length of symbol k
short others [257]; // next symbol in current branch of tree
memset (bits , 0, sizeof (bits ));
memset (codesize, 0, sizeof (codesize));
for (i = 0; i < 257; i++)
others [i] = -1; // init links to empty
// Including the pseudo-symbol 256 in the Huffman procedure guarantees
// that no real symbol is given code-value of all ones, because 256
// will be placed in the largest codeword category.
freq [256] = 1; // make sure there is a nonzero count
// Huffman's basic algorithm to assign optimal code lengths to symbols
while (true)
{
// Find the smallest nonzero frequency, set c1 = its symbol.
// In case of ties, take the larger symbol number.
int c1 = -1;
uint32 v = 0xFFFFFFFF;
for (i = 0; i <= 256; i++)
{
if (freq [i] && freq [i] <= v)
{
v = freq [i];
c1 = i;
}
}
// Find the next smallest nonzero frequency, set c2 = its symbol.
// In case of ties, take the larger symbol number.
int c2 = -1;
v = 0xFFFFFFFF;
for (i = 0; i <= 256; i++)
{
if (freq [i] && freq [i] <= v && i != c1)
{
v = freq [i];
c2 = i;
}
}
// Done if we've merged everything into one frequency.
if (c2 < 0)
break;
// Else merge the two counts/trees.
freq [c1] += freq [c2];
freq [c2] = 0;
// Increment the codesize of everything in c1's tree branch.
codesize [c1] ++;
while (others [c1] >= 0)
{
c1 = others [c1];
codesize [c1] ++;
}
// chain c2 onto c1's tree branch
others [c1] = c2;
// Increment the codesize of everything in c2's tree branch.
codesize [c2] ++;
while (others [c2] >= 0)
{
c2 = others [c2];
codesize [c2] ++;
}
}
// Now count the number of symbols of each code length.
for (i = 0; i <= 256; i++)
{
if (codesize [i])
{
// The JPEG standard seems to think that this can't happen,
// but I'm paranoid...
if (codesize [i] > MAX_CLEN)
{
DNG_REPORT ("Huffman code size table overflow");
ThrowProgramError ();
}
bits [codesize [i]]++;
}
}
// JPEG doesn't allow symbols with code lengths over 16 bits, so if the pure
// Huffman procedure assigned any such lengths, we must adjust the coding.
// Here is what the JPEG spec says about how this next bit works:
// Since symbols are paired for the longest Huffman code, the symbols are
// removed from this length category two at a time. The prefix for the pair
// (which is one bit shorter) is allocated to one of the pair; then,
// skipping the BITS entry for that prefix length, a code word from the next
// shortest nonzero BITS entry is converted into a prefix for two code words
// one bit longer.
for (i = MAX_CLEN; i > 16; i--)
{
while (bits [i] > 0)
{
// Kludge: I have never been able to test this logic, and there
// are comments on the web that this encoder has bugs with 16-bit
// data, so just throw an error if we get here and revert to a
// default table. - tknoll 12/1/03.
DNG_REPORT ("Info: Optimal huffman table bigger than 16 bits");
ThrowProgramError ();
// Original logic:
j = i - 2; // find length of new prefix to be used
while (bits [j] == 0)
j--;
bits [i ] -= 2; // remove two symbols
bits [i - 1] ++; // one goes in this length
bits [j + 1] += 2; // two new symbols in this length
bits [j ] --; // symbol of this length is now a prefix
}
}
// Remove the count for the pseudo-symbol 256 from
// the largest codelength.
while (bits [i] == 0) // find largest codelength still in use
i--;
bits [i] --;
// Return final symbol counts (only for lengths 0..16).
memcpy (htbl->bits, bits, sizeof (htbl->bits));
// Return a list of the symbols sorted by code length.
// It's not real clear to me why we don't need to consider the codelength
// changes made above, but the JPEG spec seems to think this works.
int p = 0;
for (i = 1; i <= MAX_CLEN; i++)
{
for (j = 0; j <= 255; j++)
{
if (codesize [j] == i)
{
htbl->huffval [p] = (uint8) j;
p++;
}
}
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* HuffOptimize --
*
* Find the best coding parameters for a Huffman-coded scan.
* When called, the scan data has already been converted to
* a sequence of MCU groups of source image samples, which
* are stored in a "big" array, mcuTable.
*
* It counts the times each category symbol occurs. Based on
* this counting, optimal Huffman tables are built. Then it
* uses this optimal Huffman table and counting table to find
* the best PSV.
*
* Results:
- * Optimal Huffman tables are retured in cPtr->dcHuffTblPtrs[tbl].
- * Best PSV is retured in cPtr->Ss.
+ * Optimal Huffman tables are returned in cPtr->dcHuffTblPtrs[tbl].
+ * Best PSV is returned in cPtr->Ss.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::HuffOptimize ()
{
// Collect the frequency counts.
FreqCountSet ();
// Generate Huffman encoding tables.
for (uint32 channel = 0; channel < fSrcChannels; channel++)
{
try
{
GenHuffCoding (&huffTable [channel], freqCount [channel]);
}
catch (...)
{
DNG_REPORT ("Info: Reverting to default huffman table");
for (uint32 j = 0; j <= 256; j++)
{
freqCount [channel] [j] = (j <= 16 ? 1 : 0);
}
GenHuffCoding (&huffTable [channel], freqCount [channel]);
}
FixHuffTbl (&huffTable [channel]);
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* EmitMarker --
*
* Emit a marker code into the output stream.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::EmitMarker (JpegMarker mark)
{
EmitByte (0xFF);
EmitByte (mark);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* Emit2bytes --
*
* Emit a 2-byte integer; these are always MSB first in JPEG
* files
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::Emit2bytes (int value)
{
EmitByte ((value >> 8) & 0xFF);
EmitByte (value & 0xFF);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* EmitDht --
*
- * Emit a DHT marker, follwed by the huffman data.
+ * Emit a DHT marker, followed by the huffman data.
*
* Results:
* None
*
* Side effects:
* None
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::EmitDht (int index)
{
int i;
HuffmanTable *htbl = &huffTable [index];
EmitMarker (M_DHT);
int length = 0;
for (i = 1; i <= 16; i++)
length += htbl->bits [i];
Emit2bytes (length + 2 + 1 + 16);
EmitByte (index);
for (i = 1; i <= 16; i++)
EmitByte (htbl->bits [i]);
for (i = 0; i < length; i++)
EmitByte (htbl->huffval [i]);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* EmitSof --
*
* Emit a SOF marker plus data.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::EmitSof (JpegMarker code)
{
EmitMarker (code);
Emit2bytes (3 * fSrcChannels + 2 + 5 + 1); // length
EmitByte ((uint8) fSrcBitDepth);
Emit2bytes (fSrcRows);
Emit2bytes (fSrcCols);
EmitByte ((uint8) fSrcChannels);
for (uint32 i = 0; i < fSrcChannels; i++)
{
EmitByte ((uint8) i);
EmitByte ((uint8) ((1 << 4) + 1)); // Not subsampled.
EmitByte (0); // Tq shall be 0 for lossless.
}
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* EmitSos --
*
* Emit a SOS marker plus data.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::EmitSos ()
{
EmitMarker (M_SOS);
Emit2bytes (2 * fSrcChannels + 2 + 1 + 3); // length
EmitByte ((uint8) fSrcChannels); // Ns
for (uint32 i = 0; i < fSrcChannels; i++)
{
// Cs,Td,Ta
EmitByte ((uint8) i);
EmitByte ((uint8) (i << 4));
}
EmitByte (1); // PSV - hardcoded - tknoll
EmitByte (0); // Spectral selection end - Se
EmitByte (0); // The point transform parameter
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* WriteFileHeader --
*
* Write the file header.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::WriteFileHeader ()
{
EmitMarker (M_SOI); // first the SOI
EmitSof (M_SOF3);
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* WriteScanHeader --
*
* Write the start of a scan (everything through the SOS marker).
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::WriteScanHeader ()
{
// Emit Huffman tables.
for (uint32 i = 0; i < fSrcChannels; i++)
{
EmitDht (i);
}
EmitSos ();
}
/*****************************************************************************/
/*
*--------------------------------------------------------------
*
* WriteFileTrailer --
*
* Write the End of image marker at the end of a JPEG file.
*
* Results:
* None.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
void dng_lossless_encoder::WriteFileTrailer ()
{
EmitMarker (M_EOI);
}
/*****************************************************************************/
void dng_lossless_encoder::Encode ()
{
DNG_ASSERT (fSrcChannels <= 4, "Too many components in scan");
// Count the times each difference category occurs.
// Construct the optimal Huffman table.
HuffOptimize ();
// Write the frame and scan headers.
WriteFileHeader ();
WriteScanHeader ();
// Encode the image.
HuffEncode ();
// Clean up everything.
WriteFileTrailer ();
}
/*****************************************************************************/
void EncodeLosslessJPEG (const uint16 *srcData,
uint32 srcRows,
uint32 srcCols,
uint32 srcChannels,
uint32 srcBitDepth,
int32 srcRowStep,
int32 srcColStep,
dng_stream &stream)
{
dng_lossless_encoder encoder (srcData,
srcRows,
srcCols,
srcChannels,
srcBitDepth,
srcRowStep,
srcColStep,
stream);
encoder.Encode ();
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_mosaic_info.h b/core/libs/dngwriter/extra/dng_sdk/dng_mosaic_info.h
index b1e443c676..b57a9da658 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_mosaic_info.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_mosaic_info.h
@@ -1,200 +1,200 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_mosaic_info.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Support for descriptive information about color filter array patterns.
*/
/*****************************************************************************/
#ifndef __dng_mosaic_info__
#define __dng_mosaic_info__
/*****************************************************************************/
#include "dng_classes.h"
#include "dng_rect.h"
#include "dng_sdk_limits.h"
#include "dng_types.h"
/*****************************************************************************/
/// \brief Support for describing color filter array patterns and manipulating mosaic sample data.
///
/// See CFAPattern tag in \ref spec_tiff_ep "TIFF/EP specification" and CFAPlaneColor, CFALayout, and BayerGreenSplit
/// tags in the \ref spec_dng "DNG 1.1.0 specification".
class dng_mosaic_info
{
public:
/// Size of fCFAPattern.
dng_point fCFAPatternSize;
/// CFA pattern from CFAPattern tag in the \ref spec_tiff_ep "TIFF/EP specification."
uint8 fCFAPattern [kMaxCFAPattern] [kMaxCFAPattern];
/// Number of color planes in DNG input.
uint32 fColorPlanes;
uint8 fCFAPlaneColor [kMaxColorPlanes];
/// Value of CFALayout tag in the \ref spec_dng "DNG 1.3 specification."
/// CFALayout describes the spatial layout of the CFA. The currently defined values are:
/// - 1 = Rectangular (or square) layout.
/// - 2 = Staggered layout A: even columns are offset down by 1/2 row.
/// - 3 = Staggered layout B: even columns are offset up by 1/2 row.
/// - 4 = Staggered layout C: even rows are offset right by 1/2 column.
/// - 5 = Staggered layout D: even rows are offset left by 1/2 column.
/// - 6 = Staggered layout E: even rows are offset up by 1/2 row, even columns are offset left by 1/2 column.
/// - 7 = Staggered layout F: even rows are offset up by 1/2 row, even columns are offset right by 1/2 column.
/// - 8 = Staggered layout G: even rows are offset down by 1/2 row, even columns are offset left by 1/2 column.
/// - 9 = Staggered layout H: even rows are offset down by 1/2 row, even columns are offset right by 1/2 column.
uint32 fCFALayout;
/// Value of BayerGreeSplit tag in DNG file.
/// BayerGreenSplit only applies to CFA images using a Bayer pattern filter array. This tag
/// specifies, in arbitrary units, how closely the values of the green pixels in the blue/green rows
/// track the values of the green pixels in the red/green rows.
///
/// A value of zero means the two kinds of green pixels track closely, while a non-zero value
/// means they sometimes diverge. The useful range for this tag is from 0 (no divergence) to about
/// 5000 (large divergence).
uint32 fBayerGreenSplit;
protected:
dng_point fSrcSize;
dng_point fCroppedSize;
real64 fAspectRatio;
public:
dng_mosaic_info ();
virtual ~dng_mosaic_info ();
virtual void Parse (dng_host &host,
dng_stream &stream,
dng_info &info);
virtual void PostParse (dng_host &host,
dng_negative &negative);
/// Returns whether the RAW data in this DNG file from a color filter array (mosaiced) source.
/// \retval true if this DNG file is from a color filter array (mosiaced) source.
bool IsColorFilterArray () const
{
return fCFAPatternSize != dng_point (0, 0);
}
/// Enable generating four-plane output from three-plane Bayer input.
/// Extra plane is a second version of the green channel. First green is produced
/// using green mosaic samples from one set of rows/columns (even/odd) and the second
/// green channel is produced using the other set of rows/columns. One can compare the
/// two versions to judge whether BayerGreenSplit needs to be set for a given input source.
virtual bool SetFourColorBayer ();
/// Returns scaling factor relative to input size needed to capture output data.
/// Staggered (or rotated) sensing arrays are produced to a larger output than the number of input samples.
/// This method indicates how much larger.
- /// \retval a point with integer scaling factors for the horizotal and vertical dimensions.
+ /// \retval a point with integer scaling factors for the horizontal and vertical dimensions.
virtual dng_point FullScale () const;
/// Returns integer factors by which mosaic data must be downsampled to produce an image which is as close
/// to prefSize as possible in longer dimension, but no smaller than minSize.
/// \param minSize Number of pixels as minium for longer dimension of downsampled image.
/// \param prefSize Number of pixels as target for longer dimension of downsampled image.
/// \param cropFactor Faction of the image to be used after cropping.
/// \retval Point containing integer factors by which image must be downsampled.
virtual dng_point DownScale (uint32 minSize,
uint32 prefSize,
real64 cropFactor) const;
/// Return size of demosaiced image for passed in downscaling factor.
/// \param downScale Integer downsampling factor obtained from DownScale method.
/// \retval Size of resulting demosaiced image.
virtual dng_point DstSize (const dng_point &downScale) const;
/// Demosaic interpolation of a single plane for non-downsampled case.
/// \param host dng_host to use for buffer allocation requests, user cancellation testing, and progress updates.
/// \param negative DNG negative of mosaiced data.
/// \param srcImage Source image for mosaiced data.
/// \param dstImage Destination image for resulting interpolated data.
/// \param srcPlane Which plane to interpolate.
virtual void InterpolateGeneric (dng_host &host,
dng_negative &negative,
const dng_image &srcImage,
dng_image &dstImage,
uint32 srcPlane = 0) const;
/// Demosaic interpolation of a single plane for downsampled case.
/// \param host dng_host to use for buffer allocation requests, user cancellation testing, and progress updates.
/// \param negative DNG negative of mosaiced data.
/// \param srcImage Source image for mosaiced data.
/// \param dstImage Destination image for resulting interpolated data.
/// \param downScale Amount (in horizontal and vertical) by which to subsample image.
/// \param srcPlane Which plane to interpolate.
virtual void InterpolateFast (dng_host &host,
dng_negative &negative,
const dng_image &srcImage,
dng_image &dstImage,
const dng_point &downScale,
uint32 srcPlane = 0) const;
/// Demosaic interpolation of a single plane. Chooses between generic and fast interpolators based on parameters.
/// \param host dng_host to use for buffer allocation requests, user cancellation testing, and progress updates.
/// \param negative DNG negative of mosaiced data.
/// \param srcImage Source image for mosaiced data.
/// \param dstImage Destination image for resulting interpolated data.
/// \param downScale Amount (in horizontal and vertical) by which to subsample image.
/// \param srcPlane Which plane to interpolate.
virtual void Interpolate (dng_host &host,
dng_negative &negative,
const dng_image &srcImage,
dng_image &dstImage,
const dng_point &downScale,
uint32 srcPlane = 0) const;
protected:
virtual bool IsSafeDownScale (const dng_point &downScale) const;
uint32 SizeForDownScale (const dng_point &downScale) const;
virtual bool ValidSizeDownScale (const dng_point &downScale,
uint32 minSize) const;
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_negative.h b/core/libs/dngwriter/extra/dng_sdk/dng_negative.h
index ccb61245e9..06c52e7e8c 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_negative.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_negative.h
@@ -1,1656 +1,1656 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_negative.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
*
*/
/*****************************************************************************/
#ifndef __dng_negative__
#define __dng_negative__
/*****************************************************************************/
#include "dng_1d_function.h"
#include "dng_auto_ptr.h"
#include "dng_classes.h"
#include "dng_fingerprint.h"
#include "dng_linearization_info.h"
#include "dng_matrix.h"
#include "dng_mosaic_info.h"
#include "dng_opcode_list.h"
#include "dng_orientation.h"
#include "dng_rational.h"
#include "dng_sdk_limits.h"
#include "dng_string.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_types.h"
#include "dng_utils.h"
#include "dng_xy_coord.h"
#include <vector>
/*****************************************************************************/
/// \brief Noise model for photon and sensor read noise, assuming that they are
/// independent random variables and spatially invariant.
///
/// The noise model is N (x) = sqrt (scale*x + offset), where x represents a linear
/// signal value in the range [0,1], and N (x) is the standard deviation (i.e.,
/// noise). The parameters scale and offset are both sensor-dependent and
/// ISO-dependent. scale must be positive, and offset must be non-negative.
class dng_noise_function: public dng_1d_function
{
protected:
real64 fScale;
real64 fOffset;
public:
dng_noise_function ()
: fScale (0.0)
, fOffset (0.0)
{
}
dng_noise_function (real64 scale,
real64 offset)
: fScale (scale)
, fOffset (offset)
{
}
virtual real64 Evaluate (real64 x) const
{
return sqrt (fScale * x + fOffset);
}
real64 Scale () const
{
return fScale;
}
real64 Offset () const
{
return fOffset;
}
void SetScale (real64 scale)
{
fScale = scale;
}
void SetOffset (real64 offset)
{
fOffset = offset;
}
bool IsValid () const
{
return (fScale > 0.0 && fOffset >= 0.0);
}
};
/*****************************************************************************/
/// \brief Noise profile for a negative.
///
/// For mosaiced negatives, the noise profile describes the approximate noise
/// characteristics of a mosaic negative after linearization, but prior to
/// demosaicing. For demosaiced negatives (i.e., linear DNGs), the noise profile
/// describes the approximate noise characteristics of the image data immediately
/// following the demosaic step, prior to the processing of opcode list 3.
///
/// A noise profile may contain 1 or N noise functions, where N is the number of
/// color planes for the negative. Otherwise the noise profile is considered to be
/// invalid for that negative. If the noise profile contains 1 noise function, then
/// it is assumed that this single noise function applies to all color planes of the
/// negative. Otherwise, the N noise functions map to the N planes of the negative in
/// order specified in the CFAPlaneColor tag.
class dng_noise_profile
{
protected:
std::vector<dng_noise_function> fNoiseFunctions;
public:
dng_noise_profile ();
explicit dng_noise_profile (const std::vector<dng_noise_function> &functions);
bool IsValid () const;
bool IsValidForNegative (const dng_negative &negative) const;
const dng_noise_function & NoiseFunction (uint32 plane) const;
uint32 NumFunctions () const;
};
/*****************************************************************************/
/// \brief Main class for holding DNG image data and associated metadata.
class dng_negative
{
public:
enum RawImageStageEnum
{
rawImageStagePreOpcode1,
rawImageStagePostOpcode1,
rawImageStagePostOpcode2,
rawImageStagePreOpcode3,
rawImageStagePostOpcode3,
rawImageStageNone
};
protected:
// The object stores an associated allocator. It does not do
// anything to keep it alive or to release it when the object destructs.
// Hence, clients will need to make sure that the allocator's lifespan
// encompasses that of the dng_negative object.
dng_memory_allocator &fAllocator;
// Non-localized ASCII model name.
dng_string fModelName;
// Localized UTF-8 model name.
dng_string fLocalName;
// Base orientation of both the thumbnail and raw data. This is
// generally based on the EXIF values.
bool fHasBaseOrientation;
dng_orientation fBaseOrientation;
// The area of raw image that should be included in the final converted
// image. This stems from extra pixels around the edges of the sensor
// including both the black mask and some additional padding.
// The default crop can be smaller than the "active" area which includes
// the padding but not the black masked pixels.
dng_urational fDefaultCropSizeH;
dng_urational fDefaultCropSizeV;
dng_urational fDefaultCropOriginH;
dng_urational fDefaultCropOriginV;
// Default scale factors. Generally, 1.0 for square pixel cameras. They
// can compensate for non-square pixels. The choice of exact values will
// generally depend on what the camera does. These are particularly
// interesting for the Nikon D1X and the Fuji diamond mosaic.
dng_urational fDefaultScaleH;
dng_urational fDefaultScaleV;
// Best quality scale factor. Used for the Nikon D1X and Fuji cameras
// to force everything to be a scale up rather than scale down. So,
// generally this is 1.0 / min (fDefaultScaleH, fDefaultScaleV) but
// this isn't used if the scale factors are only slightly different
// from 1.0.
dng_urational fBestQualityScale;
// Scale factors used in demosaic algorithm (calculated).
// Maps raw image coordinates to full image coordinates -- i.e.,
// original image coordinates on raw sensor data to coordinates
// in fStage3Image which is the output of the interpolation step.
// So, if we downsample when interpolating, these numbers get
// smaller.
real64 fRawToFullScaleH;
real64 fRawToFullScaleV;
// Relative amount of noise at ISO 100. This is measured per camera model
// based on looking at flat areas of color.
dng_urational fBaselineNoise;
// How much noise reduction has already been applied (0.0 to 1.0) to the
// the raw image data? 0.0 = none, 1.0 = "ideal" amount--i.e. don't apply any
// more by default. 0/0 for unknown.
dng_urational fNoiseReductionApplied;
// Amount of noise for this negative (see dng_noise_profile for details).
dng_noise_profile fNoiseProfile;
// Zero point for the exposure compensation slider. This reflects how
// the manufacturer sets up the camera and its conversions.
dng_srational fBaselineExposure;
// Relative amount of sharpening required. This is chosen per camera
// model based on how strong the anti-alias filter is on the camera
// and the quality of the lenses. This scales the sharpness slider
// value.
dng_urational fBaselineSharpness;
// Chroma blur radius (or 0/0 for auto). Set to 0/1 to disable
// chroma blurring.
dng_urational fChromaBlurRadius;
// Anti-alias filter strength (0.0 to 1.0). Used as a hint
// to the demosaic algorithms.
dng_urational fAntiAliasStrength;
// Linear response limit. The point at which the sensor goes
// non-linear and color information becomes unreliable. Used in
// the highlight-recovery logic.
dng_urational fLinearResponseLimit;
// Scale factor for shadows slider. The Fuji HDR cameras, for example,
// need a more sensitive shadow slider.
dng_urational fShadowScale;
- // Colormetric reference.
+ // Colorimetric reference.
uint32 fColorimetricReference;
// Number of color channels for this image (e.g. 1, 3, or 4).
uint32 fColorChannels;
// Amount by which each channel has already been scaled. Some cameras
// have analog amplifiers on the color channels and these can result
// in different scalings per channel. This provides some level of
// analog white balancing. The Nikon D1 also did digital scaling but
// this caused problems with highlight recovery.
dng_vector fAnalogBalance;
// The "As Shot" neutral color coordinates in native camera space.
// This overrides fCameraWhiteXY if both are specified. This
// specifies the values per channel that would result in a neutral
// color for the "As Shot" case. This is generally supplied by
// the camera.
dng_vector fCameraNeutral;
// The "As Shot" white balance xy coordinates. Sometimes this is
// supplied by the camera. Sometimes the camera just supplies a name
// for the white balance.
dng_xy_coord fCameraWhiteXY;
// Individual camera calibrations.
// Camera data --> camera calibration --> "inverse" of color matrix
// This will be a 4x4 matrix for a 4-color camera. The defaults are
// almost always the identity matrix and for the cases where they
// aren't, they are diagonal matrices.
dng_matrix fCameraCalibration1;
dng_matrix fCameraCalibration2;
// Signature which allows a profile to announce that it is compatible
// with these calibration matrices.
dng_string fCameraCalibrationSignature;
// List of camera profiles.
std::vector<dng_camera_profile *> fCameraProfile;
// "As shot" camera profile name.
dng_string fAsShotProfileName;
// Raw image data digest. This is a MD5 fingerprint of the raw image data
// in the file, computed using a specific algorithm. It can be used
// verify the raw data has not been corrupted.
mutable dng_fingerprint fRawImageDigest;
- // Raw data unique ID. This is an unique identifer for the actual
+ // Raw data unique ID. This is an unique identifier for the actual
// raw image data in the file. It can be used to index into caches
// for this data.
mutable dng_fingerprint fRawDataUniqueID;
// Original raw file name. Just the file name, not the full path.
dng_string fOriginalRawFileName;
- // Is the original raw file data availaible?
+ // Is the original raw file data available?
bool fHasOriginalRawFileData;
// The compressed original raw file data.
AutoPtr<dng_memory_block> fOriginalRawFileData;
// MD5 digest of original raw file data block.
mutable dng_fingerprint fOriginalRawFileDigest;
// DNG private data block.
AutoPtr<dng_memory_block> fDNGPrivateData;
// Is the maker note safe to copy from file to file? Defaults to false
// because many maker notes are not safe.
bool fIsMakerNoteSafe;
// MakerNote binary data block.
AutoPtr<dng_memory_block> fMakerNote;
// EXIF data.
AutoPtr<dng_exif> fExif;
// A copy of the EXIF data before is was synchronized with other metadata sources.
AutoPtr<dng_exif> fOriginalExif;
// IPTC binary data block and offset in original file.
AutoPtr<dng_memory_block> fIPTCBlock;
uint64 fIPTCOffset;
// Did the legacy ITPC block use UTF8?
bool fUsedUTF8forIPTC;
// XMP data.
AutoPtr<dng_xmp> fXMP;
// Was there a valid embedded XMP block?
bool fValidEmbeddedXMP;
// Is the XMP data from a sidecar file?
bool fXMPinSidecar;
// If the XMP data is from a sidecar file, is the sidecar file newer
// than the raw file?
bool fXMPisNewer;
// Information required to linearize and range map the raw data.
AutoPtr<dng_linearization_info> fLinearizationInfo;
- // Information required to demoasic the raw data.
+ // Information required to demosaic the raw data.
AutoPtr<dng_mosaic_info> fMosaicInfo;
// Opcode list 1. (Applied to stored data)
dng_opcode_list fOpcodeList1;
// Opcode list 2. (Applied to range mapped data)
dng_opcode_list fOpcodeList2;
// Opcode list 3. (Post demosaic)
dng_opcode_list fOpcodeList3;
// Stage 1 image, which is image data stored in a DNG file.
AutoPtr<dng_image> fStage1Image;
// Stage 2 image, which is the stage 1 image after it has been
// linearized and range mapped.
AutoPtr<dng_image> fStage2Image;
// Stage 3 image, which is the stage 2 image after it has been
// demosaiced.
AutoPtr<dng_image> fStage3Image;
// Additiona gain applied when building the stage 3 image.
real64 fStage3Gain;
// Were any approximations (e.g. downsampling, etc.) applied
// file reading this image?
bool fIsPreview;
// Does the file appear to be damaged?
bool fIsDamaged;
// At what processing stage did we grab a copy of raw image data?
RawImageStageEnum fRawImageStage;
// The raw image data that we grabbed, if any.
AutoPtr<dng_image> fRawImage;
public:
virtual ~dng_negative ();
static dng_negative * Make (dng_memory_allocator &allocator);
/// Provide access to the memory allocator used for this object.
dng_memory_allocator & Allocator () const
{
return fAllocator;
}
/// Getter for ModelName.
void SetModelName (const char *name)
{
fModelName.Set_ASCII (name);
}
/// Setter for ModelName.
const dng_string & ModelName () const
{
return fModelName;
}
/// Setter for LocalName.
void SetLocalName (const char *name)
{
fLocalName.Set (name);
}
/// Getter for LocalName.
const dng_string & LocalName () const
{
return fLocalName;
}
/// Setter for BaseOrientation.
void SetBaseOrientation (const dng_orientation &orientation);
/// Has BaseOrientation been set?
bool HasBaseOrientation () const
{
return fHasBaseOrientation;
}
/// Getter for BaseOrientation.
const dng_orientation & BaseOrientation () const
{
return fBaseOrientation;
}
/// Hook to allow SDK host code to add additional rotations.
virtual dng_orientation Orientation () const;
/// Logically rotates the image by changing the orientation values.
/// This will also update the XMP data.
void ApplyOrientation (const dng_orientation &orientation);
/// Setter for DefaultCropSize.
void SetDefaultCropSize (const dng_urational &sizeH,
const dng_urational &sizeV)
{
fDefaultCropSizeH = sizeH;
fDefaultCropSizeV = sizeV;
}
/// Setter for DefaultCropSize.
void SetDefaultCropSize (uint32 sizeH,
uint32 sizeV)
{
SetDefaultCropSize (dng_urational (sizeH, 1),
dng_urational (sizeV, 1));
}
/// Getter for DefaultCropSize horizontal.
const dng_urational & DefaultCropSizeH () const
{
return fDefaultCropSizeH;
}
/// Getter for DefaultCropSize vertical.
const dng_urational & DefaultCropSizeV () const
{
return fDefaultCropSizeV;
}
/// Setter for DefaultCropOrigin.
void SetDefaultCropOrigin (const dng_urational &originH,
const dng_urational &originV)
{
fDefaultCropOriginH = originH;
fDefaultCropOriginV = originV;
}
/// Setter for DefaultCropOrigin.
void SetDefaultCropOrigin (uint32 originH,
uint32 originV)
{
SetDefaultCropOrigin (dng_urational (originH, 1),
dng_urational (originV, 1));
}
/// Set default crop around center of image.
void SetDefaultCropCentered (const dng_point &rawSize)
{
uint32 sizeH = Round_uint32 (fDefaultCropSizeH.As_real64 ());
uint32 sizeV = Round_uint32 (fDefaultCropSizeV.As_real64 ());
SetDefaultCropOrigin ((rawSize.h - sizeH) >> 1,
(rawSize.v - sizeV) >> 1);
}
/// Get default crop origin horizontal value.
const dng_urational & DefaultCropOriginH () const
{
return fDefaultCropOriginH;
}
/// Get default crop origin vertical value.
const dng_urational & DefaultCropOriginV () const
{
return fDefaultCropOriginV;
}
/// Setter for DefaultScale.
void SetDefaultScale (const dng_urational &scaleH,
const dng_urational &scaleV)
{
fDefaultScaleH = scaleH;
fDefaultScaleV = scaleV;
}
/// Get default scale horizontal value.
const dng_urational & DefaultScaleH () const
{
return fDefaultScaleH;
}
/// Get default scale vertical value.
const dng_urational & DefaultScaleV () const
{
return fDefaultScaleV;
}
/// Setter for BestQualityScale.
void SetBestQualityScale (const dng_urational &scale)
{
fBestQualityScale = scale;
}
/// Getter for BestQualityScale.
const dng_urational & BestQualityScale () const
{
return fBestQualityScale;
}
/// API for raw to full image scaling factors horizontal.
real64 RawToFullScaleH () const
{
return fRawToFullScaleH;
}
/// API for raw to full image scaling factors vertical.
real64 RawToFullScaleV () const
{
return fRawToFullScaleV;
}
/// Get default scale factor.
- /// When specifing a single scale factor, we use the horizontal
+ /// When specifying a single scale factor, we use the horizontal
/// scale factor, and let the vertical scale factor be calculated
/// based on the pixel aspect ratio.
real64 DefaultScale () const
{
return DefaultScaleH ().As_real64 ();
}
/// Default cropped image size (at scale == 1.0) width.
real64 SquareWidth () const
{
return DefaultCropSizeH ().As_real64 ();
}
/// Default cropped image size (at scale == 1.0) height.
real64 SquareHeight () const
{
return DefaultCropSizeV ().As_real64 () *
DefaultScaleV ().As_real64 () /
DefaultScaleH ().As_real64 ();
}
/// Default cropped image aspect ratio.
real64 AspectRatio () const
{
return SquareWidth () /
SquareHeight ();
}
/// Pixel aspect ratio of stage 3 image.
real64 PixelAspectRatio () const
{
return (DefaultScaleH ().As_real64 () / RawToFullScaleH ()) /
(DefaultScaleV ().As_real64 () / RawToFullScaleV ());
}
/// Default cropped image size at given scale factor width.
uint32 FinalWidth (real64 scale) const
{
return Round_uint32 (SquareWidth () * scale);
}
/// Default cropped image size at given scale factor height.
uint32 FinalHeight (real64 scale) const
{
return Round_uint32 (SquareHeight () * scale);
}
/// Default cropped image size at default scale factor width.
uint32 DefaultFinalWidth () const
{
return FinalWidth (DefaultScale ());
}
/// Default cropped image size at default scale factor height.
uint32 DefaultFinalHeight () const
{
return FinalHeight (DefaultScale ());
}
/// Get best quality width.
/// For a naive conversion, one could use either the default size,
/// or the best quality size.
uint32 BestQualityFinalWidth () const
{
return FinalWidth (DefaultScale () * BestQualityScale ().As_real64 ());
}
/// Get best quality height.
/// For a naive conversion, one could use either the default size,
/// or the best quality size.
uint32 BestQualityFinalHeight () const
{
return FinalHeight (DefaultScale () * BestQualityScale ().As_real64 ());
}
/// The default crop area after applying the specified horizontal and
/// vertical scale factors to the stage 3 image.
dng_rect DefaultCropArea (real64 scaleH = 1.0,
real64 scaleV = 1.0) const;
/// Setter for BaselineNoise.
void SetBaselineNoise (real64 noise)
{
fBaselineNoise.Set_real64 (noise, 100);
}
/// Getter for BaselineNoise as dng_urational.
const dng_urational & BaselineNoiseR () const
{
return fBaselineNoise;
}
/// Getter for BaselineNoise as real64.
real64 BaselineNoise () const
{
return fBaselineNoise.As_real64 ();
}
/// Setter for NoiseReductionApplied.
void SetNoiseReductionApplied (const dng_urational &value)
{
fNoiseReductionApplied = value;
}
/// Getter for NoiseReductionApplied.
const dng_urational & NoiseReductionApplied () const
{
return fNoiseReductionApplied;
}
/// Setter for noise profile.
void SetNoiseProfile (const dng_noise_profile &noiseProfile)
{
fNoiseProfile = noiseProfile;
}
/// Does this negative have a valid noise profile?
bool HasNoiseProfile () const
{
return fNoiseProfile.IsValidForNegative (*this);
}
/// Getter for noise profile.
const dng_noise_profile & NoiseProfile () const
{
return fNoiseProfile;
}
/// Setter for BaselineExposure.
void SetBaselineExposure (real64 exposure)
{
fBaselineExposure.Set_real64 (exposure, 100);
}
/// Getter for BaselineExposure as dng_urational.
const dng_srational & BaselineExposureR () const
{
return fBaselineExposure;
}
/// Getter for BaselineExposure as real64.
real64 BaselineExposure () const
{
return BaselineExposureR ().As_real64 ();
}
/// Setter for BaselineSharpness.
void SetBaselineSharpness (real64 sharpness)
{
fBaselineSharpness.Set_real64 (sharpness, 100);
}
/// Getter for BaselineSharpness as dng_urational.
const dng_urational & BaselineSharpnessR () const
{
return fBaselineSharpness;
}
/// Getter for BaselineSharpness as real64.
real64 BaselineSharpness () const
{
return BaselineSharpnessR ().As_real64 ();
}
/// Setter for ChromaBlurRadius.
void SetChromaBlurRadius (const dng_urational &radius)
{
fChromaBlurRadius = radius;
}
/// Getter for ChromaBlurRadius as dng_urational.
const dng_urational & ChromaBlurRadius () const
{
return fChromaBlurRadius;
}
/// Setter for AntiAliasStrength.
void SetAntiAliasStrength (const dng_urational &strength)
{
fAntiAliasStrength = strength;
}
/// Getter for AntiAliasStrength as dng_urational.
const dng_urational & AntiAliasStrength () const
{
return fAntiAliasStrength;
}
/// Setter for LinearResponseLimit.
void SetLinearResponseLimit (real64 limit)
{
fLinearResponseLimit.Set_real64 (limit, 100);
}
/// Getter for LinearResponseLimit as dng_urational.
const dng_urational & LinearResponseLimitR () const
{
return fLinearResponseLimit;
}
/// Getter for LinearResponseLimit as real64.
real64 LinearResponseLimit () const
{
return LinearResponseLimitR ().As_real64 ();
}
/// Setter for ShadowScale.
void SetShadowScale (const dng_urational &scale);
/// Getter for ShadowScale as dng_urational.
const dng_urational & ShadowScaleR () const
{
return fShadowScale;
}
/// Getter for ShadowScale as real64.
real64 ShadowScale () const
{
return ShadowScaleR ().As_real64 ();
}
// API for ColorimetricReference.
void SetColorimetricReference (uint32 ref)
{
fColorimetricReference = ref;
}
uint32 ColorimetricReference () const
{
return fColorimetricReference;
}
/// Setter for ColorChannels.
void SetColorChannels (uint32 channels)
{
fColorChannels = channels;
}
/// Getter for ColorChannels.
uint32 ColorChannels () const
{
return fColorChannels;
}
/// Setter for Monochrome.
void SetMonochrome ()
{
SetColorChannels (1);
}
/// Getter for Monochrome.
bool IsMonochrome () const
{
return ColorChannels () == 1;
}
/// Setter for AnalogBalance.
void SetAnalogBalance (const dng_vector &b);
/// Getter for AnalogBalance as dng_urational.
dng_urational AnalogBalanceR (uint32 channel) const;
/// Getter for AnalogBalance as real64.
real64 AnalogBalance (uint32 channel) const;
/// Setter for CameraNeutral.
void SetCameraNeutral (const dng_vector &n);
/// Clear CameraNeutral.
void ClearCameraNeutral ()
{
fCameraNeutral.Clear ();
}
/// Determine if CameraNeutral has been set but not cleared.
bool HasCameraNeutral () const
{
return fCameraNeutral.NotEmpty ();
}
/// Getter for CameraNeutral.
const dng_vector & CameraNeutral () const
{
return fCameraNeutral;
}
dng_urational CameraNeutralR (uint32 channel) const;
/// Setter for CameraWhiteXY.
void SetCameraWhiteXY (const dng_xy_coord &coord);
bool HasCameraWhiteXY () const
{
return fCameraWhiteXY.IsValid ();
}
const dng_xy_coord & CameraWhiteXY () const;
void GetCameraWhiteXY (dng_urational &x,
dng_urational &y) const;
// API for camera calibration:
/// Setter for first of up to two color matrices used for individual camera calibrations.
///
/// The sequence of matrix transforms is:
/// Camera data --> camera calibration --> "inverse" of color matrix
///
/// This will be a 4x4 matrix for a four-color camera. The defaults are
/// almost always the identity matrix, and for the cases where they
/// aren't, they are diagonal matrices.
void SetCameraCalibration1 (const dng_matrix &m);
/// Setter for second of up to two color matrices used for individual camera calibrations.
///
/// The sequence of matrix transforms is:
/// Camera data --> camera calibration --> "inverse" of color matrix
///
/// This will be a 4x4 matrix for a four-color camera. The defaults are
/// almost always the identity matrix, and for the cases where they
/// aren't, they are diagonal matrices.
void SetCameraCalibration2 (const dng_matrix &m);
/// Getter for first of up to two color matrices used for individual camera calibrations.
const dng_matrix & CameraCalibration1 () const
{
return fCameraCalibration1;
}
/// Getter for second of up to two color matrices used for individual camera calibrations.
const dng_matrix & CameraCalibration2 () const
{
return fCameraCalibration2;
}
void SetCameraCalibrationSignature (const char *signature)
{
fCameraCalibrationSignature.Set (signature);
}
const dng_string & CameraCalibrationSignature () const
{
return fCameraCalibrationSignature;
}
// Camera Profile API:
void AddProfile (AutoPtr<dng_camera_profile> &profile);
void ClearProfiles ();
uint32 ProfileCount () const;
const dng_camera_profile & ProfileByIndex (uint32 index) const;
const dng_camera_profile * ProfileByID (const dng_camera_profile_id &id,
bool useDefaultIfNoMatch = true) const;
bool HasProfileID (const dng_camera_profile_id &id) const
{
return ProfileByID (id, false) != NULL;
}
// Returns the camera profile to embed when saving to DNG:
virtual const dng_camera_profile * CameraProfileToEmbed () const;
// API for AsShotProfileName.
void SetAsShotProfileName (const char *name)
{
fAsShotProfileName.Set (name);
}
const dng_string & AsShotProfileName () const
{
return fAsShotProfileName;
}
// Makes a dng_color_spec object for this negative.
virtual dng_color_spec * MakeColorSpec (const dng_camera_profile_id &id) const;
// API for RawImageDigest:
void SetRawImageDigest (const dng_fingerprint &digest)
{
fRawImageDigest = digest;
}
void ClearRawImageDigest ()
{
fRawImageDigest.Clear ();
}
const dng_fingerprint & RawImageDigest () const
{
return fRawImageDigest;
}
void FindRawImageDigest (dng_host &host) const;
void ValidateRawImageDigest (dng_host &host);
// API for RawDataUniqueID:
void SetRawDataUniqueID (const dng_fingerprint &id)
{
fRawDataUniqueID = id;
}
const dng_fingerprint & RawDataUniqueID () const
{
return fRawDataUniqueID;
}
void FindRawDataUniqueID (dng_host &host) const;
void RecomputeRawDataUniqueID (dng_host &host);
// API for original raw file name:
void SetOriginalRawFileName (const char *name)
{
fOriginalRawFileName.Set (name);
}
bool HasOriginalRawFileName () const
{
return fOriginalRawFileName.NotEmpty ();
}
const dng_string & OriginalRawFileName () const
{
return fOriginalRawFileName;
}
// API for original raw file data:
void SetHasOriginalRawFileData (bool hasData)
{
fHasOriginalRawFileData = hasData;
}
bool CanEmbedOriginalRaw () const
{
return fHasOriginalRawFileData && HasOriginalRawFileName ();
}
void SetOriginalRawFileData (AutoPtr<dng_memory_block> &data)
{
fOriginalRawFileData.Reset (data.Release ());
}
const void * OriginalRawFileData () const
{
return fOriginalRawFileData.Get () ? fOriginalRawFileData->Buffer ()
: NULL;
}
uint32 OriginalRawFileDataLength () const
{
return fOriginalRawFileData.Get () ? fOriginalRawFileData->LogicalSize ()
: 0;
}
// API for original raw file data digest.
void SetOriginalRawFileDigest (const dng_fingerprint &digest)
{
fOriginalRawFileDigest = digest;
}
const dng_fingerprint & OriginalRawFileDigest () const
{
return fOriginalRawFileDigest;
}
void FindOriginalRawFileDigest () const;
void ValidateOriginalRawFileDigest ();
// API for DNG private data:
void SetPrivateData (AutoPtr<dng_memory_block> &block)
{
fDNGPrivateData.Reset (block.Release ());
}
void ClearPrivateData ()
{
fDNGPrivateData.Reset ();
}
const uint8 * PrivateData () const
{
return fDNGPrivateData.Get () ? fDNGPrivateData->Buffer_uint8 ()
: NULL;
}
uint32 PrivateLength () const
{
return fDNGPrivateData.Get () ? fDNGPrivateData->LogicalSize ()
: 0;
}
// API for MakerNote data:
void SetMakerNoteSafety (bool safe)
{
fIsMakerNoteSafe = safe;
}
bool IsMakerNoteSafe () const
{
return fIsMakerNoteSafe;
}
void SetMakerNote (AutoPtr<dng_memory_block> &block)
{
fMakerNote.Reset (block.Release ());
}
void ClearMakerNote ()
{
fMakerNote.Reset ();
}
const void * MakerNoteData () const
{
return fMakerNote.Get () ? fMakerNote->Buffer ()
: NULL;
}
uint32 MakerNoteLength () const
{
return fMakerNote.Get () ? fMakerNote->LogicalSize ()
: 0;
}
// API for EXIF metadata:
dng_exif * GetExif ()
{
return fExif.Get ();
}
const dng_exif * GetExif () const
{
return fExif.Get ();
}
virtual dng_memory_block * BuildExifBlock (const dng_resolution *resolution = NULL,
bool includeIPTC = false,
bool minimalEXIF = false,
const dng_jpeg_preview *thumbnail = NULL) const;
// API for original EXIF metadata.
dng_exif * GetOriginalExif ()
{
return fOriginalExif.Get ();
}
const dng_exif * GetOriginalExif () const
{
return fOriginalExif.Get ();
}
// API for IPTC metadata:
void SetIPTC (AutoPtr<dng_memory_block> &block,
uint64 offset);
void SetIPTC (AutoPtr<dng_memory_block> &block);
void ClearIPTC ();
const void * IPTCData () const;
uint32 IPTCLength () const;
uint64 IPTCOffset () const;
dng_fingerprint IPTCDigest (bool includePadding = true) const;
void RebuildIPTC (bool padForTIFF,
bool forceUTF8);
bool UsedUTF8forIPTC () const
{
return fUsedUTF8forIPTC;
}
void SetUsedUTF8forIPTC (bool used)
{
fUsedUTF8forIPTC = used;
}
// API for XMP metadata:
bool SetXMP (dng_host &host,
const void *buffer,
uint32 count,
bool xmpInSidecar = false,
bool xmpIsNewer = false);
dng_xmp * GetXMP ()
{
return fXMP.Get ();
}
const dng_xmp * GetXMP () const
{
return fXMP.Get ();
}
bool XMPinSidecar () const
{
return fXMPinSidecar;
}
// API for linearization information:
const dng_linearization_info * GetLinearizationInfo () const
{
return fLinearizationInfo.Get ();
}
void ClearLinearizationInfo ()
{
fLinearizationInfo.Reset ();
}
// Linearization curve. Usually used to increase compression ratios
// by storing the compressed data in a more visually uniform space.
// This is a 16-bit LUT that maps the stored data back to linear.
void SetLinearization (AutoPtr<dng_memory_block> &curve);
// Active area (non-black masked pixels). These pixels are trimmed
// during linearization step.
void SetActiveArea (const dng_rect &area);
// Areas that are known to contain black masked pixels that can
// be used to estimate black levels.
void SetMaskedAreas (uint32 count,
const dng_rect *area);
void SetMaskedArea (const dng_rect &area)
{
SetMaskedAreas (1, &area);
}
// Sensor black level information.
void SetBlackLevel (real64 black,
int32 plane = -1);
void SetQuadBlacks (real64 black0,
real64 black1,
real64 black2,
real64 black3);
void SetRowBlacks (const real64 *blacks,
uint32 count);
void SetColumnBlacks (const real64 *blacks,
uint32 count);
// Sensor white level information.
uint32 WhiteLevel (uint32 plane = 0) const;
void SetWhiteLevel (uint32 white,
int32 plane = -1);
// API for mosaic information:
const dng_mosaic_info * GetMosaicInfo () const
{
return fMosaicInfo.Get ();
}
void ClearMosaicInfo ()
{
fMosaicInfo.Reset ();
}
// ColorKeys APIs:
void SetColorKeys (ColorKeyCode color0,
ColorKeyCode color1,
ColorKeyCode color2,
ColorKeyCode color3 = colorKeyMaxEnum);
void SetRGB ()
{
SetColorChannels (3);
SetColorKeys (colorKeyRed,
colorKeyGreen,
colorKeyBlue);
}
void SetCMY ()
{
SetColorChannels (3);
SetColorKeys (colorKeyCyan,
colorKeyMagenta,
colorKeyYellow);
}
void SetGMCY ()
{
SetColorChannels (4);
SetColorKeys (colorKeyGreen,
colorKeyMagenta,
colorKeyCyan,
colorKeyYellow);
}
// APIs to set mosaic patterns.
void SetBayerMosaic (uint32 phase);
void SetFujiMosaic (uint32 phase);
void SetQuadMosaic (uint32 pattern);
// BayerGreenSplit.
void SetGreenSplit (uint32 split);
// APIs for opcode lists.
const dng_opcode_list & OpcodeList1 () const
{
return fOpcodeList1;
}
dng_opcode_list & OpcodeList1 ()
{
return fOpcodeList1;
}
const dng_opcode_list & OpcodeList2 () const
{
return fOpcodeList2;
}
dng_opcode_list & OpcodeList2 ()
{
return fOpcodeList2;
}
const dng_opcode_list & OpcodeList3 () const
{
return fOpcodeList3;
}
dng_opcode_list & OpcodeList3 ()
{
return fOpcodeList3;
}
// First part of parsing logic.
virtual void Parse (dng_host &host,
dng_stream &stream,
dng_info &info);
// Second part of parsing logic. This is split off from the
// first part because these operations are useful when extending
// this sdk to support non-DNG raw formats.
virtual void PostParse (dng_host &host,
dng_stream &stream,
dng_info &info);
// Synchronize metadata sources.
virtual void SynchronizeMetadata ();
// Routines to update the date/time field in the EXIF and XMP
// metadata.
void UpdateDateTime (const dng_date_time_info &dt);
void UpdateDateTimeToNow ();
// Developer's utility function to switch to four color Bayer
// interpolation. This is useful for evaluating how much green
// split a Bayer pattern sensor has.
virtual bool SetFourColorBayer ();
// Access routines for the image stages.
const dng_image * Stage1Image () const
{
return fStage1Image.Get ();
}
const dng_image * Stage2Image () const
{
return fStage2Image.Get ();
}
const dng_image * Stage3Image () const
{
return fStage3Image.Get ();
}
// Returns the processing stage of the raw image data.
RawImageStageEnum RawImageStage () const
{
return fRawImageStage;
}
// Returns the raw image data.
const dng_image & RawImage () const;
// Read the stage 1 image.
virtual void ReadStage1Image (dng_host &host,
dng_stream &stream,
dng_info &info);
// Assign the stage 1 image.
void SetStage1Image (AutoPtr<dng_image> &image);
// Assign the stage 2 image.
void SetStage2Image (AutoPtr<dng_image> &image);
// Assign the stage 3 image.
void SetStage3Image (AutoPtr<dng_image> &image);
// Build the stage 2 (linearized and range mapped) image.
void BuildStage2Image (dng_host &host,
uint32 pixelType = ttShort);
// Build the stage 3 (demosaiced) image.
void BuildStage3Image (dng_host &host,
int32 srcPlane = -1);
// Additional gain applied when building the stage 3 image.
void SetStage3Gain (real64 gain)
{
fStage3Gain = gain;
}
real64 Stage3Gain () const
{
return fStage3Gain;
}
// IsPreview API:
void SetIsPreview (bool preview)
{
fIsPreview = preview;
}
bool IsPreview () const
{
return fIsPreview;
}
// IsDamaged API:
void SetIsDamaged (bool damaged)
{
fIsDamaged = damaged;
}
bool IsDamaged () const
{
return fIsDamaged;
}
protected:
dng_negative (dng_memory_allocator &allocator);
virtual void Initialize ();
virtual dng_exif * MakeExif ();
virtual dng_xmp * MakeXMP ();
virtual dng_linearization_info * MakeLinearizationInfo ();
void NeedLinearizationInfo ();
virtual dng_mosaic_info * MakeMosaicInfo ();
void NeedMosaicInfo ();
virtual void DoBuildStage2 (dng_host &host,
uint32 pixelType);
virtual void DoInterpolateStage3 (dng_host &host,
int32 srcPlane);
virtual void DoMergeStage3 (dng_host &host);
virtual void DoBuildStage3 (dng_host &host,
int32 srcPlane);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_parse_utils.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_parse_utils.cpp
index 7f20f76a16..77cc41fc20 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_parse_utils.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_parse_utils.cpp
@@ -1,3238 +1,3238 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_parse_utils.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_parse_utils.h"
#include "dng_date_time.h"
#include "dng_globals.h"
#include "dng_ifd.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_types.h"
#include "dng_stream.h"
#include "dng_exceptions.h"
#include "dng_utils.h"
/*****************************************************************************/
#if qDNGValidate
/*****************************************************************************/
struct dng_name_table
{
uint32 key;
const char *name;
};
/*****************************************************************************/
static const char * LookupName (uint32 key,
const dng_name_table *table,
uint32 table_entries)
{
for (uint32 index = 0; index < table_entries; index++)
{
if (key == table [index] . key)
{
return table [index] . name;
}
}
return NULL;
}
/*****************************************************************************/
const char * LookupParentCode (uint32 parentCode)
{
const dng_name_table kParentCodeNames [] =
{
{ 0, "IFD 0" },
{ tcExifIFD, "Exif IFD" },
{ tcGPSInfo, "GPS IFD" },
{ tcInteroperabilityIFD, "Interoperability IFD" },
{ tcKodakDCRPrivateIFD, "Kodak DCR Private IFD" },
{ tcKodakKDCPrivateIFD, "Kodak KDC Private IFD" },
{ tcCanonMakerNote, "Canon MakerNote" },
{ tcEpsonMakerNote, "Epson MakerNote" },
{ tcFujiMakerNote, "Fuji MakerNote" },
{ tcHasselbladMakerNote, "Hasselblad MakerNote" },
{ tcKodakMakerNote, "Kodak MakerNote" },
{ tcKodakMakerNote65280, "Kodak MakerNote 65280" },
{ tcLeicaMakerNote, "Leica MakerNote" },
{ tcMamiyaMakerNote, "Mamiya MakerNote" },
{ tcMinoltaMakerNote, "Minolta MakerNote" },
{ tcNikonMakerNote, "Nikon MakerNote" },
{ tcOlympusMakerNote, "Olympus MakerNote" },
{ tcOlympusMakerNote8208, "Olympus MakerNote 8208" },
{ tcOlympusMakerNote8224, "Olympus MakerNote 8224" },
{ tcOlympusMakerNote8240, "Olympus MakerNote 8240" },
{ tcOlympusMakerNote8256, "Olympus MakerNote 8256" },
{ tcOlympusMakerNote8272, "Olympus MakerNote 8272" },
{ tcOlympusMakerNote12288, "Olympus MakerNote 12288" },
{ tcPanasonicMakerNote, "Panasonic MakerNote" },
{ tcPentaxMakerNote, "Pentax MakerNote" },
{ tcPhaseOneMakerNote, "Phase One MakerNote" },
{ tcRicohMakerNote, "Ricoh MakerNote" },
{ tcRicohMakerNoteCameraInfo, "Ricoh MakerNote Camera Info" },
{ tcSonyMakerNote, "Sony MakerNote" },
{ tcSonyMakerNoteSubInfo, "Sony MakerNote SubInfo" },
{ tcSonyPrivateIFD1, "Sony Private IFD 1" },
{ tcSonyPrivateIFD2, "Sony Private IFD 2" },
{ tcSonyPrivateIFD3A, "Sony Private IFD 3A" },
{ tcSonyPrivateIFD3B, "Sony Private IFD 3B" },
{ tcSonyPrivateIFD3C, "Sony Private IFD 3C" },
{ tcCanonCRW, "Canon CRW" },
{ tcContaxRAW, "Contax RAW" },
{ tcFujiRAF, "Fuji RAF" },
{ tcLeafMOS, "Leaf MOS" },
{ tcMinoltaMRW, "Minolta MRW" },
{ tcPanasonicRAW, "Panasonic RAW" },
{ tcFoveonX3F, "Foveon X3F" },
{ tcJPEG, "JPEG" },
{ tcAdobePSD, "Adobe PSD" }
};
const char *name = LookupName (parentCode,
kParentCodeNames,
sizeof (kParentCodeNames ) /
sizeof (kParentCodeNames [0]));
if (name)
{
return name;
}
static char s [32];
if (parentCode >= tcFirstSubIFD &&
parentCode <= tcLastSubIFD)
{
sprintf (s, "SubIFD %u", (unsigned) (parentCode - tcFirstSubIFD + 1));
}
else if (parentCode >= tcFirstChainedIFD &&
parentCode <= tcLastChainedIFD)
{
sprintf (s, "Chained IFD %u", (unsigned) (parentCode - tcFirstChainedIFD + 1));
}
else
{
sprintf (s, "ParentIFD %u", (unsigned) parentCode);
}
return s;
}
/*****************************************************************************/
const char * LookupTagCode (uint32 parentCode,
uint32 tagCode)
{
const dng_name_table kTagNames [] =
{
{ tcNewSubFileType, "NewSubFileType" },
{ tcSubFileType, "SubFileType" },
{ tcImageWidth, "ImageWidth" },
{ tcImageLength, "ImageLength" },
{ tcBitsPerSample, "BitsPerSample" },
{ tcCompression, "Compression" },
{ tcPhotometricInterpretation, "PhotometricInterpretation" },
{ tcThresholding, "Thresholding" },
{ tcCellWidth, "CellWidth" },
{ tcCellLength, "CellLength" },
{ tcFillOrder, "FillOrder" },
{ tcImageDescription, "ImageDescription" },
{ tcMake, "Make" },
{ tcModel, "Model" },
{ tcStripOffsets, "StripOffsets" },
{ tcOrientation, "Orientation" },
{ tcSamplesPerPixel, "SamplesPerPixel" },
{ tcRowsPerStrip, "RowsPerStrip" },
{ tcStripByteCounts, "StripByteCounts" },
{ tcMinSampleValue, "MinSampleValue" },
{ tcMaxSampleValue, "MaxSampleValue" },
{ tcXResolution, "XResolution" },
{ tcYResolution, "YResolution" },
{ tcPlanarConfiguration, "PlanarConfiguration" },
{ tcFreeOffsets, "FreeOffsets" },
{ tcFreeByteCounts, "FreeByteCounts" },
{ tcGrayResponseUnit, "GrayResponseUnit" },
{ tcGrayResponseCurve, "GrayResponseCurve" },
{ tcResolutionUnit, "ResolutionUnit" },
{ tcTransferFunction, "TransferFunction" },
{ tcSoftware, "Software" },
{ tcDateTime, "DateTime" },
{ tcArtist, "Artist" },
{ tcHostComputer, "HostComputer" },
{ tcWhitePoint, "WhitePoint" },
{ tcPrimaryChromaticities, "PrimaryChromaticities" },
{ tcColorMap, "ColorMap" },
{ tcTileWidth, "TileWidth" },
{ tcTileLength, "TileLength" },
{ tcTileOffsets, "TileOffsets" },
{ tcTileByteCounts, "TileByteCounts" },
{ tcSubIFDs, "SubIFDs" },
{ tcExtraSamples, "ExtraSamples" },
{ tcSampleFormat, "SampleFormat" },
{ tcJPEGTables, "JPEGTables" },
{ tcJPEGProc, "JPEGProc" },
{ tcJPEGInterchangeFormat, "JPEGInterchangeFormat" },
{ tcJPEGInterchangeFormatLength, "JPEGInterchangeFormatLength" },
{ tcYCbCrCoefficients, "YCbCrCoefficients" },
{ tcYCbCrSubSampling, "YCbCrSubSampling" },
{ tcYCbCrPositioning, "YCbCrPositioning" },
{ tcReferenceBlackWhite, "ReferenceBlackWhite" },
{ tcXMP, "XMP" },
{ tcKodakCameraSerialNumber, "KodakCameraSerialNumber" },
{ tcCFARepeatPatternDim, "CFARepeatPatternDim" },
{ tcCFAPattern, "CFAPattern" },
{ tcBatteryLevel, "BatteryLevel" },
{ tcKodakDCRPrivateIFD, "KodakDCRPrivateIFD" },
{ tcCopyright, "Copyright" },
{ tcExposureTime, "ExposureTime" },
{ tcFNumber, "FNumber" },
{ tcIPTC_NAA, "IPTC/NAA" },
{ tcLeafPKTS, "LeafPKTS" },
{ tcAdobeData, "AdobeData" },
{ tcExifIFD, "ExifIFD" },
{ tcICCProfile, "ICCProfile" },
{ tcExposureProgram, "ExposureProgram" },
{ tcSpectralSensitivity, "SpectralSensitivity" },
{ tcGPSInfo, "GPSInfo" },
{ tcISOSpeedRatings, "ISOSpeedRatings" },
{ tcOECF, "OECF" },
{ tcInterlace, "Interlace" },
{ tcTimeZoneOffset, "TimeZoneOffset" },
{ tcSelfTimerMode, "SelfTimerMode" },
{ tcExifVersion, "ExifVersion" },
{ tcDateTimeOriginal, "DateTimeOriginal" },
{ tcDateTimeDigitized, "DateTimeDigitized" },
{ tcComponentsConfiguration, "ComponentsConfiguration" },
{ tcCompressedBitsPerPixel, "CompressedBitsPerPixel" },
{ tcShutterSpeedValue, "ShutterSpeedValue" },
{ tcApertureValue, "ApertureValue" },
{ tcBrightnessValue, "BrightnessValue" },
{ tcExposureBiasValue, "ExposureBiasValue" },
{ tcMaxApertureValue, "MaxApertureValue" },
{ tcSubjectDistance, "SubjectDistance" },
{ tcMeteringMode, "MeteringMode" },
{ tcLightSource, "LightSource" },
{ tcFlash, "Flash" },
{ tcFocalLength, "FocalLength" },
{ tcFlashEnergy, "FlashEnergy" },
{ tcSpatialFrequencyResponse, "SpatialFrequencyResponse" },
{ tcNoise, "Noise" },
{ tcFocalPlaneXResolution, "FocalPlaneXResolution" },
{ tcFocalPlaneYResolution, "FocalPlaneYResolution" },
{ tcFocalPlaneResolutionUnit, "FocalPlaneResolutionUnit" },
{ tcImageNumber, "ImageNumber" },
{ tcSecurityClassification, "SecurityClassification" },
{ tcImageHistory, "ImageHistory" },
{ tcSubjectArea, "SubjectArea" },
{ tcExposureIndex, "ExposureIndex" },
{ tcTIFF_EP_StandardID, "TIFF/EPStandardID" },
{ tcSensingMethod, "SensingMethod" },
{ tcMakerNote, "MakerNote" },
{ tcUserComment, "UserComment" },
{ tcSubsecTime, "SubsecTime" },
{ tcSubsecTimeOriginal, "SubsecTimeOriginal" },
{ tcSubsecTimeDigitized, "SubsecTimeDigitized" },
{ tcAdobeLayerData, "AdobeLayerData" },
{ tcFlashPixVersion, "FlashPixVersion" },
{ tcColorSpace, "ColorSpace" },
{ tcPixelXDimension, "PixelXDimension" },
{ tcPixelYDimension, "PixelYDimension" },
{ tcRelatedSoundFile, "RelatedSoundFile" },
{ tcInteroperabilityIFD, "InteroperabilityIFD" },
{ tcFlashEnergyExif, "FlashEnergyExif" },
{ tcSpatialFrequencyResponseExif, "SpatialFrequencyResponseExif" },
{ tcFocalPlaneXResolutionExif, "FocalPlaneXResolutionExif" },
{ tcFocalPlaneYResolutionExif, "FocalPlaneYResolutionExif" },
{ tcFocalPlaneResolutionUnitExif, "FocalPlaneResolutionUnitExif" },
{ tcSubjectLocation, "SubjectLocation" },
{ tcExposureIndexExif, "ExposureIndexExif" },
{ tcSensingMethodExif, "SensingMethodExif" },
{ tcFileSource, "FileSource" },
{ tcSceneType, "SceneType" },
{ tcCFAPatternExif, "CFAPatternExif" },
{ tcCustomRendered, "CustomRendered" },
{ tcExposureMode, "ExposureMode" },
{ tcWhiteBalance, "WhiteBalance" },
{ tcDigitalZoomRatio, "DigitalZoomRatio" },
{ tcFocalLengthIn35mmFilm, "FocalLengthIn35mmFilm" },
{ tcSceneCaptureType, "SceneCaptureType" },
{ tcGainControl, "GainControl" },
{ tcContrast, "Contrast" },
{ tcSaturation, "Saturation" },
{ tcSharpness, "Sharpness" },
{ tcDeviceSettingDescription, "DeviceSettingDescription" },
{ tcSubjectDistanceRange, "SubjectDistanceRange" },
{ tcImageUniqueID, "ImageUniqueID" },
{ tcGamma, "Gamma" },
{ tcPrintImageMatchingInfo, "PrintImageMatchingInfo" },
{ tcDNGVersion, "DNGVersion" },
{ tcDNGBackwardVersion, "DNGBackwardVersion" },
{ tcUniqueCameraModel, "UniqueCameraModel" },
{ tcLocalizedCameraModel, "LocalizedCameraModel" },
{ tcCFAPlaneColor, "CFAPlaneColor" },
{ tcCFALayout, "CFALayout" },
{ tcLinearizationTable, "LinearizationTable" },
{ tcBlackLevelRepeatDim, "BlackLevelRepeatDim" },
{ tcBlackLevel, "BlackLevel" },
{ tcBlackLevelDeltaH, "BlackLevelDeltaH" },
{ tcBlackLevelDeltaV, "BlackLevelDeltaV" },
{ tcWhiteLevel, "WhiteLevel" },
{ tcDefaultScale, "DefaultScale" },
{ tcDefaultCropOrigin, "DefaultCropOrigin" },
{ tcDefaultCropSize, "DefaultCropSize" },
{ tcColorMatrix1, "ColorMatrix1" },
{ tcColorMatrix2, "ColorMatrix2" },
{ tcCameraCalibration1, "CameraCalibration1" },
{ tcCameraCalibration2, "CameraCalibration2" },
{ tcReductionMatrix1, "ReductionMatrix1" },
{ tcReductionMatrix2, "ReductionMatrix2" },
{ tcAnalogBalance, "AnalogBalance" },
{ tcAsShotNeutral, "AsShotNeutral" },
{ tcAsShotWhiteXY, "AsShotWhiteXY" },
{ tcBaselineExposure, "BaselineExposure" },
{ tcBaselineNoise, "BaselineNoise" },
{ tcBaselineSharpness, "BaselineSharpness" },
{ tcBayerGreenSplit, "BayerGreenSplit" },
{ tcLinearResponseLimit, "LinearResponseLimit" },
{ tcCameraSerialNumber, "CameraSerialNumber" },
{ tcLensInfo, "LensInfo" },
{ tcChromaBlurRadius, "ChromaBlurRadius" },
{ tcAntiAliasStrength, "AntiAliasStrength" },
{ tcShadowScale, "ShadowScale" },
{ tcDNGPrivateData, "DNGPrivateData" },
{ tcMakerNoteSafety, "MakerNoteSafety" },
{ tcCalibrationIlluminant1, "CalibrationIlluminant1" },
{ tcCalibrationIlluminant2, "CalibrationIlluminant2" },
{ tcBestQualityScale, "BestQualityScale" },
{ tcRawDataUniqueID, "RawDataUniqueID" },
{ tcOriginalRawFileName, "OriginalRawFileName" },
{ tcOriginalRawFileData, "OriginalRawFileData" },
{ tcActiveArea, "ActiveArea" },
{ tcMaskedAreas, "MaskedAreas" },
{ tcAsShotICCProfile, "AsShotICCProfile" },
{ tcAsShotPreProfileMatrix, "AsShotPreProfileMatrix" },
{ tcCurrentICCProfile, "CurrentICCProfile" },
{ tcCurrentPreProfileMatrix, "CurrentPreProfileMatrix" },
{ tcColorimetricReference, "ColorimetricReference" },
{ tcCameraCalibrationSignature, "CameraCalibrationSignature" },
{ tcProfileCalibrationSignature, "ProfileCalibrationSignature" },
{ tcExtraCameraProfiles, "ExtraCameraProfiles" },
{ tcAsShotProfileName, "AsShotProfileName" },
{ tcNoiseReductionApplied, "NoiseReductionApplied" },
{ tcProfileName, "ProfileName" },
{ tcProfileHueSatMapDims, "ProfileHueSatMapDims" },
{ tcProfileHueSatMapData1, "ProfileHueSatMapData1" },
{ tcProfileHueSatMapData2, "ProfileHueSatMapData2" },
{ tcProfileToneCurve, "ProfileToneCurve" },
{ tcProfileEmbedPolicy, "ProfileEmbedPolicy" },
{ tcProfileCopyright, "ProfileCopyright" },
{ tcForwardMatrix1, "ForwardMatrix1" },
{ tcForwardMatrix2, "ForwardMatrix2" },
{ tcPreviewApplicationName, "PreviewApplicationName" },
{ tcPreviewApplicationVersion, "PreviewApplicationVersion" },
{ tcPreviewSettingsName, "PreviewSettingsName" },
{ tcPreviewSettingsDigest, "PreviewSettingsDigest" },
{ tcPreviewColorSpace, "PreviewColorSpace" },
{ tcPreviewDateTime, "PreviewDateTime" },
{ tcRawImageDigest, "RawImageDigest" },
{ tcOriginalRawFileDigest, "OriginalRawFileDigest" },
{ tcSubTileBlockSize, "SubTileBlockSize" },
{ tcRowInterleaveFactor, "RowInterleaveFactor" },
{ tcProfileLookTableDims, "ProfileLookTableDims" },
{ tcProfileLookTableData, "ProfileLookTableData" },
{ tcOpcodeList1, "OpcodeList1" },
{ tcOpcodeList2, "OpcodeList2" },
{ tcOpcodeList3, "OpcodeList3" },
{ tcNoiseProfile, "NoiseProfile" },
{ tcKodakKDCPrivateIFD, "KodakKDCPrivateIFD" }
};
const dng_name_table kGPSTagNames [] =
{
{ tcGPSVersionID, "GPSVersionID" },
{ tcGPSLatitudeRef, "GPSLatitudeRef" },
{ tcGPSLatitude, "GPSLatitude" },
{ tcGPSLongitudeRef, "GPSLongitudeRef" },
{ tcGPSLongitude, "GPSLongitude" },
{ tcGPSAltitudeRef, "GPSAltitudeRef" },
{ tcGPSAltitude, "GPSAltitude" },
{ tcGPSTimeStamp, "GPSTimeStamp" },
{ tcGPSSatellites, "GPSSatellites" },
{ tcGPSStatus, "GPSStatus" },
{ tcGPSMeasureMode, "GPSMeasureMode" },
{ tcGPSDOP, "GPSDOP" },
{ tcGPSSpeedRef, "GPSSpeedRef" },
{ tcGPSSpeed, "GPSSpeed" },
{ tcGPSTrackRef, "GPSTrackRef" },
{ tcGPSTrack, "GPSTrack" },
{ tcGPSImgDirectionRef, "GPSImgDirectionRef" },
{ tcGPSImgDirection, "GPSImgDirection" },
{ tcGPSMapDatum, "GPSMapDatum" },
{ tcGPSDestLatitudeRef, "GPSDestLatitudeRef" },
{ tcGPSDestLatitude, "GPSDestLatitude" },
{ tcGPSDestLongitudeRef, "GPSDestLongitudeRef" },
{ tcGPSDestLongitude, "GPSDestLongitude" },
{ tcGPSDestBearingRef, "GPSDestBearingRef" },
{ tcGPSDestBearing, "GPSDestBearing" },
{ tcGPSDestDistanceRef, "GPSDestDistanceRef" },
{ tcGPSDestDistance, "GPSDestDistance" },
{ tcGPSProcessingMethod, "GPSProcessingMethod" },
{ tcGPSAreaInformation, "GPSAreaInformation" },
{ tcGPSDateStamp, "GPSDateStamp" },
{ tcGPSDifferential, "GPSDifferential" }
};
const dng_name_table kInteroperabilityTagNames [] =
{
{ tcInteroperabilityIndex, "InteroperabilityIndex" },
{ tcInteroperabilityVersion, "InteroperabilityVersion" },
{ tcRelatedImageFileFormat, "RelatedImageFileFormat" },
{ tcRelatedImageWidth, "RelatedImageWidth" },
{ tcRelatedImageLength, "RelatedImageLength" }
};
const dng_name_table kFujiTagNames [] =
{
{ tcFujiHeader, "FujiHeader" },
{ tcFujiRawInfo1, "FujiRawInfo1" },
{ tcFujiRawInfo2, "FujiRawInfo2" }
};
const dng_name_table kContaxTagNames [] =
{
{ tcContaxHeader, "ContaxHeader" }
};
const char *name = NULL;
if (parentCode == 0 ||
parentCode == tcExifIFD ||
parentCode == tcLeafMOS ||
parentCode >= tcFirstSubIFD && parentCode <= tcLastSubIFD ||
parentCode >= tcFirstChainedIFD && parentCode <= tcLastChainedIFD)
{
name = LookupName (tagCode,
kTagNames,
sizeof (kTagNames ) /
sizeof (kTagNames [0]));
}
else if (parentCode == tcGPSInfo)
{
name = LookupName (tagCode,
kGPSTagNames,
sizeof (kGPSTagNames ) /
sizeof (kGPSTagNames [0]));
}
else if (parentCode == tcInteroperabilityIFD)
{
name = LookupName (tagCode,
kInteroperabilityTagNames,
sizeof (kInteroperabilityTagNames ) /
sizeof (kInteroperabilityTagNames [0]));
}
else if (parentCode == tcFujiRAF)
{
name = LookupName (tagCode,
kFujiTagNames,
sizeof (kFujiTagNames ) /
sizeof (kFujiTagNames [0]));
}
else if (parentCode == tcContaxRAW)
{
name = LookupName (tagCode,
kContaxTagNames,
sizeof (kContaxTagNames ) /
sizeof (kContaxTagNames [0]));
}
if (name)
{
return name;
}
static char s [32];
if (parentCode == tcCanonCRW)
{
sprintf (s, "CRW_%04X", (unsigned) tagCode);
}
else if (parentCode == tcMinoltaMRW)
{
char c1 = (char) ((tagCode >> 24) & 0xFF);
char c2 = (char) ((tagCode >> 16) & 0xFF);
char c3 = (char) ((tagCode >> 8) & 0xFF);
char c4 = (char) ((tagCode ) & 0xFF);
if (c1 < ' ') c1 = '_';
if (c2 < ' ') c2 = '_';
if (c3 < ' ') c3 = '_';
if (c4 < ' ') c4 = '_';
sprintf (s, "MRW%c%c%c%c", c1, c2, c3, c4);
}
else if (parentCode == tcFujiRawInfo1)
{
sprintf (s, "RAF1_%04X", (unsigned) tagCode);
}
else if (parentCode == tcFujiRawInfo2)
{
sprintf (s, "RAF2_%04X", (unsigned) tagCode);
}
else
{
sprintf (s, "Tag%u", (unsigned) tagCode);
}
return s;
}
/*****************************************************************************/
const char * LookupTagType (uint32 tagType)
{
const dng_name_table kTagTypeNames [] =
{
{ ttByte, "Byte" },
{ ttAscii, "ASCII" },
{ ttShort, "Short" },
{ ttLong, "Long" },
{ ttRational, "Rational" },
{ ttSByte, "SByte" },
{ ttUndefined, "Undefined" },
{ ttSShort, "SShort" },
{ ttSLong, "SLong" },
{ ttSRational, "SRational" },
{ ttFloat, "Float" },
{ ttDouble, "Double" },
{ ttIFD, "IFD" },
{ ttUnicode, "Unicode" },
{ ttComplex, "Complex" }
};
const char *name = LookupName (tagType,
kTagTypeNames,
sizeof (kTagTypeNames ) /
sizeof (kTagTypeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "Type%u", (unsigned) tagType);
return s;
}
/*****************************************************************************/
const char * LookupNewSubFileType (uint32 key)
{
const dng_name_table kNewSubFileTypeNames [] =
{
{ sfMainImage , "Main Image" },
{ sfPreviewImage , "Preview Image" },
{ sfAltPreviewImage, "Alt Preview Image" }
};
const char *name = LookupName (key,
kNewSubFileTypeNames,
sizeof (kNewSubFileTypeNames ) /
sizeof (kNewSubFileTypeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupCompression (uint32 key)
{
const dng_name_table kCompressionNames [] =
{
{ ccUncompressed, "Uncompressed" },
{ ccLZW, "LZW" },
{ ccOldJPEG, "Old JPEG" },
{ ccJPEG, "JPEG" },
{ ccDeflate, "Deflate" },
{ ccPackBits, "PackBits" },
{ ccOldDeflate, "OldDeflate" }
};
const char *name = LookupName (key,
kCompressionNames,
sizeof (kCompressionNames ) /
sizeof (kCompressionNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupPhotometricInterpretation (uint32 key)
{
const dng_name_table kPhotometricInterpretationNames [] =
{
{ piWhiteIsZero, "WhiteIsZero" },
{ piBlackIsZero, "BlackIsZero" },
{ piRGB, "RGB" },
{ piRGBPalette, "RGBPalette" },
{ piTransparencyMask, "TransparencyMask" },
{ piCMYK, "CMYK" },
{ piYCbCr, "YCbCr" },
{ piCIELab, "CIELab" },
{ piICCLab, "ICCLab" },
{ piCFA, "CFA" },
{ piLinearRaw, "LinearRaw" }
};
const char *name = LookupName (key,
kPhotometricInterpretationNames,
sizeof (kPhotometricInterpretationNames ) /
sizeof (kPhotometricInterpretationNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupOrientation (uint32 key)
{
const dng_name_table kOrientationNames [] =
{
{ 1, "1 - 0th row is top, 0th column is left" },
{ 2, "2 - 0th row is top, 0th column is right" },
{ 3, "3 - 0th row is bottom, 0th column is right" },
{ 4, "4 - 0th row is bottom, 0th column is left" },
{ 5, "5 - 0th row is left, 0th column is top" },
{ 6, "6 - 0th row is right, 0th column is top" },
{ 7, "7 - 0th row is right, 0th column is bottom" },
{ 8, "8 - 0th row is left, 0th column is bottom" },
{ 9, "9 - unknown" }
};
const char *name = LookupName (key,
kOrientationNames,
sizeof (kOrientationNames ) /
sizeof (kOrientationNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupResolutionUnit (uint32 key)
{
const dng_name_table kResolutionUnitNames [] =
{
{ ruNone, "None" },
{ ruInch, "Inch" },
{ ruCM, "cm" },
{ ruMM, "mm" },
{ ruMicroM, "Micrometer" }
};
const char *name = LookupName (key,
kResolutionUnitNames,
sizeof (kResolutionUnitNames ) /
sizeof (kResolutionUnitNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupCFAColor (uint32 key)
{
const dng_name_table kCFAColorNames [] =
{
{ 0, "Red" },
{ 1, "Green" },
{ 2, "Blue" },
{ 3, "Cyan" },
{ 4, "Magenta" },
{ 5, "Yellow" },
{ 6, "White" }
};
const char *name = LookupName (key,
kCFAColorNames,
sizeof (kCFAColorNames ) /
sizeof (kCFAColorNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "Color%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupSensingMethod (uint32 key)
{
const dng_name_table kSensingMethodNames [] =
{
{ 0, "Undefined" },
{ 1, "MonochromeArea" },
{ 2, "OneChipColorArea" },
{ 3, "TwoChipColorArea" },
{ 4, "ThreeChipColorArea" },
{ 5, "ColorSequentialArea" },
{ 6, "MonochromeLinear" },
{ 7, "TriLinear" },
{ 8, "ColorSequentialLinear" }
};
const char *name = LookupName (key,
kSensingMethodNames,
sizeof (kSensingMethodNames ) /
sizeof (kSensingMethodNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupExposureProgram (uint32 key)
{
const dng_name_table kExposureProgramNames [] =
{
{ epUnidentified, "Unidentified" },
{ epManual, "Manual" },
{ epProgramNormal, "Program Normal" },
{ epAperturePriority, "Aperture Priority" },
{ epShutterPriority, "Shutter Priority" },
{ epProgramCreative, "Program Creative" },
{ epProgramAction, "Program Action" },
{ epPortraitMode, "Portrait Mode" },
{ epLandscapeMode, "Landscape Mode" }
};
const char *name = LookupName (key,
kExposureProgramNames,
sizeof (kExposureProgramNames ) /
sizeof (kExposureProgramNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupMeteringMode (uint32 key)
{
const dng_name_table kMeteringModeNames [] =
{
{ mmUnidentified, "Unknown" },
{ mmAverage, "Average" },
{ mmCenterWeightedAverage, "CenterWeightedAverage" },
{ mmSpot, "Spot" },
{ mmMultiSpot, "MultiSpot" },
{ mmPattern, "Pattern" },
{ mmPartial, "Partial" },
{ mmOther, "Other" }
};
const char *name = LookupName (key,
kMeteringModeNames,
sizeof (kMeteringModeNames ) /
sizeof (kMeteringModeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupLightSource (uint32 key)
{
const dng_name_table kLightSourceNames [] =
{
{ lsUnknown, "Unknown" },
{ lsDaylight, "Daylight" },
{ lsFluorescent, "Fluorescent" },
{ lsTungsten, "Tungsten (incandescent light)" },
{ lsFlash, "Flash" },
{ lsFineWeather, "Fine weather" },
{ lsCloudyWeather, "Cloudy weather" },
{ lsShade, "Shade" },
{ lsDaylightFluorescent, "Daylight fluorescent (D 5700 - 7100K)" },
{ lsDayWhiteFluorescent, "Day white fluorescent (N 4600 - 5400K)" },
{ lsCoolWhiteFluorescent, "Cool white fluorescent (W 3900 - 4500K)" },
{ lsWhiteFluorescent, "White fluorescent (WW 3200 - 3700K)" },
{ lsStandardLightA, "Standard light A" },
{ lsStandardLightB, "Standard light B" },
{ lsStandardLightC, "Standard light C" },
{ lsD55, "D55" },
{ lsD65, "D65" },
{ lsD75, "D75" },
{ lsD50, "D50" },
{ lsISOStudioTungsten, "ISO studio tungsten" },
{ lsOther, "Other" }
};
const char *name = LookupName (key,
kLightSourceNames,
sizeof (kLightSourceNames ) /
sizeof (kLightSourceNames [0]));
if (name)
{
return name;
}
static char s [32];
if (key & 0x08000)
{
sprintf (s, "%uK", (unsigned) (key & 0x7FFF));
}
else
{
sprintf (s, "%u", (unsigned) key);
}
return s;
}
/*****************************************************************************/
const char * LookupColorSpace (uint32 key)
{
const dng_name_table kColorSpaceNames [] =
{
{ 1, "sRGB" },
{ 0xFFFF, "Uncalibrated" }
};
const char *name = LookupName (key,
kColorSpaceNames,
sizeof (kColorSpaceNames ) /
sizeof (kColorSpaceNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupFileSource (uint32 key)
{
const dng_name_table kFileSourceNames [] =
{
{ 3, "DSC" }
};
const char *name = LookupName (key,
kFileSourceNames,
sizeof (kFileSourceNames ) /
sizeof (kFileSourceNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupSceneType (uint32 key)
{
const dng_name_table kSceneTypeNames [] =
{
{ 1, "A directly photographed image" }
};
const char *name = LookupName (key,
kSceneTypeNames,
sizeof (kSceneTypeNames ) /
sizeof (kSceneTypeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupCustomRendered (uint32 key)
{
const dng_name_table kCustomRenderedNames [] =
{
{ 0, "Normal process" },
{ 1, "Custom process" }
};
const char *name = LookupName (key,
kCustomRenderedNames,
sizeof (kCustomRenderedNames ) /
sizeof (kCustomRenderedNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupExposureMode (uint32 key)
{
const dng_name_table kExposureModeNames [] =
{
{ 0, "Auto exposure" },
{ 1, "Manual exposure" },
{ 2, "Auto bracket" }
};
const char *name = LookupName (key,
kExposureModeNames,
sizeof (kExposureModeNames ) /
sizeof (kExposureModeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupWhiteBalance (uint32 key)
{
const dng_name_table kWhiteBalanceNames [] =
{
{ 0, "Auto white balance" },
{ 1, "Manual white balance" }
};
const char *name = LookupName (key,
kWhiteBalanceNames,
sizeof (kWhiteBalanceNames ) /
sizeof (kWhiteBalanceNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupSceneCaptureType (uint32 key)
{
const dng_name_table kSceneCaptureTypeNames [] =
{
{ 0, "Standard" },
{ 1, "Landscape" },
{ 2, "Portrait" },
{ 3, "Night scene" }
};
const char *name = LookupName (key,
kSceneCaptureTypeNames,
sizeof (kSceneCaptureTypeNames ) /
sizeof (kSceneCaptureTypeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupGainControl (uint32 key)
{
const dng_name_table kGainControlNames [] =
{
{ 0, "None" },
{ 1, "Low gain up" },
{ 2, "High gain up" },
{ 3, "Low gain down" },
{ 4, "High gain down" }
};
const char *name = LookupName (key,
kGainControlNames,
sizeof (kGainControlNames ) /
sizeof (kGainControlNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupContrast (uint32 key)
{
const dng_name_table kContrastNames [] =
{
{ 0, "Normal" },
{ 1, "Soft" },
{ 2, "Hard" }
};
const char *name = LookupName (key,
kContrastNames,
sizeof (kContrastNames ) /
sizeof (kContrastNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupSaturation (uint32 key)
{
const dng_name_table kSaturationNames [] =
{
{ 0, "Normal" },
{ 1, "Low saturation" },
{ 2, "High saturation" }
};
const char *name = LookupName (key,
kSaturationNames,
sizeof (kSaturationNames ) /
sizeof (kSaturationNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupSharpness (uint32 key)
{
const dng_name_table kSharpnessNames [] =
{
{ 0, "Normal" },
{ 1, "Soft" },
{ 2, "Hard" }
};
const char *name = LookupName (key,
kSharpnessNames,
sizeof (kSharpnessNames ) /
sizeof (kSharpnessNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupSubjectDistanceRange (uint32 key)
{
const dng_name_table kSubjectDistanceRangeNames [] =
{
{ 0, "Unknown" },
{ 1, "Macro" },
{ 2, "Close view" },
{ 3, "Distant view" }
};
const char *name = LookupName (key,
kSubjectDistanceRangeNames,
sizeof (kSubjectDistanceRangeNames ) /
sizeof (kSubjectDistanceRangeNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupComponent (uint32 key)
{
const dng_name_table kComponentNames [] =
{
{ 0, "-" },
{ 1, "Y" },
{ 2, "Cb" },
{ 3, "Cr" },
{ 4, "R" },
{ 5, "G" },
{ 6, "B" }
};
const char *name = LookupName (key,
kComponentNames,
sizeof (kComponentNames ) /
sizeof (kComponentNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupCFALayout (uint32 key)
{
const dng_name_table kCFALayoutNames [] =
{
{ 1, "Rectangular (or square) layout" },
{ 2, "Staggered layout A: even columns are offset down by 1/2 row" },
{ 3, "Staggered layout B: even columns are offset up by 1/2 row" },
{ 4, "Staggered layout C: even rows are offset right by 1/2 column" },
{ 5, "Staggered layout D: even rows are offset left by 1/2 column" },
{ 6, "Staggered layout E: even rows are offset up by 1/2 row, even columns are offset left by 1/2 column" },
{ 7, "Staggered layout F: even rows are offset up by 1/2 row, even columns are offset right by 1/2 column" },
{ 8, "Staggered layout G: even rows are offset down by 1/2 row, even columns are offset left by 1/2 column" },
{ 9, "Staggered layout H: even rows are offset down by 1/2 row, even columns are offset right by 1/2 column" }
};
const char *name = LookupName (key,
kCFALayoutNames,
sizeof (kCFALayoutNames ) /
sizeof (kCFALayoutNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupMakerNoteSafety (uint32 key)
{
const dng_name_table kMakerNoteSafetyNames [] =
{
{ 0, "Unsafe" },
{ 1, "Safe" }
};
const char *name = LookupName (key,
kMakerNoteSafetyNames,
sizeof (kMakerNoteSafetyNames ) /
sizeof (kMakerNoteSafetyNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupColorimetricReference (uint32 key)
{
const dng_name_table kColorimetricReferenceNames [] =
{
{ crSceneReferred, "Scene Referred" },
{ crICCProfilePCS, "ICC Profile PCS" }
};
const char *name = LookupName (key,
kColorimetricReferenceNames,
sizeof (kColorimetricReferenceNames ) /
sizeof (kColorimetricReferenceNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupPreviewColorSpace (uint32 key)
{
const dng_name_table kPreviewColorSpaceNames [] =
{
{ previewColorSpace_Unknown , "Unknown" },
{ previewColorSpace_GrayGamma22, "Gray Gamma 2.2" },
{ previewColorSpace_sRGB , "sRGB" },
{ previewColorSpace_AdobeRGB , "Adobe RGB (1998)" },
{ previewColorSpace_ProPhotoRGB, "Pro Photo RGB" }
};
const char *name = LookupName (key,
kPreviewColorSpaceNames,
sizeof (kPreviewColorSpaceNames ) /
sizeof (kPreviewColorSpaceNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "%u", (unsigned) key);
return s;
}
/*****************************************************************************/
const char * LookupJPEGMarker (uint32 key)
{
const dng_name_table kJPEGMarkerNames [] =
{
{ M_TEM, "TEM" },
{ M_SOF0, "SOF0" },
{ M_SOF1, "SOF1" },
{ M_SOF2, "SOF2" },
{ M_SOF3, "SOF3" },
{ M_DHT, "DHT" },
{ M_SOF5, "SOF5" },
{ M_SOF6, "SOF6" },
{ M_SOF7, "SOF7" },
{ M_JPG, "JPG" },
{ M_SOF9, "SOF9" },
{ M_SOF10, "SOF10" },
{ M_SOF11, "SOF11" },
{ M_DAC, "DAC" },
{ M_SOF13, "SOF13" },
{ M_SOF14, "SOF14" },
{ M_SOF15, "SOF15" },
{ M_RST0, "RST0" },
{ M_RST1, "RST1" },
{ M_RST2, "RST2" },
{ M_RST3, "RST3" },
{ M_RST4, "RST4" },
{ M_RST5, "RST5" },
{ M_RST6, "RST6" },
{ M_RST7, "RST7" },
{ M_SOI, "SOI" },
{ M_EOI, "EOI" },
{ M_SOS, "SOS" },
{ M_DQT, "DQT" },
{ M_DNL, "DNL" },
{ M_DRI, "DRI" },
{ M_DHP, "DHP" },
{ M_EXP, "EXP" },
{ M_APP0, "APP0" },
{ M_APP1, "APP1" },
{ M_APP2, "APP2" },
{ M_APP3, "APP3" },
{ M_APP4, "APP4" },
{ M_APP5, "APP5" },
{ M_APP6, "APP6" },
{ M_APP7, "APP7" },
{ M_APP8, "APP8" },
{ M_APP9, "APP9" },
{ M_APP10, "APP10" },
{ M_APP11, "APP11" },
{ M_APP12, "APP12" },
{ M_APP13, "APP13" },
{ M_APP14, "APP14" },
{ M_APP15, "APP15" },
{ M_JPG0, "JPG0" },
{ M_JPG1, "JPG1" },
{ M_JPG2, "JPG2" },
{ M_JPG3, "JPG3" },
{ M_JPG4, "JPG4" },
{ M_JPG5, "JPG5" },
{ M_JPG6, "JPG6" },
{ M_JPG7, "JPG7" },
{ M_JPG8, "JPG8" },
{ M_JPG9, "JPG9" },
{ M_JPG10, "JPG10" },
{ M_JPG11, "JPG11" },
{ M_JPG12, "JPG12" },
{ M_JPG13, "JPG13" },
{ M_COM, "COM" },
{ M_ERROR, "ERROR" }
};
const char *name = LookupName (key,
kJPEGMarkerNames,
sizeof (kJPEGMarkerNames ) /
sizeof (kJPEGMarkerNames [0]));
if (name)
{
return name;
}
static char s [32];
sprintf (s, "0x%02X", (unsigned) key);
return s;
}
/*****************************************************************************/
void DumpHexAscii (dng_stream &stream,
uint32 count)
{
uint32 rows = (count + 15) >> 4;
if (rows > gDumpLineLimit)
rows = gDumpLineLimit;
for (uint32 row = 0; row < rows; row++)
{
printf (" ");
uint32 col;
uint32 cols = count - (row << 4);
if (cols > 16)
cols = 16;
uint8 x [16];
for (col = 0; col < 16; col++)
{
x [col] = ' ';
if (col < cols)
{
x [col] = stream.Get_uint8 ();
printf ("%02x ", x [col]);
}
else
{
printf (" ");
}
}
printf (" ");
for (col = 0; col < 16; col++)
{
if (x [col] >= (uint8) ' ' && x [col] <= (uint8) '~')
{
printf ("%c", x [col]);
}
else
{
printf (".");
}
}
printf ("\n");
}
if (count > rows * 16)
{
printf (" ... %u more bytes\n", (unsigned) (count - rows * 16));
}
}
/*****************************************************************************/
void DumpHexAscii (const uint8 *buf,
uint32 count)
{
uint32 rows = (count + 15) >> 4;
if (rows > gDumpLineLimit)
rows = gDumpLineLimit;
for (uint32 row = 0; row < rows; row++)
{
printf (" ");
uint32 col;
uint32 cols = count - (row << 4);
if (cols > 16)
cols = 16;
uint8 x [16];
for (col = 0; col < 16; col++)
{
x [col] = ' ';
if (col < cols)
{
x [col] = *(buf++);
printf ("%02x ", x [col]);
}
else
{
printf (" ");
}
}
printf (" ");
for (col = 0; col < 16; col++)
{
if (x [col] >= (uint8) ' ' && x [col] <= (uint8) '~')
{
printf ("%c", x [col]);
}
else
{
printf (".");
}
}
printf ("\n");
}
if (count > rows * 16)
{
printf (" ... %u more bytes\n", (unsigned) (count - rows * 16));
}
}
/*****************************************************************************/
void DumpXMP (dng_stream &stream,
uint32 count)
{
uint32 lineLength = 0;
while (count > 0)
{
uint32 x = stream.Get_uint8 ();
if (x == 0) break;
count--;
if (lineLength == 0)
{
printf ("XMP: ");
lineLength = 5;
}
if (x == '\n' ||
x == '\r')
{
printf ("\n");
lineLength = 0;
}
else
{
if (lineLength >= 128)
{
printf ("\nXMP: ");
lineLength = 5;
}
if (x >= ' ' && x <= '~')
{
printf ("%c", (char) x);
lineLength += 1;
}
else
{
printf ("\\%03o", (unsigned) x);
lineLength += 4;
}
}
}
if (lineLength != 0)
{
printf ("\n");
}
}
/*****************************************************************************/
void DumpString (const dng_string &s)
{
const uint32 kMaxDumpString = gDumpLineLimit * 64;
printf ("\"");
const char *ss = s.Get ();
uint32 total = 0;
while (*ss != 0 && total++ < kMaxDumpString)
{
uint32 c = dng_string::DecodeUTF8 (ss);
if (c >= ' ' && c <= '~')
{
printf ("%c", (char) c);
}
else switch (c)
{
case '\t':
{
printf ("\\t");
break;
}
case '\n':
{
printf ("\\n");
break;
}
case '\r':
{
printf ("\\r");
break;
}
default:
{
printf ("[%X]", (unsigned) c);
break;
}
}
}
uint32 extra = (uint32) strlen (ss);
if (extra > 0)
{
printf ("...\" (%u more bytes)", (unsigned) extra);
}
else
{
printf ("\"");
}
}
/*****************************************************************************/
void DumpTagValues (dng_stream &stream,
const char *entry_name,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
const char *tag_name)
{
const uint32 kMaxDumpSingleLine = 4;
const uint32 kMaxDumpArray = Max_uint32 (gDumpLineLimit, kMaxDumpSingleLine);
printf ("%s:", tag_name ? tag_name
: LookupTagCode (parentCode, tagCode));
switch (tagType)
{
case ttShort:
case ttLong:
case ttIFD:
case ttSByte:
case ttSShort:
case ttSLong:
case ttRational:
case ttSRational:
case ttFloat:
case ttDouble:
{
if (tagCount > kMaxDumpSingleLine)
{
printf (" %u entries", (unsigned) tagCount);
}
for (uint32 j = 0; j < tagCount && j < kMaxDumpArray; j++)
{
if (tagCount <= kMaxDumpSingleLine)
{
if (j == 0)
{
printf (" %s =", entry_name);
}
printf (" ");
}
else
{
printf ("\n %s [%u] = ", entry_name, (unsigned) j);
}
switch (tagType)
{
case ttByte:
case ttShort:
case ttLong:
case ttIFD:
{
uint32 x = stream.TagValue_uint32 (tagType);
printf ("%u", (unsigned) x);
break;
}
case ttSByte:
case ttSShort:
case ttSLong:
{
int32 x = stream.TagValue_int32 (tagType);
printf ("%d", (int) x);
break;
}
case ttRational:
{
dng_urational x = stream.TagValue_urational (tagType);
printf ("%u/%u", (unsigned) x.n, (unsigned) x.d);
break;
}
case ttSRational:
{
dng_srational x = stream.TagValue_srational (tagType);
printf ("%d/%d", (int) x.n, (int) x.d);
break;
}
default:
{
real64 x = stream.TagValue_real64 (tagType);
printf ("%f", x);
}
}
}
printf ("\n");
if (tagCount > kMaxDumpArray)
{
printf (" ... %u more entries\n", (unsigned) (tagCount - kMaxDumpArray));
}
break;
}
case ttAscii:
{
dng_string s;
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
s,
false);
printf (" ");
DumpString (s);
printf ("\n");
break;
}
default:
{
uint32 tagSize = tagCount * TagTypeSize (tagType);
if (tagCount == 1 && (tagType == ttByte ||
tagType == ttUndefined))
{
uint8 x = stream.Get_uint8 ();
printf (" %s = %u\n", LookupTagType (tagType), x);
}
else
{
printf (" %s, size = %u\n", LookupTagType (tagType), (unsigned) tagSize);
DumpHexAscii (stream, tagSize);
}
break;
}
}
}
/*****************************************************************************/
void DumpMatrix (const dng_matrix &m)
{
for (uint32 row = 0; row < m.Rows (); row++)
{
for (uint32 col = 0; col < m.Cols (); col++)
{
if (col == 0)
printf (" ");
else
printf (" ");
printf ("%8.4f", m [row] [col]);
}
printf ("\n");
}
}
/*****************************************************************************/
void DumpVector (const dng_vector &v)
{
for (uint32 index = 0; index < v.Count (); index++)
{
printf (" %0.4f", v [index]);
}
printf ("\n");
}
/*****************************************************************************/
void DumpDateTime (const dng_date_time &dt)
{
printf ("%04d:%02d:%02d %02d:%02d:%02d",
(int) dt.fYear,
(int) dt.fMonth,
(int) dt.fDay,
(int) dt.fHour,
(int) dt.fMinute,
(int) dt.fSecond);
}
/*****************************************************************************/
void DumpExposureTime (real64 x)
{
if (x > 0.0)
{
if (x >= 0.25)
{
printf ("%0.2f sec", x);
}
else if (x >= 0.01)
{
printf ("1/%0.1f sec", 1.0 / x);
}
else
{
printf ("1/%0.0f sec", 1.0 / x);
}
}
else
{
printf ("<invalid>");
}
}
/*****************************************************************************/
void DumpFingerprint (const dng_fingerprint &p)
{
printf ("<");
for (uint32 j = 0; j < 16; j++)
{
printf ("%02x", p.data [j]);
}
printf (">");
}
/*****************************************************************************/
void DumpHueSatMap (dng_stream &stream,
uint32 hues,
uint32 sats,
uint32 vals,
bool skipSat0)
{
uint32 doneLines = 0;
uint32 skipLines = 0;
for (uint32 v = 0; v < vals; v++)
{
for (uint32 h = 0; h < hues; h++)
{
for (uint32 s = skipSat0 ? 1 : 0; s < sats; s++)
{
real32 dh = stream.Get_real32 ();
real32 ds = stream.Get_real32 ();
real32 dv = stream.Get_real32 ();
if (gDumpLineLimit == 0 ||
gDumpLineLimit > doneLines)
{
doneLines++;
if (vals == 1)
{
printf (" h [%2u] s [%2u]: h=%8.4f s=%6.4f v=%6.4f\n",
(unsigned) h,
(unsigned) s,
(double) dh,
(double) ds,
(double) dv);
}
else
{
printf (" v [%2u] h [%2u] s [%2u]: h=%8.4f s=%6.4f v=%6.4f\n",
(unsigned) v,
(unsigned) h,
(unsigned) s,
(double) dh,
(double) ds,
(double) dv);
}
}
else
{
skipLines++;
}
}
}
}
if (skipLines > 0)
{
printf (" ... %u more entries\n", (unsigned) skipLines);
}
}
/*****************************************************************************/
#endif
/*****************************************************************************/
bool CheckTagType (uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint16 validType0,
uint16 validType1,
uint16 validType2,
uint16 validType3)
{
if (tagType != validType0 &&
tagType != validType1 &&
tagType != validType2 &&
tagType != validType3)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s has unexpected type (%s)",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode),
LookupTagType (tagType));
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
return false;
}
return true;
}
/*****************************************************************************/
bool CheckTagCount (uint32 parentCode,
uint32 tagCode,
uint32 tagCount,
uint32 minCount,
uint32 maxCount)
{
if (maxCount < minCount)
maxCount = minCount;
if (tagCount < minCount ||
tagCount > maxCount)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s has unexpected count (%u)",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode),
(unsigned) tagCount);
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
return false;
}
return true;
}
/*****************************************************************************/
bool CheckColorImage (uint32 parentCode,
uint32 tagCode,
uint32 colorPlanes)
{
if (colorPlanes == 0)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s is not allowed with unknown color plane count "
" (missing ColorMatrix1 tag?)",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
return false;
}
if (colorPlanes == 1)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s is not allowed with monochrome images",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#endif
return false;
}
return true;
}
/*****************************************************************************/
bool CheckMainIFD (uint32 parentCode,
uint32 tagCode,
uint32 newSubFileType)
{
if (newSubFileType != sfMainImage)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s is not allowed IFDs with NewSubFileType != 0",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
return false;
}
return true;
}
/*****************************************************************************/
bool CheckRawIFD (uint32 parentCode,
uint32 tagCode,
uint32 photometricInterpretation)
{
if (photometricInterpretation != piCFA &&
photometricInterpretation != piLinearRaw)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s is not allowed in IFDs with a non-raw PhotometricInterpretation",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
return false;
}
return true;
}
/*****************************************************************************/
bool CheckCFA (uint32 parentCode,
uint32 tagCode,
uint32 photometricInterpretation)
{
if (photometricInterpretation != piCFA)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s is not allowed in IFDs with a non-CFA PhotometricInterpretation",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
return false;
}
return true;
}
/*****************************************************************************/
void ParseStringTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagCount,
dng_string &s,
bool trimBlanks,
bool isASCII)
{
if (tagCount == 0 ||
tagCount == 0xFFFFFFFF)
{
s.Clear ();
return;
}
dng_memory_data temp_buffer (tagCount + 1);
char *buffer = temp_buffer.Buffer_char ();
stream.Get (buffer, tagCount);
// Make sure the string is null terminated.
if (buffer [tagCount - 1] != 0)
{
buffer [tagCount] = 0;
#if qDNGValidate
{
bool hasNull = false;
for (uint32 j = 0; j < tagCount; j++)
{
if (buffer [j] == 0)
{
hasNull = true;
break;
}
}
if (!hasNull && parentCode < tcFirstMakerNoteIFD)
{
char message [256];
sprintf (message,
"%s %s is not NULL terminated",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
}
if (isASCII)
{
s.Set_ASCII (buffer);
}
else
{
s.Set (buffer);
}
#if qDNGValidate
if (parentCode < tcFirstMakerNoteIFD)
{
if (isASCII && !s.IsASCII ())
{
char message [256];
sprintf (message,
"%s %s has non-ASCII characters",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
#endif
if (trimBlanks)
{
s.TrimTrailingBlanks ();
}
}
/*****************************************************************************/
void ParseDualStringTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagCount,
dng_string &s1,
dng_string &s2)
{
if (tagCount == 0 ||
tagCount == 0xFFFFFFFF)
{
s1.Clear ();
s2.Clear ();
return;
}
dng_memory_data temp_buffer (tagCount + 1);
char *buffer = temp_buffer.Buffer_char ();
stream.Get (buffer, tagCount);
// Make sure the string is null terminated.
if (buffer [tagCount - 1] != 0)
{
buffer [tagCount] = 0;
#if qDNGValidate
{
uint32 nullCount = 0;
for (uint32 j = 0; j < tagCount; j++)
{
if (buffer [j] == 0)
{
nullCount++;
}
}
if (nullCount < 2 && parentCode < tcFirstMakerNoteIFD)
{
char message [256];
sprintf (message,
"%s %s is not NULL terminated",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
}
s1.Set_ASCII (buffer);
s2.Set_ASCII (NULL );
for (uint32 j = 1; j < tagCount - 1; j++)
{
if (buffer [j - 1] != 0 &&
buffer [j ] == 0)
{
s2.Set_ASCII (buffer + j + 1);
break;
}
}
#if qDNGValidate
{
if (!s1.IsASCII () ||
!s2.IsASCII ())
{
char message [256];
sprintf (message,
"%s %s has non-ASCII characters",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
#endif
s1.TrimTrailingBlanks ();
s2.TrimTrailingBlanks ();
}
/*****************************************************************************/
void ParseEncodedStringTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagCount,
dng_string &s)
{
if (tagCount < 8)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s has unexpected count (%u)",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode),
(unsigned) tagCount);
ReportWarning (message);
}
#else
parentCode; // Unused
tagCode; // Unused
#endif
s.Clear ();
return;
}
char label [8];
stream.Get (label, 8);
// Sometimes lowercase is used by mistake. Accept this, but issue
// warning.
{
bool hadLower = false;
for (uint32 j = 0; j < 8; j++)
{
if (label [j] >= 'a' && label [j] <= 'z')
{
label [j] = 'A' + (label [j] - 'a');
hadLower = true;
}
}
#if qDNGValidate
if (hadLower)
{
char message [256];
sprintf (message,
"%s %s text encoding label not all uppercase",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#endif
}
if (memcmp (label, "UNICODE\000", 8) == 0)
{
uint32 uChars = (tagCount - 8) >> 1;
dng_memory_data temp_buffer ((uChars + 1) * 2);
uint16 *buffer = temp_buffer.Buffer_uint16 ();
for (uint32 j = 0; j < uChars; j++)
{
buffer [j] = stream.Get_uint16 ();
}
buffer [uChars] = 0;
#if qDNGValidate
{
// If the writer used UTF-8 rather than UTF-16, and padded
// the string with blanks, then there will be lots of 0x2020
// (unicode dagger symbol) characters in the string.
uint32 count2020 = 0;
for (uint32 k = 0; buffer [k] != 0; k++)
{
if (buffer [k] == 0x2020)
{
count2020++;
}
}
if (count2020 > 1)
{
char message [256];
sprintf (message,
"%s %s text appears to be UTF-8 rather than UTF-16",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
#endif
s.Set_UTF16 (buffer);
}
else
{
uint32 aChars = tagCount - 8;
dng_memory_data temp_buffer (aChars + 1);
char *buffer = temp_buffer.Buffer_char ();
stream.Get (buffer, aChars);
buffer [aChars] = 0;
enum dng_encoding
{
dng_encoding_ascii,
dng_encoding_jis_x208_1990,
dng_encoding_unknown
};
dng_encoding encoding = dng_encoding_unknown;
if (memcmp (label, "ASCII\000\000\000", 8) == 0)
{
encoding = dng_encoding_ascii;
}
else if (memcmp (label, "JIS\000\000\000\000\000\000", 8) == 0)
{
encoding = dng_encoding_jis_x208_1990;
}
else
{
// Some Nikon D1 files have UserComment tags with zero encoding bits and
// garbage text values. So don't try to parse tags with unknown text
// encoding unless all the characters are printing ASCII.
#if qDNGValidate
if (memcmp (label, "\000\000\000\000\000\000\000\000\000", 8) == 0)
{
// Many camera makes store null tags with all zero encoding, so
// don't report a warning message for null strings.
if (buffer [0] != 0)
{
char message [256];
sprintf (message,
"%s %s has unknown encoding",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
else
{
char message [256];
sprintf (message,
"%s %s has unexpected text encoding",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#endif
}
// If text encoding was unknown, and the text is anything
// other than pure ASCII, then ignore it.
if (encoding == dng_encoding_unknown)
{
encoding = dng_encoding_ascii;
for (uint32 i = 0; i < aChars && buffer [i] != 0; i++)
{
if (buffer [i] < ' ' ||
buffer [i] > '~')
{
buffer [0] = 0;
break;
}
}
}
switch (encoding)
{
case dng_encoding_ascii:
{
s.Set_ASCII (buffer);
break;
}
case dng_encoding_jis_x208_1990:
{
s.Set_JIS_X208_1990 (buffer);
break;
}
case dng_encoding_unknown:
{
s.Set_SystemEncoding (buffer);
break;
}
default:
break;
}
#if qDNGValidate
{
if (encoding == dng_encoding_ascii && !s.IsASCII ())
{
char message [256];
sprintf (message,
"%s %s has non-ASCII characters",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
}
#endif
}
s.TrimTrailingBlanks ();
}
/*****************************************************************************/
bool ParseMatrixTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint32 rows,
uint32 cols,
dng_matrix &m)
{
if (CheckTagCount (parentCode, tagCode, tagCount, rows * cols))
{
dng_matrix temp (rows, cols);
for (uint32 row = 0; row < rows; row++)
for (uint32 col = 0; col < cols; col++)
{
temp [row] [col] = stream.TagValue_real64 (tagType);
}
m = temp;
return true;
}
return false;
}
/*****************************************************************************/
bool ParseVectorTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint32 count,
dng_vector &v)
{
if (CheckTagCount (parentCode, tagCode, tagCount, count))
{
dng_vector temp (count);
for (uint32 index = 0; index < count; index++)
{
temp [index] = stream.TagValue_real64 (tagType);
}
v = temp;
return true;
}
return false;
}
/*****************************************************************************/
bool ParseDateTimeTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
dng_date_time &dt)
{
if (!CheckTagType (parentCode, tagCode, tagType, ttAscii))
{
return false;
}
// Kludge: Some versions of PaintShop Pro write these fields
// with a length of 21 rather than 20. Otherwise they are
- // correctly formated. So relax this test and allow these
+ // correctly formatted. So relax this test and allow these
// these longer than standard tags to be parsed.
(void) CheckTagCount (parentCode, tagCode, tagCount, 20);
if (tagCount < 20)
{
return false;
}
char s [21];
stream.Get (s, 20);
s [20] = 0;
// See if this is a valid date/time string.
if (dt.Parse (s))
{
return true;
}
// Accept strings that contain only blanks, colons, and zeros as
// valid "null" dates.
dt = dng_date_time ();
for (uint32 index = 0; index < 21; index++)
{
char c = s [index];
if (c == 0)
{
return true;
}
if (c != ' ' && c != ':' && c != '0')
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s is not a valid date/time",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode));
ReportWarning (message);
}
#endif
return false;
}
}
return false;
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_render.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_render.cpp
index 5b8c12abaa..a41882e6b5 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_render.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_render.cpp
@@ -1,1281 +1,1281 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_render.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_render.h"
#include "dng_1d_table.h"
#include "dng_bottlenecks.h"
#include "dng_camera_profile.h"
#include "dng_color_space.h"
#include "dng_color_spec.h"
#include "dng_filter_task.h"
#include "dng_host.h"
#include "dng_image.h"
#include "dng_negative.h"
#include "dng_resample.h"
#include "dng_utils.h"
/*****************************************************************************/
dng_function_exposure_ramp::dng_function_exposure_ramp (real64 white,
real64 black,
real64 minBlack)
: fSlope (1.0 / (white - black))
, fBlack (black)
, fRadius (0.0)
, fQScale (0.0)
{
const real64 kMaxCurveX = 0.5; // Fraction of minBlack.
const real64 kMaxCurveY = 1.0 / 16.0; // Fraction of white.
fRadius = Min_real64 (kMaxCurveX * minBlack,
kMaxCurveY / fSlope);
if (fRadius > 0.0)
fQScale= fSlope / (4.0 * fRadius);
else
fQScale = 0.0;
}
/*****************************************************************************/
real64 dng_function_exposure_ramp::Evaluate (real64 x) const
{
if (x <= fBlack - fRadius)
return 0.0;
if (x >= fBlack + fRadius)
return Min_real64 ((x - fBlack) * fSlope, 1.0);
real64 y = x - (fBlack - fRadius);
return fQScale * y * y;
}
/*****************************************************************************/
dng_function_exposure_tone::dng_function_exposure_tone (real64 exposure)
: fIsNOP (exposure >= 0.0)
, fSlope (0.0)
, a (0.0)
, b (0.0)
, c (0.0)
{
if (!fIsNOP)
{
// Find slope to use for the all except the highest two f-stops.
fSlope = pow (2.0, exposure);
- // Find quadradic parameters that match this darking at the crossover
+ // Find quadratic parameters that match this darking at the crossover
// point, yet still map pure white to pure white.
a = 16.0 / 9.0 * (1.0 - fSlope);
b = fSlope - 0.5 * a;
c = 1.0 - a - b;
}
}
/*****************************************************************************/
real64 dng_function_exposure_tone::Evaluate (real64 x) const
{
if (!fIsNOP)
{
if (x <= 0.25)
x = x * fSlope;
else
x = (a * x + b) * x + c;
}
return x;
}
/*****************************************************************************/
real64 dng_tone_curve_acr3_default::Evaluate (real64 x) const
{
static const real32 kTable [] =
{
0.00000f, 0.00078f, 0.00160f, 0.00242f,
0.00314f, 0.00385f, 0.00460f, 0.00539f,
0.00623f, 0.00712f, 0.00806f, 0.00906f,
0.01012f, 0.01122f, 0.01238f, 0.01359f,
0.01485f, 0.01616f, 0.01751f, 0.01890f,
0.02033f, 0.02180f, 0.02331f, 0.02485f,
0.02643f, 0.02804f, 0.02967f, 0.03134f,
0.03303f, 0.03475f, 0.03648f, 0.03824f,
0.04002f, 0.04181f, 0.04362f, 0.04545f,
0.04730f, 0.04916f, 0.05103f, 0.05292f,
0.05483f, 0.05675f, 0.05868f, 0.06063f,
0.06259f, 0.06457f, 0.06655f, 0.06856f,
0.07057f, 0.07259f, 0.07463f, 0.07668f,
0.07874f, 0.08081f, 0.08290f, 0.08499f,
0.08710f, 0.08921f, 0.09134f, 0.09348f,
0.09563f, 0.09779f, 0.09996f, 0.10214f,
0.10433f, 0.10652f, 0.10873f, 0.11095f,
0.11318f, 0.11541f, 0.11766f, 0.11991f,
0.12218f, 0.12445f, 0.12673f, 0.12902f,
0.13132f, 0.13363f, 0.13595f, 0.13827f,
0.14061f, 0.14295f, 0.14530f, 0.14765f,
0.15002f, 0.15239f, 0.15477f, 0.15716f,
0.15956f, 0.16197f, 0.16438f, 0.16680f,
0.16923f, 0.17166f, 0.17410f, 0.17655f,
0.17901f, 0.18148f, 0.18395f, 0.18643f,
0.18891f, 0.19141f, 0.19391f, 0.19641f,
0.19893f, 0.20145f, 0.20398f, 0.20651f,
0.20905f, 0.21160f, 0.21416f, 0.21672f,
0.21929f, 0.22185f, 0.22440f, 0.22696f,
0.22950f, 0.23204f, 0.23458f, 0.23711f,
0.23963f, 0.24215f, 0.24466f, 0.24717f,
0.24967f, 0.25216f, 0.25465f, 0.25713f,
0.25961f, 0.26208f, 0.26454f, 0.26700f,
0.26945f, 0.27189f, 0.27433f, 0.27676f,
0.27918f, 0.28160f, 0.28401f, 0.28641f,
0.28881f, 0.29120f, 0.29358f, 0.29596f,
0.29833f, 0.30069f, 0.30305f, 0.30540f,
0.30774f, 0.31008f, 0.31241f, 0.31473f,
0.31704f, 0.31935f, 0.32165f, 0.32395f,
0.32623f, 0.32851f, 0.33079f, 0.33305f,
0.33531f, 0.33756f, 0.33981f, 0.34205f,
0.34428f, 0.34650f, 0.34872f, 0.35093f,
0.35313f, 0.35532f, 0.35751f, 0.35969f,
0.36187f, 0.36404f, 0.36620f, 0.36835f,
0.37050f, 0.37264f, 0.37477f, 0.37689f,
0.37901f, 0.38112f, 0.38323f, 0.38533f,
0.38742f, 0.38950f, 0.39158f, 0.39365f,
0.39571f, 0.39777f, 0.39982f, 0.40186f,
0.40389f, 0.40592f, 0.40794f, 0.40996f,
0.41197f, 0.41397f, 0.41596f, 0.41795f,
0.41993f, 0.42191f, 0.42388f, 0.42584f,
0.42779f, 0.42974f, 0.43168f, 0.43362f,
0.43554f, 0.43747f, 0.43938f, 0.44129f,
0.44319f, 0.44509f, 0.44698f, 0.44886f,
0.45073f, 0.45260f, 0.45447f, 0.45632f,
0.45817f, 0.46002f, 0.46186f, 0.46369f,
0.46551f, 0.46733f, 0.46914f, 0.47095f,
0.47275f, 0.47454f, 0.47633f, 0.47811f,
0.47989f, 0.48166f, 0.48342f, 0.48518f,
0.48693f, 0.48867f, 0.49041f, 0.49214f,
0.49387f, 0.49559f, 0.49730f, 0.49901f,
0.50072f, 0.50241f, 0.50410f, 0.50579f,
0.50747f, 0.50914f, 0.51081f, 0.51247f,
0.51413f, 0.51578f, 0.51742f, 0.51906f,
0.52069f, 0.52232f, 0.52394f, 0.52556f,
0.52717f, 0.52878f, 0.53038f, 0.53197f,
0.53356f, 0.53514f, 0.53672f, 0.53829f,
0.53986f, 0.54142f, 0.54297f, 0.54452f,
0.54607f, 0.54761f, 0.54914f, 0.55067f,
0.55220f, 0.55371f, 0.55523f, 0.55673f,
0.55824f, 0.55973f, 0.56123f, 0.56271f,
0.56420f, 0.56567f, 0.56715f, 0.56861f,
0.57007f, 0.57153f, 0.57298f, 0.57443f,
0.57587f, 0.57731f, 0.57874f, 0.58017f,
0.58159f, 0.58301f, 0.58443f, 0.58583f,
0.58724f, 0.58864f, 0.59003f, 0.59142f,
0.59281f, 0.59419f, 0.59556f, 0.59694f,
0.59830f, 0.59966f, 0.60102f, 0.60238f,
0.60373f, 0.60507f, 0.60641f, 0.60775f,
0.60908f, 0.61040f, 0.61173f, 0.61305f,
0.61436f, 0.61567f, 0.61698f, 0.61828f,
0.61957f, 0.62087f, 0.62216f, 0.62344f,
0.62472f, 0.62600f, 0.62727f, 0.62854f,
0.62980f, 0.63106f, 0.63232f, 0.63357f,
0.63482f, 0.63606f, 0.63730f, 0.63854f,
0.63977f, 0.64100f, 0.64222f, 0.64344f,
0.64466f, 0.64587f, 0.64708f, 0.64829f,
0.64949f, 0.65069f, 0.65188f, 0.65307f,
0.65426f, 0.65544f, 0.65662f, 0.65779f,
0.65897f, 0.66013f, 0.66130f, 0.66246f,
0.66362f, 0.66477f, 0.66592f, 0.66707f,
0.66821f, 0.66935f, 0.67048f, 0.67162f,
0.67275f, 0.67387f, 0.67499f, 0.67611f,
0.67723f, 0.67834f, 0.67945f, 0.68055f,
0.68165f, 0.68275f, 0.68385f, 0.68494f,
0.68603f, 0.68711f, 0.68819f, 0.68927f,
0.69035f, 0.69142f, 0.69249f, 0.69355f,
0.69461f, 0.69567f, 0.69673f, 0.69778f,
0.69883f, 0.69988f, 0.70092f, 0.70196f,
0.70300f, 0.70403f, 0.70506f, 0.70609f,
0.70711f, 0.70813f, 0.70915f, 0.71017f,
0.71118f, 0.71219f, 0.71319f, 0.71420f,
0.71520f, 0.71620f, 0.71719f, 0.71818f,
0.71917f, 0.72016f, 0.72114f, 0.72212f,
0.72309f, 0.72407f, 0.72504f, 0.72601f,
0.72697f, 0.72794f, 0.72890f, 0.72985f,
0.73081f, 0.73176f, 0.73271f, 0.73365f,
0.73460f, 0.73554f, 0.73647f, 0.73741f,
0.73834f, 0.73927f, 0.74020f, 0.74112f,
0.74204f, 0.74296f, 0.74388f, 0.74479f,
0.74570f, 0.74661f, 0.74751f, 0.74842f,
0.74932f, 0.75021f, 0.75111f, 0.75200f,
0.75289f, 0.75378f, 0.75466f, 0.75555f,
0.75643f, 0.75730f, 0.75818f, 0.75905f,
0.75992f, 0.76079f, 0.76165f, 0.76251f,
0.76337f, 0.76423f, 0.76508f, 0.76594f,
0.76679f, 0.76763f, 0.76848f, 0.76932f,
0.77016f, 0.77100f, 0.77183f, 0.77267f,
0.77350f, 0.77432f, 0.77515f, 0.77597f,
0.77680f, 0.77761f, 0.77843f, 0.77924f,
0.78006f, 0.78087f, 0.78167f, 0.78248f,
0.78328f, 0.78408f, 0.78488f, 0.78568f,
0.78647f, 0.78726f, 0.78805f, 0.78884f,
0.78962f, 0.79040f, 0.79118f, 0.79196f,
0.79274f, 0.79351f, 0.79428f, 0.79505f,
0.79582f, 0.79658f, 0.79735f, 0.79811f,
0.79887f, 0.79962f, 0.80038f, 0.80113f,
0.80188f, 0.80263f, 0.80337f, 0.80412f,
0.80486f, 0.80560f, 0.80634f, 0.80707f,
0.80780f, 0.80854f, 0.80926f, 0.80999f,
0.81072f, 0.81144f, 0.81216f, 0.81288f,
0.81360f, 0.81431f, 0.81503f, 0.81574f,
0.81645f, 0.81715f, 0.81786f, 0.81856f,
0.81926f, 0.81996f, 0.82066f, 0.82135f,
0.82205f, 0.82274f, 0.82343f, 0.82412f,
0.82480f, 0.82549f, 0.82617f, 0.82685f,
0.82753f, 0.82820f, 0.82888f, 0.82955f,
0.83022f, 0.83089f, 0.83155f, 0.83222f,
0.83288f, 0.83354f, 0.83420f, 0.83486f,
0.83552f, 0.83617f, 0.83682f, 0.83747f,
0.83812f, 0.83877f, 0.83941f, 0.84005f,
0.84069f, 0.84133f, 0.84197f, 0.84261f,
0.84324f, 0.84387f, 0.84450f, 0.84513f,
0.84576f, 0.84639f, 0.84701f, 0.84763f,
0.84825f, 0.84887f, 0.84949f, 0.85010f,
0.85071f, 0.85132f, 0.85193f, 0.85254f,
0.85315f, 0.85375f, 0.85436f, 0.85496f,
0.85556f, 0.85615f, 0.85675f, 0.85735f,
0.85794f, 0.85853f, 0.85912f, 0.85971f,
0.86029f, 0.86088f, 0.86146f, 0.86204f,
0.86262f, 0.86320f, 0.86378f, 0.86435f,
0.86493f, 0.86550f, 0.86607f, 0.86664f,
0.86720f, 0.86777f, 0.86833f, 0.86889f,
0.86945f, 0.87001f, 0.87057f, 0.87113f,
0.87168f, 0.87223f, 0.87278f, 0.87333f,
0.87388f, 0.87443f, 0.87497f, 0.87552f,
0.87606f, 0.87660f, 0.87714f, 0.87768f,
0.87821f, 0.87875f, 0.87928f, 0.87981f,
0.88034f, 0.88087f, 0.88140f, 0.88192f,
0.88244f, 0.88297f, 0.88349f, 0.88401f,
0.88453f, 0.88504f, 0.88556f, 0.88607f,
0.88658f, 0.88709f, 0.88760f, 0.88811f,
0.88862f, 0.88912f, 0.88963f, 0.89013f,
0.89063f, 0.89113f, 0.89163f, 0.89212f,
0.89262f, 0.89311f, 0.89360f, 0.89409f,
0.89458f, 0.89507f, 0.89556f, 0.89604f,
0.89653f, 0.89701f, 0.89749f, 0.89797f,
0.89845f, 0.89892f, 0.89940f, 0.89987f,
0.90035f, 0.90082f, 0.90129f, 0.90176f,
0.90222f, 0.90269f, 0.90316f, 0.90362f,
0.90408f, 0.90454f, 0.90500f, 0.90546f,
0.90592f, 0.90637f, 0.90683f, 0.90728f,
0.90773f, 0.90818f, 0.90863f, 0.90908f,
0.90952f, 0.90997f, 0.91041f, 0.91085f,
0.91130f, 0.91173f, 0.91217f, 0.91261f,
0.91305f, 0.91348f, 0.91392f, 0.91435f,
0.91478f, 0.91521f, 0.91564f, 0.91606f,
0.91649f, 0.91691f, 0.91734f, 0.91776f,
0.91818f, 0.91860f, 0.91902f, 0.91944f,
0.91985f, 0.92027f, 0.92068f, 0.92109f,
0.92150f, 0.92191f, 0.92232f, 0.92273f,
0.92314f, 0.92354f, 0.92395f, 0.92435f,
0.92475f, 0.92515f, 0.92555f, 0.92595f,
0.92634f, 0.92674f, 0.92713f, 0.92753f,
0.92792f, 0.92831f, 0.92870f, 0.92909f,
0.92947f, 0.92986f, 0.93025f, 0.93063f,
0.93101f, 0.93139f, 0.93177f, 0.93215f,
0.93253f, 0.93291f, 0.93328f, 0.93366f,
0.93403f, 0.93440f, 0.93478f, 0.93515f,
0.93551f, 0.93588f, 0.93625f, 0.93661f,
0.93698f, 0.93734f, 0.93770f, 0.93807f,
0.93843f, 0.93878f, 0.93914f, 0.93950f,
0.93986f, 0.94021f, 0.94056f, 0.94092f,
0.94127f, 0.94162f, 0.94197f, 0.94231f,
0.94266f, 0.94301f, 0.94335f, 0.94369f,
0.94404f, 0.94438f, 0.94472f, 0.94506f,
0.94540f, 0.94573f, 0.94607f, 0.94641f,
0.94674f, 0.94707f, 0.94740f, 0.94774f,
0.94807f, 0.94839f, 0.94872f, 0.94905f,
0.94937f, 0.94970f, 0.95002f, 0.95035f,
0.95067f, 0.95099f, 0.95131f, 0.95163f,
0.95194f, 0.95226f, 0.95257f, 0.95289f,
0.95320f, 0.95351f, 0.95383f, 0.95414f,
0.95445f, 0.95475f, 0.95506f, 0.95537f,
0.95567f, 0.95598f, 0.95628f, 0.95658f,
0.95688f, 0.95718f, 0.95748f, 0.95778f,
0.95808f, 0.95838f, 0.95867f, 0.95897f,
0.95926f, 0.95955f, 0.95984f, 0.96013f,
0.96042f, 0.96071f, 0.96100f, 0.96129f,
0.96157f, 0.96186f, 0.96214f, 0.96242f,
0.96271f, 0.96299f, 0.96327f, 0.96355f,
0.96382f, 0.96410f, 0.96438f, 0.96465f,
0.96493f, 0.96520f, 0.96547f, 0.96574f,
0.96602f, 0.96629f, 0.96655f, 0.96682f,
0.96709f, 0.96735f, 0.96762f, 0.96788f,
0.96815f, 0.96841f, 0.96867f, 0.96893f,
0.96919f, 0.96945f, 0.96971f, 0.96996f,
0.97022f, 0.97047f, 0.97073f, 0.97098f,
0.97123f, 0.97149f, 0.97174f, 0.97199f,
0.97223f, 0.97248f, 0.97273f, 0.97297f,
0.97322f, 0.97346f, 0.97371f, 0.97395f,
0.97419f, 0.97443f, 0.97467f, 0.97491f,
0.97515f, 0.97539f, 0.97562f, 0.97586f,
0.97609f, 0.97633f, 0.97656f, 0.97679f,
0.97702f, 0.97725f, 0.97748f, 0.97771f,
0.97794f, 0.97817f, 0.97839f, 0.97862f,
0.97884f, 0.97907f, 0.97929f, 0.97951f,
0.97973f, 0.97995f, 0.98017f, 0.98039f,
0.98061f, 0.98082f, 0.98104f, 0.98125f,
0.98147f, 0.98168f, 0.98189f, 0.98211f,
0.98232f, 0.98253f, 0.98274f, 0.98295f,
0.98315f, 0.98336f, 0.98357f, 0.98377f,
0.98398f, 0.98418f, 0.98438f, 0.98458f,
0.98478f, 0.98498f, 0.98518f, 0.98538f,
0.98558f, 0.98578f, 0.98597f, 0.98617f,
0.98636f, 0.98656f, 0.98675f, 0.98694f,
0.98714f, 0.98733f, 0.98752f, 0.98771f,
0.98789f, 0.98808f, 0.98827f, 0.98845f,
0.98864f, 0.98882f, 0.98901f, 0.98919f,
0.98937f, 0.98955f, 0.98973f, 0.98991f,
0.99009f, 0.99027f, 0.99045f, 0.99063f,
0.99080f, 0.99098f, 0.99115f, 0.99133f,
0.99150f, 0.99167f, 0.99184f, 0.99201f,
0.99218f, 0.99235f, 0.99252f, 0.99269f,
0.99285f, 0.99302f, 0.99319f, 0.99335f,
0.99351f, 0.99368f, 0.99384f, 0.99400f,
0.99416f, 0.99432f, 0.99448f, 0.99464f,
0.99480f, 0.99495f, 0.99511f, 0.99527f,
0.99542f, 0.99558f, 0.99573f, 0.99588f,
0.99603f, 0.99619f, 0.99634f, 0.99649f,
0.99664f, 0.99678f, 0.99693f, 0.99708f,
0.99722f, 0.99737f, 0.99751f, 0.99766f,
0.99780f, 0.99794f, 0.99809f, 0.99823f,
0.99837f, 0.99851f, 0.99865f, 0.99879f,
0.99892f, 0.99906f, 0.99920f, 0.99933f,
0.99947f, 0.99960f, 0.99974f, 0.99987f,
1.00000f
};
const uint32 kTableSize = sizeof (kTable ) /
sizeof (kTable [0]);
real32 y = (real32) x * (real32) (kTableSize - 1);
int32 index = Pin_int32 (0, (int32) y, kTableSize - 2);
real32 fract = y - (real32) index;
return kTable [index ] * (1.0f - fract) +
kTable [index + 1] * ( fract);
}
/*****************************************************************************/
real64 dng_tone_curve_acr3_default::EvaluateInverse (real64 x) const
{
static const real32 kTable [] =
{
0.00000f, 0.00121f, 0.00237f, 0.00362f,
0.00496f, 0.00621f, 0.00738f, 0.00848f,
0.00951f, 0.01048f, 0.01139f, 0.01227f,
0.01312f, 0.01393f, 0.01471f, 0.01547f,
0.01620f, 0.01692f, 0.01763f, 0.01831f,
0.01899f, 0.01965f, 0.02030f, 0.02094f,
0.02157f, 0.02218f, 0.02280f, 0.02340f,
0.02399f, 0.02458f, 0.02517f, 0.02574f,
0.02631f, 0.02688f, 0.02744f, 0.02800f,
0.02855f, 0.02910f, 0.02965f, 0.03019f,
0.03072f, 0.03126f, 0.03179f, 0.03232f,
0.03285f, 0.03338f, 0.03390f, 0.03442f,
0.03493f, 0.03545f, 0.03596f, 0.03647f,
0.03698f, 0.03749f, 0.03799f, 0.03849f,
0.03899f, 0.03949f, 0.03998f, 0.04048f,
0.04097f, 0.04146f, 0.04195f, 0.04244f,
0.04292f, 0.04341f, 0.04389f, 0.04437f,
0.04485f, 0.04533f, 0.04580f, 0.04628f,
0.04675f, 0.04722f, 0.04769f, 0.04816f,
0.04863f, 0.04910f, 0.04956f, 0.05003f,
0.05049f, 0.05095f, 0.05141f, 0.05187f,
0.05233f, 0.05278f, 0.05324f, 0.05370f,
0.05415f, 0.05460f, 0.05505f, 0.05551f,
0.05595f, 0.05640f, 0.05685f, 0.05729f,
0.05774f, 0.05818f, 0.05863f, 0.05907f,
0.05951f, 0.05995f, 0.06039f, 0.06083f,
0.06126f, 0.06170f, 0.06214f, 0.06257f,
0.06301f, 0.06344f, 0.06388f, 0.06431f,
0.06474f, 0.06517f, 0.06560f, 0.06602f,
0.06645f, 0.06688f, 0.06731f, 0.06773f,
0.06815f, 0.06858f, 0.06900f, 0.06943f,
0.06985f, 0.07027f, 0.07069f, 0.07111f,
0.07152f, 0.07194f, 0.07236f, 0.07278f,
0.07319f, 0.07361f, 0.07402f, 0.07444f,
0.07485f, 0.07526f, 0.07567f, 0.07608f,
0.07650f, 0.07691f, 0.07732f, 0.07772f,
0.07813f, 0.07854f, 0.07895f, 0.07935f,
0.07976f, 0.08016f, 0.08057f, 0.08098f,
0.08138f, 0.08178f, 0.08218f, 0.08259f,
0.08299f, 0.08339f, 0.08379f, 0.08419f,
0.08459f, 0.08499f, 0.08539f, 0.08578f,
0.08618f, 0.08657f, 0.08697f, 0.08737f,
0.08776f, 0.08816f, 0.08855f, 0.08894f,
0.08934f, 0.08973f, 0.09012f, 0.09051f,
0.09091f, 0.09130f, 0.09169f, 0.09208f,
0.09247f, 0.09286f, 0.09324f, 0.09363f,
0.09402f, 0.09440f, 0.09479f, 0.09518f,
0.09556f, 0.09595f, 0.09633f, 0.09672f,
0.09710f, 0.09749f, 0.09787f, 0.09825f,
0.09863f, 0.09901f, 0.09939f, 0.09978f,
0.10016f, 0.10054f, 0.10092f, 0.10130f,
0.10167f, 0.10205f, 0.10243f, 0.10281f,
0.10319f, 0.10356f, 0.10394f, 0.10432f,
0.10469f, 0.10507f, 0.10544f, 0.10582f,
0.10619f, 0.10657f, 0.10694f, 0.10731f,
0.10768f, 0.10806f, 0.10843f, 0.10880f,
0.10917f, 0.10954f, 0.10991f, 0.11029f,
0.11066f, 0.11103f, 0.11141f, 0.11178f,
0.11215f, 0.11253f, 0.11290f, 0.11328f,
0.11365f, 0.11403f, 0.11440f, 0.11478f,
0.11516f, 0.11553f, 0.11591f, 0.11629f,
0.11666f, 0.11704f, 0.11742f, 0.11780f,
0.11818f, 0.11856f, 0.11894f, 0.11932f,
0.11970f, 0.12008f, 0.12046f, 0.12084f,
0.12122f, 0.12161f, 0.12199f, 0.12237f,
0.12276f, 0.12314f, 0.12352f, 0.12391f,
0.12429f, 0.12468f, 0.12506f, 0.12545f,
0.12583f, 0.12622f, 0.12661f, 0.12700f,
0.12738f, 0.12777f, 0.12816f, 0.12855f,
0.12894f, 0.12933f, 0.12972f, 0.13011f,
0.13050f, 0.13089f, 0.13129f, 0.13168f,
0.13207f, 0.13247f, 0.13286f, 0.13325f,
0.13365f, 0.13404f, 0.13444f, 0.13483f,
0.13523f, 0.13563f, 0.13603f, 0.13642f,
0.13682f, 0.13722f, 0.13762f, 0.13802f,
0.13842f, 0.13882f, 0.13922f, 0.13962f,
0.14003f, 0.14043f, 0.14083f, 0.14124f,
0.14164f, 0.14204f, 0.14245f, 0.14285f,
0.14326f, 0.14366f, 0.14407f, 0.14448f,
0.14489f, 0.14530f, 0.14570f, 0.14611f,
0.14652f, 0.14693f, 0.14734f, 0.14776f,
0.14817f, 0.14858f, 0.14900f, 0.14941f,
0.14982f, 0.15024f, 0.15065f, 0.15107f,
0.15148f, 0.15190f, 0.15232f, 0.15274f,
0.15316f, 0.15357f, 0.15399f, 0.15441f,
0.15483f, 0.15526f, 0.15568f, 0.15610f,
0.15652f, 0.15695f, 0.15737f, 0.15779f,
0.15822f, 0.15864f, 0.15907f, 0.15950f,
0.15992f, 0.16035f, 0.16078f, 0.16121f,
0.16164f, 0.16207f, 0.16250f, 0.16293f,
0.16337f, 0.16380f, 0.16423f, 0.16467f,
0.16511f, 0.16554f, 0.16598f, 0.16641f,
0.16685f, 0.16729f, 0.16773f, 0.16816f,
0.16860f, 0.16904f, 0.16949f, 0.16993f,
0.17037f, 0.17081f, 0.17126f, 0.17170f,
0.17215f, 0.17259f, 0.17304f, 0.17349f,
0.17393f, 0.17438f, 0.17483f, 0.17528f,
0.17573f, 0.17619f, 0.17664f, 0.17709f,
0.17754f, 0.17799f, 0.17845f, 0.17890f,
0.17936f, 0.17982f, 0.18028f, 0.18073f,
0.18119f, 0.18165f, 0.18211f, 0.18257f,
0.18303f, 0.18350f, 0.18396f, 0.18442f,
0.18489f, 0.18535f, 0.18582f, 0.18629f,
0.18676f, 0.18723f, 0.18770f, 0.18817f,
0.18864f, 0.18911f, 0.18958f, 0.19005f,
0.19053f, 0.19100f, 0.19147f, 0.19195f,
0.19243f, 0.19291f, 0.19339f, 0.19387f,
0.19435f, 0.19483f, 0.19531f, 0.19579f,
0.19627f, 0.19676f, 0.19724f, 0.19773f,
0.19821f, 0.19870f, 0.19919f, 0.19968f,
0.20017f, 0.20066f, 0.20115f, 0.20164f,
0.20214f, 0.20263f, 0.20313f, 0.20362f,
0.20412f, 0.20462f, 0.20512f, 0.20561f,
0.20611f, 0.20662f, 0.20712f, 0.20762f,
0.20812f, 0.20863f, 0.20913f, 0.20964f,
0.21015f, 0.21066f, 0.21117f, 0.21168f,
0.21219f, 0.21270f, 0.21321f, 0.21373f,
0.21424f, 0.21476f, 0.21527f, 0.21579f,
0.21631f, 0.21683f, 0.21735f, 0.21787f,
0.21839f, 0.21892f, 0.21944f, 0.21997f,
0.22049f, 0.22102f, 0.22155f, 0.22208f,
0.22261f, 0.22314f, 0.22367f, 0.22420f,
0.22474f, 0.22527f, 0.22581f, 0.22634f,
0.22688f, 0.22742f, 0.22796f, 0.22850f,
0.22905f, 0.22959f, 0.23013f, 0.23068f,
0.23123f, 0.23178f, 0.23232f, 0.23287f,
0.23343f, 0.23398f, 0.23453f, 0.23508f,
0.23564f, 0.23620f, 0.23675f, 0.23731f,
0.23787f, 0.23843f, 0.23899f, 0.23956f,
0.24012f, 0.24069f, 0.24125f, 0.24182f,
0.24239f, 0.24296f, 0.24353f, 0.24410f,
0.24468f, 0.24525f, 0.24582f, 0.24640f,
0.24698f, 0.24756f, 0.24814f, 0.24872f,
0.24931f, 0.24989f, 0.25048f, 0.25106f,
0.25165f, 0.25224f, 0.25283f, 0.25342f,
0.25401f, 0.25460f, 0.25520f, 0.25579f,
0.25639f, 0.25699f, 0.25759f, 0.25820f,
0.25880f, 0.25940f, 0.26001f, 0.26062f,
0.26122f, 0.26183f, 0.26244f, 0.26306f,
0.26367f, 0.26429f, 0.26490f, 0.26552f,
0.26614f, 0.26676f, 0.26738f, 0.26800f,
0.26863f, 0.26925f, 0.26988f, 0.27051f,
0.27114f, 0.27177f, 0.27240f, 0.27303f,
0.27367f, 0.27431f, 0.27495f, 0.27558f,
0.27623f, 0.27687f, 0.27751f, 0.27816f,
0.27881f, 0.27945f, 0.28011f, 0.28076f,
0.28141f, 0.28207f, 0.28272f, 0.28338f,
0.28404f, 0.28470f, 0.28536f, 0.28602f,
0.28669f, 0.28736f, 0.28802f, 0.28869f,
0.28937f, 0.29004f, 0.29071f, 0.29139f,
0.29207f, 0.29274f, 0.29342f, 0.29410f,
0.29479f, 0.29548f, 0.29616f, 0.29685f,
0.29754f, 0.29823f, 0.29893f, 0.29962f,
0.30032f, 0.30102f, 0.30172f, 0.30242f,
0.30312f, 0.30383f, 0.30453f, 0.30524f,
0.30595f, 0.30667f, 0.30738f, 0.30809f,
0.30881f, 0.30953f, 0.31025f, 0.31097f,
0.31170f, 0.31242f, 0.31315f, 0.31388f,
0.31461f, 0.31534f, 0.31608f, 0.31682f,
0.31755f, 0.31829f, 0.31904f, 0.31978f,
0.32053f, 0.32127f, 0.32202f, 0.32277f,
0.32353f, 0.32428f, 0.32504f, 0.32580f,
0.32656f, 0.32732f, 0.32808f, 0.32885f,
0.32962f, 0.33039f, 0.33116f, 0.33193f,
0.33271f, 0.33349f, 0.33427f, 0.33505f,
0.33583f, 0.33662f, 0.33741f, 0.33820f,
0.33899f, 0.33978f, 0.34058f, 0.34138f,
0.34218f, 0.34298f, 0.34378f, 0.34459f,
0.34540f, 0.34621f, 0.34702f, 0.34783f,
0.34865f, 0.34947f, 0.35029f, 0.35111f,
0.35194f, 0.35277f, 0.35360f, 0.35443f,
0.35526f, 0.35610f, 0.35694f, 0.35778f,
0.35862f, 0.35946f, 0.36032f, 0.36117f,
0.36202f, 0.36287f, 0.36372f, 0.36458f,
0.36545f, 0.36631f, 0.36718f, 0.36805f,
0.36891f, 0.36979f, 0.37066f, 0.37154f,
0.37242f, 0.37331f, 0.37419f, 0.37507f,
0.37596f, 0.37686f, 0.37775f, 0.37865f,
0.37955f, 0.38045f, 0.38136f, 0.38227f,
0.38317f, 0.38409f, 0.38500f, 0.38592f,
0.38684f, 0.38776f, 0.38869f, 0.38961f,
0.39055f, 0.39148f, 0.39242f, 0.39335f,
0.39430f, 0.39524f, 0.39619f, 0.39714f,
0.39809f, 0.39904f, 0.40000f, 0.40097f,
0.40193f, 0.40289f, 0.40386f, 0.40483f,
0.40581f, 0.40679f, 0.40777f, 0.40875f,
0.40974f, 0.41073f, 0.41172f, 0.41272f,
0.41372f, 0.41472f, 0.41572f, 0.41673f,
0.41774f, 0.41875f, 0.41977f, 0.42079f,
0.42181f, 0.42284f, 0.42386f, 0.42490f,
0.42594f, 0.42697f, 0.42801f, 0.42906f,
0.43011f, 0.43116f, 0.43222f, 0.43327f,
0.43434f, 0.43540f, 0.43647f, 0.43754f,
0.43862f, 0.43970f, 0.44077f, 0.44186f,
0.44295f, 0.44404f, 0.44514f, 0.44624f,
0.44734f, 0.44845f, 0.44956f, 0.45068f,
0.45179f, 0.45291f, 0.45404f, 0.45516f,
0.45630f, 0.45744f, 0.45858f, 0.45972f,
0.46086f, 0.46202f, 0.46318f, 0.46433f,
0.46550f, 0.46667f, 0.46784f, 0.46901f,
0.47019f, 0.47137f, 0.47256f, 0.47375f,
0.47495f, 0.47615f, 0.47735f, 0.47856f,
0.47977f, 0.48099f, 0.48222f, 0.48344f,
0.48467f, 0.48590f, 0.48714f, 0.48838f,
0.48963f, 0.49088f, 0.49213f, 0.49340f,
0.49466f, 0.49593f, 0.49721f, 0.49849f,
0.49977f, 0.50106f, 0.50236f, 0.50366f,
0.50496f, 0.50627f, 0.50758f, 0.50890f,
0.51023f, 0.51155f, 0.51289f, 0.51422f,
0.51556f, 0.51692f, 0.51827f, 0.51964f,
0.52100f, 0.52237f, 0.52374f, 0.52512f,
0.52651f, 0.52790f, 0.52930f, 0.53070f,
0.53212f, 0.53353f, 0.53495f, 0.53638f,
0.53781f, 0.53925f, 0.54070f, 0.54214f,
0.54360f, 0.54506f, 0.54653f, 0.54800f,
0.54949f, 0.55098f, 0.55247f, 0.55396f,
0.55548f, 0.55699f, 0.55851f, 0.56003f,
0.56156f, 0.56310f, 0.56464f, 0.56621f,
0.56777f, 0.56933f, 0.57091f, 0.57248f,
0.57407f, 0.57568f, 0.57727f, 0.57888f,
0.58050f, 0.58213f, 0.58376f, 0.58541f,
0.58705f, 0.58871f, 0.59037f, 0.59204f,
0.59373f, 0.59541f, 0.59712f, 0.59882f,
0.60052f, 0.60226f, 0.60399f, 0.60572f,
0.60748f, 0.60922f, 0.61099f, 0.61276f,
0.61455f, 0.61635f, 0.61814f, 0.61996f,
0.62178f, 0.62361f, 0.62545f, 0.62730f,
0.62917f, 0.63104f, 0.63291f, 0.63480f,
0.63671f, 0.63862f, 0.64054f, 0.64249f,
0.64443f, 0.64638f, 0.64835f, 0.65033f,
0.65232f, 0.65433f, 0.65633f, 0.65836f,
0.66041f, 0.66245f, 0.66452f, 0.66660f,
0.66868f, 0.67078f, 0.67290f, 0.67503f,
0.67717f, 0.67932f, 0.68151f, 0.68368f,
0.68587f, 0.68809f, 0.69033f, 0.69257f,
0.69482f, 0.69709f, 0.69939f, 0.70169f,
0.70402f, 0.70634f, 0.70869f, 0.71107f,
0.71346f, 0.71587f, 0.71829f, 0.72073f,
0.72320f, 0.72567f, 0.72818f, 0.73069f,
0.73323f, 0.73579f, 0.73838f, 0.74098f,
0.74360f, 0.74622f, 0.74890f, 0.75159f,
0.75429f, 0.75704f, 0.75979f, 0.76257f,
0.76537f, 0.76821f, 0.77109f, 0.77396f,
0.77688f, 0.77982f, 0.78278f, 0.78579f,
0.78883f, 0.79187f, 0.79498f, 0.79809f,
0.80127f, 0.80445f, 0.80767f, 0.81095f,
0.81424f, 0.81757f, 0.82094f, 0.82438f,
0.82782f, 0.83133f, 0.83488f, 0.83847f,
0.84210f, 0.84577f, 0.84951f, 0.85328f,
0.85713f, 0.86103f, 0.86499f, 0.86900f,
0.87306f, 0.87720f, 0.88139f, 0.88566f,
0.89000f, 0.89442f, 0.89891f, 0.90350f,
0.90818f, 0.91295f, 0.91780f, 0.92272f,
0.92780f, 0.93299f, 0.93828f, 0.94369f,
0.94926f, 0.95493f, 0.96082f, 0.96684f,
0.97305f, 0.97943f, 0.98605f, 0.99291f,
1.00000f
};
const uint32 kTableSize = sizeof (kTable ) /
sizeof (kTable [0]);
real32 y = (real32) x * (real32) (kTableSize - 1);
int32 index = Pin_int32 (0, (int32) y, kTableSize - 2);
real32 fract = y - (real32) index;
return kTable [index ] * (1.0f - fract) +
kTable [index + 1] * ( fract);
}
/*****************************************************************************/
const dng_1d_function & dng_tone_curve_acr3_default::Get ()
{
static dng_tone_curve_acr3_default static_dng_tone_curve_acr3_default;
return static_dng_tone_curve_acr3_default;
}
/*****************************************************************************/
class dng_render_task: public dng_filter_task
{
protected:
const dng_negative &fNegative;
const dng_render &fParams;
dng_point fSrcOffset;
dng_vector fCameraWhite;
dng_matrix fCameraToRGB;
AutoPtr<dng_hue_sat_map> fHueSatMap;
dng_1d_table fExposureRamp;
AutoPtr<dng_hue_sat_map> fLookTable;
dng_1d_table fToneCurve;
dng_matrix fRGBtoFinal;
dng_1d_table fEncodeGamma;
AutoPtr<dng_memory_block> fTempBuffer [kMaxMPThreads];
public:
dng_render_task (const dng_image &srcImage,
dng_image &dstImage,
const dng_negative &negative,
const dng_render &params,
const dng_point &srcOffset);
virtual dng_rect SrcArea (const dng_rect &dstArea);
virtual void Start (uint32 threadCount,
const dng_point &tileSize,
dng_memory_allocator *allocator,
dng_abort_sniffer *sniffer);
virtual void ProcessArea (uint32 threadIndex,
dng_pixel_buffer &srcBuffer,
dng_pixel_buffer &dstBuffer);
};
/*****************************************************************************/
dng_render_task::dng_render_task (const dng_image &srcImage,
dng_image &dstImage,
const dng_negative &negative,
const dng_render &params,
const dng_point &srcOffset)
: dng_filter_task (srcImage,
dstImage)
, fNegative (negative )
, fParams (params )
, fSrcOffset (srcOffset)
, fCameraWhite ()
, fCameraToRGB ()
, fHueSatMap ()
, fExposureRamp ()
, fLookTable ()
, fToneCurve ()
, fRGBtoFinal ()
, fEncodeGamma ()
{
fSrcPixelType = ttFloat;
fDstPixelType = ttFloat;
}
/*****************************************************************************/
dng_rect dng_render_task::SrcArea (const dng_rect &dstArea)
{
return dstArea + fSrcOffset;
}
/*****************************************************************************/
void dng_render_task::Start (uint32 threadCount,
const dng_point &tileSize,
dng_memory_allocator *allocator,
dng_abort_sniffer *sniffer)
{
dng_filter_task::Start (threadCount,
tileSize,
allocator,
sniffer);
// Compute camera space to linear ProPhoto RGB parameters.
if (!fNegative.IsMonochrome ())
{
dng_camera_profile_id profileID; // Default profile ID.
AutoPtr<dng_color_spec> spec (fNegative.MakeColorSpec (profileID));
if (fParams.WhiteXY ().IsValid ())
{
spec->SetWhiteXY (fParams.WhiteXY ());
}
else if (fNegative.HasCameraNeutral ())
{
spec->SetWhiteXY (spec->NeutralToXY (fNegative.CameraNeutral ()));
}
else if (fNegative.HasCameraWhiteXY ())
{
spec->SetWhiteXY (fNegative.CameraWhiteXY ());
}
else
{
spec->SetWhiteXY (D55_xy_coord ());
}
fCameraWhite = spec->CameraWhite ();
fCameraToRGB = dng_space_ProPhoto::Get ().MatrixFromPCS () *
spec->CameraToPCS ();
// Find Hue/Sat table, if any.
const dng_camera_profile *profile = fNegative.ProfileByID (profileID);
if (profile)
{
fHueSatMap.Reset (profile->HueSatMapForWhite (spec->WhiteXY ()));
if (profile->HasLookTable ())
{
fLookTable.Reset (new dng_hue_sat_map (profile->LookTable ()));
}
}
}
// Compute exposure/shadows ramp.
real64 exposure = fParams.Exposure () +
fNegative.BaselineExposure () -
(log (fNegative.Stage3Gain ()) / log (2.0));
{
real64 white = 1.0 / pow (2.0, Max_real64 (0.0, exposure));
real64 black = fParams.Shadows () *
fNegative.ShadowScale () *
fNegative.Stage3Gain () *
0.001;
black = Min_real64 (black, 0.99 * white);
dng_function_exposure_ramp rampFunction (white,
black,
black);
fExposureRamp.Initialize (*allocator, rampFunction);
}
// Compute tone curve.
{
- // If there is any negative exposure compenation to perform
+ // If there is any negative exposure compensation to perform
// (beyond what the camera provides for with its baseline exposure),
// we fake this by darkening the tone curve.
dng_function_exposure_tone exposureTone (exposure);
dng_1d_concatenate totalTone (exposureTone,
fParams.ToneCurve ());
fToneCurve.Initialize (*allocator, totalTone);
}
// Compute linear ProPhoto RGB to final space parameters.
{
const dng_color_space &finalSpace = fParams.FinalSpace ();
fRGBtoFinal = finalSpace.MatrixFromPCS () *
dng_space_ProPhoto::Get ().MatrixToPCS ();
fEncodeGamma.Initialize (*allocator, finalSpace.GammaFunction ());
}
// Allocate temp buffer to hold one row of RGB data.
uint32 tempBufferSize = tileSize.h * sizeof (real32) * 3;
for (uint32 threadIndex = 0; threadIndex < threadCount; threadIndex++)
{
fTempBuffer [threadIndex] . Reset (allocator->Allocate (tempBufferSize));
}
}
/*****************************************************************************/
void dng_render_task::ProcessArea (uint32 threadIndex,
dng_pixel_buffer &srcBuffer,
dng_pixel_buffer &dstBuffer)
{
dng_rect srcArea = srcBuffer.fArea;
dng_rect dstArea = dstBuffer.fArea;
uint32 srcCols = srcArea.W ();
real32 *tPtrR = fTempBuffer [threadIndex]->Buffer_real32 ();
real32 *tPtrG = tPtrR + srcCols;
real32 *tPtrB = tPtrG + srcCols;
for (int32 srcRow = srcArea.t; srcRow < srcArea.b; srcRow++)
{
// First convert from camera native space to linear PhotoRGB,
// applying the white balance and camera profile.
{
const real32 *sPtrA = (const real32 *)
srcBuffer.ConstPixel (srcRow,
srcArea.l,
0);
if (fSrcPlanes == 1)
{
// For monochrome cameras, this just requires copying
// the data into all three color channels.
DoCopyBytes (sPtrA, tPtrR, srcCols * sizeof (real32));
DoCopyBytes (sPtrA, tPtrG, srcCols * sizeof (real32));
DoCopyBytes (sPtrA, tPtrB, srcCols * sizeof (real32));
}
else
{
const real32 *sPtrB = sPtrA + srcBuffer.fPlaneStep;
const real32 *sPtrC = sPtrB + srcBuffer.fPlaneStep;
if (fSrcPlanes == 3)
{
DoBaselineABCtoRGB (sPtrA,
sPtrB,
sPtrC,
tPtrR,
tPtrG,
tPtrB,
srcCols,
fCameraWhite,
fCameraToRGB);
}
else
{
const real32 *sPtrD = sPtrC + srcBuffer.fPlaneStep;
DoBaselineABCDtoRGB (sPtrA,
sPtrB,
sPtrC,
sPtrD,
tPtrR,
tPtrG,
tPtrB,
srcCols,
fCameraWhite,
fCameraToRGB);
}
// Apply Hue/Sat map, if any.
if (fHueSatMap.Get ())
{
DoBaselineHueSatMap (tPtrR,
tPtrG,
tPtrB,
tPtrR,
tPtrG,
tPtrB,
srcCols,
*fHueSatMap.Get ());
}
}
}
// Apply exposure curve.
DoBaseline1DTable (tPtrR,
tPtrR,
srcCols,
fExposureRamp);
DoBaseline1DTable (tPtrG,
tPtrG,
srcCols,
fExposureRamp);
DoBaseline1DTable (tPtrB,
tPtrB,
srcCols,
fExposureRamp);
// Apply Hue/Sat map, if any.
if (fLookTable.Get ())
{
DoBaselineHueSatMap (tPtrR,
tPtrG,
tPtrB,
tPtrR,
tPtrG,
tPtrB,
srcCols,
*fLookTable.Get ());
}
// Apply baseline tone curve.
DoBaselineRGBTone (tPtrR,
tPtrG,
tPtrB,
tPtrR,
tPtrG,
tPtrB,
srcCols,
fToneCurve);
// Convert to final color space.
int32 dstRow = srcRow + (dstArea.t - srcArea.t);
if (fDstPlanes == 1)
{
real32 *dPtrG = dstBuffer.DirtyPixel_real32 (dstRow,
dstArea.l,
0);
DoBaselineRGBtoGray (tPtrR,
tPtrG,
tPtrB,
dPtrG,
srcCols,
fRGBtoFinal);
DoBaseline1DTable (dPtrG,
dPtrG,
srcCols,
fEncodeGamma);
}
else
{
real32 *dPtrR = dstBuffer.DirtyPixel_real32 (dstRow,
dstArea.l,
0);
real32 *dPtrG = dPtrR + dstBuffer.fPlaneStep;
real32 *dPtrB = dPtrG + dstBuffer.fPlaneStep;
DoBaselineRGBtoRGB (tPtrR,
tPtrG,
tPtrB,
dPtrR,
dPtrG,
dPtrB,
srcCols,
fRGBtoFinal);
DoBaseline1DTable (dPtrR,
dPtrR,
srcCols,
fEncodeGamma);
DoBaseline1DTable (dPtrG,
dPtrG,
srcCols,
fEncodeGamma);
DoBaseline1DTable (dPtrB,
dPtrB,
srcCols,
fEncodeGamma);
}
}
}
/*****************************************************************************/
dng_render::dng_render (dng_host &host,
const dng_negative &negative)
: fHost (host)
, fNegative (negative)
, fWhiteXY ()
, fExposure (0.0)
, fShadows (5.0)
, fToneCurve (&dng_tone_curve_acr3_default::Get ())
, fFinalSpace (&dng_space_sRGB::Get ())
, fFinalPixelType (ttByte)
, fMaximumSize (0)
, fProfileToneCurve ()
{
// Switch to NOP default parameters for non-scence referred data.
if (fNegative.ColorimetricReference () != crSceneReferred)
{
fShadows = 0.0;
fToneCurve = &dng_1d_identity::Get ();
}
// Use default tone curve from profile if any.
const dng_camera_profile *profile = fNegative.ProfileByID (dng_camera_profile_id ());
if (profile && profile->ToneCurve ().IsValid ())
{
fProfileToneCurve.Reset (new dng_spline_solver);
profile->ToneCurve ().Solve (*fProfileToneCurve.Get ());
fToneCurve = fProfileToneCurve.Get ();
}
}
/*****************************************************************************/
dng_image * dng_render::Render ()
{
const dng_image *srcImage = fNegative.Stage3Image ();
dng_rect srcBounds = fNegative.DefaultCropArea ();
dng_point dstSize;
dstSize.h = fNegative.DefaultFinalWidth ();
dstSize.v = fNegative.DefaultFinalHeight ();
if (MaximumSize ())
{
if (Max_uint32 (dstSize.h, dstSize.v) > MaximumSize ())
{
real64 ratio = fNegative.AspectRatio ();
if (ratio >= 1.0)
{
dstSize.h = MaximumSize ();
dstSize.v = Max_uint32 (1, Round_uint32 (dstSize.h / ratio));
}
else
{
dstSize.v = MaximumSize ();
dstSize.h = Max_uint32 (1, Round_uint32 (dstSize.v * ratio));
}
}
}
AutoPtr<dng_image> tempImage;
if (srcBounds.Size () != dstSize)
{
tempImage.Reset (fHost.Make_dng_image (dstSize,
srcImage->Planes (),
srcImage->PixelType ()));
ResampleImage (fHost,
*srcImage,
*tempImage.Get (),
srcBounds,
tempImage->Bounds (),
dng_resample_bicubic::Get ());
srcImage = tempImage.Get ();
srcBounds = tempImage->Bounds ();
}
uint32 dstPlanes = FinalSpace ().IsMonochrome () ? 1 : 3;
AutoPtr<dng_image> dstImage (fHost.Make_dng_image (srcBounds.Size (),
dstPlanes,
FinalPixelType ()));
dng_render_task task (*srcImage,
*dstImage.Get (),
fNegative,
*this,
srcBounds.TL ());
fHost.PerformAreaTask (task,
dstImage->Bounds ());
return dstImage.Release ();
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_render.h b/core/libs/dngwriter/extra/dng_sdk/dng_render.h
index 0f62f3e76f..e0f09ed85a 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_render.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_render.h
@@ -1,310 +1,310 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_render.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** \file
* Classes for conversion of RAW data to final image.
*/
/*****************************************************************************/
#ifndef __dng_render__
#define __dng_render__
/*****************************************************************************/
#include "dng_1d_function.h"
#include "dng_auto_ptr.h"
#include "dng_classes.h"
#include "dng_spline.h"
#include "dng_xy_coord.h"
/******************************************************************************/
/// \brief Curve for pre-exposure-compensation adjustment based on noise floor, shadows, and highlight level.
class dng_function_exposure_ramp: public dng_1d_function
{
public:
real64 fSlope; // Slope of straight segment.
real64 fBlack; // Intercept of straight segment.
real64 fRadius; // Rounding radius.
- real64 fQScale; // Quadradic scale.
+ real64 fQScale; // Quadratic scale.
public:
dng_function_exposure_ramp (real64 white,
real64 black,
real64 minBlack);
virtual real64 Evaluate (real64 x) const;
};
/******************************************************************************/
-/// \brief Exposure compensation curve for a given compensation amount in stops using quadric for roll-off.
+/// \brief Exposure compensation curve for a given compensation amount in stops using quadratic for roll-off.
class dng_function_exposure_tone: public dng_1d_function
{
protected:
bool fIsNOP; // Is this a NOP function?
real64 fSlope; // Slope for lower part of curve.
real64 a; // Quadradic parameters for upper two f-stops.
real64 b;
real64 c;
public:
dng_function_exposure_tone (real64 exposure);
/// Returns output value for a given input tone.
virtual real64 Evaluate (real64 x) const;
};
/*****************************************************************************/
/// Default ACR3 tone curve.
class dng_tone_curve_acr3_default: public dng_1d_function
{
public:
/// Returns output value for a given input tone.
virtual real64 Evaluate (real64 x) const;
/// Returns nearest input value for a given output tone.
virtual real64 EvaluateInverse (real64 x) const;
static const dng_1d_function & Get ();
};
/*****************************************************************************/
/// \brief Encoding gamma curve for a given color space.
class dng_function_gamma_encode: public dng_1d_function
{
protected:
const dng_color_space &fSpace;
public:
dng_function_gamma_encode (const dng_color_space &space);
virtual real64 Evaluate (real64 x) const;
};
/*****************************************************************************/
/// \brief Class used to render digital negative to displayable image.
class dng_render
{
protected:
dng_host &fHost;
const dng_negative &fNegative;
dng_xy_coord fWhiteXY;
real64 fExposure;
real64 fShadows;
const dng_1d_function *fToneCurve;
const dng_color_space *fFinalSpace;
uint32 fFinalPixelType;
uint32 fMaximumSize;
private:
AutoPtr<dng_spline_solver> fProfileToneCurve;
public:
/// Construct a rendering instance that will be used to convert a given digital negative.
/// \param host The host to use for memory allocation, progress updates, and abort testing.
/// \param negative The digital negative to convert to a displayable image.
dng_render (dng_host &host,
const dng_negative &negative);
virtual ~dng_render ()
{
}
/// Set the white point to be used for conversion.
/// \param white White point to use.
void SetWhiteXY (const dng_xy_coord &white)
{
fWhiteXY = white;
}
/// Get the white point to be used for conversion.
/// \retval White point to use.
const dng_xy_coord WhiteXY () const
{
return fWhiteXY;
}
/// Set exposure compensation.
/// \param exposure Compensation value in stops, positive or negative.
void SetExposure (real64 exposure)
{
fExposure = exposure;
}
/// Get exposure compensation.
/// \retval Compensation value in stops, positive or negative.
real64 Exposure () const
{
return fExposure;
}
/// Set shadow clip amount.
/// \param shadows Shadow clip amount.
void SetShadows (real64 shadows)
{
fShadows = shadows;
}
/// Get shadow clip amount.
/// \retval Shadow clip amount.
real64 Shadows () const
{
return fShadows;
}
/// Set custom tone curve for conversion.
/// \param curve 1D function that defines tone mapping to use during conversion.
void SetToneCurve (const dng_1d_function &curve)
{
fToneCurve = &curve;
}
/// Get custom tone curve for conversion.
/// \retval 1D function that defines tone mapping to use during conversion.
const dng_1d_function & ToneCurve () const
{
return *fToneCurve;
}
/// Set final color space in which resulting image data should be represented.
/// (See dng_color_space.h for possible values.)
/// \param space Color space to use.
void SetFinalSpace (const dng_color_space &space)
{
fFinalSpace = &space;
}
/// Get final color space in which resulting image data should be represented.
/// \retval Color space to use.
const dng_color_space & FinalSpace () const
{
return *fFinalSpace;
}
/// Set pixel type of final image data.
/// Can be ttByte (default), ttShort, or ttFloat.
/// \param type Pixel type to use.
void SetFinalPixelType (uint32 type)
{
fFinalPixelType = type;
}
/// Get pixel type of final image data.
/// Can be ttByte (default), ttShort, or ttFloat.
/// \retval Pixel type to use.
uint32 FinalPixelType () const
{
return fFinalPixelType;
}
/// Set maximum dimension, in pixels, of resulting image.
/// If final image would have either dimension larger than maximum, the larger
/// of the two dimensions is set to this maximum size and the smaller dimension
/// is adjusted to preserve aspect ratio.
/// \param size Maximum size to allow.
void SetMaximumSize (uint32 size)
{
fMaximumSize = size;
}
/// Get maximum dimension, in pixels, of resulting image.
/// If the final image would have either dimension larger than this maximum, the larger
/// of the two dimensions is set to this maximum size and the smaller dimension
/// is adjusted to preserve the image's aspect ratio.
/// \retval Maximum allowed size.
uint32 MaximumSize () const
{
return fMaximumSize;
}
/// Actually render a digital negative to a displayable image.
/// Input digital negative is passed to the constructor of this dng_render class.
/// \retval The final resulting image.
virtual dng_image * Render ();
private:
// Hidden copy constructor and assignment operator.
dng_render (const dng_render &render);
dng_render & operator= (const dng_render &render);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_shared.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_shared.cpp
index dfda4691db..01451155b7 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_shared.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_shared.cpp
@@ -1,2927 +1,2927 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_shared.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_shared.h"
#include "dng_camera_profile.h"
#include "dng_exceptions.h"
#include "dng_globals.h"
#include "dng_parse_utils.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_utils.h"
/*****************************************************************************/
dng_camera_profile_info::dng_camera_profile_info ()
: fBigEndian (false)
, fColorPlanes (0)
, fCalibrationIlluminant1 (lsUnknown)
, fCalibrationIlluminant2 (lsUnknown)
, fColorMatrix1 ()
, fColorMatrix2 ()
, fForwardMatrix1 ()
, fForwardMatrix2 ()
, fReductionMatrix1 ()
, fReductionMatrix2 ()
, fProfileCalibrationSignature ()
, fProfileName ()
, fProfileCopyright ()
, fEmbedPolicy (pepAllowCopying)
, fProfileHues (0)
, fProfileSats (0)
, fProfileVals (0)
, fHueSatDeltas1Offset (0)
, fHueSatDeltas1Count (0)
, fHueSatDeltas2Offset (0)
, fHueSatDeltas2Count (0)
, fLookTableHues (0)
, fLookTableSats (0)
, fLookTableVals (0)
, fLookTableOffset (0)
, fLookTableCount (0)
, fToneCurveOffset (0)
, fToneCurveCount (0)
, fUniqueCameraModel ()
{
}
/*****************************************************************************/
dng_camera_profile_info::~dng_camera_profile_info ()
{
}
/*****************************************************************************/
bool dng_camera_profile_info::ParseTag (dng_stream &stream,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint64 tagOffset)
{
switch (tagCode)
{
case tcCalibrationIlluminant1:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fCalibrationIlluminant1 = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("CalibrationIlluminant1: %s\n",
LookupLightSource (fCalibrationIlluminant1));
}
#endif
break;
}
case tcCalibrationIlluminant2:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fCalibrationIlluminant2 = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("CalibrationIlluminant2: %s\n",
LookupLightSource (fCalibrationIlluminant2));
}
#endif
break;
}
case tcColorMatrix1:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (fColorPlanes == 0)
{
fColorPlanes = Pin_uint32 (0, tagCount / 3, kMaxColorPlanes);
}
if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
fColorPlanes,
3,
fColorMatrix1))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("ColorMatrix1:\n");
DumpMatrix (fColorMatrix1);
}
#endif
break;
}
case tcColorMatrix2:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
fColorPlanes,
3,
fColorMatrix2))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("ColorMatrix2:\n");
DumpMatrix (fColorMatrix2);
}
#endif
break;
}
case tcForwardMatrix1:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
3,
fColorPlanes,
fForwardMatrix1))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("ForwardMatrix1:\n");
DumpMatrix (fForwardMatrix1);
}
#endif
break;
}
case tcForwardMatrix2:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
3,
fColorPlanes,
fForwardMatrix2))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("ForwardMatrix2:\n");
DumpMatrix (fForwardMatrix2);
}
#endif
break;
}
case tcReductionMatrix1:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
3,
fColorPlanes,
fReductionMatrix1))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("ReductionMatrix1:\n");
DumpMatrix (fReductionMatrix1);
}
#endif
break;
}
case tcReductionMatrix2:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
3,
fColorPlanes,
fReductionMatrix2))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("ReductionMatrix2:\n");
DumpMatrix (fReductionMatrix2);
}
#endif
break;
}
case tcProfileCalibrationSignature:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fProfileCalibrationSignature,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileCalibrationSignature: ");
DumpString (fProfileCalibrationSignature);
printf ("\n");
}
#endif
break;
}
case tcProfileName:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fProfileName,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileName: ");
DumpString (fProfileName);
printf ("\n");
}
#endif
break;
}
case tcProfileCopyright:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fProfileCopyright,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileCopyright: ");
DumpString (fProfileCopyright);
printf ("\n");
}
#endif
break;
}
case tcProfileEmbedPolicy:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fEmbedPolicy = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
const char *policy;
switch (fEmbedPolicy)
{
case pepAllowCopying:
policy = "Allow copying";
break;
case pepEmbedIfUsed:
policy = "Embed if used";
break;
case pepEmbedNever:
policy = "Embed never";
break;
case pepNoRestrictions:
policy = "No restrictions";
break;
default:
policy = "INVALID VALUE";
}
printf ("ProfileEmbedPolicy: %s\n", policy);
}
#endif
break;
}
case tcProfileHueSatMapDims:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 2, 3);
fProfileHues = stream.TagValue_uint32 (tagType);
fProfileSats = stream.TagValue_uint32 (tagType);
if (tagCount > 2)
fProfileVals = stream.TagValue_uint32 (tagType);
else
fProfileVals = 1;
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileHueSatMapDims: Hues = %u, Sats = %u, Vals = %u\n",
(unsigned) fProfileHues,
(unsigned) fProfileSats,
(unsigned) fProfileVals);
}
#endif
break;
}
case tcProfileHueSatMapData1:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
return false;
bool skipSat0 = (tagCount == fProfileHues *
(fProfileSats - 1) *
fProfileVals * 3);
if (!skipSat0)
{
if (!CheckTagCount (parentCode, tagCode, tagCount, fProfileHues *
fProfileSats *
fProfileVals * 3))
return false;
}
fBigEndian = stream.BigEndian ();
fHueSatDeltas1Offset = tagOffset;
fHueSatDeltas1Count = tagCount;
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileHueSatMapData1:\n");
DumpHueSatMap (stream,
fProfileHues,
fProfileSats,
fProfileVals,
skipSat0);
}
#endif
break;
}
case tcProfileHueSatMapData2:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
return false;
bool skipSat0 = (tagCount == fProfileHues *
(fProfileSats - 1) *
fProfileVals * 3);
if (!skipSat0)
{
if (!CheckTagCount (parentCode, tagCode, tagCount, fProfileHues *
fProfileSats *
fProfileVals * 3))
return false;
}
fBigEndian = stream.BigEndian ();
fHueSatDeltas2Offset = tagOffset;
fHueSatDeltas2Count = tagCount;
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileHueSatMapData2:\n");
DumpHueSatMap (stream,
fProfileHues,
fProfileSats,
fProfileVals,
skipSat0);
}
#endif
break;
}
case tcProfileLookTableDims:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 2, 3);
fLookTableHues = stream.TagValue_uint32 (tagType);
fLookTableSats = stream.TagValue_uint32 (tagType);
if (tagCount > 2)
fLookTableVals = stream.TagValue_uint32 (tagType);
else
fLookTableVals = 1;
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileLookTableDims: Hues = %u, Sats = %u, Vals = %u\n",
(unsigned) fLookTableHues,
(unsigned) fLookTableSats,
(unsigned) fLookTableVals);
}
#endif
break;
}
case tcProfileLookTableData:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
return false;
bool skipSat0 = (tagCount == fLookTableHues *
(fLookTableSats - 1) *
fLookTableVals * 3);
if (!skipSat0)
{
if (!CheckTagCount (parentCode, tagCode, tagCount, fLookTableHues *
fLookTableSats *
fLookTableVals * 3))
return false;
}
fBigEndian = stream.BigEndian ();
fLookTableOffset = tagOffset;
fLookTableCount = tagCount;
#if qDNGValidate
if (gVerbose)
{
printf ("ProfileLookTableData:\n");
DumpHueSatMap (stream,
fLookTableHues,
fLookTableSats,
fLookTableVals,
skipSat0);
}
#endif
break;
}
case tcProfileToneCurve:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 4, tagCount))
return false;
if ((tagCount & 1) != 0)
{
#if qDNGValidate
{
char message [256];
sprintf (message,
"%s %s has odd count (%u)",
LookupParentCode (parentCode),
LookupTagCode (parentCode, tagCode),
(unsigned) tagCount);
ReportWarning (message);
}
#endif
return false;
}
fBigEndian = stream.BigEndian ();
fToneCurveOffset = tagOffset;
fToneCurveCount = tagCount;
#if qDNGValidate
if (gVerbose)
{
DumpTagValues (stream,
"Coord",
parentCode,
tagCode,
tagType,
tagCount);
}
#endif
break;
}
case tcUniqueCameraModel:
{
// Note: This code is only used when parsing stand-alone
// profiles. The embedded profiles are assumed to be restricted
// to the model they are embedded in.
CheckTagType (parentCode, tagCode, tagType, ttAscii);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fUniqueCameraModel,
false);
bool didTrim = fUniqueCameraModel.TrimTrailingBlanks ();
#if qDNGValidate
if (didTrim)
{
ReportWarning ("UniqueCameraModel string has trailing blanks");
}
if (gVerbose)
{
printf ("UniqueCameraModel: ");
DumpString (fUniqueCameraModel);
printf ("\n");
}
#else
(void) didTrim; // Unused
#endif
break;
}
default:
{
return false;
}
}
return true;
}
/*****************************************************************************/
bool dng_camera_profile_info::ParseExtended (dng_stream &stream)
{
try
{
// Offsets are relative to the start of this structure, not the entire file.
uint64 startPosition = stream.Position ();
// Read header. Like a TIFF header, but with different magic number
// Plus all offsets are relative to the start of the IFD, not to the
// stream or file.
uint16 byteOrder = stream.Get_uint16 ();
if (byteOrder == byteOrderMM)
fBigEndian = true;
else if (byteOrder == byteOrderII)
fBigEndian = false;
else
return false;
TempBigEndian setEndianness (stream, fBigEndian);
uint16 magicNumber = stream.Get_uint16 ();
if (magicNumber != magicExtendedProfile)
{
return false;
}
uint32 offset = stream.Get_uint32 ();
stream.Skip (offset - 8);
// Start on IFD entries.
uint32 ifdEntries = stream.Get_uint16 ();
if (ifdEntries < 1)
{
return false;
}
for (uint32 tag_index = 0; tag_index < ifdEntries; tag_index++)
{
stream.SetReadPosition (startPosition + 8 + 2 + tag_index * 12);
uint16 tagCode = stream.Get_uint16 ();
uint32 tagType = stream.Get_uint16 ();
uint32 tagCount = stream.Get_uint32 ();
uint64 tagOffset = stream.Position ();
if (TagTypeSize (tagType) * tagCount > 4)
{
tagOffset = startPosition + stream.Get_uint32 ();
stream.SetReadPosition (tagOffset);
}
if (!ParseTag (stream,
0,
tagCode,
tagType,
tagCount,
tagOffset))
{
#if qDNGValidate
if (gVerbose)
{
stream.SetReadPosition (tagOffset);
printf ("*");
DumpTagValues (stream,
LookupTagType (tagType),
0,
tagCode,
tagType,
tagCount);
}
#endif
}
}
return true;
}
catch (...)
{
// Eat parsing errors.
}
return false;
}
/*****************************************************************************/
dng_shared::dng_shared ()
: fExifIFD (0)
, fGPSInfo (0)
, fInteroperabilityIFD (0)
, fKodakDCRPrivateIFD (0)
, fKodakKDCPrivateIFD (0)
, fXMPCount (0)
, fXMPOffset (0)
, fIPTC_NAA_Count (0)
, fIPTC_NAA_Offset (0)
, fMakerNoteCount (0)
, fMakerNoteOffset (0)
, fMakerNoteSafety (0)
, fDNGVersion (0)
, fDNGBackwardVersion (0)
, fUniqueCameraModel ()
, fLocalizedCameraModel ()
, fCameraProfile ()
, fExtraCameraProfiles ()
, fCameraCalibration1 ()
, fCameraCalibration2 ()
, fCameraCalibrationSignature ()
, fAnalogBalance ()
, fAsShotNeutral ()
, fAsShotWhiteXY ()
, fBaselineExposure (0, 1)
, fBaselineNoise (1, 1)
, fNoiseReductionApplied (0, 0)
, fBaselineSharpness (1, 1)
, fLinearResponseLimit (1, 1)
, fShadowScale (1, 1)
, fDNGPrivateDataCount (0)
, fDNGPrivateDataOffset (0)
, fRawImageDigest ()
, fRawDataUniqueID ()
, fOriginalRawFileName ()
, fOriginalRawFileDataCount (0)
, fOriginalRawFileDataOffset (0)
, fOriginalRawFileDigest ()
, fAsShotICCProfileCount (0)
, fAsShotICCProfileOffset (0)
, fAsShotPreProfileMatrix ()
, fCurrentICCProfileCount (0)
, fCurrentICCProfileOffset (0)
, fCurrentPreProfileMatrix ()
, fColorimetricReference (crSceneReferred)
, fAsShotProfileName ()
, fNoiseProfile ()
{
}
/*****************************************************************************/
dng_shared::~dng_shared ()
{
}
/*****************************************************************************/
bool dng_shared::ParseTag (dng_stream &stream,
dng_exif &exif,
uint32 parentCode,
bool /* isMainIFD */,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint64 tagOffset,
int64 /* offsetDelta */)
{
if (parentCode == 0)
{
if (Parse_ifd0 (stream,
exif,
parentCode,
tagCode,
tagType,
tagCount,
tagOffset))
{
return true;
}
}
if (parentCode == 0 ||
parentCode == tcExifIFD)
{
if (Parse_ifd0_exif (stream,
exif,
parentCode,
tagCode,
tagType,
tagCount,
tagOffset))
{
return true;
}
}
return false;
}
/*****************************************************************************/
// Parses tags that should only appear in IFD 0.
bool dng_shared::Parse_ifd0 (dng_stream &stream,
dng_exif & /* exif */,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint64 tagOffset)
{
switch (tagCode)
{
case tcXMP:
{
CheckTagType (parentCode, tagCode, tagType, ttByte);
fXMPCount = tagCount;
fXMPOffset = fXMPCount ? tagOffset : 0;
#if qDNGValidate
if (gVerbose)
{
printf ("XMP: Count = %u, Offset = %u\n",
(unsigned) fXMPCount,
(unsigned) fXMPOffset);
if (fXMPCount)
{
DumpXMP (stream, fXMPCount);
}
}
#endif
break;
}
case tcIPTC_NAA:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttAscii);
fIPTC_NAA_Count = tagCount * TagTypeSize (tagType);
fIPTC_NAA_Offset = fIPTC_NAA_Count ? tagOffset : 0;
#if qDNGValidate
if (gVerbose)
{
printf ("IPTC/NAA: Count = %u, Offset = %u\n",
(unsigned) fIPTC_NAA_Count,
(unsigned) fIPTC_NAA_Offset);
if (fIPTC_NAA_Count)
{
DumpHexAscii (stream, fIPTC_NAA_Count);
}
// Compute and output the digest.
dng_memory_data buffer (fIPTC_NAA_Count);
stream.SetReadPosition (fIPTC_NAA_Offset);
stream.Get (buffer.Buffer (), fIPTC_NAA_Count);
const uint8 *data = buffer.Buffer_uint8 ();
uint32 count = fIPTC_NAA_Count;
// Method 1: Counting all bytes (this is correct).
{
dng_md5_printer printer;
printer.Process (data, count);
printf ("IPTCDigest: ");
DumpFingerprint (printer.Result ());
printf ("\n");
}
// Method 2: Ignoring zero padding.
{
uint32 removed = 0;
while ((removed < 3) && (count > 0) && (data [count - 1] == 0))
{
removed++;
count--;
}
if (removed != 0)
{
dng_md5_printer printer;
printer.Process (data, count);
printf ("IPTCDigest (ignoring zero padding): ");
DumpFingerprint (printer.Result ());
printf ("\n");
}
}
}
#endif
break;
}
case tcExifIFD:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fExifIFD = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ExifIFD: %u\n", (unsigned) fExifIFD);
}
#endif
break;
}
case tcGPSInfo:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fGPSInfo = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("GPSInfo: %u\n", (unsigned) fGPSInfo);
}
#endif
break;
}
case tcKodakDCRPrivateIFD:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fKodakDCRPrivateIFD = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("KodakDCRPrivateIFD: %u\n", (unsigned) fKodakDCRPrivateIFD);
}
#endif
break;
}
case tcKodakKDCPrivateIFD:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fKodakKDCPrivateIFD = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("KodakKDCPrivateIFD: %u\n", (unsigned) fKodakKDCPrivateIFD);
}
#endif
break;
}
case tcDNGVersion:
{
CheckTagType (parentCode, tagCode, tagType, ttByte);
CheckTagCount (parentCode, tagCode, tagCount, 4);
uint32 b0 = stream.Get_uint8 ();
uint32 b1 = stream.Get_uint8 ();
uint32 b2 = stream.Get_uint8 ();
uint32 b3 = stream.Get_uint8 ();
fDNGVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
#if qDNGValidate
if (gVerbose)
{
printf ("DNGVersion: %u.%u.%u.%u\n",
(unsigned) b0,
(unsigned) b1,
(unsigned) b2,
(unsigned) b3);
}
#endif
break;
}
case tcDNGBackwardVersion:
{
CheckTagType (parentCode, tagCode, tagType, ttByte);
CheckTagCount (parentCode, tagCode, tagCount, 4);
uint32 b0 = stream.Get_uint8 ();
uint32 b1 = stream.Get_uint8 ();
uint32 b2 = stream.Get_uint8 ();
uint32 b3 = stream.Get_uint8 ();
fDNGBackwardVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;
#if qDNGValidate
if (gVerbose)
{
printf ("DNGBackwardVersion: %u.%u.%u.%u\n",
(unsigned) b0,
(unsigned) b1,
(unsigned) b2,
(unsigned) b3);
}
#endif
break;
}
case tcUniqueCameraModel:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fUniqueCameraModel,
false);
bool didTrim = fUniqueCameraModel.TrimTrailingBlanks ();
#if qDNGValidate
if (didTrim)
{
ReportWarning ("UniqueCameraModel string has trailing blanks");
}
if (gVerbose)
{
printf ("UniqueCameraModel: ");
DumpString (fUniqueCameraModel);
printf ("\n");
}
#else
(void) didTrim; // Unused
#endif
break;
}
case tcLocalizedCameraModel:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fLocalizedCameraModel,
false,
false);
bool didTrim = fLocalizedCameraModel.TrimTrailingBlanks ();
#if qDNGValidate
if (didTrim)
{
ReportWarning ("LocalizedCameraModel string has trailing blanks");
}
if (gVerbose)
{
printf ("LocalizedCameraModel: ");
DumpString (fLocalizedCameraModel);
printf ("\n");
}
#else
(void) didTrim; // Unused
#endif
break;
}
case tcCameraCalibration1:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
fCameraProfile.fColorPlanes,
fCameraProfile.fColorPlanes,
fCameraCalibration1))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("CameraCalibration1:\n");
DumpMatrix (fCameraCalibration1);
}
#endif
break;
}
case tcCameraCalibration2:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
fCameraProfile.fColorPlanes,
fCameraProfile.fColorPlanes,
fCameraCalibration2))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("CameraCalibration2:\n");
DumpMatrix (fCameraCalibration2);
}
#endif
break;
}
case tcCameraCalibrationSignature:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fCameraCalibrationSignature,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("CameraCalibrationSignature: ");
DumpString (fCameraCalibrationSignature);
printf ("\n");
}
#endif
break;
}
case tcAnalogBalance:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
if (!ParseVectorTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
fCameraProfile.fColorPlanes,
fAnalogBalance))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("AnalogBalance:");
DumpVector (fAnalogBalance);
}
#endif
break;
}
case tcAsShotNeutral:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
if (!ParseVectorTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
fCameraProfile.fColorPlanes,
fAsShotNeutral))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("AsShotNeutral:");
DumpVector (fAsShotNeutral);
}
#endif
break;
}
case tcAsShotWhiteXY:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
return false;
fAsShotWhiteXY.x = stream.TagValue_real64 (tagType);
fAsShotWhiteXY.y = stream.TagValue_real64 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("AsShotWhiteXY: %0.4f %0.4f\n",
fAsShotWhiteXY.x,
fAsShotWhiteXY.y);
}
#endif
break;
}
case tcBaselineExposure:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fBaselineExposure = stream.TagValue_srational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("BaselineExposure: %+0.2f\n",
fBaselineExposure.As_real64 ());
}
#endif
break;
}
case tcBaselineNoise:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fBaselineNoise = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("BaselineNoise: %0.2f\n",
fBaselineNoise.As_real64 ());
}
#endif
break;
}
case tcNoiseReductionApplied:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttRational))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 1))
return false;
fNoiseReductionApplied = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("NoiseReductionApplied: %u/%u\n",
(unsigned) fNoiseReductionApplied.n,
(unsigned) fNoiseReductionApplied.d);
}
#endif
break;
}
case tcNoiseProfile:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttDouble))
return false;
// Must be an even, positive number of doubles in a noise profile.
if (!tagCount || (tagCount & 1))
return false;
// Determine number of planes (i.e., half the number of doubles).
const uint32 numPlanes = Pin_uint32 (0,
tagCount >> 1,
kMaxColorPlanes);
// Parse the noise function parameters.
std::vector<dng_noise_function> noiseFunctions;
for (uint32 i = 0; i < numPlanes; i++)
{
const real64 scale = stream.TagValue_real64 (tagType);
const real64 offset = stream.TagValue_real64 (tagType);
noiseFunctions.push_back (dng_noise_function (scale, offset));
}
// Store the noise profile.
fNoiseProfile = dng_noise_profile (noiseFunctions);
// Debug.
#if qDNGValidate
if (gVerbose)
{
printf ("NoiseProfile:\n");
printf (" Planes: %u\n", numPlanes);
for (uint32 plane = 0; plane < numPlanes; plane++)
{
printf (" Noise function for plane %u: scale = %.8lf, offset = %.8lf\n",
plane,
noiseFunctions [plane].Scale (),
noiseFunctions [plane].Offset ());
}
}
#endif
break;
}
case tcBaselineSharpness:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fBaselineSharpness = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("BaselineSharpness: %0.2f\n",
fBaselineSharpness.As_real64 ());
}
#endif
break;
}
case tcLinearResponseLimit:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fLinearResponseLimit = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("LinearResponseLimit: %0.2f\n",
fLinearResponseLimit.As_real64 ());
}
#endif
break;
}
case tcShadowScale:
{
CheckTagType (parentCode, tagCode, tagType, ttRational);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fShadowScale = stream.TagValue_urational (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ShadowScale: %0.4f\n",
fShadowScale.As_real64 ());
}
#endif
break;
}
case tcDNGPrivateData:
{
CheckTagType (parentCode, tagCode, tagType, ttByte);
fDNGPrivateDataCount = tagCount;
fDNGPrivateDataOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("DNGPrivateData: Count = %u, Offset = %u\n",
(unsigned) fDNGPrivateDataCount,
(unsigned) fDNGPrivateDataOffset);
DumpHexAscii (stream, tagCount);
}
#endif
break;
}
case tcMakerNoteSafety:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fMakerNoteSafety = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("MakerNoteSafety: %s\n",
LookupMakerNoteSafety (fMakerNoteSafety));
}
#endif
break;
}
case tcRawImageDigest:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
return false;
stream.Get (fRawImageDigest.data, 16);
#if qDNGValidate
if (gVerbose)
{
printf ("RawImageDigest: ");
DumpFingerprint (fRawImageDigest);
printf ("\n");
}
#endif
break;
}
case tcRawDataUniqueID:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
return false;
stream.Get (fRawDataUniqueID.data, 16);
#if qDNGValidate
if (gVerbose)
{
printf ("RawDataUniqueID: ");
DumpFingerprint (fRawDataUniqueID);
printf ("\n");
}
#endif
break;
}
case tcOriginalRawFileName:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fOriginalRawFileName,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("OriginalRawFileName: ");
DumpString (fOriginalRawFileName);
printf ("\n");
}
#endif
break;
}
case tcOriginalRawFileData:
{
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fOriginalRawFileDataCount = tagCount;
fOriginalRawFileDataOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("OriginalRawFileData: Count = %u, Offset = %u\n",
(unsigned) fOriginalRawFileDataCount,
(unsigned) fOriginalRawFileDataOffset);
DumpHexAscii (stream, tagCount);
}
#endif
break;
}
case tcOriginalRawFileDigest:
{
if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
return false;
if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
return false;
stream.Get (fOriginalRawFileDigest.data, 16);
#if qDNGValidate
if (gVerbose)
{
printf ("OriginalRawFileDigest: ");
DumpFingerprint (fOriginalRawFileDigest);
printf ("\n");
}
#endif
break;
}
case tcAsShotICCProfile:
{
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fAsShotICCProfileCount = tagCount;
fAsShotICCProfileOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("AsShotICCProfile: Count = %u, Offset = %u\n",
(unsigned) fAsShotICCProfileCount,
(unsigned) fAsShotICCProfileOffset);
DumpHexAscii (stream, tagCount);
}
#endif
break;
}
case tcAsShotPreProfileMatrix:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
uint32 rows = fCameraProfile.fColorPlanes;
if (tagCount == fCameraProfile.fColorPlanes * 3)
{
rows = 3;
}
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
rows,
fCameraProfile.fColorPlanes,
fAsShotPreProfileMatrix))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("AsShotPreProfileMatrix:\n");
DumpMatrix (fAsShotPreProfileMatrix);
}
#endif
break;
}
case tcCurrentICCProfile:
{
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fCurrentICCProfileCount = tagCount;
fCurrentICCProfileOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("CurrentICCProfile: Count = %u, Offset = %u\n",
(unsigned) fCurrentICCProfileCount,
(unsigned) fCurrentICCProfileOffset);
DumpHexAscii (stream, tagCount);
}
#endif
break;
}
case tcCurrentPreProfileMatrix:
{
CheckTagType (parentCode, tagCode, tagType, ttSRational);
if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
return false;
uint32 rows = fCameraProfile.fColorPlanes;
if (tagCount == fCameraProfile.fColorPlanes * 3)
{
rows = 3;
}
if (!ParseMatrixTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
rows,
fCameraProfile.fColorPlanes,
fCurrentPreProfileMatrix))
return false;
#if qDNGValidate
if (gVerbose)
{
printf ("CurrentPreProfileMatrix:\n");
DumpMatrix (fCurrentPreProfileMatrix);
}
#endif
break;
}
case tcColorimetricReference:
{
CheckTagType (parentCode, tagCode, tagType, ttShort);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fColorimetricReference = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("ColorimetricReference: %s\n",
LookupColorimetricReference (fColorimetricReference));
}
#endif
break;
}
case tcExtraCameraProfiles:
{
CheckTagType (parentCode, tagCode, tagType, ttLong);
CheckTagCount (parentCode, tagCode, tagCount, 1, tagCount);
#if qDNGValidate
if (gVerbose)
{
printf ("ExtraCameraProfiles: %u\n", (unsigned) tagCount);
}
#endif
fExtraCameraProfiles.reserve (tagCount);
for (uint32 index = 0; index < tagCount; index++)
{
#if qDNGValidate
if (gVerbose)
{
printf ("\nExtraCameraProfile [%u]:\n\n", (unsigned) index);
}
#endif
stream.SetReadPosition (tagOffset + index * 4);
uint32 profileOffset = stream.TagValue_uint32 (tagType);
dng_camera_profile_info profileInfo;
stream.SetReadPosition (profileOffset);
if (profileInfo.ParseExtended (stream))
{
fExtraCameraProfiles.push_back (profileInfo);
}
else
{
#if qDNGValidate
ReportWarning ("Unable to parse extra camera profile");
#endif
}
}
#if qDNGValidate
if (gVerbose)
{
printf ("\nDone with ExtraCameraProfiles\n\n");
}
#endif
break;
}
case tcAsShotProfileName:
{
CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);
ParseStringTag (stream,
parentCode,
tagCode,
tagCount,
fAsShotProfileName,
false,
false);
#if qDNGValidate
if (gVerbose)
{
printf ("AsShotProfileName: ");
DumpString (fAsShotProfileName);
printf ("\n");
}
#endif
break;
}
default:
{
// The main camera profile tags also appear in IFD 0
return fCameraProfile.ParseTag (stream,
parentCode,
tagCode,
tagType,
tagCount,
tagOffset);
}
}
return true;
}
/*****************************************************************************/
// Parses tags that should only appear in IFD 0 or EXIF IFD.
bool dng_shared::Parse_ifd0_exif (dng_stream &stream,
dng_exif & /* exif */,
uint32 parentCode,
uint32 tagCode,
uint32 tagType,
uint32 tagCount,
uint64 tagOffset)
{
switch (tagCode)
{
case tcMakerNote:
{
CheckTagType (parentCode, tagCode, tagType, ttUndefined);
fMakerNoteCount = tagCount;
fMakerNoteOffset = tagOffset;
#if qDNGValidate
if (gVerbose)
{
printf ("MakerNote: Count = %u, Offset = %u\n",
(unsigned) fMakerNoteCount,
(unsigned) fMakerNoteOffset);
DumpHexAscii (stream, tagCount);
}
#endif
break;
}
case tcInteroperabilityIFD:
{
CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);
CheckTagCount (parentCode, tagCode, tagCount, 1);
fInteroperabilityIFD = stream.TagValue_uint32 (tagType);
#if qDNGValidate
if (gVerbose)
{
printf ("InteroperabilityIFD: %u\n", (unsigned) fInteroperabilityIFD);
}
#endif
break;
}
default:
{
return false;
}
}
return true;
}
/*****************************************************************************/
void dng_shared::PostParse (dng_host & /* host */,
dng_exif & /* exif */)
{
// Fill in default values for DNG images.
if (fDNGVersion != 0)
{
// Support for DNG versions before 1.0.0.0.
if (fDNGVersion < dngVersion_1_0_0_0)
{
#if qDNGValidate
ReportWarning ("DNGVersion less than 1.0.0.0");
#endif
// The CalibrationIlluminant tags were added just before
// DNG version 1.0.0.0, and were hardcoded before that.
fCameraProfile.fCalibrationIlluminant1 = lsStandardLightA;
fCameraProfile.fCalibrationIlluminant2 = lsD65;
fDNGVersion = dngVersion_1_0_0_0;
}
// Default value for DNGBackwardVersion tag.
if (fDNGBackwardVersion == 0)
{
fDNGBackwardVersion = fDNGVersion & 0xFFFF0000;
}
// Check DNGBackwardVersion value.
if (fDNGBackwardVersion < dngVersion_1_0_0_0)
{
#if qDNGValidate
ReportWarning ("DNGBackwardVersion less than 1.0.0.0");
#endif
fDNGBackwardVersion = dngVersion_1_0_0_0;
}
if (fDNGBackwardVersion > fDNGVersion)
{
#if qDNGValidate
ReportWarning ("DNGBackwardVersion > DNGVersion");
#endif
fDNGBackwardVersion = fDNGVersion;
}
// Check UniqueCameraModel.
if (fUniqueCameraModel.IsEmpty ())
{
#if qDNGValidate
ReportWarning ("Missing or invalid UniqueCameraModel");
#endif
fUniqueCameraModel.Set ("Digital Negative");
}
// If we don't know the color depth yet, it must be a monochrome DNG.
if (fCameraProfile.fColorPlanes == 0)
{
fCameraProfile.fColorPlanes = 1;
}
// Check color info.
if (fCameraProfile.fColorPlanes > 1)
{
// Check illuminant pair.
if (fCameraProfile.fColorMatrix2.NotEmpty ())
{
if (fCameraProfile.fCalibrationIlluminant1 == lsUnknown ||
(fCameraProfile.fCalibrationIlluminant2 == lsUnknown ||
(fCameraProfile.fCalibrationIlluminant1 == fCameraProfile.fCalibrationIlluminant2)))
{
#if qDNGValidate
ReportWarning ("Invalid CalibrationIlluminant pair");
#endif
fCameraProfile.fColorMatrix2 = dng_matrix ();
}
}
// If the colorimetric reference is the ICC profile PCS, then the
// data must already be white balanced. The "AsShotWhiteXY" is required
// to be the ICC Profile PCS white point.
if (fColorimetricReference == crICCProfilePCS)
{
if (fAsShotNeutral.NotEmpty ())
{
#if qDNGValidate
ReportWarning ("AsShotNeutral not allowed for this "
"ColorimetricReference value");
#endif
fAsShotNeutral.Clear ();
}
dng_xy_coord pcs = PCStoXY ();
#if qDNGValidate
if (fAsShotWhiteXY.IsValid ())
{
if (Abs_real64 (fAsShotWhiteXY.x - pcs.x) > 0.01 ||
Abs_real64 (fAsShotWhiteXY.y - pcs.y) > 0.01)
{
ReportWarning ("AsShotWhiteXY does not match the ICC Profile PCS");
}
}
#endif
fAsShotWhiteXY = pcs;
}
else
{
// Warn if both AsShotNeutral and AsShotWhiteXY are specified.
if (fAsShotNeutral.NotEmpty () && fAsShotWhiteXY.IsValid ())
{
#if qDNGValidate
ReportWarning ("Both AsShotNeutral and AsShotWhiteXY included");
#endif
fAsShotWhiteXY = dng_xy_coord ();
}
// Warn if neither AsShotNeutral nor AsShotWhiteXY are specified.
#if qDNGValidate
if (fAsShotNeutral.IsEmpty () && !fAsShotWhiteXY.IsValid ())
{
ReportWarning ("Neither AsShotNeutral nor AsShotWhiteXY included",
"legal but not recommended");
}
#endif
}
// Default values of calibration signatures are required for legacy
- // compatiblity.
+ // compatibility.
if (fCameraProfile.fCalibrationIlluminant1 == lsStandardLightA &&
fCameraProfile.fCalibrationIlluminant2 == lsD65 &&
fCameraCalibration1.Rows () == fCameraProfile.fColorPlanes &&
fCameraCalibration1.Cols () == fCameraProfile.fColorPlanes &&
fCameraCalibration2.Rows () == fCameraProfile.fColorPlanes &&
fCameraCalibration2.Cols () == fCameraProfile.fColorPlanes &&
fCameraCalibrationSignature.IsEmpty () &&
fCameraProfile.fProfileCalibrationSignature.IsEmpty () )
{
fCameraCalibrationSignature.Set (kAdobeCalibrationSignature);
fCameraProfile.fProfileCalibrationSignature.Set (kAdobeCalibrationSignature);
}
}
// Check BaselineNoise.
if (fBaselineNoise.As_real64 () <= 0.0)
{
#if qDNGValidate
ReportWarning ("Invalid BaselineNoise");
#endif
fBaselineNoise = dng_urational (1, 1);
}
// Check BaselineSharpness.
if (fBaselineSharpness.As_real64 () <= 0.0)
{
#if qDNGValidate
ReportWarning ("Invalid BaselineSharpness");
#endif
fBaselineSharpness = dng_urational (1, 1);
}
// Check NoiseProfile.
if (!fNoiseProfile.IsValid () && fNoiseProfile.NumFunctions () != 0)
{
#if qDNGValidate
ReportWarning ("Invalid NoiseProfile");
#endif
fNoiseProfile = dng_noise_profile ();
}
// Check LinearResponseLimit.
if (fLinearResponseLimit.As_real64 () < 0.5 ||
fLinearResponseLimit.As_real64 () > 1.0)
{
#if qDNGValidate
ReportWarning ("Invalid LinearResponseLimit");
#endif
fLinearResponseLimit = dng_urational (1, 1);
}
// Check ShadowScale.
if (fShadowScale.As_real64 () <= 0.0)
{
#if qDNGValidate
ReportWarning ("Invalid ShadowScale");
#endif
fShadowScale = dng_urational (1, 1);
}
}
}
/*****************************************************************************/
bool dng_shared::IsValidDNG ()
{
// Check DNGVersion value.
if (fDNGVersion < dngVersion_1_0_0_0)
{
#if qDNGValidate
ReportError ("Missing or invalid DNGVersion");
#endif
return false;
}
// Check DNGBackwardVersion value.
if (fDNGBackwardVersion > dngVersion_Current)
{
#if qDNGValidate
ReportError ("DNGBackwardVersion (or DNGVersion) is too high");
#endif
return false;
}
// Check color transform info.
if (fCameraProfile.fColorPlanes > 1)
{
// CameraCalibration1 is optional, but it must be valid if present.
if (fCameraCalibration1.Cols () != 0 ||
fCameraCalibration1.Rows () != 0)
{
if (fCameraCalibration1.Cols () != fCameraProfile.fColorPlanes ||
fCameraCalibration1.Rows () != fCameraProfile.fColorPlanes)
{
#if qDNGValidate
ReportError ("CameraCalibration1 is wrong size");
#endif
return false;
}
- // Make sure it is invertable.
+ // Make sure it is invertible.
try
{
(void) Invert (fCameraCalibration1);
}
catch (...)
{
#if qDNGValidate
- ReportError ("CameraCalibration1 is not invertable");
+ ReportError ("CameraCalibration1 is not invertible");
#endif
return false;
}
}
// CameraCalibration2 is optional, but it must be valid if present.
if (fCameraCalibration2.Cols () != 0 ||
fCameraCalibration2.Rows () != 0)
{
if (fCameraCalibration2.Cols () != fCameraProfile.fColorPlanes ||
fCameraCalibration2.Rows () != fCameraProfile.fColorPlanes)
{
#if qDNGValidate
ReportError ("CameraCalibration2 is wrong size");
#endif
return false;
}
- // Make sure it is invertable.
+ // Make sure it is invertible.
try
{
(void) Invert (fCameraCalibration2);
}
catch (...)
{
#if qDNGValidate
- ReportError ("CameraCalibration2 is not invertable");
+ ReportError ("CameraCalibration2 is not invertible");
#endif
return false;
}
}
// Check analog balance
dng_matrix analogBalance;
if (fAnalogBalance.NotEmpty ())
{
analogBalance = fAnalogBalance.AsDiagonal ();
try
{
(void) Invert (analogBalance);
}
catch (...)
{
#if qDNGValidate
- ReportError ("AnalogBalance is not invertable");
+ ReportError ("AnalogBalance is not invertible");
#endif
return false;
}
}
}
return true;
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_stream.h b/core/libs/dngwriter/extra/dng_sdk/dng_stream.h
index 48d9c69274..5d83fe386d 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_stream.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_stream.h
@@ -1,698 +1,698 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_stream.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/** Data stream abstraction for serializing and deserializing sequences of
* basic types and RAW image data.
*/
/*****************************************************************************/
#ifndef __dng_stream__
#define __dng_stream__
/*****************************************************************************/
#include "dng_classes.h"
#include "dng_types.h"
#include "dng_memory.h"
#include "dng_rational.h"
#include "dng_utils.h"
/*****************************************************************************/
// Constants for invalid offset in streams.
const uint64 kDNGStreamInvalidOffset = 0xFFFFFFFFFFFFFFFFLL;
/*****************************************************************************/
/// Base stream abstraction. Has support for going between stream and pointer
/// abstraction.
class dng_stream
{
public:
enum
{
kSmallBufferSize = 4 * 1024,
kBigBufferSize = 64 * 1024,
kDefaultBufferSize = kSmallBufferSize
};
private:
bool fSwapBytes;
bool fHaveLength;
uint64 fLength;
const uint64 fOffsetInOriginalFile;
uint64 fPosition;
dng_memory_data fMemBlock;
uint8 *fBuffer;
uint32 fBufferSize;
uint64 fBufferStart;
uint64 fBufferEnd;
uint64 fBufferLimit;
bool fBufferDirty;
dng_abort_sniffer *fSniffer;
protected:
dng_stream (dng_abort_sniffer *sniffer = NULL,
uint32 bufferSize = kDefaultBufferSize,
uint64 offsetInOriginalFile = kDNGStreamInvalidOffset);
virtual uint64 DoGetLength ();
virtual void DoRead (void *data,
uint32 count,
uint64 offset);
virtual void DoSetLength (uint64 length);
virtual void DoWrite (const void *data,
uint32 count,
uint64 offset);
public:
/// Construct a stream with initial data.
/// \param data Pointer to initial contents of stream.
/// \param count Number of bytes data is valid for.
/// \param offsetInOriginalFile If data came from a file originally,
/// offset can be saved here for later use.
dng_stream (const void *data,
uint32 count,
uint64 offsetInOriginalFile = kDNGStreamInvalidOffset);
virtual ~dng_stream ();
/// Getter for whether stream is swapping byte order on input/output.
/// \retval If true, data will be swapped on input/output.
bool SwapBytes () const
{
return fSwapBytes;
}
/// Setter for whether stream is swapping byte order on input/output.
/// \param swapBytes If true, stream will swap byte order on input or
/// output for future reads/writes.
void SetSwapBytes (bool swapBytes)
{
fSwapBytes = swapBytes;
}
/// Getter for whether data in stream is big endian.
/// \retval If true, data in stream is big endian.
bool BigEndian () const;
/// Setter for whether data in stream is big endian.
/// \param bigEndian If true, data in stream is big endian.
void SetBigEndian (bool bigEndian = true);
/// Getter for whether data in stream is big endian.
/// \retval If true, data in stream is big endian.
bool LittleEndian () const
{
return !BigEndian ();
}
/// Setter for whether data in stream is big endian.
/// \param littleEndian If true, data in stream is big endian.
void SetLittleEndian (bool littleEndian = true)
{
SetBigEndian (!littleEndian);
}
/// Returns the size of the buffer used by the stream.
uint32 BufferSize () const
{
return fBufferSize;
}
/// Getter for length of data in stream.
/// \retval Length of readable data in stream.
uint64 Length ()
{
if (!fHaveLength)
{
fLength = DoGetLength ();
fHaveLength = true;
}
return fLength;
}
/// Getter for current offset in stream.
/// \retval current offset from start of stream.
uint64 Position () const
{
return fPosition;
}
/// Getter for current position in original file, taking into account
/// OffsetInOriginalFile stream data was taken from.
/// \retval kInvalidOffset if no offset in original file is set, sum
/// of offset in original file and current position otherwise.
uint64 PositionInOriginalFile () const;
/// Getter for offset in original file.
/// \retval kInvalidOffset if no offset in original file is set,
/// offset in original file otherwise.
uint64 OffsetInOriginalFile () const;
/// Return pointer to stream contents if the stream is entirely
/// available as a single memory block, NULL otherwise.
const void * Data () const;
/// Return the entire stream as a single memory block.
/// This works for all streams, but requires copying the data to a new buffer.
/// \param allocator Allocator used to allocate memory.
dng_memory_block * AsMemoryBlock (dng_memory_allocator &allocator);
/// Seek to a new position in stream for reading.
void SetReadPosition (uint64 offset);
/// Skip forward in stream.
/// \param delta Number of bytes to skip forward.
void Skip (uint64 delta)
{
SetReadPosition (Position () + delta);
}
/// Get data from stream. Exception is thrown and no data is read if
/// insufficient data available in stream.
/// \param data Buffer to put data into. Must be valid for count bytes.
/// \param count Bytes of data to read.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
void Get (void *data, uint32 count);
/// Seek to a new position in stream for writing.
void SetWritePosition (uint64 offset);
/// Force any stored data in stream to be written to underlying storage.
void Flush ();
/// Set length of available data.
- /// \param length Number of bytes of avialble data in stream.
+ /// \param length Number of bytes of available data in stream.
void SetLength (uint64 length);
/// Write data to stream.
/// \param data Buffer of data to write to stream.
/// \param count Bytes of in data.
void Put (const void *data, uint32 count);
/// Get an unsigned 8-bit integer from stream and advance read position.
/// \retval One unsigned 8-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
uint8 Get_uint8 ()
{
// Fast check to see if in buffer
if (fPosition >= fBufferStart && fPosition < fBufferEnd)
{
return fBuffer [fPosition++ - fBufferStart];
}
// Not in buffer, let main routine do the work.
uint8 x;
Get (&x, 1);
return x;
}
/// Put an unsigned 8-bit integer to stream and advance write position.
/// \param x One unsigned 8-bit integer.
void Put_uint8 (uint8 x)
{
if (fBufferDirty &&
fPosition >= fBufferStart &&
fPosition <= fBufferEnd &&
fPosition < fBufferLimit)
{
fBuffer [fPosition - fBufferStart] = x;
fPosition++;
if (fBufferEnd < fPosition)
fBufferEnd = fPosition;
fLength = Max_uint64 (Length (), fPosition);
}
else
{
Put (&x, 1);
}
}
/// Get an unsigned 16-bit integer from stream and advance read position.
/// Byte swap if byte swapping is turned on.
/// \retval One unsigned 16-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
uint16 Get_uint16 ();
/// Put an unsigned 16-bit integer to stream and advance write position.
/// Byte swap if byte swapping is turned on.
/// \param x One unsigned 16-bit integer.
void Put_uint16 (uint16 x);
/// Get an unsigned 32-bit integer from stream and advance read position.
/// Byte swap if byte swapping is turned on.
/// \retval One unsigned 32-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
uint32 Get_uint32 ();
/// Put an unsigned 32-bit integer to stream and advance write position.
/// Byte swap if byte swapping is turned on.
/// \param x One unsigned 32-bit integer.
void Put_uint32 (uint32 x);
/// Get an unsigned 64-bit integer from stream and advance read position.
/// Byte swap if byte swapping is turned on.
/// \retval One unsigned 64-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
uint64 Get_uint64 ();
/// Put an unsigned 64-bit integer to stream and advance write position.
/// Byte swap if byte swapping is turned on.
/// \param x One unsigned 64-bit integer.
void Put_uint64 (uint64 x);
/// Get one 8-bit integer from stream and advance read position.
/// \retval One 8-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
int8 Get_int8 ()
{
return (int8) Get_uint8 ();
}
/// Put one 8-bit integer to stream and advance write position.
/// \param x One 8-bit integer.
void Put_int8 (int8 x)
{
Put_uint8 ((uint8) x);
}
/// Get one 16-bit integer from stream and advance read position.
/// Byte swap if byte swapping is turned on.
/// \retval One 16-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
int16 Get_int16 ()
{
return (int16) Get_uint16 ();
}
/// Put one 16-bit integer to stream and advance write position.
/// Byte swap if byte swapping is turned on.
/// \param x One 16-bit integer.
void Put_int16 (int16 x)
{
Put_uint16 ((uint16) x);
}
/// Get one 32-bit integer from stream and advance read position.
/// Byte swap if byte swapping is turned on.
/// \retval One 32-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
int32 Get_int32 ()
{
return (int32) Get_uint32 ();
}
/// Put one 32-bit integer to stream and advance write position.
/// Byte swap if byte swapping is turned on.
/// \param x One 32-bit integer.
void Put_int32 (int32 x)
{
Put_uint32 ((uint32) x);
}
/// Get one 64-bit integer from stream and advance read position.
/// Byte swap if byte swapping is turned on.
/// \retval One 64-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
int64 Get_int64 ()
{
return (int64) Get_uint64 ();
}
/// Put one 64-bit integer to stream and advance write position.
/// Byte swap if byte swapping is turned on.
/// \param x One 64-bit integer.
void Put_int64 (int64 x)
{
Put_uint64 ((uint64) x);
}
/// Get one 32-bit IEEE floating-point number from stream and advance
/// read position. Byte swap if byte swapping is turned on.
/// \retval One 32-bit IEEE floating-point number.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
real32 Get_real32 ();
/// Put one 32-bit IEEE floating-point number to stream and advance write
/// position. Byte swap if byte swapping is turned on.
/// \param x One 32-bit IEEE floating-point number.
void Put_real32 (real32 x);
/// Get one 64-bit IEEE floating-point number from stream and advance
/// read position. Byte swap if byte swapping is turned on.
/// \retval One 64-bit IEEE floating-point number .
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
real64 Get_real64 ();
/// Put one 64-bit IEEE floating-point number to stream and advance write
/// position. Byte swap if byte swapping is turned on.
/// \param x One64-bit IEEE floating-point number.
void Put_real64 (real64 x);
/// Get an 8-bit character string from stream and advance read position.
/// Routine always reads until a NUL character (8-bits of zero) is read.
/// (That is, only maxLength bytes will be returned in buffer, but the
/// stream is always advanced until a NUL is read or EOF is reached.)
/// \param data Buffer in which string is returned.
/// \param maxLength Maximum number of bytes to place in buffer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if stream runs out before NUL is seen.
void Get_CString (char *data,
uint32 maxLength);
/// Get a 16-bit character string from stream and advance read position.
/// 16-bit characters are truncated to 8-bits.
/// Routine always reads until a NUL character (16-bits of zero) is read.
/// (That is, only maxLength bytes will be returned in buffer, but the
/// stream is always advanced until a NUL is read or EOF is reached.)
/// \param data Buffer to place string in.
/// \param maxLength Maximum number of bytes to place in buffer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if stream runs out before NUL is seen.
void Get_UString (char *data,
uint32 maxLength);
/// Writes the specified number of zero bytes to stream.
/// \param count Number of zero bytes to write.
void PutZeros (uint64 count);
/// Writes zeros to align the stream position to a multiple of 2.
void PadAlign2 ();
/// Writes zeros to align the stream position to a multiple of 4.
void PadAlign4 ();
/// Get a value of size indicated by tag type from stream and advance
/// read position. Byte swap if byte swapping is turned on and tag type
/// is larger than a byte. Value is returned as an unsigned 32-bit integer.
/// \param tagType Tag type of data stored in stream.
/// \retval One unsigned 32-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
uint32 TagValue_uint32 (uint32 tagType);
/// Get a value of size indicated by tag type from stream and advance read
/// position. Byte swap if byte swapping is turned on and tag type is larger
/// than a byte. Value is returned as a 32-bit integer.
/// \param tagType Tag type of data stored in stream.
/// \retval One 32-bit integer.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
int32 TagValue_int32 (uint32 tagType);
/// Get a value of size indicated by tag type from stream and advance read
/// position. Byte swap if byte swapping is turned on and tag type is larger
/// than a byte. Value is returned as a dng_urational.
/// \param tagType Tag type of data stored in stream.
/// \retval One dng_urational.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
dng_urational TagValue_urational (uint32 tagType);
/// Get a value of size indicated by tag type from stream and advance read
/// position. Byte swap if byte swapping is turned on and tag type is larger
/// than a byte. Value is returned as a dng_srational.
/// \param tagType Tag type of data stored in stream.
/// \retval One dng_srational.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
dng_srational TagValue_srational (uint32 tagType);
/// Get a value of size indicated by tag type from stream and advance read
/// position. Byte swap if byte swapping is turned on and tag type is larger
/// than a byte. Value is returned as a 64-bit IEEE floating-point number.
/// \param tagType Tag type of data stored in stream.
/// \retval One 64-bit IEEE floating-point number.
/// \exception dng_exception with fErrorCode equal to dng_error_end_of_file
/// if not enough data in stream.
real64 TagValue_real64 (uint32 tagType);
/// Getter for sniffer associated with stream.
/// \retval The sniffer for this stream.
dng_abort_sniffer * Sniffer () const
{
return fSniffer;
}
/// Putter for sniffer associated with stream.
/// \param sniffer The new sniffer to use (or NULL for none).
void SetSniffer (dng_abort_sniffer *sniffer)
{
fSniffer = sniffer;
}
/// Copy a specified number of bytes to a target stream.
/// \param dstStream The target stream.
/// \param count The number of bytes to copy.
virtual void CopyToStream (dng_stream &dstStream,
uint64 count);
/// Makes the target stream a copy of this stream.
/// \param dstStream The target stream.
void DuplicateStream (dng_stream &dstStream);
private:
// Hidden copy constructor and assignment operator.
dng_stream (const dng_stream &stream);
dng_stream & operator= (const dng_stream &stream);
};
/*****************************************************************************/
class TempBigEndian
{
private:
dng_stream & fStream;
bool fOldSwap;
public:
TempBigEndian (dng_stream &stream,
bool bigEndian = true);
virtual ~TempBigEndian ();
};
/*****************************************************************************/
class TempLittleEndian: public TempBigEndian
{
public:
TempLittleEndian (dng_stream &stream,
bool littleEndian = true)
: TempBigEndian (stream, !littleEndian)
{
}
virtual ~TempLittleEndian ()
{
}
};
/*****************************************************************************/
class TempStreamSniffer
{
private:
dng_stream & fStream;
dng_abort_sniffer *fOldSniffer;
public:
TempStreamSniffer (dng_stream &stream,
dng_abort_sniffer *sniffer);
virtual ~TempStreamSniffer ();
private:
// Hidden copy constructor and assignment operator.
TempStreamSniffer (const TempStreamSniffer &temp);
TempStreamSniffer & operator= (const TempStreamSniffer &temp);
};
/*****************************************************************************/
class PreserveStreamReadPosition
{
private:
dng_stream & fStream;
uint64 fPosition;
public:
PreserveStreamReadPosition (dng_stream &stream)
: fStream (stream)
, fPosition (stream.Position ())
{
}
~PreserveStreamReadPosition ()
{
fStream.SetReadPosition (fPosition);
}
private:
// Hidden copy constructor and assignment operator.
PreserveStreamReadPosition (const PreserveStreamReadPosition &rhs);
PreserveStreamReadPosition & operator= (const PreserveStreamReadPosition &rhs);
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_tag_values.h b/core/libs/dngwriter/extra/dng_sdk/dng_tag_values.h
index 5b925d04a3..1de66f0d42 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_tag_values.h
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_tag_values.h
@@ -1,362 +1,362 @@
/*****************************************************************************/
// Copyright 2006-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_tag_values.h#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#ifndef __dng_tag_values__
#define __dng_tag_values__
/*****************************************************************************/
// Values for NewSubFileType tag.
enum
{
// The main image data.
sfMainImage = 0,
// Preview image for the primary settings.
sfPreviewImage = 1,
// Preview image for non-primary settings.
sfAltPreviewImage = 0x10001
};
/******************************************************************************/
// Values for PhotometricInterpretation tag.
enum
{
piWhiteIsZero = 0,
piBlackIsZero = 1,
piRGB = 2,
piRGBPalette = 3,
piTransparencyMask = 4,
piCMYK = 5,
piYCbCr = 6,
piCIELab = 8,
piICCLab = 9,
piCFA = 32803, // TIFF-EP spec
piLinearRaw = 34892
};
/******************************************************************************/
// Values for PlanarConfiguration tag.
enum
{
pcInterleaved = 1,
pcPlanar = 2,
pcRowInterleaved = 100000 // Internal use only
};
/******************************************************************************/
// Values for ExtraSamples tag.
enum
{
esUnspecified = 0,
esAssociatedAlpha = 1,
esUnassociatedAlpha = 2
};
/******************************************************************************/
// Values for SampleFormat tag.
enum
{
sfUnsignedInteger = 1,
sfSignedInteger = 2,
sfFloatingPoint = 3,
sfUndefined = 4
};
/******************************************************************************/
// Values for Compression tag.
enum
{
ccUncompressed = 1,
ccLZW = 5,
ccOldJPEG = 6,
ccJPEG = 7,
ccDeflate = 8,
ccPackBits = 32773,
ccOldDeflate = 32946
};
/******************************************************************************/
// Values for Predictor tag.
enum
{
cpNullPredictor = 1,
cpHorizontalDifference = 2
};
/******************************************************************************/
// Values for ResolutionUnit tag.
enum
{
ruNone = 1,
ruInch = 2,
ruCM = 3,
ruMM = 4,
ruMicroM = 5
};
/******************************************************************************/
// Values for LightSource tag.
enum
{
lsUnknown = 0,
lsDaylight = 1,
lsFluorescent = 2,
lsTungsten = 3,
lsFlash = 4,
lsFineWeather = 9,
lsCloudyWeather = 10,
lsShade = 11,
lsDaylightFluorescent = 12, // D 5700 - 7100K
lsDayWhiteFluorescent = 13, // N 4600 - 5400K
lsCoolWhiteFluorescent = 14, // W 3900 - 4500K
lsWhiteFluorescent = 15, // WW 3200 - 3700K
lsStandardLightA = 17,
lsStandardLightB = 18,
lsStandardLightC = 19,
lsD55 = 20,
lsD65 = 21,
lsD75 = 22,
lsD50 = 23,
lsISOStudioTungsten = 24,
lsOther = 255
};
/******************************************************************************/
// Values for ExposureProgram tag.
enum
{
epUnidentified = 0,
epManual = 1,
epProgramNormal = 2,
epAperturePriority = 3,
epShutterPriority = 4,
epProgramCreative = 5,
epProgramAction = 6,
epPortraitMode = 7,
epLandscapeMode = 8
};
/******************************************************************************/
// Values for MeteringMode tag.
enum
{
mmUnidentified = 0,
mmAverage = 1,
mmCenterWeightedAverage = 2,
mmSpot = 3,
mmMultiSpot = 4,
mmPattern = 5,
mmPartial = 6,
mmOther = 255
};
/******************************************************************************/
// CFA color codes from the TIFF/EP specification.
enum ColorKeyCode
{
colorKeyRed = 0,
colorKeyGreen = 1,
colorKeyBlue = 2,
colorKeyCyan = 3,
colorKeyMagenta = 4,
colorKeyYellow = 5,
colorKeyWhite = 6,
colorKeyMaxEnum = 0xFF
};
/*****************************************************************************/
// Values for the ColorimetricReference tag. It specifies the colorimetric
// reference used for images with PhotometricInterpretation values of CFA
// or LinearRaw.
enum
{
// Scene referred (default):
crSceneReferred = 0,
// Output referred using the parameters of the ICC profile PCS.
crICCProfilePCS = 1
};
/*****************************************************************************/
// Values for the ProfileEmbedPolicy tag.
enum
{
- // Freely embedable and copyable into installations that encounter this
+ // Freely embeddable and copyable into installations that encounter this
// profile, so long as the profile is only used to process DNG files.
pepAllowCopying = 0,
- // Can be embeded in a DNG for portable processing, but cannot be used
+ // Can be embedded in a DNG for portable processing, but cannot be used
// to process other files that the profile is not embedded in.
pepEmbedIfUsed = 1,
// Can only be used if installed on the machine processing the file.
// Note that this only applies to stand-alone profiles. Profiles that
// are already embedded inside a DNG file allowed to remain embedded
// in that DNG, even if the DNG is resaved.
pepEmbedNever = 2,
// No restricts on profile use or embedding.
pepNoRestrictions = 3
};
/*****************************************************************************/
// Values for the PreviewColorSpace tag.
enum PreviewColorSpaceEnum
{
previewColorSpace_Unknown = 0,
previewColorSpace_GrayGamma22 = 1,
previewColorSpace_sRGB = 2,
previewColorSpace_AdobeRGB = 3,
previewColorSpace_ProPhotoRGB = 4,
previewColorSpace_LastValid = previewColorSpace_ProPhotoRGB,
previewColorSpace_MaxEnum = 0xFFFFFFFF
};
/*****************************************************************************/
// TIFF-style byte order markers.
enum
{
byteOrderII = 0x4949, // 'II'
byteOrderMM = 0x4D4D // 'MM'
};
/*****************************************************************************/
// "Magic" numbers.
enum
{
// DNG related.
magicTIFF = 42, // TIFF (and DNG)
magicExtendedProfile = 0x4352, // 'CR'
// Other raw formats - included here so the DNG SDK can parse them.
magicPanasonic = 85,
magicOlympusA = 0x4F52,
magicOlympusB = 0x5352
};
/*****************************************************************************/
// DNG Version numbers
enum
{
dngVersion_None = 0,
dngVersion_1_0_0_0 = 0x01000000,
dngVersion_1_1_0_0 = 0x01010000,
dngVersion_1_2_0_0 = 0x01020000,
dngVersion_1_3_0_0 = 0x01030000,
dngVersion_Current = dngVersion_1_3_0_0,
dngVersion_SaveDefault = dngVersion_Current
};
/*****************************************************************************/
#endif
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_utils.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_utils.cpp
index d4f7b07fea..cb894fbf0d 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_utils.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_utils.cpp
@@ -1,219 +1,219 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_utils.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_point.h"
#include "dng_rect.h"
#include "dng_utils.h"
#include "dng_assertions.h"
#if qMacOS
#include <CoreServices/CoreServices.h>
#endif
#if qWinOS
#include <windows.h>
#else
#include <sys/time.h>
#endif
/*****************************************************************************/
#if qDNGDebug
void dng_show_message (const char *s)
{
#if qDNGPrintMessages
{
fprintf (stderr, "%s\n", s);
}
#elif qMacOS
{
char ss [256];
strcpy (ss, s);
uint32 len = strlen (ss);
for (uint32 j = len + 1; j >= 1; j--)
ss [j] = ss [j - 1];
ss [0] = (char) len;
DebugStr ((unsigned char *) ss);
}
#elif qWinOS
{
MessageBoxA (NULL, (LPSTR) s, NULL, MB_OK);
}
#endif
}
#endif
/*****************************************************************************/
#if qDNGDebug
void dng_show_message_f (const char *fmt, ... )
{
char buffer [1024];
va_list ap;
va_start (ap, fmt);
vsnprintf (buffer, sizeof (buffer), fmt, ap);
va_end (ap);
dng_show_message (buffer);
}
#endif
/*****************************************************************************/
real64 TickTimeInSeconds ()
{
#if qWinOS
// One might think it prudent to cache the frequency here, however
// low-power CPU modes can, and do, change the value returned.
- // Thus the frequencey needs to be retrieved each time.
+ // Thus the frequency needs to be retrieved each time.
// Note that the frequency changing can cause the return
// result to jump backwards, which is why the TickCountInSeconds
// (below) also exists.
LARGE_INTEGER freq;
LARGE_INTEGER cycles;
QueryPerformanceFrequency (&freq );
QueryPerformanceCounter (&cycles);
return (real64) cycles.QuadPart /
(real64) freq .QuadPart;
#else
// Perhaps a better call exists. (e.g. avoid adjtime effects)
struct timeval tv;
gettimeofday (&tv, NULL);
return tv.tv_sec + tv.tv_usec / 1000000.0;
#endif
}
/*****************************************************************************/
real64 TickCountInSeconds ()
{
#if qWinOS
return GetTickCount () * (1.0 / 1000.0);
#elif qMacOS
return TickCount () * (1.0 / 60.0);
#else
return TickTimeInSeconds ();
#endif
}
/*****************************************************************************/
dng_timer::dng_timer (const char *message)
: fMessage (message )
, fStartTime (TickTimeInSeconds ())
{
}
/*****************************************************************************/
dng_timer::~dng_timer ()
{
real64 totalTime = TickTimeInSeconds () - fStartTime;
fprintf (stderr, "%s: %0.3f sec\n", fMessage, totalTime);
}
/*****************************************************************************/
real64 MaxSquaredDistancePointToRect (const dng_point_real64 &point,
const dng_rect_real64 &rect)
{
real64 distSqr = DistanceSquared (point,
rect.TL ());
distSqr = Max_real64 (distSqr,
DistanceSquared (point,
rect.BL ()));
distSqr = Max_real64 (distSqr,
DistanceSquared (point,
rect.BR ()));
distSqr = Max_real64 (distSqr,
DistanceSquared (point,
rect.TR ()));
return distSqr;
}
/*****************************************************************************/
real64 MaxDistancePointToRect (const dng_point_real64 &point,
const dng_rect_real64 &rect)
{
return sqrt (MaxSquaredDistancePointToRect (point,
rect));
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/dng_sdk/dng_xmp.cpp b/core/libs/dngwriter/extra/dng_sdk/dng_xmp.cpp
index c1549f3ec1..12d2722fe4 100644
--- a/core/libs/dngwriter/extra/dng_sdk/dng_xmp.cpp
+++ b/core/libs/dngwriter/extra/dng_sdk/dng_xmp.cpp
@@ -1,3673 +1,3673 @@
/*****************************************************************************/
// Copyright 2006-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/
/* $Id: //mondo/dng_sdk_1_3/dng_sdk/source/dng_xmp.cpp#1 $ */
/* $DateTime: 2009/06/22 05:04:49 $ */
/* $Change: 578634 $ */
/* $Author: tknoll $ */
/*****************************************************************************/
#include "dng_xmp.h"
#include "dng_assertions.h"
#include "dng_date_time.h"
#include "dng_exceptions.h"
#include "dng_exif.h"
#include "dng_image_writer.h"
#include "dng_iptc.h"
#include "dng_negative.h"
#include "dng_string.h"
#include "dng_string_list.h"
#include "dng_utils.h"
#include "dng_xmp_sdk.h"
/*****************************************************************************/
dng_xmp::dng_xmp (dng_memory_allocator &allocator)
: fAllocator (allocator)
, fSDK (NULL)
{
fSDK = new dng_xmp_sdk ();
if (!fSDK)
{
ThrowMemoryFull ();
}
}
/*****************************************************************************/
dng_xmp::dng_xmp (const dng_xmp &xmp)
: fAllocator (xmp.fAllocator)
, fSDK (NULL)
{
fSDK = new dng_xmp_sdk (*xmp.fSDK);
if (!fSDK)
{
ThrowMemoryFull ();
}
}
/*****************************************************************************/
dng_xmp::~dng_xmp ()
{
if (fSDK)
{
delete fSDK;
}
}
/*****************************************************************************/
void dng_xmp::TrimDecimal (char *s)
{
uint32 len = (uint32) strlen (s);
while (len > 0)
{
if (s [len - 1] == '0')
s [--len] = 0;
else
break;
}
if (len > 0)
{
if (s [len - 1] == '.')
s [--len] = 0;
}
}
/*****************************************************************************/
dng_string dng_xmp::EncodeFingerprint (const dng_fingerprint &f)
{
dng_string result;
if (f.IsValid ())
{
char s [33];
for (uint32 j = 0; j < 16; j++)
{
sprintf (s + j * 2,
"%02X",
f.data [j]);
}
result.Set (s);
}
return result;
}
/*****************************************************************************/
dng_fingerprint dng_xmp::DecodeFingerprint (const dng_string &s)
{
dng_fingerprint result;
if (s.Length () == 32)
{
for (uint32 j = 0; j < 16; j++)
{
unsigned x = 0;
sscanf (s.Get () + j * 2, "%02X", &x);
result.data [j] = (uint8) x;
}
}
return result;
}
/*****************************************************************************/
dng_string dng_xmp::EncodeGPSVersion (uint32 version)
{
dng_string result;
if (version)
{
uint8 b0 = (uint8) (version >> 24);
uint8 b1 = (uint8) (version >> 16);
uint8 b2 = (uint8) (version >> 8);
uint8 b3 = (uint8) (version );
if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9)
{
char s [32];
sprintf (s,
"%u.%u.%u.%u",
(unsigned) b0,
(unsigned) b1,
(unsigned) b2,
(unsigned) b3);
result.Set (s);
}
}
return result;
}
/*****************************************************************************/
uint32 dng_xmp::DecodeGPSVersion (const dng_string &s)
{
uint32 result = 0;
if (s.Length () == 7)
{
unsigned b0 = 0;
unsigned b1 = 0;
unsigned b2 = 0;
unsigned b3 = 0;
if (sscanf (s.Get (),
"%u.%u.%u.%u",
&b0,
&b1,
&b2,
&b3) == 4)
{
result = (b0 << 24) |
(b1 << 16) |
(b2 << 8) |
(b3 );
}
}
return result;
}
/*****************************************************************************/
dng_string dng_xmp::EncodeGPSCoordinate (const dng_string &ref,
const dng_urational *coord)
{
dng_string result;
if (ref.Length () == 1 && coord [0].IsValid () &&
coord [1].IsValid ())
{
char refChar = ForceUppercase (ref.Get () [0]);
if (refChar == 'N' ||
refChar == 'S' ||
refChar == 'E' ||
refChar == 'W')
{
char s [256];
// Use the seconds case if all three values are
// integers.
if (coord [0].d == 1 &&
coord [1].d == 1 &&
coord [2].d == 1)
{
sprintf (s,
"%u,%u,%u%c",
coord [0].n,
coord [1].n,
coord [2].n,
refChar);
}
// Else we need to use the fractional minutes case.
else
{
// Find value minutes.
real64 x = coord [0].As_real64 () * 60.0 +
coord [1].As_real64 () +
coord [2].As_real64 () * (1.0 / 60.0);
// Round to fractional four decimal places.
uint32 y = Round_uint32 (x * 10000.0);
// Split into degrees and minutes.
uint32 d = y / (60 * 10000);
uint32 m = y % (60 * 10000);
char min [32];
sprintf (min, "%.4f", m * (1.0 / 10000.0));
TrimDecimal (min);
sprintf (s,
"%u,%s%c",
d,
min,
refChar);
}
result.Set (s);
}
}
return result;
}
/*****************************************************************************/
void dng_xmp::DecodeGPSCoordinate (const dng_string &s,
dng_string &ref,
dng_urational *coord)
{
ref.Clear ();
coord [0].Clear ();
coord [1].Clear ();
coord [2].Clear ();
if (s.Length () > 1)
{
char refChar = ForceUppercase (s.Get () [s.Length () - 1]);
if (refChar == 'N' ||
refChar == 'S' ||
refChar == 'E' ||
refChar == 'W')
{
dng_string ss (s);
ss.Truncate (ss.Length () - 1);
unsigned degrees = 0;
real64 minutes = 0.0;
real64 seconds = 0.0;
int count = sscanf (ss.Get (),
"%u,%lf,%lf",
&degrees,
&minutes,
&seconds);
if (count < 2)
{
return;
}
coord [0] = dng_urational ((uint32) degrees, 1);
if (count == 2)
{
coord [1].Set_real64 (minutes, 10000);
coord [2].Clear ();
}
else
{
coord [1].Set_real64 (minutes, 1);
coord [2].Set_real64 (seconds, 100);
}
char r [2];
r [0] = refChar;
r [1] = 0;
ref.Set (r);
}
}
}
/*****************************************************************************/
dng_string dng_xmp::EncodeGPSDateTime (const dng_string &dateStamp,
const dng_urational *timeStamp)
{
dng_string result;
if (timeStamp [0].IsValid () &&
timeStamp [1].IsValid () &&
timeStamp [2].IsValid ())
{
char s [256];
char sec [32];
sprintf (sec,
"%09.6f",
timeStamp [2].As_real64 ());
TrimDecimal (sec);
int year = 0;
int month = 0;
int day = 0;
if (dateStamp.NotEmpty ())
{
sscanf (dateStamp.Get (),
"%d:%d:%d",
&year,
&month,
&day);
}
if (year >= 1 && year <= 9999 &&
month >= 1 && month <= 12 &&
day >= 1 && day <= 31)
{
sprintf (s,
"%04d-%02d-%02dT%02u:%02u:%sZ",
year,
month,
day,
(unsigned) Round_uint32 (timeStamp [0].As_real64 ()),
(unsigned) Round_uint32 (timeStamp [1].As_real64 ()),
sec);
}
else
{
sprintf (s,
"%02u:%02u:%sZ",
(unsigned) Round_uint32 (timeStamp [0].As_real64 ()),
(unsigned) Round_uint32 (timeStamp [1].As_real64 ()),
sec);
}
result.Set (s);
}
return result;
}
/*****************************************************************************/
void dng_xmp::DecodeGPSDateTime (const dng_string &s,
dng_string &dateStamp,
dng_urational *timeStamp)
{
dateStamp.Clear ();
timeStamp [0].Clear ();
timeStamp [1].Clear ();
timeStamp [2].Clear ();
if (s.NotEmpty ())
{
unsigned year = 0;
unsigned month = 0;
unsigned day = 0;
unsigned hour = 0;
unsigned minute = 0;
double second = 0.0;
if (sscanf (s.Get (),
"%u-%u-%uT%u:%u:%lf",
&year,
&month,
&day,
&hour,
&minute,
&second) == 6)
{
if (year >= 1 && year <= 9999 &&
month >= 1 && month <= 12 &&
day >= 1 && day <= 31 )
{
char ss [64];
sprintf (ss,
"%04u-%02u-%02u",
year,
month,
day);
dateStamp.Set (ss);
}
}
else if (sscanf (s.Get (),
"%u:%u:%lf",
&hour,
&minute,
&second) != 3)
{
return;
}
timeStamp [0] = dng_urational ((uint32) hour , 1);
timeStamp [1] = dng_urational ((uint32) minute, 1);
timeStamp [2].Set_real64 (second, 1000);
}
}
/*****************************************************************************/
void dng_xmp::Parse (dng_host &host,
const void *buffer,
uint32 count)
{
fSDK->Parse (host,
(const char *) buffer,
count);
}
/*****************************************************************************/
dng_memory_block * dng_xmp::Serialize (bool asPacket,
uint32 targetBytes,
uint32 padBytes,
bool forJPEG) const
{
return fSDK->Serialize (fAllocator,
asPacket,
targetBytes,
padBytes,
forJPEG);
}
/*****************************************************************************/
void dng_xmp::PackageForJPEG (AutoPtr<dng_memory_block> &stdBlock,
AutoPtr<dng_memory_block> &extBlock,
dng_string &extDigest) const
{
fSDK->PackageForJPEG (fAllocator,
stdBlock,
extBlock,
extDigest);
}
/*****************************************************************************/
void dng_xmp::MergeFromJPEG (const dng_xmp &xmp)
{
fSDK->MergeFromJPEG (xmp.fSDK);
}
/*****************************************************************************/
bool dng_xmp::HasMeta () const
{
return fSDK->HasMeta ();
}
/*****************************************************************************/
bool dng_xmp::Exists (const char *ns,
const char *path) const
{
return fSDK->Exists (ns, path);
}
/*****************************************************************************/
bool dng_xmp::HasNameSpace (const char *ns) const
{
return fSDK->HasNameSpace (ns);
}
/*****************************************************************************/
bool dng_xmp::IteratePaths (IteratePathsCallback *callback,
void *callbackData,
const char *ns,
const char *path)
{
return fSDK->IteratePaths (callback, callbackData, ns, path);
}
/*****************************************************************************/
void dng_xmp::Remove (const char *ns,
const char *path)
{
fSDK->Remove (ns, path);
}
/*****************************************************************************/
void dng_xmp::RemoveProperties (const char *ns)
{
fSDK->RemoveProperties (ns);
}
/*****************************************************************************/
void dng_xmp::Set (const char *ns,
const char *path,
const char *text)
{
fSDK->Set (ns, path, text);
}
/*****************************************************************************/
bool dng_xmp::GetString (const char *ns,
const char *path,
dng_string &s) const
{
return fSDK->GetString (ns, path, s);
}
/*****************************************************************************/
void dng_xmp::SetString (const char *ns,
const char *path,
const dng_string &s)
{
fSDK->SetString (ns, path, s);
}
/*****************************************************************************/
bool dng_xmp::SyncString (const char *ns,
const char *path,
dng_string &s,
uint32 options)
{
bool isDefault = s.IsEmpty ();
// Sync 1: Force XMP to match non-XMP.
if (options & ignoreXMP)
{
if (isDefault)
{
Remove (ns, path);
}
else
{
SetString (ns, path, s);
}
return false;
}
- // Sync 2: From non-XMP to XMP if non-XMP is prefered.
+ // Sync 2: From non-XMP to XMP if non-XMP is preferred.
if ((options & preferNonXMP) && !isDefault)
{
SetString (ns, path, s);
return false;
}
- // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
+ // Sync 3: From XMP to non-XMP if XMP is preferred or default non-XMP.
if ((options & preferXMP) || isDefault)
{
if (GetString (ns, path, s))
{
if (options & requireASCII)
{
if (options & preferNonXMP)
{
if (!s.IsASCII ())
{
// We prefer non-XMP, but we also require
// ASCII and the XMP contains non-ASCII
- // charactors. So keep the non-XMP as a
+ // characters. So keep the non-XMP as a
// null string.
s.Clear ();
}
}
else
{
s.ForceASCII ();
}
}
return true;
}
}
// Sync 4: From non-XMP to XMP.
if (!isDefault)
{
SetString (ns, path, s);
}
return false;
}
/*****************************************************************************/
bool dng_xmp::GetStringList (const char *ns,
const char *path,
dng_string_list &list) const
{
return fSDK->GetStringList (ns, path, list);
}
/*****************************************************************************/
void dng_xmp::SetStringList (const char *ns,
const char *path,
const dng_string_list &list,
bool isBag)
{
fSDK->SetStringList (ns, path, list, isBag);
}
/*****************************************************************************/
void dng_xmp::SyncStringList (const char *ns,
const char *path,
dng_string_list &list,
bool isBag,
uint32 options)
{
bool isDefault = (list.Count () == 0);
// First make sure the XMP is not badly formatted, since
// this breaks some Photoshop logic.
ValidateStringList (ns, path);
// Sync 1: Force XMP to match non-XMP.
if (options & ignoreXMP)
{
if (isDefault)
{
Remove (ns, path);
}
else
{
SetStringList (ns, path, list, isBag);
}
return;
}
- // Sync 2: From non-XMP to XMP if non-XMP is prefered.
+ // Sync 2: From non-XMP to XMP if non-XMP is preferred.
if ((options & preferNonXMP) && !isDefault)
{
SetStringList (ns, path, list, isBag);
return;
}
- // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
+ // Sync 3: From XMP to non-XMP if XMP is preferred or default non-XMP.
if ((options & preferXMP) || isDefault)
{
if (GetStringList (ns, path, list))
{
return;
}
}
// Sync 4: From non-XMP to XMP.
if (!isDefault)
{
SetStringList (ns, path, list, isBag);
}
}
/*****************************************************************************/
void dng_xmp::SetStructField (const char *ns,
const char *path,
const char *fieldNS,
const char *fieldName,
const dng_string &s)
{
dng_string ss (s);
ss.SetLineEndings ('\n');
ss.StripLowASCII ();
fSDK->SetStructField (ns, path, fieldNS, fieldName, ss.Get ());
}
/*****************************************************************************/
void dng_xmp::SetStructField (const char *ns,
const char *path,
const char *fieldNS,
const char *fieldName,
const char *s)
{
fSDK->SetStructField (ns, path, fieldNS, fieldName, s);
}
/*****************************************************************************/
void dng_xmp::DeleteStructField (const char *ns,
const char *path,
const char *fieldNS,
const char *fieldName)
{
fSDK->DeleteStructField (ns, path, fieldNS, fieldName);
}
/*****************************************************************************/
bool dng_xmp::GetStructField (const char *ns,
const char *path,
const char *fieldNS,
const char *fieldName,
dng_string &s) const
{
return fSDK->GetStructField (ns, path, fieldNS, fieldName, s);
}
/*****************************************************************************/
void dng_xmp::SetAltLangDefault (const char *ns,
const char *path,
const dng_string &s)
{
fSDK->SetAltLangDefault (ns, path, s);
}
/*****************************************************************************/
bool dng_xmp::GetAltLangDefault (const char *ns,
const char *path,
dng_string &s) const
{
return fSDK->GetAltLangDefault (ns, path, s);
}
/*****************************************************************************/
bool dng_xmp::SyncAltLangDefault (const char *ns,
const char *path,
dng_string &s,
uint32 options)
{
bool isDefault = s.IsEmpty ();
// Sync 1: Force XMP to match non-XMP.
if (options & ignoreXMP)
{
if (isDefault)
{
Remove (ns, path);
}
else
{
SetAltLangDefault (ns, path, s);
}
return false;
}
- // Sync 2: From non-XMP to XMP if non-XMP is prefered.
+ // Sync 2: From non-XMP to XMP if non-XMP is preferred.
if ((options & preferNonXMP) && !isDefault)
{
SetAltLangDefault (ns, path, s);
return false;
}
- // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
+ // Sync 3: From XMP to non-XMP if XMP is preferred or default non-XMP.
if ((options & preferXMP) || isDefault)
{
if (GetAltLangDefault (ns, path, s))
{
if (options & requireASCII)
{
if (options & preferNonXMP)
{
if (!s.IsASCII ())
{
// We prefer non-XMP, but we also require
// ASCII and the XMP contains non-ASCII
- // charactors. So keep the non-XMP as a
+ // characters. So keep the non-XMP as a
// null string.
s.Clear ();
}
}
else
{
s.ForceASCII ();
}
}
return true;
}
}
// Sync 4: From non-XMP to XMP.
if (!isDefault)
{
SetAltLangDefault (ns, path, s);
}
return false;
}
/*****************************************************************************/
bool dng_xmp::GetBoolean (const char *ns,
const char *path,
bool &x) const
{
dng_string s;
if (GetString (ns, path, s))
{
if (s.Matches ("True"))
{
x = true;
return true;
}
if (s.Matches ("False"))
{
x = false;
return true;
}
}
return false;
}
/*****************************************************************************/
void dng_xmp::SetBoolean (const char *ns,
const char *path,
bool x)
{
Set (ns, path, x ? "True" : "False");
}
/*****************************************************************************/
bool dng_xmp::Get_int32 (const char *ns,
const char *path,
int32 &x) const
{
dng_string s;
if (GetString (ns, path, s))
{
if (s.NotEmpty ())
{
int y = 0;
if (sscanf (s.Get (), "%d", &y) == 1)
{
x = y;
return true;
}
}
}
return false;
}
/*****************************************************************************/
void dng_xmp::Set_int32 (const char *ns,
const char *path,
int32 x,
bool usePlus)
{
char s [64];
if (x > 0 && usePlus)
{
sprintf (s, "+%d", (int) x);
}
else
{
sprintf (s, "%d", (int) x);
}
Set (ns, path, s);
}
/*****************************************************************************/
bool dng_xmp::Get_uint32 (const char *ns,
const char *path,
uint32 &x) const
{
dng_string s;
if (GetString (ns, path, s))
{
if (s.NotEmpty ())
{
unsigned y = 0;
if (sscanf (s.Get (), "%u", &y) == 1)
{
x = y;
return true;
}
}
}
return false;
}
/*****************************************************************************/
void dng_xmp::Set_uint32 (const char *ns,
const char *path,
uint32 x)
{
char s [64];
sprintf (s,
"%u",
(unsigned) x);
Set (ns, path, s);
}
/*****************************************************************************/
void dng_xmp::Sync_uint32 (const char *ns,
const char *path,
uint32 &x,
bool isDefault,
uint32 options)
{
// Sync 1: Force XMP to match non-XMP.
if (options & ignoreXMP)
{
if (isDefault)
{
Remove (ns, path);
}
else
{
Set_uint32 (ns, path, x);
}
return;
}
- // Sync 2: From non-XMP to XMP if non-XMP is prefered.
+ // Sync 2: From non-XMP to XMP if non-XMP is preferred.
if ((options & preferNonXMP) && !isDefault)
{
Set_uint32 (ns, path, x);
return;
}
- // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
+ // Sync 3: From XMP to non-XMP if XMP is preferred or default non-XMP.
if ((options & preferXMP) || isDefault)
{
if (Get_uint32 (ns, path, x))
{
return;
}
}
// Sync 4: From non-XMP to XMP.
if (!isDefault)
{
Set_uint32 (ns, path, x);
}
}
/*****************************************************************************/
void dng_xmp::Sync_uint32_array (const char *ns,
const char *path,
uint32 *data,
uint32 &count,
uint32 maxCount,
uint32 options)
{
dng_string_list list;
for (uint32 j = 0; j < count; j++)
{
char s [32];
sprintf (s, "%u", (unsigned) data [j]);
dng_string ss;
ss.Set (s);
list.Append (ss);
}
SyncStringList (ns,
path,
list,
false,
options);
count = 0;
for (uint32 k = 0; k < maxCount; k++)
{
data [k] = 0;
if (k < list.Count ())
{
unsigned x = 0;
if (sscanf (list [k].Get (), "%u", &x) == 1)
{
data [count++] = x;
}
}
}
}
/*****************************************************************************/
bool dng_xmp::Get_real64 (const char *ns,
const char *path,
real64 &x) const
{
dng_string s;
if (GetString (ns, path, s))
{
if (s.NotEmpty ())
{
double y = 0;
if (sscanf (s.Get (), "%lf", &y) == 1)
{
x = y;
return true;
}
}
}
return false;
}
/*****************************************************************************/
void dng_xmp::Set_real64 (const char *ns,
const char *path,
real64 x,
uint32 places,
bool trim,
bool usePlus)
{
char s [64];
if (x > 0.0 && usePlus)
{
sprintf (s, "+%0.*f", (unsigned) places, (double) x);
}
else
{
sprintf (s, "%0.*f", (unsigned) places, (double) x);
}
if (trim)
{
while (s [strlen (s) - 1] == '0')
{
s [strlen (s) - 1] = 0;
}
if (s [strlen (s) - 1] == '.')
{
s [strlen (s) - 1] = 0;
}
}
Set (ns, path, s);
}
/*****************************************************************************/
bool dng_xmp::Get_urational (const char *ns,
const char *path,
dng_urational &r) const
{
dng_string s;
if (GetString (ns, path, s))
{
if (s.NotEmpty ())
{
unsigned n = 0;
unsigned d = 0;
if (sscanf (s.Get (), "%u/%u", &n, &d) == 2)
{
if (d != 0)
{
r = dng_urational (n, d);
return true;
}
}
}
}
return false;
}
/*****************************************************************************/
void dng_xmp::Set_urational (const char *ns,
const char *path,
const dng_urational &r)
{
char s [64];
sprintf (s,
"%u/%u",
(unsigned) r.n,
(unsigned) r.d);
Set (ns, path, s);
}
/*****************************************************************************/
void dng_xmp::Sync_urational (const char *ns,
const char *path,
dng_urational &r,
uint32 options)
{
bool isDefault = r.NotValid ();
// Sync 1: Force XMP to match non-XMP.
if (options & ignoreXMP)
{
if (isDefault)
{
Remove (ns, path);
}
else
{
Set_urational (ns, path, r);
}
return;
}
- // Sync 2: From non-XMP to XMP if non-XMP is prefered.
+ // Sync 2: From non-XMP to XMP if non-XMP is preferred.
if ((options & preferNonXMP) && !isDefault)
{
Set_urational (ns, path, r);
return;
}
- // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
+ // Sync 3: From XMP to non-XMP if XMP is preferred or default non-XMP.
if ((options & preferXMP) || isDefault)
{
if (Get_urational (ns, path, r))
{
return;
}
}
// Sync 4: From non-XMP to XMP.
if (!isDefault)
{
Set_urational (ns, path, r);
}
}
/*****************************************************************************/
bool dng_xmp::Get_srational (const char *ns,
const char *path,
dng_srational &r) const
{
dng_string s;
if (GetString (ns, path, s))
{
if (s.NotEmpty ())
{
int n = 0;
int d = 0;
if (sscanf (s.Get (), "%d/%d", &n, &d) == 2)
{
if (d != 0)
{
r = dng_srational (n, d);
return true;
}
}
}
}
return false;
}
/*****************************************************************************/
void dng_xmp::Set_srational (const char *ns,
const char *path,
const dng_srational &r)
{
char s [64];
sprintf (s,
"%d/%d",
(int) r.n,
(int) r.d);
Set (ns, path, s);
}
/*****************************************************************************/
void dng_xmp::Sync_srational (const char *ns,
const char *path,
dng_srational &r,
uint32 options)
{
bool isDefault = r.NotValid ();
// Sync 1: Force XMP to match non-XMP.
if (options & ignoreXMP)
{
if (isDefault)
{
Remove (ns, path);
}
else
{
Set_srational (ns, path, r);
}
return;
}
- // Sync 2: From non-XMP to XMP if non-XMP is prefered.
+ // Sync 2: From non-XMP to XMP if non-XMP is preferred.
if ((options & preferNonXMP) && !isDefault)
{
Set_srational (ns, path, r);
return;
}
- // Sync 3: From XMP to non-XMP if XMP is prefered or default non-XMP.
+ // Sync 3: From XMP to non-XMP if XMP is preferred or default non-XMP.
if ((options & preferXMP) || isDefault)
{
if (Get_srational (ns, path, r))
{
return;
}
}
// Sync 4: From non-XMP to XMP.
if (!isDefault)
{
Set_srational (ns, path, r);
}
}
/*****************************************************************************/
bool dng_xmp::GetFingerprint (const char *ns,
const char *path,
dng_fingerprint &print) const
{
dng_string s;
if (GetString (ns, path, s))
{
dng_fingerprint temp = DecodeFingerprint (s);
if (temp.IsValid ())
{
print = temp;
return true;
}
}
return false;
}
/******************************************************************************/
void dng_xmp::SetFingerprint (const char *ns,
const char *tag,
const dng_fingerprint &print)
{
dng_string s = EncodeFingerprint (print);
if (s.IsEmpty ())
{
Remove (ns, tag);
}
else
{
SetString (ns, tag, s);
}
}
/******************************************************************************/
dng_fingerprint dng_xmp::GetIPTCDigest () const
{
dng_fingerprint digest;
if (GetFingerprint (XMP_NS_PHOTOSHOP,
"LegacyIPTCDigest",
digest))
{
return digest;
}
return dng_fingerprint ();
}
/******************************************************************************/
void dng_xmp::SetIPTCDigest (dng_fingerprint &digest)
{
SetFingerprint (XMP_NS_PHOTOSHOP,
"LegacyIPTCDigest",
digest);
}
/*****************************************************************************/
void dng_xmp::SyncIPTC (dng_iptc &iptc,
uint32 options)
{
SyncAltLangDefault (XMP_NS_DC,
"title",
iptc.fTitle,
options);
SyncString (XMP_NS_PHOTOSHOP,
"Category",
iptc.fCategory,
options);
{
uint32 x = 0xFFFFFFFF;
if (iptc.fUrgency >= 0)
{
x = (uint32) iptc.fUrgency;
}
Sync_uint32 (XMP_NS_PHOTOSHOP,
"Urgency",
x,
x == 0xFFFFFFFF,
options);
if (x >= 0 && x <= 9)
{
iptc.fUrgency = (int32) x;
}
}
SyncStringList (XMP_NS_PHOTOSHOP,
"SupplementalCategories",
iptc.fSupplementalCategories,
true,
options);
SyncStringList (XMP_NS_PHOTOSHOP,
"Keywords",
iptc.fKeywords,
true,
options);
SyncString (XMP_NS_PHOTOSHOP,
"Instructions",
iptc.fInstructions,
options);
{
dng_string s = iptc.fDateTimeCreated.Encode_ISO_8601 ();
if (SyncString (XMP_NS_PHOTOSHOP,
"DateCreated",
s,
options))
{
iptc.fDateTimeCreated.Decode_ISO_8601 (s.Get ());
}
}
SyncString (XMP_NS_PHOTOSHOP,
"Author",
iptc.fAuthor,
options);
SyncString (XMP_NS_PHOTOSHOP,
"AuthorsPosition",
iptc.fAuthorsPosition,
options);
SyncString (XMP_NS_PHOTOSHOP,
"City",
iptc.fCity,
options);
SyncString (XMP_NS_PHOTOSHOP,
"State",
iptc.fState,
options);
SyncString (XMP_NS_PHOTOSHOP,
"Country",
iptc.fCountry,
options);
SyncString (XMP_NS_IPTC,
"CountryCode",
iptc.fCountryCode,
options);
SyncString (XMP_NS_IPTC,
"Location",
iptc.fLocation,
options);
SyncString (XMP_NS_PHOTOSHOP,
"TransmissionReference",
iptc.fTransmissionReference,
options);
SyncString (XMP_NS_PHOTOSHOP,
"Headline",
iptc.fHeadline,
options);
SyncString (XMP_NS_PHOTOSHOP,
"Credit",
iptc.fCredit,
options);
SyncString (XMP_NS_PHOTOSHOP,
"Source",
iptc.fSource,
options);
SyncAltLangDefault (XMP_NS_DC,
"rights",
iptc.fCopyrightNotice,
options);
SyncAltLangDefault (XMP_NS_DC,
"description",
iptc.fDescription,
options);
SyncString (XMP_NS_PHOTOSHOP,
"CaptionWriter",
iptc.fDescriptionWriter,
options);
}
/*****************************************************************************/
void dng_xmp::IngestIPTC (dng_negative &negative,
bool xmpIsNewer)
{
if (negative.IPTCLength ())
{
// Parse the IPTC block.
dng_iptc iptc;
iptc.Parse (negative.IPTCData (),
negative.IPTCLength (),
negative.IPTCOffset ());
if (iptc.fForceUTF8)
{
negative.SetUsedUTF8forIPTC (true);
}
// Compute fingerprint of IPTC data both ways, including and
// excluding the padding data.
dng_fingerprint iptcDigest1 = negative.IPTCDigest (true );
dng_fingerprint iptcDigest2 = negative.IPTCDigest (false);
// See if there is an IPTC fingerprint stored in the XMP.
dng_fingerprint xmpDigest = GetIPTCDigest ();
if (xmpDigest.IsValid ())
{
// If they match, the XMP was already synced with this
// IPTC block, and we should not resync since it might
// overwrite changes in the XMP data.
if (iptcDigest1 == xmpDigest)
{
return;
}
// If it matches the incorrectly computed digest, skip
// the sync, but fix the digest in the XMP.
if (iptcDigest2 == xmpDigest)
{
SetIPTCDigest (iptcDigest1);
return;
}
// Else the IPTC has changed, so force an update.
xmpIsNewer = false;
}
// Remember the fingerprint of the IPTC we are syncing with.
SetIPTCDigest (iptcDigest1);
// Find the sync options.
uint32 options = xmpIsNewer ? preferXMP
: preferNonXMP;
// Synchronize the fields.
SyncIPTC (iptc, options);
}
// After the IPTC data is moved to XMP, we don't need it anymore.
negative.ClearIPTC ();
}
/*****************************************************************************/
void dng_xmp::RebuildIPTC (dng_negative &negative,
bool padForTIFF,
bool forceUTF8)
{
// If there is no XMP, then there is no IPTC.
if (!fSDK->HasMeta ())
{
return;
}
// Extract the legacy IPTC fields from the XMP data.
dng_iptc iptc;
SyncIPTC (iptc, preferXMP);
// Build legacy IPTC record
if (iptc.NotEmpty ())
{
iptc.fForceUTF8 = forceUTF8;
AutoPtr<dng_memory_block> block (iptc.Spool (negative.Allocator (),
padForTIFF));
negative.SetIPTC (block);
}
}
/*****************************************************************************/
void dng_xmp::SyncFlash (uint32 &flashState,
uint32 &flashMask,
uint32 options)
{
bool isDefault = (flashState == 0xFFFFFFFF);
if ((options & ignoreXMP) || !isDefault)
{
Remove (XMP_NS_EXIF, "Flash");
}
if (!isDefault)
{
fSDK->SetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Fired",
(flashState & 0x1) ? "True" : "False");
if (((flashMask >> 1) & 3) == 3)
{
char s [8];
sprintf (s, "%u", (flashState >> 1) & 3);
fSDK->SetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Return",
s);
}
if (((flashMask >> 3) & 3) == 3)
{
char s [8];
sprintf (s, "%u", (flashState >> 3) & 3);
fSDK->SetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Mode",
s);
}
if ((flashMask & (1 << 5)) != 0)
{
fSDK->SetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Function",
(flashState & (1 << 5)) ? "True" : "False");
}
if ((flashMask & (1 << 6)) != 0)
{
fSDK->SetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"RedEyeMode",
(flashState & (1 << 6)) ? "True" : "False");
}
}
else if (fSDK->Exists (XMP_NS_EXIF, "Flash"))
{
dng_string s;
if (fSDK->GetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Fired",
s))
{
flashState = 0;
flashMask = 1;
if (s.Matches ("True"))
{
flashState |= 1;
}
if (fSDK->GetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Return",
s))
{
unsigned x = 0;
if (sscanf (s.Get (), "%u", &x) == 1 && x <= 3)
{
flashState |= x << 1;
flashMask |= 3 << 1;
}
}
if (fSDK->GetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Mode",
s))
{
unsigned x = 0;
if (sscanf (s.Get (), "%u", &x) == 1 && x <= 3)
{
flashState |= x << 3;
flashMask |= 3 << 3;
}
}
if (fSDK->GetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"Function",
s))
{
flashMask |= 1 << 5;
if (s.Matches ("True"))
{
flashState |= 1 << 5;
}
}
if (fSDK->GetStructField (XMP_NS_EXIF,
"Flash",
XMP_NS_EXIF,
"RedEyeMode",
s))
{
flashMask |= 1 << 6;
if (s.Matches ("True"))
{
flashState |= 1 << 6;
}
}
}
}
}
/*****************************************************************************/
void dng_xmp::SyncExif (dng_exif &exif,
const dng_exif *originalExif,
bool doingUpdateFromXMP)
{
DNG_ASSERT (!doingUpdateFromXMP || originalExif,
"Must have original EXIF if doingUpdateFromXMP");
// Default synchronization options for the read-only fields.
uint32 options = doingUpdateFromXMP ? ignoreXMP
: preferNonXMP;
// Make:
SyncString (XMP_NS_TIFF,
"Make",
exif.fMake,
options | requireASCII);
// Model:
SyncString (XMP_NS_TIFF,
"Model",
exif.fModel,
options | requireASCII);
// Exif version number:
{
dng_string exifVersion;
if (exif.fExifVersion)
{
unsigned b0 = ((exif.fExifVersion >> 24) & 0x0FF) - '0';
unsigned b1 = ((exif.fExifVersion >> 16) & 0x0FF) - '0';
unsigned b2 = ((exif.fExifVersion >> 8) & 0x0FF) - '0';
unsigned b3 = ((exif.fExifVersion ) & 0x0FF) - '0';
if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9)
{
char s [5];
sprintf (s,
"%1u%1u%1u%1u",
b0,
b1,
b2,
b3);
exifVersion.Set (s);
}
}
SyncString (XMP_NS_EXIF,
"ExifVersion",
exifVersion,
options);
if (exifVersion.NotEmpty ())
{
unsigned b0;
unsigned b1;
unsigned b2;
unsigned b3;
if (sscanf (exifVersion.Get (),
"%1u%1u%1u%1u",
&b0,
&b1,
&b2,
&b3) == 4)
{
if (b0 <= 9 && b1 <= 9 && b2 <= 9 && b3 <= 9)
{
b0 += '0';
b1 += '0';
b2 += '0';
b3 += '0';
exif.fExifVersion = (b0 << 24) |
(b1 << 16) |
(b2 << 8) |
(b3 );
}
}
}
// Provide default value for ExifVersion.
if (!exif.fExifVersion)
{
exif.fExifVersion = DNG_CHAR4 ('0','2','2','1');
Set (XMP_NS_EXIF,
"ExifVersion",
"0221");
}
}
// ExposureTime / ShutterSpeedValue:
{
// Process twice in case XMP contains only one of the
// two fields.
for (uint32 pass = 0; pass < 2; pass++)
{
dng_urational et = exif.fExposureTime;
Sync_urational (XMP_NS_EXIF,
"ExposureTime",
et,
options);
if (et.IsValid ())
{
exif.SetExposureTime (et.As_real64 (), false);
}
dng_srational ss = exif.fShutterSpeedValue;
Sync_srational (XMP_NS_EXIF,
"ShutterSpeedValue",
ss,
options);
if (ss.IsValid ())
{
exif.SetShutterSpeedValue (ss.As_real64 ());
}
}
}
// FNumber / ApertureValue:
{
for (uint32 pass = 0; pass < 2; pass++)
{
dng_urational fs = exif.fFNumber;
Sync_urational (XMP_NS_EXIF,
"FNumber",
fs,
options);
if (fs.IsValid ())
{
exif.SetFNumber (fs.As_real64 ());
}
dng_urational av = exif.fApertureValue;
Sync_urational (XMP_NS_EXIF,
"ApertureValue",
av,
options);
if (av.IsValid ())
{
exif.SetApertureValue (av.As_real64 ());
}
}
}
// Exposure program:
Sync_uint32 (XMP_NS_EXIF,
"ExposureProgram",
exif.fExposureProgram,
exif.fExposureProgram == 0xFFFFFFFF,
options);
// ISO Speed Ratings:
{
uint32 isoSpeedRatingsCount = 0;
for (uint32 j = 0; j < 3; j++)
{
if (exif.fISOSpeedRatings [j] == 0)
{
break;
}
isoSpeedRatingsCount++;
}
Sync_uint32_array (XMP_NS_EXIF,
"ISOSpeedRatings",
exif.fISOSpeedRatings,
isoSpeedRatingsCount,
3,
options);
}
// ExposureIndex:
Sync_urational (XMP_NS_EXIF,
"ExposureIndex",
exif.fExposureIndex,
options);
UpdateExifDates( exif );
// Brightness Value:
Sync_srational (XMP_NS_EXIF,
"BrightnessValue",
exif.fBrightnessValue,
options);
// Exposure Bias:
Sync_srational (XMP_NS_EXIF,
"ExposureBiasValue",
exif.fExposureBiasValue,
options);
// Max Aperture:
Sync_urational (XMP_NS_EXIF,
"MaxApertureValue",
exif.fMaxApertureValue,
options);
// Subject Distance:
Sync_urational (XMP_NS_EXIF,
"SubjectDistance",
exif.fSubjectDistance,
options);
// Metering Mode:
Sync_uint32 (XMP_NS_EXIF,
"MeteringMode",
exif.fMeteringMode,
exif.fMeteringMode == 0xFFFFFFFF,
options);
// Light Source:
Sync_uint32 (XMP_NS_EXIF,
"LightSource",
exif.fLightSource,
exif.fLightSource > 0x0FFFF,
options);
// Flash State:
SyncFlash (exif.fFlash,
exif.fFlashMask,
options);
// Focal Length:
Sync_urational (XMP_NS_EXIF,
"FocalLength",
exif.fFocalLength,
options);
// Sensing Method.
Sync_uint32 (XMP_NS_EXIF,
"SensingMethod",
exif.fSensingMethod,
exif.fSensingMethod > 0x0FFFF,
options);
// File Source.
Sync_uint32 (XMP_NS_EXIF,
"FileSource",
exif.fFileSource,
exif.fFileSource > 0x0FF,
options);
// Scene Type.
Sync_uint32 (XMP_NS_EXIF,
"SceneType",
exif.fSceneType,
exif.fSceneType > 0x0FF,
options);
// Focal Length in 35mm Film:
Sync_uint32 (XMP_NS_EXIF,
"FocalLengthIn35mmFilm",
exif.fFocalLengthIn35mmFilm,
exif.fFocalLengthIn35mmFilm == 0,
options);
// Custom Rendered:
Sync_uint32 (XMP_NS_EXIF,
"CustomRendered",
exif.fCustomRendered,
exif.fCustomRendered > 0x0FFFF,
options);
// Exposure Mode:
Sync_uint32 (XMP_NS_EXIF,
"ExposureMode",
exif.fExposureMode,
exif.fExposureMode > 0x0FFFF,
options);
// White Balance:
Sync_uint32 (XMP_NS_EXIF,
"WhiteBalance",
exif.fWhiteBalance,
exif.fWhiteBalance > 0x0FFFF,
options);
// Scene Capture Type:
Sync_uint32 (XMP_NS_EXIF,
"SceneCaptureType",
exif.fSceneCaptureType,
exif.fSceneCaptureType > 0x0FFFF,
options);
// Gain Control:
Sync_uint32 (XMP_NS_EXIF,
"GainControl",
exif.fGainControl,
exif.fGainControl > 0x0FFFF,
options);
// Contrast:
Sync_uint32 (XMP_NS_EXIF,
"Contrast",
exif.fContrast,
exif.fContrast > 0x0FFFF,
options);
// Saturation:
Sync_uint32 (XMP_NS_EXIF,
"Saturation",
exif.fSaturation,
exif.fSaturation > 0x0FFFF,
options);
// Sharpness:
Sync_uint32 (XMP_NS_EXIF,
"Sharpness",
exif.fSharpness,
exif.fSharpness > 0x0FFFF,
options);
// Subject Distance Range:
Sync_uint32 (XMP_NS_EXIF,
"SubjectDistanceRange",
exif.fSubjectDistanceRange,
exif.fSubjectDistanceRange > 0x0FFFF,
options);
// Subject Area:
Sync_uint32_array (XMP_NS_EXIF,
"SubjectArea",
exif.fSubjectArea,
exif.fSubjectAreaCount,
sizeof (exif.fSubjectArea ) /
sizeof (exif.fSubjectArea [0]),
options);
// Digital Zoom Ratio:
Sync_urational (XMP_NS_EXIF,
"DigitalZoomRatio",
exif.fDigitalZoomRatio,
options);
// Focal Plane Resolution:
Sync_urational (XMP_NS_EXIF,
"FocalPlaneXResolution",
exif.fFocalPlaneXResolution,
options);
Sync_urational (XMP_NS_EXIF,
"FocalPlaneYResolution",
exif.fFocalPlaneYResolution,
options);
Sync_uint32 (XMP_NS_EXIF,
"FocalPlaneResolutionUnit",
exif.fFocalPlaneResolutionUnit,
exif.fFocalPlaneResolutionUnit > 0x0FFFF,
options);
// ImageDescription: (XMP is always preferred)
if (fSDK->GetAltLangDefault (XMP_NS_DC,
"description",
exif.fImageDescription))
{
if (!exif.fImageDescription.IsASCII ())
{
exif.fImageDescription.Clear ();
}
}
else if (doingUpdateFromXMP)
{
exif.fImageDescription.Clear ();
if (originalExif->fImageDescription.NotEmpty ())
{
fSDK->SetAltLangDefault (XMP_NS_DC,
"description",
dng_string ());
}
}
else if (exif.fImageDescription.NotEmpty ())
{
fSDK->SetAltLangDefault (XMP_NS_DC,
"description",
exif.fImageDescription);
}
// Artist: (XMP is always preferred)
{
dng_string_list xmpList;
if (fSDK->GetStringList (XMP_NS_DC,
"creator",
xmpList))
{
exif.fArtist.Clear ();
if (xmpList.Count () > 0)
{
if (xmpList [0].IsASCII ())
{
exif.fArtist = xmpList [0];
}
}
}
else if (doingUpdateFromXMP)
{
exif.fArtist.Clear ();
if (originalExif->fArtist.NotEmpty ())
{
dng_string_list fakeList;
fakeList.Append (dng_string ());
SetStringList (XMP_NS_DC,
"creator",
fakeList,
false);
}
}
else if (exif.fArtist.NotEmpty ())
{
dng_string_list newList;
newList.Append (exif.fArtist);
SetStringList (XMP_NS_DC,
"creator",
newList,
false);
}
}
// Software: (XMP is always preferred)
if (fSDK->GetString (XMP_NS_XAP,
"CreatorTool",
exif.fSoftware))
{
if (!exif.fSoftware.IsASCII ())
{
exif.fSoftware.Clear ();
}
}
else if (doingUpdateFromXMP)
{
exif.fSoftware.Clear ();
if (originalExif->fSoftware.NotEmpty ())
{
fSDK->SetString (XMP_NS_XAP,
"CreatorTool",
dng_string ());
}
}
else if (exif.fSoftware.NotEmpty ())
{
fSDK->SetString (XMP_NS_XAP,
"CreatorTool",
exif.fSoftware);
}
// Copyright: (XMP is always preferred)
if (fSDK->GetAltLangDefault (XMP_NS_DC,
"rights",
exif.fCopyright))
{
if (!exif.fCopyright.IsASCII ())
{
exif.fCopyright.Clear ();
}
}
else if (doingUpdateFromXMP)
{
exif.fCopyright.Clear ();
if (originalExif->fCopyright.NotEmpty ())
{
fSDK->SetAltLangDefault (XMP_NS_DC,
"rights",
dng_string ());
}
}
else if (exif.fCopyright.NotEmpty ())
{
fSDK->SetAltLangDefault (XMP_NS_DC,
"rights",
exif.fCopyright);
}
// Camera serial number private tag:
SyncString (XMP_NS_AUX,
"SerialNumber",
exif.fCameraSerialNumber,
options | requireASCII);
// Lens Info:
{
dng_string s;
if (exif.fLensInfo [0].IsValid ())
{
char ss [256];
sprintf (ss,
"%u/%u %u/%u %u/%u %u/%u",
(unsigned) exif.fLensInfo [0].n,
(unsigned) exif.fLensInfo [0].d,
(unsigned) exif.fLensInfo [1].n,
(unsigned) exif.fLensInfo [1].d,
(unsigned) exif.fLensInfo [2].n,
(unsigned) exif.fLensInfo [2].d,
(unsigned) exif.fLensInfo [3].n,
(unsigned) exif.fLensInfo [3].d);
s.Set (ss);
}
SyncString (XMP_NS_AUX,
"LensInfo",
s,
options | requireASCII);
if (s.NotEmpty ())
{
unsigned n [4];
unsigned d [4];
if (sscanf (s.Get (),
"%u/%u %u/%u %u/%u %u/%u",
&n [0],
&d [0],
&n [1],
&d [1],
&n [2],
&d [2],
&n [3],
&d [3]) == 8)
{
for (uint32 j = 0; j < 4; j++)
{
exif.fLensInfo [j] = dng_urational (n [j], d [j]);
}
}
}
}
// Lens name:
{
// Since lens names are sometimes missing or wrong, allow user to edit the
// XMP and have the value stick. So prefer the XMP value if in conflict.
SyncString (XMP_NS_AUX,
"Lens",
exif.fLensName,
preferXMP);
// Generate default lens name from lens info if required.
// Ignore names that end in "f/0.0" due to third party bug.
if ((exif.fLensName.IsEmpty () ||
exif.fLensName.EndsWith ("f/0.0")) && exif.fLensInfo [0].IsValid ())
{
char s [256];
real64 minFL = exif.fLensInfo [0].As_real64 ();
real64 maxFL = exif.fLensInfo [1].As_real64 ();
// The f-stop numbers are optional.
if (exif.fLensInfo [2].IsValid ())
{
real64 minFS = exif.fLensInfo [2].As_real64 ();
real64 maxFS = exif.fLensInfo [3].As_real64 ();
if (minFL == maxFL)
sprintf (s, "%.1f mm f/%.1f", minFL, minFS);
else if (minFS == maxFS)
sprintf (s, "%.1f-%.1f mm f/%.1f", minFL, maxFL, minFS);
else
sprintf (s, "%.1f-%.1f mm f/%.1f-%.1f", minFL, maxFL, minFS, maxFS);
}
else
{
if (minFL == maxFL)
sprintf (s, "%.1f mm", minFL);
else
sprintf (s, "%.1f-%.1f mm", minFL, maxFL);
}
exif.fLensName.Set (s);
SetString (XMP_NS_AUX,
"Lens",
exif.fLensName);
}
}
// Lens ID:
SyncString (XMP_NS_AUX,
"LensID",
exif.fLensID,
options);
// Lens Serial Number:
SyncString (XMP_NS_AUX,
"LensSerialNumber",
exif.fLensSerialNumber,
options);
// Image Number:
Sync_uint32 (XMP_NS_AUX,
"ImageNumber",
exif.fImageNumber,
exif.fImageNumber == 0xFFFFFFFF,
options);
// User Comment:
if (exif.fUserComment.NotEmpty ())
{
fSDK->SetAltLangDefault (XMP_NS_EXIF,
"UserComment",
exif.fUserComment);
}
else
{
(void) fSDK->GetAltLangDefault (XMP_NS_EXIF,
"UserComment",
exif.fUserComment);
}
// Flash Compensation:
Sync_srational (XMP_NS_AUX,
"FlashCompensation",
exif.fFlashCompensation,
options);
// Owner Name:
SyncString (XMP_NS_AUX,
"OwnerName",
exif.fOwnerName,
options);
// Firmware:
SyncString (XMP_NS_AUX,
"Firmware",
exif.fFirmware,
options);
// Image Unique ID:
{
dng_string s = EncodeFingerprint (exif.fImageUniqueID);
SyncString (XMP_NS_EXIF,
"ImageUniqueID",
s,
options);
exif.fImageUniqueID = DecodeFingerprint (s);
}
// GPS Version ID:
{
dng_string s = EncodeGPSVersion (exif.fGPSVersionID);
if (SyncString (XMP_NS_EXIF,
"GPSVersionID",
s,
options))
{
exif.fGPSVersionID = DecodeGPSVersion (s);
}
}
// GPS Latitude:
{
dng_string s = EncodeGPSCoordinate (exif.fGPSLatitudeRef,
exif.fGPSLatitude);
if (SyncString (XMP_NS_EXIF,
"GPSLatitude",
s,
options))
{
DecodeGPSCoordinate (s,
exif.fGPSLatitudeRef,
exif.fGPSLatitude);
}
}
// GPS Longitude:
{
dng_string s = EncodeGPSCoordinate (exif.fGPSLongitudeRef,
exif.fGPSLongitude);
if (SyncString (XMP_NS_EXIF,
"GPSLongitude",
s,
options))
{
DecodeGPSCoordinate (s,
exif.fGPSLongitudeRef,
exif.fGPSLongitude);
}
}
// GPS Altitude Reference:
Sync_uint32 (XMP_NS_EXIF,
"GPSAltitudeRef",
exif.fGPSAltitudeRef,
exif.fGPSAltitudeRef == 0xFFFFFFFF,
options);
// GPS Altitude:
Sync_urational (XMP_NS_EXIF,
"GPSAltitude",
exif.fGPSAltitude,
options);
// GPS Date/Time:
{
dng_string s = EncodeGPSDateTime (exif.fGPSDateStamp,
exif.fGPSTimeStamp);
if (SyncString (XMP_NS_EXIF,
"GPSTimeStamp",
s,
options))
{
DecodeGPSDateTime (s,
exif.fGPSDateStamp,
exif.fGPSTimeStamp);
}
}
// GPS Satellites:
SyncString (XMP_NS_EXIF,
"GPSSatellites",
exif.fGPSSatellites,
options | requireASCII);
// GPS Status:
SyncString (XMP_NS_EXIF,
"GPSStatus",
exif.fGPSStatus,
options | requireASCII);
// GPS Measure Mode:
SyncString (XMP_NS_EXIF,
"GPSMeasureMode",
exif.fGPSMeasureMode,
options | requireASCII);
// GPS DOP:
Sync_urational (XMP_NS_EXIF,
"GPSDOP",
exif.fGPSDOP,
options);
// GPS Speed Reference:
SyncString (XMP_NS_EXIF,
"GPSSpeedRef",
exif.fGPSSpeedRef,
options | requireASCII);
// GPS Speed:
Sync_urational (XMP_NS_EXIF,
"GPSSpeed",
exif.fGPSSpeed,
options);
// GPS Track Reference:
SyncString (XMP_NS_EXIF,
"GPSTrackRef",
exif.fGPSTrackRef,
options | requireASCII);
// GPS Track:
Sync_urational (XMP_NS_EXIF,
"GPSTrack",
exif.fGPSTrack,
options);
// GPS Image Direction Reference:
SyncString (XMP_NS_EXIF,
"GPSImgDirectionRef",
exif.fGPSImgDirectionRef,
options | requireASCII);
// GPS Image Direction:
Sync_urational (XMP_NS_EXIF,
"GPSImgDirection",
exif.fGPSImgDirection,
options);
// GPS Map Datum:
SyncString (XMP_NS_EXIF,
"GPSMapDatum",
exif.fGPSMapDatum,
options | requireASCII);
// GPS Destination Latitude:
{
dng_string s = EncodeGPSCoordinate (exif.fGPSDestLatitudeRef,
exif.fGPSDestLatitude);
if (SyncString (XMP_NS_EXIF,
"GPSDestLatitude",
s,
options))
{
DecodeGPSCoordinate (s,
exif.fGPSDestLatitudeRef,
exif.fGPSDestLatitude);
}
}
// GPS Destination Longitude:
{
dng_string s = EncodeGPSCoordinate (exif.fGPSDestLongitudeRef,
exif.fGPSDestLongitude);
if (SyncString (XMP_NS_EXIF,
"GPSDestLongitude",
s,
options))
{
DecodeGPSCoordinate (s,
exif.fGPSDestLongitudeRef,
exif.fGPSDestLongitude);
}
}
// GPS Destination Bearing Reference:
SyncString (XMP_NS_EXIF,
"GPSDestBearingRef",
exif.fGPSDestBearingRef,
options | requireASCII);
// GPS Destination Bearing:
Sync_urational (XMP_NS_EXIF,
"GPSDestBearing",
exif.fGPSDestBearing,
options);
// GPS Destination Distance Reference:
SyncString (XMP_NS_EXIF,
"GPSDestDistanceRef",
exif.fGPSDestDistanceRef,
options | requireASCII);
// GPS Destination Distance:
Sync_urational (XMP_NS_EXIF,
"GPSDestDistance",
exif.fGPSDestDistance,
options);
// GPS Processing Method:
SyncString (XMP_NS_EXIF,
"GPSProcessingMethod",
exif.fGPSProcessingMethod,
options);
// GPS Area Information:
SyncString (XMP_NS_EXIF,
"GPSAreaInformation",
exif.fGPSAreaInformation,
options);
// GPS Differential:
Sync_uint32 (XMP_NS_EXIF,
"GPSDifferential",
exif.fGPSDifferential,
exif.fGPSDifferential == 0xFFFFFFFF,
options);
// We are syncing EXIF and XMP, but we are not updating the
// NativeDigest tags. It is better to just delete them than leave
// the stale values around.
Remove (XMP_NS_EXIF, "NativeDigest");
Remove (XMP_NS_TIFF, "NativeDigest");
}
/******************************************************************************/
void dng_xmp::ValidateStringList (const char *ns,
const char *path)
{
fSDK->ValidateStringList (ns, path);
}
/******************************************************************************/
void dng_xmp::ValidateMetadata ()
{
// The following values should be arrays, but are not always. So
// fix them up because Photoshop sometimes has problems parsing invalid
// tags.
ValidateStringList (XMP_NS_DC, "creator");
ValidateStringList (XMP_NS_PHOTOSHOP, "Keywords");
ValidateStringList (XMP_NS_PHOTOSHOP, "SupplementalCategories");
}
/******************************************************************************/
void dng_xmp::UpdateExifDates (dng_exif &exif)
{
// For the following three date/time fields, we always prefer XMP to
// the EXIF values. This is to allow the user to correct the date/times
// via changes in a sidecar XMP file, without modifying the original
// raw file.
// DateTime:
{
dng_string s = exif.fDateTime.Encode_ISO_8601 ();
SyncString (XMP_NS_TIFF,
"DateTime",
s,
preferXMP);
if (s.NotEmpty ())
{
exif.fDateTime.Decode_ISO_8601 (s.Get ());
}
}
// DateTimeOriginal:
{
dng_string s = exif.fDateTimeOriginal.Encode_ISO_8601 ();
SyncString (XMP_NS_EXIF,
"DateTimeOriginal",
s,
preferXMP);
if (s.NotEmpty ())
{
exif.fDateTimeOriginal.Decode_ISO_8601 (s.Get ());
// If the XAP create date is missing or empty, set it to the
// DateTimeOriginal value.
dng_string ss;
if (!GetString (XMP_NS_XAP, "CreateDate", ss) || ss.IsEmpty ())
{
SetString (XMP_NS_XAP, "CreateDate", s);
}
}
}
// Date Time Digitized:
{
dng_string s = exif.fDateTimeDigitized.Encode_ISO_8601 ();
SyncString (XMP_NS_EXIF,
"DateTimeDigitized",
s,
preferXMP);
if (s.NotEmpty ())
{
exif.fDateTimeDigitized.Decode_ISO_8601 (s.Get ());
}
}
}
/******************************************************************************/
void dng_xmp::UpdateDateTime (const dng_date_time_info &dt)
{
dng_string s = dt.Encode_ISO_8601 ();
SetString (XMP_NS_TIFF,
"DateTime",
s);
}
/*****************************************************************************/
bool dng_xmp::HasOrientation () const
{
uint32 x = 0;
if (Get_uint32 (XMP_NS_TIFF,
"Orientation",
x))
{
return (x >= 1) && (x <= 8);
}
return false;
}
/*****************************************************************************/
dng_orientation dng_xmp::GetOrientation () const
{
dng_orientation result;
uint32 x = 0;
if (Get_uint32 (XMP_NS_TIFF,
"Orientation",
x))
{
if ((x >= 1) && (x <= 8))
{
result.SetTIFF (x);
}
}
return result;
}
/******************************************************************************/
void dng_xmp::ClearOrientation ()
{
fSDK->Remove (XMP_NS_TIFF, "Orientation");
}
/******************************************************************************/
void dng_xmp::SetOrientation (const dng_orientation &orientation)
{
Set_uint32 (XMP_NS_TIFF,
"Orientation",
orientation.GetTIFF ());
}
/*****************************************************************************/
void dng_xmp::SyncOrientation (dng_negative &negative,
bool xmpIsMaster)
{
// See if XMP contains the orientation.
bool xmpHasOrientation = HasOrientation ();
// See if XMP is the master value.
if (xmpHasOrientation && (xmpIsMaster || !negative.HasBaseOrientation ()))
{
negative.SetBaseOrientation (GetOrientation ());
}
else
{
SetOrientation (negative.BaseOrientation ());
}
}
/******************************************************************************/
void dng_xmp::ClearImageInfo ()
{
Remove (XMP_NS_TIFF, "ImageWidth" );
Remove (XMP_NS_TIFF, "ImageLength");
Remove (XMP_NS_TIFF, "BitsPerSample");
Remove (XMP_NS_TIFF, "Compression");
Remove (XMP_NS_TIFF, "PhotometricInterpretation");
// "Orientation" is handled separately.
Remove (XMP_NS_TIFF, "SamplesPerPixel");
Remove (XMP_NS_TIFF, "PlanarConfiguration");
Remove (XMP_NS_TIFF, "XResolution");
Remove (XMP_NS_TIFF, "YResolution");
Remove (XMP_NS_TIFF, "ResolutionUnit");
Remove (XMP_NS_PHOTOSHOP, "ColorMode" );
Remove (XMP_NS_PHOTOSHOP, "ICCProfile");
}
/******************************************************************************/
void dng_xmp::SetImageSize (const dng_point &size)
{
Set_uint32 (XMP_NS_TIFF, "ImageWidth" , size.h);
Set_uint32 (XMP_NS_TIFF, "ImageLength", size.v);
// Mirror these values to the EXIF tags.
Set_uint32 (XMP_NS_EXIF, "PixelXDimension" , size.h);
Set_uint32 (XMP_NS_EXIF, "PixelYDimension" , size.v);
}
/******************************************************************************/
void dng_xmp::SetSampleInfo (uint32 samplesPerPixel,
uint32 bitsPerSample)
{
Set_uint32 (XMP_NS_TIFF, "SamplesPerPixel", samplesPerPixel);
char s [32];
sprintf (s, "%u", bitsPerSample);
dng_string ss;
ss.Set (s);
dng_string_list list;
for (uint32 j = 0; j < samplesPerPixel; j++)
{
list.Append (ss);
}
SetStringList (XMP_NS_TIFF, "BitsPerSample", list, false);
}
/******************************************************************************/
void dng_xmp::SetPhotometricInterpretation (uint32 pi)
{
Set_uint32 (XMP_NS_TIFF, "PhotometricInterpretation", pi);
}
/******************************************************************************/
void dng_xmp::SetResolution (const dng_resolution &res)
{
Set_urational (XMP_NS_TIFF, "XResolution", res.fXResolution);
Set_urational (XMP_NS_TIFF, "YResolution", res.fYResolution);
Set_uint32 (XMP_NS_TIFF, "ResolutionUnit", res.fResolutionUnit);
}
/*****************************************************************************/
void dng_xmp::ComposeArrayItemPath (const char *ns,
const char *arrayName,
int32 itemNumber,
dng_string &s) const
{
fSDK->ComposeArrayItemPath (ns, arrayName, itemNumber, s);
}
/*****************************************************************************/
void dng_xmp::ComposeStructFieldPath (const char *ns,
const char *structName,
const char *fieldNS,
const char *fieldName,
dng_string &s) const
{
fSDK->ComposeStructFieldPath (ns, structName, fieldNS, fieldName, s);
}
/*****************************************************************************/
int32 dng_xmp::CountArrayItems (const char *ns,
const char *path) const
{
return fSDK->CountArrayItems (ns, path);
}
/*****************************************************************************/
void dng_xmp::AppendArrayItem (const char *ns,
const char *arrayName,
const char *itemValue,
bool isBag,
bool propIsStruct)
{
fSDK->AppendArrayItem (ns,
arrayName,
itemValue,
isBag,
propIsStruct);
}
/*****************************************************************************/
diff --git a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp
index c04742ce2c..ec44cdb3a4 100644
--- a/core/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp
+++ b/core/libs/dngwriter/extra/xmp_sdk/XMPCore/ParseRDF.cpp
@@ -1,1344 +1,1344 @@
// =================================================================================================
// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
#include "XMP_Environment.h" // ! This must be the first include!
#include "XMPCore_Impl.hpp"
#include "ExpatAdapter.hpp"
#include <cstring>
#if DEBUG
#include <iostream>
#endif
using namespace std;
namespace DngXmpSdk {
#if XMP_WinBuild
#pragma warning ( disable : 4189 ) // local variable is initialized but not referenced
#pragma warning ( disable : 4505 ) // unreferenced local function has been removed
#endif
// =================================================================================================
// *** This might be faster and use less memory as a state machine. A big advantage of building an
// *** XML tree though is easy lookahead during the recursive descent processing.
// *** It would be nice to give a line number or byte offset in the exception messages.
// 7 RDF/XML Grammar (from http://www.w3.org/TR/rdf-syntax-grammar/#section-Infoset-Grammar)
//
// 7.1 Grammar summary
//
// 7.2.2 coreSyntaxTerms
// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype
//
// 7.2.3 syntaxTerms
// coreSyntaxTerms | rdf:Description | rdf:li
//
// 7.2.4 oldTerms
// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
//
// 7.2.5 nodeElementURIs
// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
//
// 7.2.6 propertyElementURIs
// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
//
// 7.2.7 propertyAttributeURIs
// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
//
// 7.2.8 doc
// root ( document-element == RDF, children == list ( RDF ) )
//
// 7.2.9 RDF
// start-element ( URI == rdf:RDF, attributes == set() )
// nodeElementList
// end-element()
//
// 7.2.10 nodeElementList
// ws* ( nodeElement ws* )*
//
// 7.2.11 nodeElement
// start-element ( URI == nodeElementURIs,
// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
// propertyEltList
// end-element()
//
// 7.2.12 ws
// A text event matching white space defined by [XML] definition White Space Rule [3] S in section Common Syntactic Constructs.
//
// 7.2.13 propertyEltList
// ws* ( propertyElt ws* )*
//
// 7.2.14 propertyElt
// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt
//
// 7.2.15 resourcePropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
// ws* nodeElement ws*
// end-element()
//
// 7.2.16 literalPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
// text()
// end-element()
//
// 7.2.17 parseTypeLiteralPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
// literal
// end-element()
//
// 7.2.18 parseTypeResourcePropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
// propertyEltList
// end-element()
//
// 7.2.19 parseTypeCollectionPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
// nodeElementList
// end-element()
//
// 7.2.20 parseTypeOtherPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
// propertyEltList
// end-element()
//
// 7.2.21 emptyPropertyElt
// start-element ( URI == propertyElementURIs,
// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
// end-element()
//
// 7.2.22 idAttr
// attribute ( URI == rdf:ID, string-value == rdf-id )
//
// 7.2.23 nodeIdAttr
// attribute ( URI == rdf:nodeID, string-value == rdf-id )
//
// 7.2.24 aboutAttr
// attribute ( URI == rdf:about, string-value == URI-reference )
//
// 7.2.25 propertyAttr
// attribute ( URI == propertyAttributeURIs, string-value == anyString )
//
// 7.2.26 resourceAttr
// attribute ( URI == rdf:resource, string-value == URI-reference )
//
// 7.2.27 datatypeAttr
// attribute ( URI == rdf:datatype, string-value == URI-reference )
//
// 7.2.28 parseLiteral
// attribute ( URI == rdf:parseType, string-value == "Literal")
//
// 7.2.29 parseResource
// attribute ( URI == rdf:parseType, string-value == "Resource")
//
// 7.2.30 parseCollection
// attribute ( URI == rdf:parseType, string-value == "Collection")
//
// 7.2.31 parseOther
// attribute ( URI == rdf:parseType, string-value == anyString - ("Resource" | "Literal" | "Collection") )
//
// 7.2.32 URI-reference
// An RDF URI Reference.
//
// 7.2.33 literal
// Any XML element content that is allowed according to [XML] definition Content of Elements Rule [43] content
// in section 3.1 Start-Tags, End-Tags, and Empty-Element Tags.
//
// 7.2.34 rdf-id
// An attribute string-value matching any legal [XML-NS] token NCName.
// =================================================================================================
// Primary Parsing Functions
// =========================
//
// Each of these is responsible for recognizing an RDF syntax production and adding the appropriate
// structure to the XMP tree. They simply return for success, failures will throw an exception.
static void
RDF_RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode );
static void
RDF_NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel );
static void
RDF_NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel );
enum { kIsTopLevel = true, kNotTopLevel = false };
static void
RDF_PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
static void
RDF_EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel );
// =================================================================================================
typedef XMP_Uns8 RDFTermKind;
// *** Logic might be safer with just masks.
enum {
kRDFTerm_Other = 0,
kRDFTerm_RDF = 1, // Start of coreSyntaxTerms.
kRDFTerm_ID = 2,
kRDFTerm_about = 3,
kRDFTerm_parseType = 4,
kRDFTerm_resource = 5,
kRDFTerm_nodeID = 6,
kRDFTerm_datatype = 7, // End of coreSyntaxTerms.
kRDFTerm_Description = 8, // Start of additions for syntaxTerms.
kRDFTerm_li = 9, // End of additions for syntaxTerms.
kRDFTerm_aboutEach = 10, // Start of oldTerms.
kRDFTerm_aboutEachPrefix = 11,
kRDFTerm_bagID = 12, // End of oldTerms.
kRDFTerm_FirstCore = kRDFTerm_RDF,
kRDFTerm_LastCore = kRDFTerm_datatype,
kRDFTerm_FirstSyntax = kRDFTerm_FirstCore, // ! Yes, the syntax terms include the core terms.
kRDFTerm_LastSyntax = kRDFTerm_li,
kRDFTerm_FirstOld = kRDFTerm_aboutEach,
kRDFTerm_LastOld = kRDFTerm_bagID
};
enum {
kRDFMask_Other = 1 << kRDFTerm_Other,
kRDFMask_RDF = 1 << kRDFTerm_RDF,
kRDFMask_ID = 1 << kRDFTerm_ID,
kRDFMask_about = 1 << kRDFTerm_about,
kRDFMask_parseType = 1 << kRDFTerm_parseType,
kRDFMask_resource = 1 << kRDFTerm_resource,
kRDFMask_nodeID = 1 << kRDFTerm_nodeID,
kRDFMask_datatype = 1 << kRDFTerm_datatype,
kRDFMask_Description = 1 << kRDFTerm_Description,
kRDFMask_li = 1 << kRDFTerm_li,
kRDFMask_aboutEach = 1 << kRDFTerm_aboutEach,
kRDFMask_aboutEachPrefix = 1 << kRDFTerm_aboutEachPrefix,
kRDFMask_bagID = 1 << kRDFTerm_bagID
};
enum {
kRDF_HasValueElem = 0x10000000UL // ! Contains rdf:value child. Must fit within kXMP_ImplReservedMask!
};
// -------------------------------------------------------------------------------------------------
// GetRDFTermKind
// --------------
static RDFTermKind
GetRDFTermKind ( const XMP_VarString & name )
{
RDFTermKind term = kRDFTerm_Other;
// Arranged to hopefully minimize the parse time for large XMP.
if ( (name.size() > 4) && (strncmp ( name.c_str(), "rdf:", 4 ) == 0) ) {
if ( name == "rdf:li" ) {
term = kRDFTerm_li;
} else if ( name == "rdf:parseType" ) {
term = kRDFTerm_parseType;
} else if ( name == "rdf:Description" ) {
term = kRDFTerm_Description;
} else if ( name == "rdf:about" ) {
term = kRDFTerm_about;
} else if ( name == "rdf:resource" ) {
term = kRDFTerm_resource;
} else if ( name == "rdf:RDF" ) {
term = kRDFTerm_RDF;
} else if ( name == "rdf:ID" ) {
term = kRDFTerm_ID;
} else if ( name == "rdf:nodeID" ) {
term = kRDFTerm_nodeID;
} else if ( name == "rdf:datatype" ) {
term = kRDFTerm_datatype;
} else if ( name == "rdf:aboutEach" ) {
term = kRDFTerm_aboutEach;
} else if ( name == "rdf:aboutEachPrefix" ) {
term = kRDFTerm_aboutEachPrefix;
} else if ( name == "rdf:bagID" ) {
term = kRDFTerm_bagID;
}
}
return term;
} // GetRDFTermKind
// =================================================================================================
// -------------------------------------------------------------------------------------------------
// IsCoreSyntaxTerm
// ----------------
//
// 7.2.2 coreSyntaxTerms
// rdf:RDF | rdf:ID | rdf:about | rdf:parseType | rdf:resource | rdf:nodeID | rdf:datatype
static bool
IsCoreSyntaxTerm ( RDFTermKind term )
{
if ( (kRDFTerm_FirstCore <= term) && (term <= kRDFTerm_LastCore) ) return true;
return false;
} // IsCoreSyntaxTerm
// -------------------------------------------------------------------------------------------------
// IsSyntaxTerm
// ------------
//
// 7.2.3 syntaxTerms
// coreSyntaxTerms | rdf:Description | rdf:li
static bool
IsSyntaxTerm ( RDFTermKind term )
{
if ( (kRDFTerm_FirstSyntax <= term) && (term <= kRDFTerm_LastSyntax) ) return true;
return false;
} // IsSyntaxTerm
// -------------------------------------------------------------------------------------------------
// IsOldTerm
// ---------
//
// 7.2.4 oldTerms
// rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID
static bool
IsOldTerm ( RDFTermKind term )
{
if ( (kRDFTerm_FirstOld <= term) && (term <= kRDFTerm_LastOld) ) return true;
return false;
} // IsOldTerm
// -------------------------------------------------------------------------------------------------
// IsNodeElementName
// -----------------
//
// 7.2.5 nodeElementURIs
// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
static bool
IsNodeElementName ( RDFTermKind term )
{
if ( (term == kRDFTerm_li) || IsOldTerm ( term ) ) return false;
return (! IsCoreSyntaxTerm ( term ));
} // IsNodeElementName
// -------------------------------------------------------------------------------------------------
// IsPropertyElementName
// ---------------------
//
// 7.2.6 propertyElementURIs
// anyURI - ( coreSyntaxTerms | rdf:Description | oldTerms )
static bool
IsPropertyElementName ( RDFTermKind term )
{
if ( (term == kRDFTerm_Description) || IsOldTerm ( term ) ) return false;
return (! IsCoreSyntaxTerm ( term ));
} // IsPropertyElementName
// -------------------------------------------------------------------------------------------------
// IsPropertyAttributeName
// -----------------------
//
// 7.2.7 propertyAttributeURIs
// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
static bool
IsPropertyAttributeName ( RDFTermKind term )
{
if ( (term == kRDFTerm_Description) || (term == kRDFTerm_li) || IsOldTerm ( term ) ) return false;
return (! IsCoreSyntaxTerm ( term ));
} // IsPropertyAttributeName
// =================================================================================================
// AddChildNode
// ============
static XMP_Node *
AddChildNode ( XMP_Node * xmpParent, const XML_Node & xmlNode, const XMP_StringPtr value, bool isTopLevel )
{
#if 0
cout << "AddChildNode, parent = " << xmpParent->name << ", child = " << xmlNode.name;
cout << ", value = \"" << value << '"';
if ( isTopLevel ) cout << ", top level";
cout << endl;
#endif
if ( xmlNode.ns.empty() ) {
XMP_Throw ( "XML namespace required for all elements and attributes", kXMPErr_BadRDF );
}
XMP_StringPtr childName = xmlNode.name.c_str();
const bool isArrayItem = (xmlNode.name == "rdf:li");
const bool isValueNode = (xmlNode.name == "rdf:value");
XMP_OptionBits childOptions = 0;
if ( isTopLevel ) {
// Lookup the schema node, adjust the XMP parent pointer.
XMP_Assert ( xmpParent->parent == 0 ); // Incoming parent must be the tree root.
XMP_Node * schemaNode = FindSchemaNode ( xmpParent, xmlNode.ns.c_str(), kXMP_CreateNodes );
if ( schemaNode->options & kXMP_NewImplicitNode ) schemaNode->options ^= kXMP_NewImplicitNode; // Clear the implicit node bit.
// *** Should use "opt &= ~flag" (no conditional), need runtime check for proper 32 bit code.
xmpParent = schemaNode;
// If this is an alias set the isAlias flag in the node and the hasAliases flag in the tree.
if ( sRegisteredAliasMap->find ( xmlNode.name ) != sRegisteredAliasMap->end() ) {
childOptions |= kXMP_PropIsAlias;
schemaNode->parent->options |= kXMP_PropHasAliases;
}
}
// Make sure that this is not a duplicate of a named node.
if ( ! (isArrayItem | isValueNode) ) {
if ( FindChildNode ( xmpParent, childName, kXMP_ExistingOnly ) != 0 ) {
XMP_Throw ( "Duplicate property or field node", kXMPErr_BadXMP );
}
}
// Add the new child to the XMP parent node.
XMP_Node * newChild = new XMP_Node ( xmpParent, childName, value, childOptions );
if ( (! isValueNode) || xmpParent->children.empty() ) {
xmpParent->children.push_back ( newChild );
} else {
xmpParent->children.insert ( xmpParent->children.begin(), newChild );
}
if ( isValueNode ) {
if ( isTopLevel || (! (xmpParent->options & kXMP_PropValueIsStruct)) ) XMP_Throw ( "Misplaced rdf:value element", kXMPErr_BadRDF );
xmpParent->options |= kRDF_HasValueElem;
}
if ( isArrayItem ) {
if ( ! (xmpParent->options & kXMP_PropValueIsArray) ) XMP_Throw ( "Misplaced rdf:li element", kXMPErr_BadRDF );
newChild->name = kXMP_ArrayItemName;
#if 0 // *** XMP_DebugBuild
newChild->_namePtr = newChild->name.c_str();
#endif
}
return newChild;
} // AddChildNode
// =================================================================================================
// AddQualifierNode
// ================
static XMP_Node *
AddQualifierNode ( XMP_Node * xmpParent, const XMP_VarString & name, const XMP_VarString & value )
{
#if 0
cout << "AddQualifierNode, parent = " << xmpParent->name << ", name = " << name;
cout << ", value = \"" << value << '"' << endl;
#endif
const bool isLang = (name == "xml:lang");
const bool isType = (name == "rdf:type");
XMP_Node * newQual = 0;
newQual = new XMP_Node ( xmpParent, name, value, kXMP_PropIsQualifier );
if ( ! (isLang | isType) ) {
xmpParent->qualifiers.push_back ( newQual );
} else if ( isLang ) {
if ( xmpParent->qualifiers.empty() ) {
xmpParent->qualifiers.push_back ( newQual );
} else {
xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), newQual );
}
xmpParent->options |= kXMP_PropHasLang;
} else {
XMP_Assert ( isType );
if ( xmpParent->qualifiers.empty() ) {
xmpParent->qualifiers.push_back ( newQual );
} else {
size_t offset = 0;
if ( XMP_PropHasLang ( xmpParent->options ) ) offset = 1;
xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin()+offset, newQual );
}
xmpParent->options |= kXMP_PropHasType;
}
xmpParent->options |= kXMP_PropHasQualifiers;
return newQual;
} // AddQualifierNode
// =================================================================================================
// AddQualifierNode
// ================
static XMP_Node *
AddQualifierNode ( XMP_Node * xmpParent, const XML_Node & attr )
{
if ( attr.ns.empty() ) {
XMP_Throw ( "XML namespace required for all elements and attributes", kXMPErr_BadRDF );
}
return AddQualifierNode ( xmpParent, attr.name, attr.value );
} // AddQualifierNode
// =================================================================================================
// FixupQualifiedNode
// ==================
//
// The parent is an RDF pseudo-struct containing an rdf:value field. Fix the XMP data model. The
// rdf:value node must be the first child, the other children are qualifiers. The form, value, and
// children of the rdf:value node are the real ones. The rdf:value node's qualifiers must be added
// to the others.
static void
FixupQualifiedNode ( XMP_Node * xmpParent )
{
size_t qualNum, qualLim;
size_t childNum, childLim;
XMP_Enforce ( (xmpParent->options & kXMP_PropValueIsStruct) && (! xmpParent->children.empty()) );
XMP_Node * valueNode = xmpParent->children[0];
XMP_Enforce ( valueNode->name == "rdf:value" );
xmpParent->qualifiers.reserve ( xmpParent->qualifiers.size() + xmpParent->children.size() + valueNode->qualifiers.size() );
// Move the qualifiers on the value node to the parent. Make sure an xml:lang qualifier stays at
// the front. Check for duplicate names between the value node's qualifiers and the parent's
// children. The parent's children are about to become qualifiers. Check here, between the
// groups. Intra-group duplicates are caught by AddChildNode.
qualNum = 0;
qualLim = valueNode->qualifiers.size();
if ( valueNode->options & kXMP_PropHasLang ) {
if ( xmpParent->options & kXMP_PropHasLang ) XMP_Throw ( "Redundant xml:lang for rdf:value element", kXMPErr_BadXMP );
XMP_Node * langQual = valueNode->qualifiers[0];
XMP_Assert ( langQual->name == "xml:lang" );
langQual->parent = xmpParent;
xmpParent->options |= kXMP_PropHasLang;
if ( xmpParent->qualifiers.empty() ) {
xmpParent->qualifiers.push_back ( langQual ); // *** Should use utilities to add qual & set parent.
} else {
xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), langQual );
}
valueNode->qualifiers[0] = 0; // We just moved it to the parent.
qualNum = 1; // Start the remaining copy after the xml:lang qualifier.
}
for ( ; qualNum != qualLim; ++qualNum ) {
XMP_Node * currQual = valueNode->qualifiers[qualNum];
if ( FindChildNode ( xmpParent, currQual->name.c_str(), kXMP_ExistingOnly ) != 0 ) {
XMP_Throw ( "Duplicate qualifier node", kXMPErr_BadXMP );
}
currQual->parent = xmpParent;
xmpParent->qualifiers.push_back ( currQual );
valueNode->qualifiers[qualNum] = 0; // We just moved it to the parent.
}
valueNode->qualifiers.clear(); // ! There should be nothing but null pointers.
// Change the parent's other children into qualifiers. This loop starts at 1, child 0 is the
// rdf:value node. Put xml:lang at the front, append all others.
for ( childNum = 1, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) {
XMP_Node * currQual = xmpParent->children[childNum];
bool isLang = (currQual->name == "xml:lang");
currQual->options |= kXMP_PropIsQualifier;
currQual->parent = xmpParent;
if ( isLang ) {
if ( xmpParent->options & kXMP_PropHasLang ) XMP_Throw ( "Duplicate xml:lang qualifier", kXMPErr_BadXMP );
xmpParent->options |= kXMP_PropHasLang;
} else if ( currQual->name == "rdf:type" ) {
xmpParent->options |= kXMP_PropHasType;
}
if ( (! isLang) || xmpParent->qualifiers.empty() ) {
xmpParent->qualifiers.push_back ( currQual );
} else {
xmpParent->qualifiers.insert ( xmpParent->qualifiers.begin(), currQual );
}
- xmpParent->children[childNum] = 0; // We just moved it to the qualifers.
+ xmpParent->children[childNum] = 0; // We just moved it to the qualifiers.
}
if ( ! xmpParent->qualifiers.empty() ) xmpParent->options |= kXMP_PropHasQualifiers;
// Move the options and value last, other checks need the parent's original options. Move the
// value node's children to be the parent's children. Delete the now useless value node.
XMP_Assert ( xmpParent->options & (kXMP_PropValueIsStruct | kRDF_HasValueElem) );
xmpParent->options &= ~ (kXMP_PropValueIsStruct | kRDF_HasValueElem);
xmpParent->options |= valueNode->options;
xmpParent->value.swap ( valueNode->value );
#if 0 // *** XMP_DebugBuild
xmpParent->_valuePtr = xmpParent->value.c_str();
#endif
xmpParent->children[0] = 0; // ! Remove the value node itself before the swap.
xmpParent->children.swap ( valueNode->children );
for ( size_t childNum = 0, childLim = xmpParent->children.size(); childNum != childLim; ++childNum ) {
XMP_Node * currChild = xmpParent->children[childNum];
currChild->parent = xmpParent;
}
delete valueNode;
} // FixupQualifiedNode
// =================================================================================================
// ProcessRDF
// ==========
//
// Parse the XML tree of the RDF and build the corresponding XMP tree.
// *** Throw an exception if no XMP is found? By option?
// *** Do parsing exceptions cause the partial tree to be deleted?
void ProcessRDF ( XMP_Node * xmpTree, const XML_Node & rdfNode, XMP_OptionBits options )
{
IgnoreParam(options);
RDF_RDF ( xmpTree, rdfNode );
} // ProcessRDF
// =================================================================================================
// RDF_RDF
// =======
//
// 7.2.9 RDF
// start-element ( URI == rdf:RDF, attributes == set() )
// nodeElementList
// end-element()
//
// The top level rdf:RDF node. It can only have xmlns attributes, which have already been removed
// during construction of the XML tree.
static void
RDF_RDF ( XMP_Node * xmpTree, const XML_Node & xmlNode )
{
if ( ! xmlNode.attrs.empty() ) XMP_Throw ( "Invalid attributes of rdf:RDF element", kXMPErr_BadRDF );
RDF_NodeElementList ( xmpTree, xmlNode, kIsTopLevel );
} // RDF_RDF
// =================================================================================================
// RDF_NodeElementList
// ===================
//
// 7.2.10 nodeElementList
// ws* ( nodeElement ws* )*
static void
RDF_NodeElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel )
{
XMP_Assert ( isTopLevel );
XML_cNodePos currChild = xmlParent.content.begin(); // *** Change these loops to the indexed pattern.
XML_cNodePos endChild = xmlParent.content.end();
for ( ; currChild != endChild; ++currChild ) {
if ( (*currChild)->IsWhitespaceNode() ) continue;
RDF_NodeElement ( xmpParent, **currChild, isTopLevel );
}
} // RDF_NodeElementList
// =================================================================================================
// RDF_NodeElement
// ===============
//
// 7.2.5 nodeElementURIs
// anyURI - ( coreSyntaxTerms | rdf:li | oldTerms )
//
// 7.2.11 nodeElement
// start-element ( URI == nodeElementURIs,
// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
// propertyEltList
// end-element()
//
// A node element URI is rdf:Description or anything else that is not an RDF term.
static void
RDF_NodeElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name );
if ( (nodeTerm != kRDFTerm_Description) && (nodeTerm != kRDFTerm_Other) ) {
XMP_Throw ( "Node element must be rdf:Description or typedNode", kXMPErr_BadRDF );
}
if ( isTopLevel && (nodeTerm == kRDFTerm_Other) ) {
XMP_Throw ( "Top level typedNode not allowed", kXMPErr_BadXMP );
} else {
RDF_NodeElementAttrs ( xmpParent, xmlNode, isTopLevel );
RDF_PropertyElementList ( xmpParent, xmlNode, isTopLevel );
}
} // RDF_NodeElement
// =================================================================================================
// RDF_NodeElementAttrs
// ====================
//
// 7.2.7 propertyAttributeURIs
// anyURI - ( coreSyntaxTerms | rdf:Description | rdf:li | oldTerms )
//
// 7.2.11 nodeElement
// start-element ( URI == nodeElementURIs,
// attributes == set ( ( idAttr | nodeIdAttr | aboutAttr )?, propertyAttr* ) )
// propertyEltList
// end-element()
//
// Process the attribute list for an RDF node element. A property attribute URI is anything other
// than an RDF term. The rdf:ID and rdf:nodeID attributes are simply ignored, as are rdf:about
// attributes on inner nodes.
static const XMP_OptionBits kExclusiveAttrMask = (kRDFMask_ID | kRDFMask_nodeID | kRDFMask_about);
static void
RDF_NodeElementAttrs ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
XMP_OptionBits exclusiveAttrs = 0; // Used to detect attributes that are mutually exclusive.
XML_cNodePos currAttr = xmlNode.attrs.begin();
XML_cNodePos endAttr = xmlNode.attrs.end();
for ( ; currAttr != endAttr; ++currAttr ) {
RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name );
switch ( attrTerm ) {
case kRDFTerm_ID :
case kRDFTerm_nodeID :
case kRDFTerm_about :
if ( exclusiveAttrs & kExclusiveAttrMask ) XMP_Throw ( "Mutally exclusive about, ID, nodeID attributes", kXMPErr_BadRDF );
exclusiveAttrs |= (1 << attrTerm);
if ( isTopLevel && (attrTerm == kRDFTerm_about) ) {
// This is the rdf:about attribute on a top level node. Set the XMP tree name if
// it doesn't have a name yet. Make sure this name matches the XMP tree name.
XMP_Assert ( xmpParent->parent == 0 ); // Must be the tree root node.
if ( xmpParent->name.empty() ) {
xmpParent->name = (*currAttr)->value;
} else if ( ! (*currAttr)->value.empty() ) {
if ( xmpParent->name != (*currAttr)->value ) XMP_Throw ( "Mismatched top level rdf:about values", kXMPErr_BadXMP );
}
}
break;
case kRDFTerm_Other :
AddChildNode ( xmpParent, **currAttr, (*currAttr)->value.c_str(), isTopLevel );
break;
default :
XMP_Throw ( "Invalid nodeElement attribute", kXMPErr_BadRDF );
break;
}
}
} // RDF_NodeElementAttrs
// =================================================================================================
// RDF_PropertyElementList
// =======================
//
// 7.2.13 propertyEltList
// ws* ( propertyElt ws* )*
static void
RDF_PropertyElementList ( XMP_Node * xmpParent, const XML_Node & xmlParent, bool isTopLevel )
{
XML_cNodePos currChild = xmlParent.content.begin();
XML_cNodePos endChild = xmlParent.content.end();
for ( ; currChild != endChild; ++currChild ) {
if ( (*currChild)->IsWhitespaceNode() ) continue;
if ( (*currChild)->kind != kElemNode ) {
XMP_Throw ( "Expected property element node not found", kXMPErr_BadRDF );
}
RDF_PropertyElement ( xmpParent, **currChild, isTopLevel );
}
} // RDF_PropertyElementList
// =================================================================================================
// RDF_PropertyElement
// ===================
//
// 7.2.14 propertyElt
// resourcePropertyElt | literalPropertyElt | parseTypeLiteralPropertyElt |
// parseTypeResourcePropertyElt | parseTypeCollectionPropertyElt | parseTypeOtherPropertyElt | emptyPropertyElt
//
// 7.2.15 resourcePropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
// ws* nodeElement ws*
// end-element()
//
// 7.2.16 literalPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
// text()
// end-element()
//
// 7.2.17 parseTypeLiteralPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
// literal
// end-element()
//
// 7.2.18 parseTypeResourcePropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
// propertyEltList
// end-element()
//
// 7.2.19 parseTypeCollectionPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
// nodeElementList
// end-element()
//
// 7.2.20 parseTypeOtherPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
// propertyEltList
// end-element()
//
// 7.2.21 emptyPropertyElt
// start-element ( URI == propertyElementURIs,
// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
// end-element()
//
// The various property element forms are not distinguished by the XML element name, but by their
// attributes for the most part. The exceptions are resourcePropertyElt and literalPropertyElt. They
// are distinguished by their XML element content.
//
// NOTE: The RDF syntax does not explicitly include the xml:lang attribute although it can appear in
-// many of these. We have to allow for it in the attibute counts below.
+// many of these. We have to allow for it in the attribute counts below.
static void
RDF_PropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
RDFTermKind nodeTerm = GetRDFTermKind ( xmlNode.name );
if ( ! IsPropertyElementName ( nodeTerm ) ) XMP_Throw ( "Invalid property element name", kXMPErr_BadRDF );
if ( xmlNode.attrs.size() > 3 ) {
// Only an emptyPropertyElt can have more than 3 attributes.
RDF_EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else {
// Look through the attributes for one that isn't rdf:ID or xml:lang, it will usually tell
// what we should be dealing with. The called routines must verify their specific syntax!
XML_cNodePos currAttr = xmlNode.attrs.begin();
XML_cNodePos endAttr = xmlNode.attrs.end();
XMP_VarString * attrName = 0;
for ( ; currAttr != endAttr; ++currAttr ) {
attrName = &((*currAttr)->name);
if ( (*attrName != "xml:lang") && (*attrName != "rdf:ID") ) break;
}
if ( currAttr != endAttr ) {
XMP_Assert ( attrName != 0 );
XMP_VarString& attrValue = (*currAttr)->value;
if ( *attrName == "rdf:datatype" ) {
RDF_LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else if ( *attrName != "rdf:parseType" ) {
RDF_EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else if ( attrValue == "Literal" ) {
RDF_ParseTypeLiteralPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else if ( attrValue == "Resource" ) {
RDF_ParseTypeResourcePropertyElement ( xmpParent, xmlNode, isTopLevel );
} else if ( attrValue == "Collection" ) {
RDF_ParseTypeCollectionPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else {
RDF_ParseTypeOtherPropertyElement ( xmpParent, xmlNode, isTopLevel );
}
} else {
// Only rdf:ID and xml:lang, could be a resourcePropertyElt, a literalPropertyElt, or an.
// emptyPropertyElt. Look at the child XML nodes to decide which.
if ( xmlNode.content.empty() ) {
RDF_EmptyPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else {
XML_cNodePos currChild = xmlNode.content.begin();
XML_cNodePos endChild = xmlNode.content.end();
for ( ; currChild != endChild; ++currChild ) {
if ( (*currChild)->kind != kCDataNode ) break;
}
if ( currChild == endChild ) {
RDF_LiteralPropertyElement ( xmpParent, xmlNode, isTopLevel );
} else {
RDF_ResourcePropertyElement ( xmpParent, xmlNode, isTopLevel );
}
}
}
}
} // RDF_PropertyElement
// =================================================================================================
// RDF_ResourcePropertyElement
// ===========================
//
// 7.2.15 resourcePropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr? ) )
// ws* nodeElement ws*
// end-element()
//
// This handles structs using an rdf:Description node, arrays using rdf:Bag/Seq/Alt, and typedNodes.
// It also catches and cleans up qualified properties written with rdf:Description and rdf:value.
static void
RDF_ResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
if ( isTopLevel && (xmlNode.name == "iX:changes") ) return; // Strip old "punchcard" chaff.
XMP_Node * newCompound = AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
XML_cNodePos currAttr = xmlNode.attrs.begin();
XML_cNodePos endAttr = xmlNode.attrs.end();
for ( ; currAttr != endAttr; ++currAttr ) {
XMP_VarString & attrName = (*currAttr)->name;
if ( attrName == "xml:lang" ) {
AddQualifierNode ( newCompound, **currAttr );
} else if ( attrName == "rdf:ID" ) {
continue; // Ignore all rdf:ID attributes.
} else {
XMP_Throw ( "Invalid attribute for resource property element", kXMPErr_BadRDF );
}
}
XML_cNodePos currChild = xmlNode.content.begin();
XML_cNodePos endChild = xmlNode.content.end();
for ( ; currChild != endChild; ++currChild ) {
if ( ! (*currChild)->IsWhitespaceNode() ) break;
}
if ( currChild == endChild ) XMP_Throw ( "Missing child of resource property element", kXMPErr_BadRDF );
if ( (*currChild)->kind != kElemNode ) XMP_Throw ( "Children of resource property element must be XML elements", kXMPErr_BadRDF );
if ( (*currChild)->name == "rdf:Bag" ) {
newCompound->options |= kXMP_PropValueIsArray;
} else if ( (*currChild)->name == "rdf:Seq" ) {
newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered;
} else if ( (*currChild)->name == "rdf:Alt" ) {
newCompound->options |= kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate;
} else {
newCompound->options |= kXMP_PropValueIsStruct;
if ( (*currChild)->name != "rdf:Description" ) {
XMP_VarString typeName ( (*currChild)->ns );
size_t colonPos = (*currChild)->name.find_first_of(':');
if ( colonPos == XMP_VarString::npos ) XMP_Throw ( "All XML elements must be in a namespace", kXMPErr_BadXMP );
typeName.append ( (*currChild)->name, colonPos, XMP_VarString::npos );
AddQualifierNode ( newCompound, XMP_VarString("rdf:type"), typeName );
}
}
RDF_NodeElement ( newCompound, **currChild, kNotTopLevel );
if ( newCompound->options & kRDF_HasValueElem ) {
FixupQualifiedNode ( newCompound );
} else if ( newCompound->options & kXMP_PropArrayIsAlternate ) {
DetectAltText ( newCompound );
}
for ( ++currChild; currChild != endChild; ++currChild ) {
if ( ! (*currChild)->IsWhitespaceNode() ) XMP_Throw ( "Invalid child of resource property element", kXMPErr_BadRDF );
}
} // RDF_ResourcePropertyElement
// =================================================================================================
// RDF_LiteralPropertyElement
// ==========================
//
// 7.2.16 literalPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, datatypeAttr?) )
// text()
// end-element()
//
// Add a leaf node with the text value and qualifiers for the attributes.
static void
RDF_LiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
XMP_Node * newChild = AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
XML_cNodePos currAttr = xmlNode.attrs.begin();
XML_cNodePos endAttr = xmlNode.attrs.end();
for ( ; currAttr != endAttr; ++currAttr ) {
XMP_VarString & attrName = (*currAttr)->name;
if ( attrName == "xml:lang" ) {
AddQualifierNode ( newChild, **currAttr );
} else if ( (attrName == "rdf:ID") || (attrName == "rdf:datatype") ) {
continue; // Ignore all rdf:ID and rdf:datatype attributes.
} else {
XMP_Throw ( "Invalid attribute for literal property element", kXMPErr_BadRDF );
}
}
XML_cNodePos currChild = xmlNode.content.begin();
XML_cNodePos endChild = xmlNode.content.end();
size_t textSize = 0;
for ( ; currChild != endChild; ++currChild ) {
if ( (*currChild)->kind != kCDataNode ) XMP_Throw ( "Invalid child of literal property element", kXMPErr_BadRDF );
textSize += (*currChild)->value.size();
}
newChild->value.reserve ( textSize );
for ( currChild = xmlNode.content.begin(); currChild != endChild; ++currChild ) {
newChild->value += (*currChild)->value;
}
#if 0 // *** XMP_DebugBuild
newChild->_valuePtr = newChild->value.c_str();
#endif
} // RDF_LiteralPropertyElement
// =================================================================================================
// RDF_ParseTypeLiteralPropertyElement
// ===================================
//
// 7.2.17 parseTypeLiteralPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseLiteral ) )
// literal
// end-element()
static void
RDF_ParseTypeLiteralPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel);
XMP_Throw ( "ParseTypeLiteral property element not allowed", kXMPErr_BadXMP );
} // RDF_ParseTypeLiteralPropertyElement
// =================================================================================================
// RDF_ParseTypeResourcePropertyElement
// ====================================
//
// 7.2.18 parseTypeResourcePropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseResource ) )
// propertyEltList
// end-element()
//
// Add a new struct node with a qualifier for the possible rdf:ID attribute. Then process the XML
// child nodes to get the struct fields.
static void
RDF_ParseTypeResourcePropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
XMP_Node * newStruct = AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
newStruct->options |= kXMP_PropValueIsStruct;
XML_cNodePos currAttr = xmlNode.attrs.begin();
XML_cNodePos endAttr = xmlNode.attrs.end();
for ( ; currAttr != endAttr; ++currAttr ) {
XMP_VarString & attrName = (*currAttr)->name;
if ( attrName == "rdf:parseType" ) {
continue; // ! The caller ensured the value is "Resource".
} else if ( attrName == "xml:lang" ) {
AddQualifierNode ( newStruct, **currAttr );
} else if ( attrName == "rdf:ID" ) {
continue; // Ignore all rdf:ID attributes.
} else {
XMP_Throw ( "Invalid attribute for ParseTypeResource property element", kXMPErr_BadRDF );
}
}
RDF_PropertyElementList ( newStruct, xmlNode, kNotTopLevel );
if ( newStruct->options & kRDF_HasValueElem ) FixupQualifiedNode ( newStruct );
// *** Need to look for arrays using rdf:Description and rdf:type.
} // RDF_ParseTypeResourcePropertyElement
// =================================================================================================
// RDF_ParseTypeCollectionPropertyElement
// ======================================
//
// 7.2.19 parseTypeCollectionPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseCollection ) )
// nodeElementList
// end-element()
static void
RDF_ParseTypeCollectionPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel);
XMP_Throw ( "ParseTypeCollection property element not allowed", kXMPErr_BadXMP );
} // RDF_ParseTypeCollectionPropertyElement
// =================================================================================================
// RDF_ParseTypeOtherPropertyElement
// =================================
//
// 7.2.20 parseTypeOtherPropertyElt
// start-element ( URI == propertyElementURIs, attributes == set ( idAttr?, parseOther ) )
// propertyEltList
// end-element()
static void
RDF_ParseTypeOtherPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
IgnoreParam(xmpParent); IgnoreParam(xmlNode); IgnoreParam(isTopLevel);
XMP_Throw ( "ParseTypeOther property element not allowed", kXMPErr_BadXMP );
} // RDF_ParseTypeOtherPropertyElement
// =================================================================================================
// RDF_EmptyPropertyElement
// ========================
//
// 7.2.21 emptyPropertyElt
// start-element ( URI == propertyElementURIs,
// attributes == set ( idAttr?, ( resourceAttr | nodeIdAttr )?, propertyAttr* ) )
// end-element()
//
// <ns:Prop1/> <!-- a simple property with an empty value -->
// <ns:Prop2 rdf:resource="http://www.adobe.com/"/> <!-- a URI value -->
// <ns:Prop3 rdf:value="..." ns:Qual="..."/> <!-- a simple qualified property -->
// <ns:Prop4 ns:Field1="..." ns:Field2="..."/> <!-- a struct with simple fields -->
//
// An emptyPropertyElt is an element with no contained content, just a possibly empty set of
// attributes. An emptyPropertyElt can represent three special cases of simple XMP properties: a
// simple property with an empty value (ns:Prop1), a simple property whose value is a URI
// (ns:Prop2), or a simple property with simple qualifiers (ns:Prop3). An emptyPropertyElt can also
// represent an XMP struct whose fields are all simple and unqualified (ns:Prop4).
//
// It is an error to use both rdf:value and rdf:resource - that can lead to invalid RDF in the
// verbose form written using a literalPropertyElt.
//
// The XMP mapping for an emptyPropertyElt is a bit different from generic RDF, partly for
// design reasons and partly for historical reasons. The XMP mapping rules are:
// 1. If there is an rdf:value attribute then this is a simple property with a text value.
// All other attributes are qualifiers.
// 2. If there is an rdf:resource attribute then this is a simple property with a URI value.
// All other attributes are qualifiers.
// 3. If there are no attributes other than xml:lang, rdf:ID, or rdf:nodeID then this is a simple
// property with an empty value.
// 4. Otherwise this is a struct, the attributes other than xml:lang, rdf:ID, or rdf:nodeID are fields.
static void
RDF_EmptyPropertyElement ( XMP_Node * xmpParent, const XML_Node & xmlNode, bool isTopLevel )
{
bool hasPropertyAttrs = false;
bool hasResourceAttr = false;
bool hasNodeIDAttr = false;
bool hasValueAttr = false;
const XML_Node * valueNode = 0; // ! Can come from rdf:value or rdf:resource.
if ( ! xmlNode.content.empty() ) XMP_Throw ( "Nested content not allowed with rdf:resource or property attributes", kXMPErr_BadRDF );
// First figure out what XMP this maps to and remember the XML node for a simple value.
XML_cNodePos currAttr = xmlNode.attrs.begin();
XML_cNodePos endAttr = xmlNode.attrs.end();
for ( ; currAttr != endAttr; ++currAttr ) {
RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name );
switch ( attrTerm ) {
case kRDFTerm_ID :
// Nothing to do.
break;
case kRDFTerm_resource :
if ( hasNodeIDAttr ) XMP_Throw ( "Empty property element can't have both rdf:resource and rdf:nodeID", kXMPErr_BadRDF );
if ( hasValueAttr ) XMP_Throw ( "Empty property element can't have both rdf:value and rdf:resource", kXMPErr_BadXMP );
hasResourceAttr = true;
if ( ! hasValueAttr ) valueNode = *currAttr;
break;
case kRDFTerm_nodeID :
if ( hasResourceAttr ) XMP_Throw ( "Empty property element can't have both rdf:resource and rdf:nodeID", kXMPErr_BadRDF );
hasNodeIDAttr = true;
break;
case kRDFTerm_Other :
if ( (*currAttr)->name == "rdf:value" ) {
if ( hasResourceAttr ) XMP_Throw ( "Empty property element can't have both rdf:value and rdf:resource", kXMPErr_BadXMP );
hasValueAttr = true;
valueNode = *currAttr;
} else if ( (*currAttr)->name != "xml:lang" ) {
hasPropertyAttrs = true;
}
break;
default :
XMP_Throw ( "Unrecognized attribute of empty property element", kXMPErr_BadRDF );
break;
}
}
// Create the right kind of child node and visit the attributes again to add the fields or qualifiers.
// ! Because of implementation vagaries, the xmpParent is the tree root for top level properties.
// ! The schema is found, created if necessary, by AddChildNode.
XMP_Node * childNode = AddChildNode ( xmpParent, xmlNode, "", isTopLevel );
bool childIsStruct = false;
if ( hasValueAttr | hasResourceAttr ) {
childNode->value = valueNode->value;
if ( ! hasValueAttr ) childNode->options |= kXMP_PropValueIsURI; // ! Might have both rdf:value and rdf:resource.
} else if ( hasPropertyAttrs ) {
childNode->options |= kXMP_PropValueIsStruct;
childIsStruct = true;
}
currAttr = xmlNode.attrs.begin();
endAttr = xmlNode.attrs.end();
for ( ; currAttr != endAttr; ++currAttr ) {
if ( *currAttr == valueNode ) continue; // Skip the rdf:value or rdf:resource attribute holding the value.
RDFTermKind attrTerm = GetRDFTermKind ( (*currAttr)->name );
switch ( attrTerm ) {
case kRDFTerm_ID :
case kRDFTerm_nodeID :
break; // Ignore all rdf:ID and rdf:nodeID attributes.w
case kRDFTerm_resource :
AddQualifierNode ( childNode, **currAttr );
break;
case kRDFTerm_Other :
if ( (! childIsStruct) || (*currAttr)->name == "xml:lang" ) {
AddQualifierNode ( childNode, **currAttr );
} else {
AddChildNode ( childNode, **currAttr, (*currAttr)->value.c_str(), false );
}
break;
default :
XMP_Throw ( "Unrecognized attribute of empty property element", kXMPErr_BadRDF );
break;
}
}
} // RDF_EmptyPropertyElement
} // namespace DngXmpSdk
// =================================================================================================
diff --git a/core/libs/dngwriter/extra/xmp_sdk/common/UnicodeConversions.cpp b/core/libs/dngwriter/extra/xmp_sdk/common/UnicodeConversions.cpp
index bebf6f07ea..5013fd2d71 100644
--- a/core/libs/dngwriter/extra/xmp_sdk/common/UnicodeConversions.cpp
+++ b/core/libs/dngwriter/extra/xmp_sdk/common/UnicodeConversions.cpp
@@ -1,1667 +1,1667 @@
// =================================================================================================
// Copyright 2004-2007 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
#include "XMP_Const.h"
#if UnicodeTestBuild
#include <cassert>
#include <stdexcept>
#define UC_Assert assert
#define UC_Throw(m,k) throw std::logic_error ( m )
#else
#define UC_Assert(cond) /* Nothing for now, should be XMP_Assert. */
#define UC_Throw(msg,id) throw XMP_Error ( id, msg )
#endif
#include "UnicodeConversions.hpp"
using namespace std;
namespace DngXmpSdk {
// =================================================================================================
// *** Look into using asm inlines, e.g. count-leading bits for multi-byte UTF-8.
CodePoint_to_UTF16_Proc CodePoint_to_UTF16BE = 0;
CodePoint_to_UTF16_Proc CodePoint_to_UTF16LE = 0;
CodePoint_from_UTF16_Proc CodePoint_from_UTF16BE = 0;
CodePoint_from_UTF16_Proc CodePoint_from_UTF16LE = 0;
UTF8_to_UTF16_Proc UTF8_to_UTF16BE = 0;
UTF8_to_UTF16_Proc UTF8_to_UTF16LE = 0;
UTF8_to_UTF32_Proc UTF8_to_UTF32BE = 0;
UTF8_to_UTF32_Proc UTF8_to_UTF32LE = 0;
UTF16_to_UTF8_Proc UTF16BE_to_UTF8 = 0;
UTF16_to_UTF8_Proc UTF16LE_to_UTF8 = 0;
UTF32_to_UTF8_Proc UTF32BE_to_UTF8 = 0;
UTF32_to_UTF8_Proc UTF32LE_to_UTF8 = 0;
UTF8_to_UTF16_Proc UTF8_to_UTF16Native = 0;
UTF8_to_UTF32_Proc UTF8_to_UTF32Native = 0;
UTF16_to_UTF8_Proc UTF16Native_to_UTF8 = 0;
UTF32_to_UTF8_Proc UTF32Native_to_UTF8 = 0;
UTF16_to_UTF32_Proc UTF16BE_to_UTF32BE = 0;
UTF16_to_UTF32_Proc UTF16BE_to_UTF32LE = 0;
UTF16_to_UTF32_Proc UTF16LE_to_UTF32BE = 0;
UTF16_to_UTF32_Proc UTF16LE_to_UTF32LE = 0;
UTF32_to_UTF16_Proc UTF32BE_to_UTF16BE = 0;
UTF32_to_UTF16_Proc UTF32BE_to_UTF16LE = 0;
UTF32_to_UTF16_Proc UTF32LE_to_UTF16BE = 0;
UTF32_to_UTF16_Proc UTF32LE_to_UTF16LE = 0;
// -------------------------------------------------------------------------------------------------
static size_t swap32to16Offset = 0; // Offset to "convert" a swapped UTF32 pointer into a swapped UTF16 pointer.
// -------------------------------------------------------------------------------------------------
static void CodePoint_to_UTF16Nat ( const UTF32Unit cpIn, UTF16Unit * utf16Out, const size_t utf16Len, size_t * utf16Written );
static void CodePoint_to_UTF16Swp ( const UTF32Unit cpIn, UTF16Unit * utf16Out, const size_t utf16Len, size_t * utf16Written );
static void CodePoint_from_UTF16Nat ( const UTF16Unit * utf16In, const size_t utf16Len, UTF32Unit * cpOut, size_t * utf16Read );
static void CodePoint_from_UTF16Swp ( const UTF16Unit * utf16In, const size_t utf16Len, UTF32Unit * cpOut, size_t * utf16Read );
// -------------------------------------------------------------------------------------------------
static void UTF8_to_UTF16Nat ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf8Read, size_t * utf16Written );
static void UTF8_to_UTF16Swp ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf8Read, size_t * utf16Written );
static void UTF8_to_UTF32Nat ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf8Read, size_t * utf32Written );
static void UTF8_to_UTF32Swp ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf8Read, size_t * utf32Written );
// -------------------------------------------------------------------------------------------------
static void UTF16Nat_to_UTF8 ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf16Read, size_t * utf8Written );
static void UTF16Swp_to_UTF8 ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf16Read, size_t * utf8Written );
static void UTF32Nat_to_UTF8 ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf32Read, size_t * utf8Written );
static void UTF32Swp_to_UTF8 ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf32Read, size_t * utf8Written );
// -------------------------------------------------------------------------------------------------
static void UTF16Nat_to_UTF32Nat ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written );
static void UTF16Nat_to_UTF32Swp ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written );
static void UTF16Swp_to_UTF32Nat ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written );
static void UTF16Swp_to_UTF32Swp ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written );
// -------------------------------------------------------------------------------------------------
static void UTF32Nat_to_UTF16Nat ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written );
static void UTF32Nat_to_UTF16Swp ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written );
static void UTF32Swp_to_UTF16Nat ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written );
static void UTF32Swp_to_UTF16Swp ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written );
// =================================================================================================
void InitializeUnicodeConversions()
{
UC_Assert ( (sizeof(UTF8Unit) == 1) && (sizeof(UTF16Unit) == 2) && (sizeof(UTF32Unit) == 4) );
UTF16Unit u16 = 0x00FF;
bool bigEndian = (*((UTF8Unit*)&u16) == 0);
UTF8_to_UTF16Native = UTF8_to_UTF16Nat;
UTF8_to_UTF32Native = UTF8_to_UTF32Nat;
UTF16Native_to_UTF8 = UTF16Nat_to_UTF8;
UTF32Native_to_UTF8 = UTF32Nat_to_UTF8;
if ( bigEndian ) {
swap32to16Offset = 0;
CodePoint_to_UTF16BE = CodePoint_to_UTF16Nat;
CodePoint_to_UTF16LE = CodePoint_to_UTF16Swp;
CodePoint_from_UTF16BE = CodePoint_from_UTF16Nat;
CodePoint_from_UTF16LE = CodePoint_from_UTF16Swp;
UTF8_to_UTF16BE = UTF8_to_UTF16Nat;
UTF8_to_UTF16LE = UTF8_to_UTF16Swp;
UTF8_to_UTF32BE = UTF8_to_UTF32Nat;
UTF8_to_UTF32LE = UTF8_to_UTF32Swp;
UTF16BE_to_UTF8 = UTF16Nat_to_UTF8;
UTF16LE_to_UTF8 = UTF16Swp_to_UTF8;
UTF32BE_to_UTF8 = UTF32Nat_to_UTF8;
UTF32LE_to_UTF8 = UTF32Swp_to_UTF8;
UTF16BE_to_UTF32BE = UTF16Nat_to_UTF32Nat;
UTF16BE_to_UTF32LE = UTF16Nat_to_UTF32Swp;
UTF16LE_to_UTF32BE = UTF16Swp_to_UTF32Nat;
UTF16LE_to_UTF32LE = UTF16Swp_to_UTF32Swp;
UTF32BE_to_UTF16BE = UTF32Nat_to_UTF16Nat;
UTF32BE_to_UTF16LE = UTF32Nat_to_UTF16Swp;
UTF32LE_to_UTF16BE = UTF32Swp_to_UTF16Nat;
UTF32LE_to_UTF16LE = UTF32Swp_to_UTF16Swp;
} else {
swap32to16Offset = 1; // ! Offset in UTF16 units!
CodePoint_to_UTF16BE = CodePoint_to_UTF16Swp;
CodePoint_to_UTF16LE = CodePoint_to_UTF16Nat;
CodePoint_from_UTF16BE = CodePoint_from_UTF16Swp;
CodePoint_from_UTF16LE = CodePoint_from_UTF16Nat;
UTF8_to_UTF16BE = UTF8_to_UTF16Swp;
UTF8_to_UTF16LE = UTF8_to_UTF16Nat;
UTF8_to_UTF32BE = UTF8_to_UTF32Swp;
UTF8_to_UTF32LE = UTF8_to_UTF32Nat;
UTF16BE_to_UTF8 = UTF16Swp_to_UTF8;
UTF16LE_to_UTF8 = UTF16Nat_to_UTF8;
UTF32BE_to_UTF8 = UTF32Swp_to_UTF8;
UTF32LE_to_UTF8 = UTF32Nat_to_UTF8;
UTF16BE_to_UTF32BE = UTF16Swp_to_UTF32Swp;
UTF16BE_to_UTF32LE = UTF16Swp_to_UTF32Nat;
UTF16LE_to_UTF32BE = UTF16Nat_to_UTF32Swp;
UTF16LE_to_UTF32LE = UTF16Nat_to_UTF32Nat;
UTF32BE_to_UTF16BE = UTF32Swp_to_UTF16Swp;
UTF32BE_to_UTF16LE = UTF32Swp_to_UTF16Nat;
UTF32LE_to_UTF16BE = UTF32Nat_to_UTF16Swp;
UTF32LE_to_UTF16LE = UTF32Nat_to_UTF16Nat;
}
} // InitializeUnicodeConversions
// =================================================================================================
#if XMP_MacBuild && __MWERKS__
#define UTF16InSwap(inPtr) UTF16Unit ( __lhbrx ( (void*)(inPtr), 0 ) )
#define UTF32InSwap(inPtr) UTF32Unit ( __lwbrx ( (void*)(inPtr), 0 ) )
#define UTF16OutSwap(outPtr,value) __sthbrx ( value, (void*)(outPtr), 0 )
#define UTF32OutSwap(outPtr,value) __stwbrx ( value, (void*)(outPtr), 0 )
#else
static inline UTF16Unit UTF16InSwap ( const UTF16Unit * inPtr )
{
UTF16Unit inUnit = *inPtr;
return (inUnit << 8) | (inUnit >> 8);
}
static inline UTF32Unit UTF32InSwap ( const UTF32Unit * inPtr )
{
UTF32Unit inUnit = *inPtr;
return (inUnit << 24) | ((inUnit << 8) & 0x00FF0000) | ((inUnit >> 8) & 0x0000FF00) | (inUnit >> 24);
}
static inline void UTF16OutSwap ( UTF16Unit * outPtr, const UTF16Unit value )
{
UTF16Unit outUnit = (value << 8) | (value >> 8);
*outPtr = outUnit;
}
static inline void UTF32OutSwap ( UTF32Unit * outPtr, const UTF32Unit value )
{
UTF32Unit outUnit = (value << 24) | ((value << 8) & 0x00FF0000) | ((value >> 8) & 0x0000FF00) | (value >> 24);
*outPtr = outUnit;
}
#endif
// =================================================================================================
void SwapUTF16 ( const UTF16Unit * utf16In, UTF16Unit * utf16Out, const size_t utf16Len )
{
for ( size_t i = 0; i < utf16Len; ++i ) utf16Out[i] = UTF16InSwap(utf16In+i);
}
void SwapUTF32 ( const UTF32Unit * utf32In, UTF32Unit * utf32Out, const size_t utf32Len ) {
for ( size_t i = 0; i < utf32Len; ++i ) utf32Out[i] = UTF32InSwap(utf32In+i);
}
// =================================================================================================
extern void ToUTF16 ( const UTF8Unit * utf8In, size_t utf8Len, std::string * utf16Str, bool bigEndian )
{
UTF8_to_UTF16_Proc Converter = UTF8_to_UTF16LE;
if ( bigEndian ) Converter = UTF8_to_UTF16BE;
enum { kBufferSize = 8*1024 };
UTF16Unit u16Buffer[kBufferSize]; // 16K bytes
size_t readCount, writeCount;
utf16Str->erase();
utf16Str->reserve ( 2*utf8Len ); // As good a guess as any.
while ( utf8Len > 0 ) {
Converter ( utf8In, utf8Len, u16Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf16Str->append ( (const char *)u16Buffer, writeCount*2 );
utf8In += readCount;
utf8Len -= readCount;
}
} // ToUTF16
// =================================================================================================
extern void ToUTF16Native ( const UTF8Unit * utf8In, size_t utf8Len, std::string * utf16Str )
{
enum { kBufferSize = 8*1024 };
UTF16Unit u16Buffer[kBufferSize]; // 16K bytes
size_t readCount, writeCount;
utf16Str->erase();
utf16Str->reserve ( 2*utf8Len ); // As good a guess as any.
while ( utf8Len > 0 ) {
UTF8_to_UTF16Nat ( utf8In, utf8Len, u16Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf16Str->append ( (const char *)u16Buffer, writeCount*2 );
utf8In += readCount;
utf8Len -= readCount;
}
} // ToUTF16Native
// =================================================================================================
extern void ToUTF32 ( const UTF8Unit * utf8In, size_t utf8Len, std::string * utf32Str, bool bigEndian )
{
UTF8_to_UTF32_Proc Converter = UTF8_to_UTF32LE;
if ( bigEndian ) Converter = UTF8_to_UTF32BE;
enum { kBufferSize = 4*1024 };
UTF32Unit u32Buffer[kBufferSize]; // 16K bytes
size_t readCount, writeCount;
utf32Str->erase();
utf32Str->reserve ( 4*utf8Len ); // As good a guess as any.
while ( utf8Len > 0 ) {
Converter ( utf8In, utf8Len, u32Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf32Str->append ( (const char *)u32Buffer, writeCount*4 );
utf8In += readCount;
utf8Len -= readCount;
}
} // ToUTF32
// =================================================================================================
extern void ToUTF32Native ( const UTF8Unit * utf8In, size_t utf8Len, std::string * utf32Str )
{
enum { kBufferSize = 4*1024 };
UTF32Unit u32Buffer[kBufferSize]; // 16K bytes
size_t readCount, writeCount;
utf32Str->erase();
utf32Str->reserve ( 4*utf8Len ); // As good a guess as any.
while ( utf8Len > 0 ) {
UTF8_to_UTF32Nat ( utf8In, utf8Len, u32Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf32Str->append ( (const char *)u32Buffer, writeCount*4 );
utf8In += readCount;
utf8Len -= readCount;
}
} // ToUTF32Native
// =================================================================================================
extern void FromUTF16 ( const UTF16Unit * utf16In, size_t utf16Len, std::string * utf8Str, bool bigEndian )
{
UTF16_to_UTF8_Proc Converter = UTF16LE_to_UTF8;
if ( bigEndian ) Converter = UTF16BE_to_UTF8;
enum { kBufferSize = 16*1024 };
UTF8Unit u8Buffer[kBufferSize];
size_t readCount, writeCount;
utf8Str->erase();
utf8Str->reserve ( 2*utf16Len ); // As good a guess as any.
while ( utf16Len > 0 ) {
Converter ( utf16In, utf16Len, u8Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf8Str->append ( (const char *)u8Buffer, writeCount );
utf16In += readCount;
utf16Len -= readCount;
}
} // FromUTF16
// =================================================================================================
extern void FromUTF16Native ( const UTF16Unit * utf16In, size_t utf16Len, std::string * utf8Str )
{
enum { kBufferSize = 16*1024 };
UTF8Unit u8Buffer[kBufferSize];
size_t readCount, writeCount;
utf8Str->erase();
utf8Str->reserve ( 2*utf16Len ); // As good a guess as any.
while ( utf16Len > 0 ) {
UTF16Nat_to_UTF8 ( utf16In, utf16Len, u8Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf8Str->append ( (const char *)u8Buffer, writeCount );
utf16In += readCount;
utf16Len -= readCount;
}
} // FromUTF16Native
// =================================================================================================
extern void FromUTF32 ( const UTF32Unit * utf32In, size_t utf32Len, std::string * utf8Str, bool bigEndian )
{
UTF32_to_UTF8_Proc Converter = UTF32LE_to_UTF8;
if ( bigEndian ) Converter = UTF32BE_to_UTF8;
enum { kBufferSize = 16*1024 };
UTF8Unit u8Buffer[kBufferSize];
size_t readCount, writeCount;
utf8Str->erase();
utf8Str->reserve ( 2*utf32Len ); // As good a guess as any.
while ( utf32Len > 0 ) {
Converter ( utf32In, utf32Len, u8Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf8Str->append ( (const char *)u8Buffer, writeCount );
utf32In += readCount;
utf32Len -= readCount;
}
} // FromUTF32
// =================================================================================================
extern void FromUTF32Native ( const UTF32Unit * utf32In, size_t utf32Len, std::string * utf8Str )
{
enum { kBufferSize = 16*1024 };
UTF8Unit u8Buffer[kBufferSize];
size_t readCount, writeCount;
utf8Str->erase();
utf8Str->reserve ( 2*utf32Len ); // As good a guess as any.
while ( utf32Len > 0 ) {
UTF32Nat_to_UTF8 ( utf32In, utf32Len, u8Buffer, kBufferSize, &readCount, &writeCount );
if ( writeCount == 0 ) UC_Throw ( "Incomplete Unicode at end of string", kXMPErr_BadXML );
utf8Str->append ( (const char *)u8Buffer, writeCount );
utf32In += readCount;
utf32Len -= readCount;
}
} // FromUTF32Native
// =================================================================================================
static void CodePoint_to_UTF8_Multi ( const UTF32Unit cpIn, UTF8Unit * utf8Out, const size_t utf8Len, size_t * utf8Written )
{
size_t unitCount = 0;
if ( cpIn > 0x10FFFF ) UC_Throw ( "Bad UTF-32 - out of range", kXMPErr_BadParam );
if ( (0xD800 <= cpIn) && (cpIn <= 0xDFFF) ) UC_Throw ( "Bad UTF-32 - surrogate code point", kXMPErr_BadParam );
// Compute the number of bytes using 6 data bits each. Then see if the highest order bits will
// fit into the leading byte. Write the UTF-8 sequence if there is enough room.
UTF32Unit temp, mask;
size_t bytesNeeded = 0;
for ( temp = cpIn; temp != 0; temp = temp >> 6 ) ++bytesNeeded;
temp = cpIn >> ((bytesNeeded-1)*6); // The highest order data bits.
mask = (0x80 >> bytesNeeded) - 1; // Available data bits in the leading byte.
if ( temp > mask ) ++bytesNeeded;
if ( bytesNeeded > utf8Len ) goto Done; // Not enough room for the output.
unitCount = bytesNeeded;
temp = cpIn;
for ( --bytesNeeded; bytesNeeded > 0; --bytesNeeded ) {
utf8Out[bytesNeeded] = 0x80 | UTF8Unit ( temp & 0x3F );
temp = temp >> 6;
}
mask = ~((1 << (8-unitCount)) - 1);
utf8Out[0] = UTF8Unit ( mask | temp );
Done:
*utf8Written = unitCount;
return;
} // CodePoint_to_UTF8_Multi
// =================================================================================================
void CodePoint_to_UTF8 ( const UTF32Unit cpIn, UTF8Unit * utf8Out, const size_t utf8Len, size_t * utf8Written )
{
size_t unitCount = 0;
UC_Assert ( (utf8Out != 0) && (utf8Written != 0) );
if ( utf8Len == 0 ) goto Done;
if ( cpIn > 0x7F ) goto MultiByte; // ! Force linear execution path for ASCII.
if ( utf8Len == 0 ) goto Done;
unitCount = 1;
*utf8Out = UTF8Unit(cpIn);
Done:
*utf8Written = unitCount;
return;
MultiByte:
CodePoint_to_UTF8_Multi( cpIn, utf8Out, utf8Len, utf8Written );
return;
} // CodePoint_to_UTF8
// =================================================================================================
static void CodePoint_from_UTF8_Multi ( const UTF8Unit * utf8In, const size_t utf8Len, UTF32Unit * cpOut, size_t * utf8Read )
{
UTF8Unit inUnit = *utf8In;
size_t unitCount = 0;
UTF32Unit cp; // ! Avoid gcc complaints about declarations after goto's.
const UTF8Unit * utf8Pos;
// -------------------------------------------------------------------------------------
// We've got a multibyte UTF-8 character. The first byte has the number of bytes and the
// highest order data bits. The other bytes each add 6 more data bits.
- #if 0 // This might be a more effcient way to count the bytes.
+ #if 0 // This might be a more efficient way to count the bytes.
static XMP_Uns8 kByteCounts[16] = { 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 3, 4 };
size_t bytesNeeded = kByteCounts [ inUnit >> 4 ];
if ( (bytesNeeded < 2) || ((bytesNeeded == 4) && ((inUnit & 0x08) != 0)) ) {
UC_Throw ( "Invalid UTF-8 sequence length", kXMPErr_BadParam );
}
#endif
size_t bytesNeeded = 0; // Count the leading 1 bits in the first byte.
for ( UTF8Unit temp = inUnit; temp > 0x7F; temp = temp << 1 ) ++bytesNeeded;
// *** Consider CPU-specific assembly inline, e.g. cntlzw on PowerPC.
if ( (bytesNeeded < 2) || (bytesNeeded > 4) ) UC_Throw ( "Invalid UTF-8 sequence length", kXMPErr_BadParam );
if ( bytesNeeded > utf8Len ) goto Done; // Not enough input in this buffer.
unitCount = bytesNeeded;
cp = inUnit & ((1 << (7-unitCount)) - 1); // Isolate the initial data bits in the bottom of cp.
utf8Pos = utf8In + 1; // We've absorbed the first byte.
for ( --bytesNeeded; bytesNeeded > 0; --bytesNeeded, ++utf8Pos ) {
inUnit = *utf8Pos;
if ( (inUnit & UTF8Unit(0xC0)) != UTF8Unit(0x80) ) UC_Throw ( "Invalid UTF-8 data byte", kXMPErr_BadParam );
cp = (cp << 6) | (inUnit & 0x3F);
}
if ( cp >= 0xD800 ) { // Skip the next comparisons most of the time.
if ( (0xD800 <= cp) && (cp <= 0xDFFF) ) UC_Throw ( "Bad UTF-8 - surrogate code point", kXMPErr_BadParam );
if ( cp > 0x10FFFF ) UC_Throw ( "Bad UTF-8 - out of range", kXMPErr_BadParam );
}
*cpOut = cp; // ! Don't put after Done, don't write if no input.
Done:
*utf8Read = unitCount;
return;
} // CodePoint_from_UTF8_Multi
// =================================================================================================
void CodePoint_from_UTF8 ( const UTF8Unit * utf8In, const size_t utf8Len, UTF32Unit * cpOut, size_t * utf8Read )
{
UTF8Unit inUnit; // ! Don't read until we know there is input.
size_t unitCount = 0;
UC_Assert ( (utf8In != 0) && (cpOut != 0) && (utf8Read != 0) );
if ( utf8Len == 0 ) goto Done;
inUnit = *utf8In;
if ( inUnit >= 0x80 ) goto MultiByte; // ! Force linear execution path for ASCII.
unitCount = 1;
*cpOut = inUnit; // ! Don't put after Done, don't write if no input.
Done:
*utf8Read = unitCount;
return;
MultiByte:
CodePoint_from_UTF8_Multi ( utf8In, utf8Len, cpOut, utf8Read );
return;
} // CodePoint_from_UTF8
// =================================================================================================
static void CodePoint_to_UTF16Nat_Surrogate ( const UTF32Unit cpIn, UTF16Unit * utf16Out, const size_t utf16Len, size_t * utf16Written )
{
size_t unitCount = 0;
UTF32Unit temp; // ! Avoid gcc complaints about declarations after goto's.
if ( cpIn > 0x10FFFF ) UC_Throw ( "Bad UTF-32 - out of range", kXMPErr_BadParam );
if ( utf16Len < 2 ) goto Done; // Not enough room for the output.
unitCount = 2;
temp = cpIn - 0x10000;
utf16Out[0] = 0xD800 | UTF16Unit ( temp >> 10 );
utf16Out[1] = 0xDC00 | UTF16Unit ( temp & 0x3FF );
Done:
*utf16Written = unitCount;
return;
} // CodePoint_to_UTF16Nat_Surrogate
// =================================================================================================
static void CodePoint_to_UTF16Nat ( const UTF32Unit cpIn, UTF16Unit * utf16Out, const size_t utf16Len, size_t * utf16Written )
{
size_t unitCount = 0;
UC_Assert ( (utf16Out != 0) && (utf16Written != 0) );
if ( utf16Len == 0 ) goto Done;
if ( cpIn >= 0xD800 ) goto CheckSurrogate; // ! Force linear execution path for the BMP.
InBMP:
unitCount = 1;
*utf16Out = UTF16Unit(cpIn);
Done:
*utf16Written = unitCount;
return;
CheckSurrogate:
if ( cpIn > 0xFFFF ) goto SurrogatePair;
if ( cpIn > 0xDFFF ) goto InBMP;
UC_Throw ( "Bad UTF-32 - surrogate code point", kXMPErr_BadParam );
SurrogatePair:
CodePoint_to_UTF16Nat_Surrogate ( cpIn, utf16Out, utf16Len, utf16Written );
return;
} // CodePoint_to_UTF16Nat
// =================================================================================================
static void CodePoint_from_UTF16Nat_Surrogate ( const UTF16Unit * utf16In, const size_t utf16Len, UTF32Unit * cpOut, size_t * utf16Read )
{
UTF16Unit hiUnit = *utf16In;
size_t unitCount = 0;
UTF16Unit loUnit; // ! Avoid gcc complaints about declarations after goto's.
UTF32Unit cp;
// ----------------------------------
// We've got a UTF-16 surrogate pair.
if ( hiUnit > 0xDBFF ) UC_Throw ( "Bad UTF-16 - leading low surrogate", kXMPErr_BadParam );
if ( utf16Len < 2 ) goto Done; // Not enough input in this buffer.
loUnit = *(utf16In+1);
if ( (loUnit < 0xDC00) || (0xDFFF < loUnit) ) UC_Throw ( "Bad UTF-16 - missing low surrogate", kXMPErr_BadParam );
unitCount = 2;
cp = (((hiUnit & 0x3FF) << 10) | (loUnit & 0x3FF)) + 0x10000;
*cpOut = cp; // ! Don't put after Done, don't write if no input.
Done:
*utf16Read = unitCount;
return;
} // CodePoint_from_UTF16Nat_Surrogate
// =================================================================================================
static void CodePoint_from_UTF16Nat ( const UTF16Unit * utf16In, const size_t utf16Len, UTF32Unit * cpOut, size_t * utf16Read )
{
UTF16Unit inUnit; // ! Don't read until we know there is input.
size_t unitCount = 0;
UC_Assert ( (utf16In != 0) && (cpOut != 0) && (utf16Read != 0) );
if ( utf16Len == 0 ) goto Done;
inUnit = *utf16In;
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) goto SurrogatePair; // ! Force linear execution path for the BMP.
unitCount = 1;
*cpOut = inUnit; // ! Don't put after Done, don't write if no input.
Done:
*utf16Read = unitCount;
return;
SurrogatePair:
CodePoint_from_UTF16Nat_Surrogate ( utf16In, utf16Len, cpOut, utf16Read );
return;
} // CodePoint_from_UTF16Nat
// =================================================================================================
static void UTF8_to_UTF16Nat ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf8Read, size_t * utf16Written )
{
const UTF8Unit * utf8Pos = utf8In;
UTF16Unit * utf16Pos = utf16Out;
size_t utf8Left = utf8Len;
size_t utf16Left = utf16Len;
UC_Assert ( (utf8In != 0) && (utf16Out != 0) && (utf8Read != 0) && (utf16Written != 0) );
while ( (utf8Left > 0) && (utf16Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf8Left;
if ( limit > utf16Left ) limit = utf16Left;
for ( i = 0; i < limit; ++i ) {
UTF8Unit inUnit = *utf8Pos;
if ( inUnit > 0x7F ) break;
*utf16Pos = inUnit;
++utf8Pos;
++utf16Pos;
}
utf8Left -= i;
utf16Left -= i;
// Do a run of non-ASCII, it copies multiple input units into 1 or 2 output units.
while ( (utf8Left > 0) && (utf16Left > 0) ) {
UTF32Unit cp;
size_t len8, len16;
UTF8Unit inUnit = *utf8Pos;
if ( inUnit <= 0x7F ) break;
CodePoint_from_UTF8_Multi ( utf8Pos, utf8Left, &cp, &len8 );
if ( len8 == 0 ) goto Done; // The input buffer ends in the middle of a character.
if ( cp <= 0xFFFF ) {
*utf16Pos = UTF16Unit(cp);
len16 = 1;
} else {
CodePoint_to_UTF16Nat_Surrogate ( cp, utf16Pos, utf16Left, &len16 );
if ( len16 == 0 ) goto Done; // Not enough room in the output buffer.
}
utf8Left -= len8;
utf8Pos += len8;
utf16Left -= len16;
utf16Pos += len16;
}
}
Done: // Set the output lengths.
*utf8Read = utf8Len - utf8Left;
*utf16Written = utf16Len - utf16Left;
} // UTF8_to_UTF16Nat
// =================================================================================================
static void UTF8_to_UTF32Nat ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf8Read, size_t * utf32Written )
{
const UTF8Unit * utf8Pos = utf8In;
UTF32Unit * utf32Pos = utf32Out;
size_t utf8Left = utf8Len;
size_t utf32Left = utf32Len;
UC_Assert ( (utf8In != 0) && (utf32Out != 0) && (utf8Read != 0) && (utf32Written != 0) );
while ( (utf8Left > 0) && (utf32Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf8Left;
if ( limit > utf32Left ) limit = utf32Left;
for ( i = 0; i < limit; ++i ) {
UTF8Unit inUnit = *utf8Pos;
if ( inUnit > 0x7F ) break;
*utf32Pos = inUnit;
++utf8Pos;
++utf32Pos;
}
utf8Left -= i;
utf32Left -= i;
// Do a run of non-ASCII, it copies variable input into 1 output unit.
while ( (utf8Left > 0) && (utf32Left > 0) ) {
size_t len;
UTF8Unit inUnit = *utf8Pos;
if ( inUnit <= 0x7F ) break;
CodePoint_from_UTF8_Multi ( utf8Pos, utf8Left, utf32Pos, &len );
if ( len == 0 ) goto Done; // The input buffer ends in the middle of a character.
utf8Left -= len;
utf8Pos += len;
utf32Left -= 1;
utf32Pos += 1;
}
}
Done: // Set the output lengths.
*utf8Read = utf8Len - utf8Left;
*utf32Written = utf32Len - utf32Left;
} // UTF8_to_UTF32Nat
// =================================================================================================
static void UTF16Nat_to_UTF8 ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf16Read, size_t * utf8Written )
{
const UTF16Unit * utf16Pos = utf16In;
UTF8Unit * utf8Pos = utf8Out;
size_t utf16Left = utf16Len;
size_t utf8Left = utf8Len;
UC_Assert ( (utf16In != 0) && (utf8Out != 0) && (utf16Read != 0) && (utf8Written != 0) );
while ( (utf16Left > 0) && (utf8Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf16Left;
if ( limit > utf8Left ) limit = utf8Left;
for ( i = 0; i < limit; ++i ) {
UTF16Unit inUnit = *utf16Pos;
if ( inUnit > 0x7F ) break;
*utf8Pos = UTF8Unit(inUnit);
++utf16Pos;
++utf8Pos;
}
utf16Left -= i;
utf8Left -= i;
// Do a run of non-ASCII inside the BMP, it copies 1 input unit into multiple output units.
while ( (utf16Left > 0) && (utf8Left > 0) ) {
size_t len8;
UTF16Unit inUnit = *utf16Pos;
if ( inUnit <= 0x7F ) break;
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) break;
CodePoint_to_UTF8_Multi ( inUnit, utf8Pos, utf8Left, &len8 );
if ( len8 == 0 ) goto Done; // Not enough room in the output buffer.
utf16Left -= 1;
utf16Pos += 1;
utf8Left -= len8;
utf8Pos += len8;
}
// Do a run of surrogate pairs, it copies 2 input units into multiple output units.
while ( (utf16Left > 0) && (utf8Left > 0) ) {
UTF32Unit cp;
size_t len16, len8;
UTF16Unit inUnit = *utf16Pos;
if ( (inUnit < 0xD800) || (0xDFFF < inUnit) ) break;
CodePoint_from_UTF16Nat_Surrogate ( utf16Pos, utf16Left, &cp, &len16 );
if ( len16 == 0 ) goto Done; // The input buffer ends in the middle of a surrogate pair.
UC_Assert ( len16 == 2 );
CodePoint_to_UTF8_Multi ( cp, utf8Pos, utf8Left, &len8 );
if ( len8 == 0 ) goto Done; // Not enough room in the output buffer.
utf16Left -= len16;
utf16Pos += len16;
utf8Left -= len8;
utf8Pos += len8;
}
}
Done: // Set the output lengths.
*utf16Read = utf16Len - utf16Left;
*utf8Written = utf8Len - utf8Left;
} // UTF16Nat_to_UTF8
// =================================================================================================
static void UTF32Nat_to_UTF8 ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf32Read, size_t * utf8Written )
{
const UTF32Unit * utf32Pos = utf32In;
UTF8Unit * utf8Pos = utf8Out;
size_t utf32Left = utf32Len;
size_t utf8Left = utf8Len;
UC_Assert ( (utf32In != 0) && (utf8Out != 0) && (utf32Read != 0) && (utf8Written != 0) );
while ( (utf32Left > 0) && (utf8Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf32Left;
if ( limit > utf8Left ) limit = utf8Left;
for ( i = 0; i < limit; ++i ) {
UTF32Unit inUnit = *utf32Pos;
if ( inUnit > 0x7F ) break;
*utf8Pos = UTF8Unit(inUnit);
++utf32Pos;
++utf8Pos;
}
utf32Left -= i;
utf8Left -= i;
// Do a run of non-ASCII, it copies 1 input unit into multiple output units.
while ( (utf32Left > 0) && (utf8Left > 0) ) {
size_t len;
UTF32Unit inUnit = *utf32Pos;
if ( inUnit <= 0x7F ) break;
CodePoint_to_UTF8_Multi ( inUnit, utf8Pos, utf8Left, &len );
if ( len == 0 ) goto Done; // Not enough room in the output buffer.
utf32Left -= 1;
utf32Pos += 1;
utf8Left -= len;
utf8Pos += len;
}
}
Done: // Set the output lengths.
*utf32Read = utf32Len - utf32Left;
*utf8Written = utf8Len - utf8Left;
} // UTF32Nat_to_UTF8
// =================================================================================================
static void UTF16Nat_to_UTF32Nat ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written )
{
const UTF16Unit * utf16Pos = utf16In;
UTF32Unit * utf32Pos = utf32Out;
size_t utf16Left = utf16Len;
size_t utf32Left = utf32Len;
UC_Assert ( (utf16In != 0) && (utf32Out != 0) && (utf16Read != 0) && (utf32Written != 0) );
while ( (utf16Left > 0) && (utf32Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf16Left;
if ( limit > utf32Left ) limit = utf32Left;
for ( i = 0; i < limit; ++i ) {
UTF16Unit inUnit = *utf16Pos;
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) break;
*utf32Pos = inUnit;
++utf16Pos;
++utf32Pos;
}
utf16Left -= i;
utf32Left -= i;
// Do a run of surrogate pairs, it copies 2 input units into 1 output unit.
while ( (utf16Left > 0) && (utf32Left > 0) ) {
size_t len;
UTF16Unit inUnit = *utf16Pos;
if ( (inUnit < 0xD800) || (0xDFFF < inUnit) ) break;
CodePoint_from_UTF16Nat_Surrogate ( utf16Pos, utf16Left, utf32Pos, &len );
if ( len == 0 ) goto Done; // The input buffer ends in the middle of a surrogate pair.
UC_Assert ( len == 2 );
utf16Left -= len;
utf16Pos += len;
utf32Left -= 1;
utf32Pos += 1;
}
}
Done: // Set the output lengths.
*utf16Read = utf16Len - utf16Left;
*utf32Written = utf32Len - utf32Left;
} // UTF16Nat_to_UTF32Nat
// =================================================================================================
static void UTF32Nat_to_UTF16Nat ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written )
{
const UTF32Unit * utf32Pos = utf32In;
UTF16Unit * utf16Pos = utf16Out;
size_t utf32Left = utf32Len;
size_t utf16Left = utf16Len;
UC_Assert ( (utf32In != 0) && (utf16Out != 0) && (utf32Read != 0) && (utf16Written != 0) );
while ( (utf32Left > 0) && (utf16Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf32Left;
if ( limit > utf16Left ) limit = utf16Left;
for ( i = 0; i < limit; ++i ) {
UTF32Unit inUnit = *utf32Pos;
if ( inUnit > 0xFFFF ) break;
*utf16Pos = UTF16Unit(inUnit);
++utf32Pos;
++utf16Pos;
}
utf32Left -= i;
utf16Left -= i;
// Do a run of non-BMP, it copies 1 input unit into 2 output units.
while ( (utf32Left > 0) && (utf16Left > 0) ) {
size_t len;
UTF32Unit inUnit = *utf32Pos;
if ( inUnit <= 0xFFFF ) break;
CodePoint_to_UTF16Nat_Surrogate ( inUnit, utf16Pos, utf16Left, &len );
if ( len == 0 ) goto Done; // Not enough room in the output buffer.
UC_Assert ( len == 2 );
utf32Left -= 1;
utf32Pos += 1;
utf16Left -= 2;
utf16Pos += 2;
}
}
Done: // Set the output lengths.
*utf32Read = utf32Len - utf32Left;
*utf16Written = utf16Len - utf16Left;
} // UTF32Nat_to_UTF16Nat
// =================================================================================================
static void CodePoint_to_UTF16Swp_Surrogate ( const UTF32Unit cpIn, UTF16Unit * utf16Out, const size_t utf16Len, size_t * utf16Written )
{
size_t unitCount = 0;
UTF32Unit temp; // ! Avoid gcc complaints about declarations after goto's.
if ( cpIn > 0x10FFFF ) UC_Throw ( "Bad UTF-32 - out of range", kXMPErr_BadParam );
if ( utf16Len < 2 ) goto Done; // Not enough room for the output.
unitCount = 2;
temp = cpIn - 0x10000;
UTF16OutSwap ( &utf16Out[0], (0xD800 | UTF16Unit ( temp >> 10 )) );
UTF16OutSwap ( &utf16Out[1], (0xDC00 | UTF16Unit ( temp & 0x3FF)) );
Done:
*utf16Written = unitCount;
return;
} // CodePoint_to_UTF16Swp_Surrogate
// =================================================================================================
static void CodePoint_to_UTF16Swp ( const UTF32Unit cpIn, UTF16Unit * utf16Out, const size_t utf16Len, size_t * utf16Written )
{
size_t unitCount = 0;
UC_Assert ( (utf16Out != 0) && (utf16Written != 0) );
if ( utf16Len == 0 ) goto Done;
if ( cpIn >= 0xD800 ) goto CheckSurrogate; // ! Force linear execution path for the BMP.
InBMP:
unitCount = 1;
UTF16OutSwap ( utf16Out, UTF16Unit(cpIn) );
Done:
*utf16Written = unitCount;
return;
CheckSurrogate:
if ( cpIn > 0xFFFF ) goto SurrogatePair;
if ( cpIn > 0xDFFF ) goto InBMP;
UC_Throw ( "Bad UTF-32 - surrogate code point", kXMPErr_BadParam );
SurrogatePair:
CodePoint_to_UTF16Swp_Surrogate ( cpIn, utf16Out, utf16Len, utf16Written );
return;
} // CodePoint_to_UTF16Swp
// =================================================================================================
static void CodePoint_from_UTF16Swp_Surrogate ( const UTF16Unit * utf16In, const size_t utf16Len, UTF32Unit * cpOut, size_t * utf16Read )
{
UTF16Unit hiUnit = UTF16InSwap(utf16In);
size_t unitCount = 0;
UTF16Unit loUnit; // ! Avoid gcc complaints about declarations after goto's.
UTF32Unit cp;
// ----------------------------------
// We've got a UTF-16 surrogate pair.
if ( hiUnit > 0xDBFF ) UC_Throw ( "Bad UTF-16 - leading low surrogate", kXMPErr_BadParam );
if ( utf16Len < 2 ) goto Done; // Not enough input in this buffer.
loUnit = UTF16InSwap(utf16In+1);
if ( (loUnit < 0xDC00) || (0xDFFF < loUnit) ) UC_Throw ( "Bad UTF-16 - missing low surrogate", kXMPErr_BadParam );
unitCount = 2;
cp = (((hiUnit & 0x3FF) << 10) | (loUnit & 0x3FF)) + 0x10000;
*cpOut = cp; // ! Don't put after Done, don't write if no input.
Done:
*utf16Read = unitCount;
return;
} // CodePoint_from_UTF16Swp_Surrogate
// =================================================================================================
static void CodePoint_from_UTF16Swp ( const UTF16Unit * utf16In, const size_t utf16Len, UTF32Unit * cpOut, size_t * utf16Read )
{
UTF16Unit inUnit; // ! Don't read until we know there is input.
size_t unitCount = 0;
UC_Assert ( (utf16In != 0) && (cpOut != 0) && (utf16Read != 0) );
if ( utf16Len == 0 ) goto Done;
inUnit = UTF16InSwap(utf16In);
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) goto SurrogatePair; // ! Force linear execution path for the BMP.
unitCount = 1;
*cpOut = inUnit; // ! Don't put after Done, don't write if no input.
Done:
*utf16Read = unitCount;
return;
SurrogatePair:
CodePoint_from_UTF16Swp_Surrogate ( utf16In, utf16Len, cpOut, utf16Read );
return;
} // CodePoint_from_UTF16Swp
// =================================================================================================
static void UTF8_to_UTF16Swp ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf8Read, size_t * utf16Written )
{
const UTF8Unit * utf8Pos = utf8In;
UTF16Unit * utf16Pos = utf16Out;
size_t utf8Left = utf8Len;
size_t utf16Left = utf16Len;
UC_Assert ( (utf8In != 0) && (utf16Out != 0) && (utf8Read != 0) && (utf16Written != 0) );
while ( (utf8Left > 0) && (utf16Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf8Left;
if ( limit > utf16Left ) limit = utf16Left;
for ( i = 0; i < limit; ++i ) {
UTF8Unit inUnit = *utf8Pos;
if ( inUnit > 0x7F ) break;
*utf16Pos = UTF16Unit(inUnit) << 8; // Better than: UTF16OutSwap ( utf16Pos, inUnit );
++utf8Pos;
++utf16Pos;
}
utf8Left -= i;
utf16Left -= i;
// Do a run of non-ASCII, it copies multiple input units into 1 or 2 output units.
while ( (utf8Left > 0) && (utf16Left > 0) ) {
UTF32Unit cp;
size_t len8, len16;
UTF8Unit inUnit = *utf8Pos;
if ( inUnit <= 0x7F ) break;
CodePoint_from_UTF8_Multi ( utf8Pos, utf8Left, &cp, &len8 );
if ( len8 == 0 ) goto Done; // The input buffer ends in the middle of a character.
if ( cp <= 0xFFFF ) {
UTF16OutSwap ( utf16Pos, UTF16Unit(cp) );
len16 = 1;
} else {
CodePoint_to_UTF16Swp_Surrogate ( cp, utf16Pos, utf16Left, &len16 );
if ( len16 == 0 ) goto Done; // Not enough room in the output buffer.
}
utf8Left -= len8;
utf8Pos += len8;
utf16Left -= len16;
utf16Pos += len16;
}
}
Done: // Set the output lengths.
*utf8Read = utf8Len - utf8Left;
*utf16Written = utf16Len - utf16Left;
} // UTF8_to_UTF16Swp
// =================================================================================================
static void UTF8_to_UTF32Swp ( const UTF8Unit * utf8In, const size_t utf8Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf8Read, size_t * utf32Written )
{
const UTF8Unit * utf8Pos = utf8In;
UTF32Unit * utf32Pos = utf32Out;
size_t utf8Left = utf8Len;
size_t utf32Left = utf32Len;
UC_Assert ( (utf8In != 0) && (utf32Out != 0) && (utf8Read != 0) && (utf32Written != 0) );
while ( (utf8Left > 0) && (utf32Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf8Left;
if ( limit > utf32Left ) limit = utf32Left;
for ( i = 0; i < limit; ++i ) {
UTF8Unit inUnit = *utf8Pos;
if ( inUnit > 0x7F ) break;
*utf32Pos = UTF32Unit(inUnit) << 24; // Better than: UTF32OutSwap ( utf32Pos, inUnit );
++utf8Pos;
++utf32Pos;
}
utf8Left -= i;
utf32Left -= i;
// Do a run of non-ASCII, it copies variable input into 1 output unit.
while ( (utf8Left > 0) && (utf32Left > 0) ) {
size_t len;
UTF32Unit cp;
UTF8Unit inUnit = *utf8Pos;
if ( inUnit <= 0x7F ) break;
CodePoint_from_UTF8_Multi ( utf8Pos, utf8Left, &cp, &len );
if ( len == 0 ) goto Done; // The input buffer ends in the middle of a character.
UTF32OutSwap ( utf32Pos, cp );
utf8Left -= len;
utf8Pos += len;
utf32Left -= 1;
utf32Pos += 1;
}
}
Done: // Set the output lengths.
*utf8Read = utf8Len - utf8Left;
*utf32Written = utf32Len - utf32Left;
} // UTF8_to_UTF32Swp
// =================================================================================================
static void UTF16Swp_to_UTF8 ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf16Read, size_t * utf8Written )
{
const UTF16Unit * utf16Pos = utf16In;
UTF8Unit * utf8Pos = utf8Out;
size_t utf16Left = utf16Len;
size_t utf8Left = utf8Len;
UC_Assert ( (utf16In != 0) && (utf8Out != 0) && (utf16Read != 0) && (utf8Written != 0) );
while ( (utf16Left > 0) && (utf8Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf16Left;
if ( limit > utf8Left ) limit = utf8Left;
for ( i = 0; i < limit; ++i ) {
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( inUnit > 0x7F ) break;
*utf8Pos = UTF8Unit(inUnit);
++utf16Pos;
++utf8Pos;
}
utf16Left -= i;
utf8Left -= i;
// Do a run of non-ASCII inside the BMP, it copies 1 input unit into multiple output units.
while ( (utf16Left > 0) && (utf8Left > 0) ) {
size_t len8;
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( inUnit <= 0x7F ) break;
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) break;
CodePoint_to_UTF8_Multi ( inUnit, utf8Pos, utf8Left, &len8 );
if ( len8 == 0 ) goto Done; // Not enough room in the output buffer.
utf16Left -= 1;
utf16Pos += 1;
utf8Left -= len8;
utf8Pos += len8;
}
// Do a run of surrogate pairs, it copies 2 input units into multiple output units.
while ( (utf16Left > 0) && (utf8Left > 0) ) {
UTF32Unit cp;
size_t len16, len8;
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( (inUnit < 0xD800) || (0xDFFF < inUnit) ) break;
CodePoint_from_UTF16Swp_Surrogate ( utf16Pos, utf16Left, &cp, &len16 );
if ( len16 == 0 ) goto Done; // The input buffer ends in the middle of a surrogate pair.
UC_Assert ( len16 == 2 );
CodePoint_to_UTF8_Multi ( cp, utf8Pos, utf8Left, &len8 );
if ( len8 == 0 ) goto Done; // Not enough room in the output buffer.
utf16Left -= len16;
utf16Pos += len16;
utf8Left -= len8;
utf8Pos += len8;
}
}
Done: // Set the output lengths.
*utf16Read = utf16Len - utf16Left;
*utf8Written = utf8Len - utf8Left;
} // UTF16Swp_to_UTF8
// =================================================================================================
static void UTF32Swp_to_UTF8 ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF8Unit * utf8Out, const size_t utf8Len,
size_t * utf32Read, size_t * utf8Written )
{
const UTF32Unit * utf32Pos = utf32In;
UTF8Unit * utf8Pos = utf8Out;
size_t utf32Left = utf32Len;
size_t utf8Left = utf8Len;
UC_Assert ( (utf32In != 0) && (utf8Out != 0) && (utf32Read != 0) && (utf8Written != 0) );
while ( (utf32Left > 0) && (utf8Left > 0) ) {
// Do a run of ASCII, it copies 1 input unit into 1 output unit.
size_t i, limit = utf32Left;
if ( limit > utf8Left ) limit = utf8Left;
for ( i = 0; i < limit; ++i ) {
UTF32Unit cp = UTF32InSwap(utf32Pos);
if ( cp > 0x7F ) break;
*utf8Pos = UTF8Unit(cp);
++utf32Pos;
++utf8Pos;
}
utf32Left -= i;
utf8Left -= i;
// Do a run of non-ASCII, it copies 1 input unit into multiple output units.
while ( (utf32Left > 0) && (utf8Left > 0) ) {
size_t len;
UTF32Unit cp = UTF32InSwap(utf32Pos);
if ( cp <= 0x7F ) break;
CodePoint_to_UTF8_Multi ( cp, utf8Pos, utf8Left, &len );
if ( len == 0 ) goto Done; // Not enough room in the output buffer.
utf32Left -= 1;
utf32Pos += 1;
utf8Left -= len;
utf8Pos += len;
}
}
Done: // Set the output lengths.
*utf32Read = utf32Len - utf32Left;
*utf8Written = utf8Len - utf8Left;
} // UTF32Swp_to_UTF8
// =================================================================================================
static void UTF16Swp_to_UTF32Swp ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written )
{
const UTF16Unit * utf16Pos = utf16In;
UTF32Unit * utf32Pos = utf32Out;
size_t utf16Left = utf16Len;
size_t utf32Left = utf32Len;
UC_Assert ( (utf16In != 0) && (utf32Out != 0) && (utf16Read != 0) && (utf32Written != 0) );
while ( (utf16Left > 0) && (utf32Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf16Left;
if ( limit > utf32Left ) limit = utf32Left;
for ( i = 0; i < limit; ++i ) {
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) break;
*utf32Pos = UTF32Unit(*utf16Pos) << 16; // Better than: UTF32OutSwap ( utf32Pos, inUnit );
++utf16Pos;
++utf32Pos;
}
utf16Left -= i;
utf32Left -= i;
// Do a run of surrogate pairs, it copies 2 input units into 1 output unit.
while ( (utf16Left > 0) && (utf32Left > 0) ) {
size_t len;
UTF32Unit cp;
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( (inUnit < 0xD800) || (0xDFFF < inUnit) ) break;
CodePoint_from_UTF16Swp_Surrogate ( utf16Pos, utf16Left, &cp, &len );
if ( len == 0 ) goto Done; // The input buffer ends in the middle of a surrogate pair.
UTF32OutSwap ( utf32Pos, cp );
UC_Assert ( len == 2 );
utf16Left -= len;
utf16Pos += len;
utf32Left -= 1;
utf32Pos += 1;
}
}
Done: // Set the output lengths.
*utf16Read = utf16Len - utf16Left;
*utf32Written = utf32Len - utf32Left;
} // UTF16Swp_to_UTF32Swp
// =================================================================================================
static void UTF32Swp_to_UTF16Swp ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written )
{
const UTF32Unit * utf32Pos = utf32In;
UTF16Unit * utf16Pos = utf16Out;
size_t utf32Left = utf32Len;
size_t utf16Left = utf16Len;
const size_t k32to16Offset = swap32to16Offset; // ! Make sure compiler treats as an invariant.
UC_Assert ( (utf32In != 0) && (utf16Out != 0) && (utf32Read != 0) && (utf16Written != 0) );
while ( (utf32Left > 0) && (utf16Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf32Left;
if ( limit > utf16Left ) limit = utf16Left;
for ( i = 0; i < limit; ++i ) {
UTF32Unit inUnit = UTF32InSwap(utf32Pos);
if ( inUnit > 0xFFFF ) break;
*utf16Pos = *(((UTF16Unit*)utf32Pos) + k32to16Offset); // Better than: UTF16OutSwap ( utf16Pos, UTF16Unit(inUnit) );
++utf32Pos;
++utf16Pos;
}
utf32Left -= i;
utf16Left -= i;
// Do a run of non-BMP, it copies 1 input unit into 2 output units.
while ( (utf32Left > 0) && (utf16Left > 0) ) {
size_t len;
UTF32Unit inUnit = UTF32InSwap(utf32Pos);
if ( inUnit <= 0xFFFF ) break;
CodePoint_to_UTF16Swp_Surrogate ( inUnit, utf16Pos, utf16Left, &len );
if ( len == 0 ) goto Done; // Not enough room in the output buffer.
UC_Assert ( len == 2 );
utf32Left -= 1;
utf32Pos += 1;
utf16Left -= 2;
utf16Pos += 2;
}
}
Done: // Set the output lengths.
*utf32Read = utf32Len - utf32Left;
*utf16Written = utf16Len - utf16Left;
} // UTF32Swp_to_UTF16Swp
// =================================================================================================
static void UTF16Nat_to_UTF32Swp ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written )
{
const UTF16Unit * utf16Pos = utf16In;
UTF32Unit * utf32Pos = utf32Out;
size_t utf16Left = utf16Len;
size_t utf32Left = utf32Len;
UC_Assert ( (utf16In != 0) && (utf32Out != 0) && (utf16Read != 0) && (utf32Written != 0) );
while ( (utf16Left > 0) && (utf32Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf16Left;
if ( limit > utf32Left ) limit = utf32Left;
for ( i = 0; i < limit; ++i ) {
UTF16Unit inUnit = *utf16Pos;
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) break;
UTF32OutSwap ( utf32Pos, inUnit );
++utf16Pos;
++utf32Pos;
}
utf16Left -= i;
utf32Left -= i;
// Do a run of surrogate pairs, it copies 2 input units into 1 output unit.
while ( (utf16Left > 0) && (utf32Left > 0) ) {
size_t len;
UTF32Unit cp;
UTF16Unit inUnit = *utf16Pos;
if ( (inUnit < 0xD800) || (0xDFFF < inUnit) ) break;
CodePoint_from_UTF16Nat_Surrogate ( utf16Pos, utf16Left, &cp, &len );
if ( len == 0 ) goto Done; // The input buffer ends in the middle of a surrogate pair.
UC_Assert ( len == 2 );
UTF32OutSwap ( utf32Pos, cp );
utf16Left -= len;
utf16Pos += len;
utf32Left -= 1;
utf32Pos += 1;
}
}
Done: // Set the output lengths.
*utf16Read = utf16Len - utf16Left;
*utf32Written = utf32Len - utf32Left;
} // UTF16Nat_to_UTF32Swp
// =================================================================================================
static void UTF16Swp_to_UTF32Nat ( const UTF16Unit * utf16In, const size_t utf16Len,
UTF32Unit * utf32Out, const size_t utf32Len,
size_t * utf16Read, size_t * utf32Written )
{
const UTF16Unit * utf16Pos = utf16In;
UTF32Unit * utf32Pos = utf32Out;
size_t utf16Left = utf16Len;
size_t utf32Left = utf32Len;
UC_Assert ( (utf16In != 0) && (utf32Out != 0) && (utf16Read != 0) && (utf32Written != 0) );
while ( (utf16Left > 0) && (utf32Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf16Left;
if ( limit > utf32Left ) limit = utf32Left;
for ( i = 0; i < limit; ++i ) {
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( (0xD800 <= inUnit) && (inUnit <= 0xDFFF) ) break;
*utf32Pos = inUnit;
++utf16Pos;
++utf32Pos;
}
utf16Left -= i;
utf32Left -= i;
// Do a run of surrogate pairs, it copies 2 input units into 1 output unit.
while ( (utf16Left > 0) && (utf32Left > 0) ) {
size_t len;
UTF16Unit inUnit = UTF16InSwap(utf16Pos);
if ( (inUnit < 0xD800) || (0xDFFF < inUnit) ) break;
CodePoint_from_UTF16Swp_Surrogate ( utf16Pos, utf16Left, utf32Pos, &len );
if ( len == 0 ) goto Done; // The input buffer ends in the middle of a surrogate pair.
UC_Assert ( len == 2 );
utf16Left -= len;
utf16Pos += len;
utf32Left -= 1;
utf32Pos += 1;
}
}
Done: // Set the output lengths.
*utf16Read = utf16Len - utf16Left;
*utf32Written = utf32Len - utf32Left;
} // UTF16Swp_to_UTF32Nat
// =================================================================================================
static void UTF32Nat_to_UTF16Swp ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written )
{
const UTF32Unit * utf32Pos = utf32In;
UTF16Unit * utf16Pos = utf16Out;
size_t utf32Left = utf32Len;
size_t utf16Left = utf16Len;
UC_Assert ( (utf32In != 0) && (utf16Out != 0) && (utf32Read != 0) && (utf16Written != 0) );
while ( (utf32Left > 0) && (utf16Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf32Left;
if ( limit > utf16Left ) limit = utf16Left;
for ( i = 0; i < limit; ++i ) {
UTF32Unit inUnit = *utf32Pos;
if ( inUnit > 0xFFFF ) break;
UTF16OutSwap ( utf16Pos, UTF16Unit(inUnit) );
++utf32Pos;
++utf16Pos;
}
utf32Left -= i;
utf16Left -= i;
// Do a run of non-BMP, it copies 1 input unit into 2 output units.
while ( (utf32Left > 0) && (utf16Left > 0) ) {
size_t len;
UTF32Unit inUnit = *utf32Pos;
if ( inUnit <= 0xFFFF ) break;
CodePoint_to_UTF16Swp_Surrogate ( inUnit, utf16Pos, utf16Left, &len );
if ( len == 0 ) goto Done; // Not enough room in the output buffer.
UC_Assert ( len == 2 );
utf32Left -= 1;
utf32Pos += 1;
utf16Left -= 2;
utf16Pos += 2;
}
}
Done: // Set the output lengths.
*utf32Read = utf32Len - utf32Left;
*utf16Written = utf16Len - utf16Left;
} // UTF32Nat_to_UTF16Swp
// =================================================================================================
static void UTF32Swp_to_UTF16Nat ( const UTF32Unit * utf32In, const size_t utf32Len,
UTF16Unit * utf16Out, const size_t utf16Len,
size_t * utf32Read, size_t * utf16Written )
{
const UTF32Unit * utf32Pos = utf32In;
UTF16Unit * utf16Pos = utf16Out;
size_t utf32Left = utf32Len;
size_t utf16Left = utf16Len;
UC_Assert ( (utf32In != 0) && (utf16Out != 0) && (utf32Read != 0) && (utf16Written != 0) );
while ( (utf32Left > 0) && (utf16Left > 0) ) {
// Do a run of BMP, it copies 1 input unit into 1 output unit.
size_t i, limit = utf32Left;
if ( limit > utf16Left ) limit = utf16Left;
for ( i = 0; i < limit; ++i ) {
UTF32Unit inUnit = UTF32InSwap(utf32Pos);
if ( inUnit > 0xFFFF ) break;
*utf16Pos = UTF16Unit(inUnit);
++utf32Pos;
++utf16Pos;
}
utf32Left -= i;
utf16Left -= i;
// Do a run of non-BMP, it copies 1 input unit into 2 output units.
while ( (utf32Left > 0) && (utf16Left > 0) ) {
size_t len;
UTF32Unit inUnit = UTF32InSwap(utf32Pos);
if ( inUnit <= 0xFFFF ) break;
CodePoint_to_UTF16Nat_Surrogate ( inUnit, utf16Pos, utf16Left, &len );
if ( len == 0 ) goto Done; // Not enough room in the output buffer.
UC_Assert ( len == 2 );
utf32Left -= 1;
utf32Pos += 1;
utf16Left -= 2;
utf16Pos += 2;
}
}
Done: // Set the output lengths.
*utf32Read = utf32Len - utf32Left;
*utf16Written = utf16Len - utf16Left;
} // UTF32Swp_to_UTF16Nat
} //namespace
// =================================================================================================
diff --git a/core/libs/dngwriter/extra/xmp_sdk/include/XMP_Const.h b/core/libs/dngwriter/extra/xmp_sdk/include/XMP_Const.h
index 22a043c0f8..863722f345 100644
--- a/core/libs/dngwriter/extra/xmp_sdk/include/XMP_Const.h
+++ b/core/libs/dngwriter/extra/xmp_sdk/include/XMP_Const.h
@@ -1,1334 +1,1334 @@
#ifndef __XMP_Const_h__
#define __XMP_Const_h__ 1
// =================================================================================================
// Copyright 2002-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms
// of the Adobe license agreement accompanying it.
// =================================================================================================
#include "XMP_Environment.h"
#include <stddef.h>
#if XMP_MacBuild // ! No stdint.h on Windows and some UNIXes.
#include <stdint.h>
#endif
namespace DngXmpSdk {
#if __cplusplus
extern "C" {
#endif
// =================================================================================================
/// \file XMP_Const.h
/// \brief Common C/C++ types and constants for the XMP toolkit.
// =================================================================================================
// =================================================================================================
// Basic types and constants
// =========================
// The XMP_... types are used on the off chance that the ..._t types present a problem. In that
// case only the declarations of the XMP_... types needs to change, not all of the uses. These
// types are used where fixed sizes are required in order to have a known ABI for a DLL build.
#if XMP_MacBuild
typedef int8_t XMP_Int8;
typedef int16_t XMP_Int16;
typedef int32_t XMP_Int32;
typedef int64_t XMP_Int64;
typedef uint8_t XMP_Uns8;
typedef uint16_t XMP_Uns16;
typedef uint32_t XMP_Uns32;
typedef uint64_t XMP_Uns64;
#else
typedef signed char XMP_Int8;
typedef signed short XMP_Int16;
typedef signed long XMP_Int32;
typedef signed long long XMP_Int64;
typedef unsigned char XMP_Uns8;
typedef unsigned short XMP_Uns16;
typedef unsigned long XMP_Uns32;
typedef unsigned long long XMP_Uns64;
#endif
typedef XMP_Uns8 XMP_Bool;
/// An "ABI safe" pointer to the internal part of an XMP object. Use to pass an XMP object across
/// client DLL boundaries. See \c TXMPMeta::GetInternalRef().
typedef struct __XMPMeta__ * XMPMetaRef;
/// An "ABI safe" pointer to the internal part of an XMP iteration object. Use to pass an XMP
/// iteration object across client DLL boundaries. See \c TXMPIterator.
typedef struct __XMPIterator__ * XMPIteratorRef;
/// An "ABI safe" pointer to the internal part of an XMP document operations object. Use to pass an
/// XMP document operations object across client DLL boundaries. See \c TXMPDocOps.
typedef struct __XMPDocOps__ * XMPDocOpsRef;
/// An "ABI safe" pointer to the internal part of an XMP file-handling object. Use to pass an XMP
/// file-handling object across client DLL boundaries. See \c TXMPFiles.
typedef struct __XMPFiles__ * XMPFilesRef;
// =================================================================================================
/// \name General scalar types and constants
/// @{
/// \typedef XMP_StringPtr
/// \brief The type for input string parameters. A <tt>const char *</tt>, a null-terminated UTF-8
/// string.
/// \typedef XMP_StringLen
/// \brief The type for string length parameters. A 32-bit unsigned integer, as big as will be
/// practically needed.
/// \typedef XMP_Index
/// \brief The type for offsets and indices. A 32-bit signed integer. It is signed to allow -1 for
/// loop termination.
/// \typedef XMP_OptionBits
/// \brief The type for a collection of 32 flag bits. Individual flags are defined as enum value bit
/// masks; see \c #kXMP_PropValueIsURI and following. A number of macros provide common set or set
/// operations, such as \c XMP_PropIsSimple. For other tests use an expression like <code>options &
/// kXMP_<theOption></code>. When passing multiple option flags use the bitwise-OR operator. '|',
-/// not the arithmatic plus, '+'.
+/// not the arithmetic plus, '+'.
typedef const char * XMP_StringPtr; // Points to a null terminated UTF-8 string.
typedef XMP_Uns32 XMP_StringLen;
typedef XMP_Int32 XMP_Index; // Signed, sometimes -1 is handy.
typedef XMP_Uns32 XMP_OptionBits; // Used as 32 individual bits.
/// \def kXMP_TrueStr
/// \brief The canonical true string value for Booleans in serialized XMP.
///
/// Code that converts from string to bool should be case insensitive, and also allow "1".
/// \def kXMP_FalseStr
/// \brief The canonical false string value for Booleans in serialized XMP.
///
/// Code that converts from string to bool should be case insensitive, and also allow "0".
#define kXMP_TrueStr "True" // Serialized XMP spellings, not for the type bool.
#define kXMP_FalseStr "False"
/// Type for yes/no/maybe answers. The values are picked to allow Boolean-like usage. The yes and
/// values are true (non-zero), the no value is false (zero).
enum {
/// The part or parts have definitely changed.
kXMPTS_Yes = 1,
/// The part or parts have definitely not changed.
kXMPTS_No = 0,
/// The part or parts might, or might not, have changed.
kXMPTS_Maybe = -1
};
typedef XMP_Int8 XMP_TriState;
/// @}
// =================================================================================================
/// \struct XMP_DateTime
/// \brief The expanded type for a date and time.
///
/// Dates and time in the serialized XMP are ISO 8601 strings. The \c XMP_DateTime struct allows
/// easy conversion with other formats.
///
/// All of the fields are 32 bit, even though most could be 8 bit. This avoids overflow when doing
/// carries for arithmetic or normalization. All fields have signed values for the same reasons.
///
/// Date-time values are occasionally used with only a date or only a time component. A date without
/// a time has zeros in the \c XMP_DateTime struct for all time fields. A time without a date has
/// zeros for all date fields (year, month, and day).
///
/// \c TXMPUtils provides utility functions for manipulating date-time values.
///
/// @see \c TXMPUtils::ConvertToDate(), \c TXMPUtils::ConvertFromDate(),
/// \c TXMPUtils::CompareDateTime(), \c TXMPUtils::ConvertToLocalTime(),
/// \c TXMPUtils::ConvertToUTCTime(), \c TXMPUtils::CurrentDateTime(),
/// \c TXMPUtils::SetTimeZone()
struct XMP_DateTime {
/// The year, can be negative.
XMP_Int32 year;
/// The month in the range 1..12.
XMP_Int32 month;
/// The day of the month in the range 1..31.
XMP_Int32 day;
/// The hour in the range 0..23.
XMP_Int32 hour;
/// The minute in the range 0..59.
XMP_Int32 minute;
/// The second in the range 0..59.
XMP_Int32 second;
/// The "sign" of the time zone, \c #kXMP_TimeIsUTC (0) means UTC, \c #kXMP_TimeWestOfUTC (-1)
/// is west, \c #kXMP_TimeEastOfUTC (+1) is east.
XMP_Int32 tzSign;
/// The time zone hour in the range 0..23.
XMP_Int32 tzHour;
/// The time zone minute in the range 0..59.
XMP_Int32 tzMinute;
/// Nanoseconds within a second, often left as zero.
XMP_Int32 nanoSecond;
};
/// Constant values for \c XMP_DateTime::tzSign field.
enum {
/// Time zone is west of UTC.
kXMP_TimeWestOfUTC = -1,
/// UTC time.
kXMP_TimeIsUTC = 0,
/// Time zone is east of UTC.
kXMP_TimeEastOfUTC = +1
};
// =================================================================================================
// Standard namespace URI constants
// ================================
/// \name XML namespace constants for standard XMP schema.
/// @{
///
/// \def kXMP_NS_XMP
/// \brief The XML namespace for the XMP "basic" schema.
///
/// \def kXMP_NS_XMP_Rights
/// \brief The XML namespace for the XMP copyright schema.
///
/// \def kXMP_NS_XMP_MM
/// \brief The XML namespace for the XMP digital asset management schema.
///
/// \def kXMP_NS_XMP_BJ
/// \brief The XML namespace for the job management schema.
///
/// \def kXMP_NS_XMP_T
/// \brief The XML namespace for the XMP text document schema.
///
/// \def kXMP_NS_XMP_T_PG
/// \brief The XML namespace for the XMP paged document schema.
///
/// \def kXMP_NS_PDF
/// \brief The XML namespace for the PDF schema.
///
/// \def kXMP_NS_Photoshop
/// \brief The XML namespace for the Photoshop custom schema.
///
/// \def kXMP_NS_EXIF
/// \brief The XML namespace for Adobe's EXIF schema.
///
/// \def kXMP_NS_TIFF
/// \brief The XML namespace for Adobe's TIFF schema.
///
/// @}
#define kXMP_NS_XMP "http://ns.adobe.com/xap/1.0/"
#define kXMP_NS_XMP_Rights "http://ns.adobe.com/xap/1.0/rights/"
#define kXMP_NS_XMP_MM "http://ns.adobe.com/xap/1.0/mm/"
#define kXMP_NS_XMP_BJ "http://ns.adobe.com/xap/1.0/bj/"
#define kXMP_NS_PDF "http://ns.adobe.com/pdf/1.3/"
#define kXMP_NS_Photoshop "http://ns.adobe.com/photoshop/1.0/"
#define kXMP_NS_PSAlbum "http://ns.adobe.com/album/1.0/"
#define kXMP_NS_EXIF "http://ns.adobe.com/exif/1.0/"
#define kXMP_NS_EXIF_Aux "http://ns.adobe.com/exif/1.0/aux/"
#define kXMP_NS_TIFF "http://ns.adobe.com/tiff/1.0/"
#define kXMP_NS_PNG "http://ns.adobe.com/png/1.0/"
#define kXMP_NS_SWF "http://ns.adobe.com/swf/1.0/"
#define kXMP_NS_JPEG "http://ns.adobe.com/jpeg/1.0/"
#define kXMP_NS_JP2K "http://ns.adobe.com/jp2k/1.0/"
#define kXMP_NS_CameraRaw "http://ns.adobe.com/camera-raw-settings/1.0/"
#define kXMP_NS_DM "http://ns.adobe.com/xmp/1.0/DynamicMedia/"
#define kXMP_NS_ASF "http://ns.adobe.com/asf/1.0/"
#define kXMP_NS_WAV "http://ns.adobe.com/xmp/wav/1.0/"
#define kXMP_NS_XMP_Note "http://ns.adobe.com/xmp/note/"
#define kXMP_NS_AdobeStockPhoto "http://ns.adobe.com/StockPhoto/1.0/"
#define kXMP_NS_CreatorAtom "http://ns.adobe.com/creatorAtom/1.0/"
/// \name XML namespace constants for qualifiers and structured property fields.
/// @{
///
/// \def kXMP_NS_XMP_IdentifierQual
/// \brief The XML namespace for qualifiers of the xmp:Identifier property.
///
/// \def kXMP_NS_XMP_Dimensions
/// \brief The XML namespace for fields of the Dimensions type.
///
/// \def kXMP_NS_XMP_Image
/// \brief The XML namespace for fields of a graphical image. Used for the Thumbnail type.
///
/// \def kXMP_NS_XMP_ResourceEvent
/// \brief The XML namespace for fields of the ResourceEvent type.
///
/// \def kXMP_NS_XMP_ResourceRef
/// \brief The XML namespace for fields of the ResourceRef type.
///
/// \def kXMP_NS_XMP_ST_Version
/// \brief The XML namespace for fields of the Version type.
///
/// \def kXMP_NS_XMP_ST_Job
/// \brief The XML namespace for fields of the JobRef type.
///
/// @}
#define kXMP_NS_XMP_IdentifierQual "http://ns.adobe.com/xmp/Identifier/qual/1.0/"
#define kXMP_NS_XMP_Dimensions "http://ns.adobe.com/xap/1.0/sType/Dimensions#"
#define kXMP_NS_XMP_Text "http://ns.adobe.com/xap/1.0/t/"
#define kXMP_NS_XMP_PagedFile "http://ns.adobe.com/xap/1.0/t/pg/"
#define kXMP_NS_XMP_Graphics "http://ns.adobe.com/xap/1.0/g/"
#define kXMP_NS_XMP_Image "http://ns.adobe.com/xap/1.0/g/img/"
#define kXMP_NS_XMP_Font "http://ns.adobe.com/xap/1.0/sType/Font#"
#define kXMP_NS_XMP_ResourceEvent "http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
#define kXMP_NS_XMP_ResourceRef "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"
#define kXMP_NS_XMP_ST_Version "http://ns.adobe.com/xap/1.0/sType/Version#"
#define kXMP_NS_XMP_ST_Job "http://ns.adobe.com/xap/1.0/sType/Job#"
#define kXMP_NS_XMP_ManifestItem "http://ns.adobe.com/xap/1.0/sType/ManifestItem#"
// Deprecated XML namespace constants
#define kXMP_NS_XMP_T "http://ns.adobe.com/xap/1.0/t/"
#define kXMP_NS_XMP_T_PG "http://ns.adobe.com/xap/1.0/t/pg/"
#define kXMP_NS_XMP_G_IMG "http://ns.adobe.com/xap/1.0/g/img/"
/// \name XML namespace constants from outside Adobe.
/// @{
///
/// \def kXMP_NS_DC
/// \brief The XML namespace for the Dublin Core schema.
///
/// \def kXMP_NS_IPTCCore
/// \brief The XML namespace for the IPTC Core schema.
///
/// \def kXMP_NS_RDF
/// \brief The XML namespace for RDF.
///
/// \def kXMP_NS_XML
/// \brief The XML namespace for XML.
///
/// @}
#define kXMP_NS_DC "http://purl.org/dc/elements/1.1/"
#define kXMP_NS_IPTCCore "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
#define kXMP_NS_DICOM "http://ns.adobe.com/DICOM/"
#define kXMP_NS_PDFA_Schema "http://www.aiim.org/pdfa/ns/schema#"
#define kXMP_NS_PDFA_Property "http://www.aiim.org/pdfa/ns/property#"
#define kXMP_NS_PDFA_Type "http://www.aiim.org/pdfa/ns/type#"
#define kXMP_NS_PDFA_Field "http://www.aiim.org/pdfa/ns/field#"
#define kXMP_NS_PDFA_ID "http://www.aiim.org/pdfa/ns/id/"
#define kXMP_NS_PDFA_Extension "http://www.aiim.org/pdfa/ns/extension/"
#define kXMP_NS_PDFX "http://ns.adobe.com/pdfx/1.3/"
#define kXMP_NS_PDFX_ID "http://www.npes.org/pdfx/ns/id/"
#define kXMP_NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
#define kXMP_NS_XML "http://www.w3.org/XML/1998/namespace"
// =================================================================================================
// Enums and macros used for option bits
// =====================================
/// \name Macros for standard option selections.
/// @{
///
/// \def kXMP_ArrayLastItem
/// \brief Options macro accesses last array item.
///
/// \def kXMP_UseNullTermination
/// \brief Options macro sets string style.
///
/// \def kXMP_NoOptions
/// \brief Options macro clears all property-type bits.
///
/// @}
#define kXMP_ArrayLastItem ((XMP_Index)(-1L))
#define kXMP_UseNullTermination ((XMP_StringLen)(~0UL))
#define kXMP_NoOptions ((XMP_OptionBits)0UL)
/// \name Macros for setting and testing general option bits.
/// @{
///
/// \def XMP_SetOption
/// \brief Macro sets an option flag bit.
/// \param var A variable storing an options flag.
/// \param opt The bit-flag constant to set.
///
/// \def XMP_ClearOption
/// \brief Macro clears an option flag bit.
/// \param var A variable storing an options flag.
/// \param opt The bit-flag constant to clear.
///
/// \def XMP_TestOption
/// \brief Macro reports whether an option flag bit is set.
/// \param var A variable storing an options flag.
/// \param opt The bit-flag constant to test.
/// \return True if the bit is set.
///
/// \def XMP_OptionIsSet
/// \brief Macro reports whether an option flag bit is set.
/// \param var A variable storing an options flag.
/// \param opt The bit-flag constant to test.
/// \return True if the bit is set.
///
/// \def XMP_OptionIsClear
/// \brief Macro reports whether an option flag bit is clear.
/// \param var A variable storing an options flag.
/// \param opt The bit-flag constant to test.
/// \return True if the bit is clear.
///
/// @}
#define XMP_SetOption(var,opt) var |= (opt)
#define XMP_ClearOption(var,opt) var &= ~(opt)
#define XMP_TestOption(var,opt) (((var) & (opt)) != 0)
#define XMP_OptionIsSet(var,opt) (((var) & (opt)) != 0)
#define XMP_OptionIsClear(var,opt) (((var) & (opt)) == 0)
/// \name Macros for setting and testing specific option bits.
/// @{
///
/// \def XMP_PropIsSimple
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_PropIsStruct
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_PropIsArray
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_ArrayIsUnordered
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_ArrayIsOrdered
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_ArrayIsAlternate
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_ArrayIsAltText
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_PropHasQualifiers
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_PropIsQualifier
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_PropHasLang
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_NodeIsSchema
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// \def XMP_PropIsAlias
/// \brief Macro reports the property type specified by an options flag.
/// \param opt The options flag to check.
///
/// @}
#define XMP_PropIsSimple(opt) (((opt) & kXMP_PropCompositeMask) == 0)
#define XMP_PropIsStruct(opt) (((opt) & kXMP_PropValueIsStruct) != 0)
#define XMP_PropIsArray(opt) (((opt) & kXMP_PropValueIsArray) != 0)
#define XMP_ArrayIsUnordered(opt) (((opt) & kXMP_PropArrayIsOrdered) == 0)
#define XMP_ArrayIsOrdered(opt) (((opt) & kXMP_PropArrayIsOrdered) != 0)
#define XMP_ArrayIsAlternate(opt) (((opt) & kXMP_PropArrayIsAlternate) != 0)
#define XMP_ArrayIsAltText(opt) (((opt) & kXMP_PropArrayIsAltText) != 0)
#define XMP_PropHasQualifiers(opt) (((opt) & kXMP_PropHasQualifiers) != 0)
#define XMP_PropIsQualifier(opt) (((opt) & kXMP_PropIsQualifier) != 0)
#define XMP_PropHasLang(opt) (((opt) & kXMP_PropHasLang) != 0)
#define XMP_NodeIsSchema(opt) (((opt) & kXMP_SchemaNode) != 0)
#define XMP_PropIsAlias(opt) (((opt) & kXMP_PropIsAlias) != 0)
// -------------------------------------------------------------------------------------------------
/// Option bit flags for the \c TXMPMeta property accessor functions.
enum {
/// The XML string form of the property value is a URI, use rdf:resource attribute. DISCOURAGED
kXMP_PropValueIsURI = 0x00000002UL,
// ------------------------------------------------------
// Options relating to qualifiers attached to a property.
/// The property has qualifiers, includes \c rdf:type and \c xml:lang.
kXMP_PropHasQualifiers = 0x00000010UL,
/// This is a qualifier for some other property, includes \c rdf:type and \c xml:lang.
/// Qualifiers can have arbitrary structure, and can themselves have qualifiers. If the
/// qualifier itself has a structured value, this flag is only set for the top node of the
/// qualifier's subtree.
kXMP_PropIsQualifier = 0x00000020UL,
/// Implies \c #kXMP_PropHasQualifiers, property has \c xml:lang.
kXMP_PropHasLang = 0x00000040UL,
/// Implies \c #kXMP_PropHasQualifiers, property has \c rdf:type.
kXMP_PropHasType = 0x00000080UL,
// --------------------------------------------
// Options relating to the data structure form.
/// The value is a structure with nested fields.
kXMP_PropValueIsStruct = 0x00000100UL,
/// The value is an array (RDF alt/bag/seq). The "ArrayIs..." flags identify specific types
/// of array; default is a general unordered array, serialized using an \c rdf:Bag container.
kXMP_PropValueIsArray = 0x00000200UL,
/// The item order does not matter.
kXMP_PropArrayIsUnordered = kXMP_PropValueIsArray,
/// Implies \c #kXMP_PropValueIsArray, item order matters. It is serialized using an \c rdf:Seq container.
kXMP_PropArrayIsOrdered = 0x00000400UL,
/// Implies \c #kXMP_PropArrayIsOrdered, items are alternates. It is serialized using an \c rdf:Alt container.
kXMP_PropArrayIsAlternate = 0x00000800UL,
// ------------------------------------
// Additional struct and array options.
/// Implies \c #kXMP_PropArrayIsAlternate, items are localized text. Each array element is a
/// simple property with an \c xml:lang attribute.
kXMP_PropArrayIsAltText = 0x00001000UL,
// kXMP_InsertBeforeItem = 0x00004000UL, ! Used by SetXyz functions.
// kXMP_InsertAfterItem = 0x00008000UL, ! Used by SetXyz functions.
// ----------------------------
// Other miscellaneous options.
/// This property is an alias name for another property. This is only returned by
/// \c TXMPMeta::GetProperty() and then only if the property name is simple, not an path expression.
kXMP_PropIsAlias = 0x00010000UL,
/// This property is the base value (actual) for a set of aliases.This is only returned by
/// \c TXMPMeta::GetProperty() and then only if the property name is simple, not an path expression.
kXMP_PropHasAliases = 0x00020000UL,
/// The value of this property is "owned" by the application, and should not generally be editable in a UI.
kXMP_PropIsInternal = 0x00040000UL,
/// The value of this property is not derived from the document content.
kXMP_PropIsStable = 0x00100000UL,
/// The value of this property is derived from the document content.
kXMP_PropIsDerived = 0x00200000UL,
// kXMPUtil_AllowCommas = 0x10000000UL, ! Used by TXMPUtils::CatenateArrayItems and ::SeparateArrayItems.
// kXMP_DeleteExisting = 0x20000000UL, ! Used by TXMPMeta::SetXyz functions to delete any pre-existing property.
// kXMP_SchemaNode = 0x80000000UL, ! Returned by iterators - #define to avoid warnings
// ------------------------------
// Masks that are multiple flags.
/// Property type bit-flag mask for all array types
kXMP_PropArrayFormMask = kXMP_PropValueIsArray | kXMP_PropArrayIsOrdered | kXMP_PropArrayIsAlternate | kXMP_PropArrayIsAltText,
/// Property type bit-flag mask for composite types (array and struct)
kXMP_PropCompositeMask = kXMP_PropValueIsStruct | kXMP_PropArrayFormMask,
/// Mask for bits that are reserved for transient use by the implementation.
kXMP_ImplReservedMask = 0x70000000L
};
#define kXMP_SchemaNode ((XMP_OptionBits)0x80000000UL)
/// Option bit flags for the \c TXMPMeta property setting functions. These option bits are shared
/// with the accessor functions:
/// \li \c #kXMP_PropValueIsURI
/// \li \c #kXMP_PropValueIsStruct
/// \li \c #kXMP_PropValueIsArray
/// \li \c #kXMP_PropArrayIsOrdered
/// \li \c #kXMP_PropArrayIsAlternate
/// \li \c #kXMP_PropArrayIsAltText
enum {
/// Option for array item location: Insert a new item before the given index.
kXMP_InsertBeforeItem = 0x00004000UL,
/// Option for array item location: Insert a new item after the given index.
kXMP_InsertAfterItem = 0x00008000UL,
/// Delete any pre-existing property.
kXMP_DeleteExisting = 0x20000000UL,
/// Bit-flag mask for property-value option bits
kXMP_PropValueOptionsMask = kXMP_PropValueIsURI,
/// Bit-flag mask for array-item location bits
kXMP_PropArrayLocationMask = kXMP_InsertBeforeItem | kXMP_InsertAfterItem
};
// -------------------------------------------------------------------------------------------------
/// Option bit flags for \c TXMPMeta::ParseFromBuffer().
enum {
/// Require a surrounding \c x:xmpmeta element.
kXMP_RequireXMPMeta = 0x0001UL,
/// This is the not last input buffer for this parse stream.
kXMP_ParseMoreBuffers = 0x0002UL,
/// Do not reconcile alias differences, throw an exception.
kXMP_StrictAliasing = 0x0004UL
};
/// Option bit flags for \c TXMPMeta::SerializeToBuffer().
enum {
// *** Option to remove empty struct/array, or leaf with empty value?
/// Omit the XML packet wrapper.
kXMP_OmitPacketWrapper = 0x0010UL,
/// Default is a writeable packet.
kXMP_ReadOnlyPacket = 0x0020UL,
/// Use a compact form of RDF.
kXMP_UseCompactFormat = 0x0040UL,
/// Include a padding allowance for a thumbnail image.
kXMP_IncludeThumbnailPad = 0x0100UL,
/// The padding parameter is the overall packet length.
kXMP_ExactPacketLength = 0x0200UL,
/// Show aliases as XML comments.
kXMP_WriteAliasComments = 0x0400UL,
/// Omit all formatting whitespace.
kXMP_OmitAllFormatting = 0x0800UL,
/// Omit the x:xmpmeta element surrounding the rdf:RDF element.
kXMP_OmitXMPMetaElement = 0x1000UL,
_XMP_LittleEndian_Bit = 0x0001UL, // ! Don't use directly, see the combined values below!
_XMP_UTF16_Bit = 0x0002UL,
_XMP_UTF32_Bit = 0x0004UL,
/// Bit-flag mask for encoding-type bits
kXMP_EncodingMask = 0x0007UL,
/// Use UTF8 encoding
kXMP_EncodeUTF8 = 0UL,
/// Use UTF16 big-endian encoding
kXMP_EncodeUTF16Big = _XMP_UTF16_Bit,
/// Use UTF16 little-endian encoding
kXMP_EncodeUTF16Little = _XMP_UTF16_Bit | _XMP_LittleEndian_Bit,
/// Use UTF32 big-endian encoding
kXMP_EncodeUTF32Big = _XMP_UTF32_Bit,
/// Use UTF13 little-endian encoding
kXMP_EncodeUTF32Little = _XMP_UTF32_Bit | _XMP_LittleEndian_Bit
};
// -------------------------------------------------------------------------------------------------
/// Option bit flags for \c TXMPIterator construction.
enum {
/// The low 8 bits are an enum of what data structure to iterate.
kXMP_IterClassMask = 0x00FFUL,
/// Iterate the property tree of a TXMPMeta object.
kXMP_IterProperties = 0x0000UL,
/// Iterate the global alias table.
kXMP_IterAliases = 0x0001UL,
/// Iterate the global namespace table.
kXMP_IterNamespaces = 0x0002UL,
/// Just do the immediate children of the root, default is subtree.
kXMP_IterJustChildren = 0x0100UL,
/// Just do the leaf nodes, default is all nodes in the subtree.
kXMP_IterJustLeafNodes = 0x0200UL,
/// Return just the leaf part of the path, default is the full path.
kXMP_IterJustLeafName = 0x0400UL,
/// Include aliases, default is just actual properties.
kXMP_IterIncludeAliases = 0x0800UL,
/// Omit all qualifiers.
kXMP_IterOmitQualifiers = 0x1000UL
};
/// Option bit flags for \c TXMPIterator::Skip().
enum {
/// Skip the subtree below the current node.
kXMP_IterSkipSubtree = 0x0001UL,
/// Skip the subtree below and remaining siblings of the current node.
kXMP_IterSkipSiblings = 0x0002UL
};
// -------------------------------------------------------------------------------------------------
/// Option bit flags for \c TXMPUtils::CatenateArrayItems() and \c TXMPUtils::SeparateArrayItems().
/// These option bits are shared with the accessor functions:
/// \li \c #kXMP_PropValueIsArray,
/// \li \c #kXMP_PropArrayIsOrdered,
/// \li \c #kXMP_PropArrayIsAlternate,
/// \li \c #kXMP_PropArrayIsAltText
enum {
/// Allow commas in item values, default is separator.
kXMPUtil_AllowCommas = 0x10000000UL
};
/// Option bit flags for \c TXMPUtils::RemoveProperties() and \c TXMPUtils::AppendProperties().
enum {
/// Do all properties, default is just external properties.
kXMPUtil_DoAllProperties = 0x0001UL,
/// Replace existing values, default is to leave them.
kXMPUtil_ReplaceOldValues = 0x0002UL,
/// Delete properties if the new value is empty.
kXMPUtil_DeleteEmptyValues = 0x0004UL,
/// Include aliases, default is just actual properties.
kXMPUtil_IncludeAliases = 0x0800UL
};
// =================================================================================================
// Types and Constants for XMPFiles
// ================================
/// File format constants for use with XMPFiles.
enum {
// ! Hex used to avoid gcc warnings. Leave the constants so the text reads big endian. There
// ! seems to be no decent way on UNIX to determine the target endianness at compile time.
// ! Forcing it on the client isn't acceptable.
// --------------------
// Public file formats.
/// Public file format constant: 'PDF '
kXMP_PDFFile = 0x50444620UL,
/// Public file format constant: 'PS ', general PostScript following DSC conventions
kXMP_PostScriptFile = 0x50532020UL,
/// Public file format constant: 'EPS ', encapsulated PostScript
kXMP_EPSFile = 0x45505320UL,
/// Public file format constant: 'JPEG'
kXMP_JPEGFile = 0x4A504547UL,
/// Public file format constant: 'JPX ', JPEG 2000, ISO 15444-1
kXMP_JPEG2KFile = 0x4A505820UL,
/// Public file format constant: 'TIFF'
kXMP_TIFFFile = 0x54494646UL,
/// Public file format constant: 'GIF '
kXMP_GIFFile = 0x47494620UL,
/// Public file format constant: 'PNG '
kXMP_PNGFile = 0x504E4720UL,
/// Public file format constant: 'SWF '
kXMP_SWFFile = 0x53574620UL,
/// Public file format constant: 'FLA '
kXMP_FLAFile = 0x464C4120UL,
/// Public file format constant: 'FLV '
kXMP_FLVFile = 0x464C5620UL,
/// Public file format constant: 'MOV ', Quicktime
kXMP_MOVFile = 0x4D4F5620UL,
/// Public file format constant: 'AVI '
kXMP_AVIFile = 0x41564920UL,
/// Public file format constant: 'CIN ', Cineon
kXMP_CINFile = 0x43494E20UL,
/// Public file format constant: 'WAV '
kXMP_WAVFile = 0x57415620UL,
/// Public file format constant: 'MP3 '
kXMP_MP3File = 0x4D503320UL,
/// Public file format constant: 'SES ', Audition session
kXMP_SESFile = 0x53455320UL,
/// Public file format constant: 'CEL ', Audition loop
kXMP_CELFile = 0x43454C20UL,
/// Public file format constant: 'MPEG'
kXMP_MPEGFile = 0x4D504547UL,
/// Public file format constant: 'MP2 '
kXMP_MPEG2File = 0x4D503220UL,
/// Public file format constant: 'MP4 ', ISO 14494-12 and -14
kXMP_MPEG4File = 0x4D503420UL,
/// Public file format constant: 'WMAV', Windows Media Audio and Video
kXMP_WMAVFile = 0x574D4156UL,
/// Public file format constant: 'AIFF'
kXMP_AIFFFile = 0x41494646UL,
/// Public file format constant: 'P2 ', a collection not really a single file
kXMP_P2File = 0x50322020UL,
/// Public file format constant: 'XDCF', a collection not really a single file
kXMP_XDCAM_FAMFile = 0x58444346UL,
/// Public file format constant: 'XDCS', a collection not really a single file
kXMP_XDCAM_SAMFile = 0x58444353UL,
/// Public file format constant: 'XDCX', a collection not really a single file
kXMP_XDCAM_EXFile = 0x58444358UL,
/// Public file format constant: 'AVHD', a collection not really a single file
kXMP_AVCHDFile = 0x41564844UL,
/// Public file format constant: 'SHDV', a collection not really a single file
kXMP_SonyHDVFile = 0x53484456UL,
/// Public file format constant: 'HTML'
kXMP_HTMLFile = 0x48544D4CUL,
/// Public file format constant: 'XML '
kXMP_XMLFile = 0x584D4C20UL,
/// Public file format constant: 'text'
kXMP_TextFile = 0x74657874UL,
// -------------------------------
// Adobe application file formats.
/// Adobe application file format constant: 'PSD '
kXMP_PhotoshopFile = 0x50534420UL,
/// Adobe application file format constant: 'AI '
kXMP_IllustratorFile = 0x41492020UL,
/// Adobe application file format constant: 'INDD'
kXMP_InDesignFile = 0x494E4444UL,
/// Adobe application file format constant: 'AEP '
kXMP_AEProjectFile = 0x41455020UL,
/// Adobe application file format constant: 'AET ', After Effects Project Template
kXMP_AEProjTemplateFile = 0x41455420UL,
/// Adobe application file format constant: 'FFX '
kXMP_AEFilterPresetFile = 0x46465820UL,
/// Adobe application file format constant: 'NCOR'
kXMP_EncoreProjectFile = 0x4E434F52UL,
/// Adobe application file format constant: 'PRPJ'
kXMP_PremiereProjectFile = 0x5052504AUL,
/// Adobe application file format constant: 'PRTL'
kXMP_PremiereTitleFile = 0x5052544CUL,
/// Adobe application file format constant: 'UCF ', Universal Container Format
kXMP_UCFFile = 0x55434620UL,
// -------
// Others.
/// Unknown file format constant: ' '
kXMP_UnknownFile = 0x20202020UL
};
/// Type for file format identification constants. See \c #kXMP_PDFFile and following.
typedef XMP_Uns32 XMP_FileFormat;
// -------------------------------------------------------------------------------------------------
/// Byte-order masks, do not use directly
enum {
kXMP_CharLittleEndianMask = 1,
kXMP_Char16BitMask = 2,
kXMP_Char32BitMask = 4
};
/// Constants to allow easy testing for 16/32 bit and big/little endian.
enum {
/// 8-bit
kXMP_Char8Bit = 0,
/// 16-bit big-endian
kXMP_Char16BitBig = kXMP_Char16BitMask,
/// 16-bit little-endian
kXMP_Char16BitLittle = kXMP_Char16BitMask | kXMP_CharLittleEndianMask,
/// 32-bit big-endian
kXMP_Char32BitBig = kXMP_Char32BitMask,
/// 32-bit little-endian
kXMP_Char32BitLittle = kXMP_Char32BitMask | kXMP_CharLittleEndianMask,
/// Variable or not-yet-known cases
kXMP_CharUnknown = 1
};
/// \name Macros to test components of the character form mask
/// @{
///
/// \def XMP_CharFormIs16Bit
/// \brief Macro reports the encoding of a character.
/// \param f The character to check.
///
/// \def XMP_CharFormIs32Bit
/// \brief Macro reports the encoding of a character.
/// \param f The character to check.
///
/// \def XMP_CharFormIsBigEndian
/// \brief Macro reports the byte-order of a character.
/// \param f The character to check.
///
/// \def XMP_CharFormIsLittleEndian
/// \brief Macro reports the byte-order of a character.
/// \param f The character to check.
///
/// \def XMP_GetCharSize
/// \brief Macro reports the byte-size of a character.
/// \param f The character to check.
///
/// \def XMP_CharToSerializeForm
/// \brief Macro converts \c XMP_Uns8 to \c XMP_OptionBits.
/// \param cf The character to convert.
///
/// \def XMP_CharFromSerializeForm
/// \brief Macro converts \c XMP_OptionBits to \c XMP_Uns8.
/// \param sf The character to convert.
///
/// @}
#define XMP_CharFormIs16Bit(f) ( ((int)(f) & kXMP_Char16BitMask) != 0 )
#define XMP_CharFormIs32Bit(f) ( ((int)(f) & kXMP_Char32BitMask) != 0 )
#define XMP_CharFormIsBigEndian(f) ( ((int)(f) & kXMP_CharLittleEndianMask) == 0 )
#define XMP_CharFormIsLittleEndian(f) ( ((int)(f) & kXMP_CharLittleEndianMask) != 0 )
#define XMP_GetCharSize(f) ( ((int)(f)&6) == 0 ? 1 : (int)(f)&6 )
#define XMP_CharToSerializeForm(cf) ( (XMP_OptionBits)(cf) )
#define XMP_CharFromSerializeForm(sf) ( (XMP_Uns8)(sf) )
/// \def kXMPFiles_UnknownOffset
/// \brief Constant for an unknown packet offset within a file.
#define kXMPFiles_UnknownOffset ((XMP_Int64)-1)
/// \def kXMPFiles_UnknownLength
/// \brief Constant for an unknown packet length within a file.
#define kXMPFiles_UnknownLength ((XMP_Int32)-1)
/// XMP packet description
struct XMP_PacketInfo {
/// Packet offset in the file in bytes, -1 if unknown.
XMP_Int64 offset;
/// Packet length in the file in bytes, -1 if unknown.
XMP_Int32 length;
/// Packet padding size in bytes, zero if unknown.
XMP_Int32 padSize; // Zero if unknown.
/// Character format using the values \c kXMP_Char8Bit, \c kXMP_Char16BitBig, etc.
XMP_Uns8 charForm;
/// True if there is a packet wrapper and the trailer says writeable by dumb packet scanners.
XMP_Bool writeable;
/// True if there is a packet wrapper, the "<?xpacket...>" XML processing instructions.
XMP_Bool hasWrapper;
/// Padding to make the struct's size be a multiple 4.
XMP_Uns8 pad;
/// Default constructor.
XMP_PacketInfo() : offset(kXMPFiles_UnknownOffset), length(kXMPFiles_UnknownLength),
padSize(0), charForm(0), writeable(0), hasWrapper(0), pad(0) {};
};
/// Version of the XMP_PacketInfo type
enum {
/// Version of the XMP_PacketInfo type
kXMP_PacketInfoVersion = 3
};
// -------------------------------------------------------------------------------------------------
/// Values for \c XMP_ThumbnailInfo::tnailFormat.
enum {
/// The thumbnail data has an unknown format.
kXMP_UnknownTNail = 0,
/// The thumbnail data is a JPEG stream, presumably compressed.
kXMP_JPEGTNail = 1,
/// The thumbnail data is a TIFF stream, presumably uncompressed.
kXMP_TIFFTNail = 2,
/// The thumbnail data is in the format of Photoshop Image Resource 1036.
kXMP_PShopTNail = 3
};
/// Thumbnail descriptor
struct XMP_ThumbnailInfo {
/// The format of the containing file.
XMP_FileFormat fileFormat;
/// Full image size in pixels.
XMP_Uns32 fullWidth, fullHeight;
/// Thumbnail image size in pixels.
XMP_Uns32 tnailWidth, tnailHeight;
/// Orientation of full image and thumbnail, as defined by Exif for tag 274.
XMP_Uns16 fullOrientation, tnailOrientation;
/// Raw image data from the host file, valid for life of the owning \c XMPFiles object. Do not modify!
const XMP_Uns8 * tnailImage;
/// The size in bytes of the thumbnail image data.
XMP_Uns32 tnailSize;
/// The format of the thumbnail image data.
XMP_Uns8 tnailFormat;
/// Padding to make the struct's size be a multiple 4.
XMP_Uns8 pad1, pad2, pad3;
/// Default constructor.
XMP_ThumbnailInfo() : fileFormat(kXMP_UnknownFile), fullWidth(0), fullHeight(0),
tnailWidth(0), tnailHeight(0), fullOrientation(0), tnailOrientation(0),
tnailImage(0), tnailSize(0), tnailFormat(kXMP_UnknownTNail) {};
};
/// Version of the XMP_ThumbnailInfo type
enum {
/// Version of the XMP_ThumbnailInfo type
kXMP_ThumbnailInfoVersion = 1
};
// -------------------------------------------------------------------------------------------------
/// Option bit flags for \c TXMPFiles::Initialize().
enum {
/// Do not initialize QuickTime, the client will.
kXMPFiles_NoQuickTimeInit = 0x0001
};
/// Option bit flags for \c TXMPFiles::GetFormatInfo().
enum {
/// Can inject first-time XMP into an existing file.
kXMPFiles_CanInjectXMP = 0x00000001,
/// Can expand XMP or other metadata in an existing file.
kXMPFiles_CanExpand = 0x00000002,
/// Can copy one file to another, writing new metadata.
kXMPFiles_CanRewrite = 0x00000004,
/// Can expand, but prefers in-place update.
kXMPFiles_PrefersInPlace = 0x00000008,
/// Supports reconciliation between XMP and other forms.
kXMPFiles_CanReconcile = 0x00000010,
/// Allows access to just the XMP, ignoring other forms.
kXMPFiles_AllowsOnlyXMP = 0x00000020,
/// File handler returns raw XMP packet information.
kXMPFiles_ReturnsRawPacket = 0x00000040,
/// File handler returns native thumbnail.
kXMPFiles_ReturnsTNail = 0x00000080,
/// The file handler does the file open and close.
kXMPFiles_HandlerOwnsFile = 0x00000100,
/// The file handler allows crash-safe file updates.
kXMPFiles_AllowsSafeUpdate = 0x00000200,
/// The file format needs the XMP packet to be read-only.
kXMPFiles_NeedsReadOnlyPacket = 0x00000400,
/// The file handler uses a "sidecar" file for the XMP.
kXMPFiles_UsesSidecarXMP = 0x00000800,
/// The format is folder oriented, for example the P2 video format.
kXMPFiles_FolderBasedFormat = 0x00001000
};
/// Option bit flags for \c TXMPFiles::OpenFile().
enum {
/// Open for read-only access.
kXMPFiles_OpenForRead = 0x00000001,
/// Open for reading and writing.
kXMPFiles_OpenForUpdate = 0x00000002,
/// Only the XMP is wanted, allows space/time optimizations.
kXMPFiles_OpenOnlyXMP = 0x00000004,
/// Cache thumbnail if possible, \c TXMPFiles::GetThumbnail() will be called.
kXMPFiles_OpenCacheTNail = 0x00000008,
/// Be strict about locating XMP and reconciling with other forms.
kXMPFiles_OpenStrictly = 0x00000010,
/// Require the use of a smart handler.
kXMPFiles_OpenUseSmartHandler = 0x00000020,
/// Force packet scanning, do not use a smart handler.
kXMPFiles_OpenUsePacketScanning = 0x00000040,
/// Only packet scan files "known" to need scanning.
kXMPFiles_OpenLimitedScanning = 0x00000080,
/// Attempt to repair a file opened for update, default is to not open (throw an exception).
kXMPFiles_OpenRepairFile = 0x00000100,
/// Set if calling from background thread.
kXMPFiles_OpenInBackground = 0x10000000
};
// A note about kXMPFiles_OpenInBackground. The XMPFiles handler for .mov files currently uses
// QuickTime. On Macintosh, calls to Enter/ExitMovies versus Enter/ExitMoviesOnThread must be made.
// This option is used to signal background use so that the .mov handler can behave appropriately.
/// Option bit flags for \c TXMPFiles::CloseFile().
enum {
/// Write into a temporary file and swap for crash safety.
kXMPFiles_UpdateSafely = 0x0001
};
// =================================================================================================
// Exception codes
// ===============
/// \name Errors Exception handling
/// @{
///
/// XMP Tookit errors result in throwing an \c XMP_Error exception. Any exception thrown within the
/// XMP Toolkit is caught in the toolkit and rethrown as an \c XMP_Error.
///
/// The \c XMP_Error class contains a numeric code and an English explanation. New numeric codes may
/// be added at any time. There are typically many possible explanations for each numeric code. The
/// explanations try to be precise about the specific circumstances causing the error.
///
/// \note The explanation string is for debugging use only. It must not be shown to users in a
/// final product. It is written for developers not users, and never localized.
///
/// XMP Toolkit error, associates an error code with a descriptive error string.
class XMP_Error {
public:
/// @brief Constructor for an XMP_Error.
///
/// @param _id The numeric code.
///
/// @param _errMsg The descriptive string, for debugging use only. It must not be shown to users
/// in a final product. It is written for developers, not users, and never localized.
XMP_Error ( XMP_Int32 _id, XMP_StringPtr _errMsg ) : id(_id), errMsg(_errMsg) {};
/// Retrieves the numeric code from an XMP_Error.
inline XMP_Int32 GetID() const { return id; };
/// Retrieves the descriptive string from an XMP_Error.
inline XMP_StringPtr GetErrMsg() const { return errMsg; };
private:
/// Exception code. See constants \c #kXMPErr_Unknown and following.
XMP_Int32 id;
/// Descriptive string, for debugging use only. It must not be shown to users in a final
/// product. It is written for developers, not users, and never localized.
XMP_StringPtr errMsg;
};
/// Exception code constants
enum {
// --------------------
// Generic error codes.
/// Generic unknown error
kXMPErr_Unknown = 0,
/// Generic undefined error
kXMPErr_TBD = 1,
/// Generic unavailable error
kXMPErr_Unavailable = 2,
/// Generic bad object error
kXMPErr_BadObject = 3,
/// Generic bad parameter error
kXMPErr_BadParam = 4,
/// Generic bad value error
kXMPErr_BadValue = 5,
/// Generic assertion failure
kXMPErr_AssertFailure = 6,
/// Generic enforcement failure
kXMPErr_EnforceFailure = 7,
/// Generic unimplemented error
kXMPErr_Unimplemented = 8,
/// Generic internal failure
kXMPErr_InternalFailure = 9,
/// Generic deprecated error
kXMPErr_Deprecated = 10,
/// Generic external failure
kXMPErr_ExternalFailure = 11,
/// Generic user abort error
kXMPErr_UserAbort = 12,
/// Generic standard exception
kXMPErr_StdException = 13,
/// Generic unknown exception
kXMPErr_UnknownException = 14,
/// Generic out-of-memory error
kXMPErr_NoMemory = 15,
// ------------------------------------
// More specific parameter error codes.
/// Bad schema parameter
kXMPErr_BadSchema = 101,
/// Bad XPath parameter
kXMPErr_BadXPath = 102,
/// Bad options parameter
kXMPErr_BadOptions = 103,
/// Bad index parameter
kXMPErr_BadIndex = 104,
/// Bad iteration position
kXMPErr_BadIterPosition = 105,
/// XML parsing error
kXMPErr_BadParse = 106,
/// Serialization error
kXMPErr_BadSerialize = 107,
/// File format error
kXMPErr_BadFileFormat = 108,
/// No file handler found for format
kXMPErr_NoFileHandler = 109,
/// Data too large for JPEG file format
kXMPErr_TooLargeForJPEG = 110,
// -----------------------------------------------
// File format and internal structure error codes.
/// XML format error
kXMPErr_BadXML = 201,
/// RDF format error
kXMPErr_BadRDF = 202,
/// XMP format error
kXMPErr_BadXMP = 203,
/// Empty iterator
kXMPErr_EmptyIterator = 204,
/// Unicode error
kXMPErr_BadUnicode = 205,
/// TIFF format error
kXMPErr_BadTIFF = 206,
/// JPEG format error
kXMPErr_BadJPEG = 207,
/// PSD format error
kXMPErr_BadPSD = 208,
/// PSIR format error
kXMPErr_BadPSIR = 209,
/// IPTC format error
kXMPErr_BadIPTC = 210,
/// MPEG format error
kXMPErr_BadMPEG = 211
};
/// @}
// =================================================================================================
// Client callbacks
// ================
// -------------------------------------------------------------------------------------------------
/// \name Special purpose callback functions
/// @{
/// A signed 32-bit integer used as a status result for the output callback routine,
/// \c XMP_TextOutputProc. Zero means no error, all other values except -1 are private to the callback.
/// The callback is wrapped to prevent exceptions being thrown across DLL boundaries. Any exceptions
/// thrown out of the callback cause a return status of -1.
typedef XMP_Int32 XMP_Status;
// -------------------------------------------------------------------------------------------------
/// The signature of a client-defined callback for text output from XMP Toolkit debugging
/// operations. The callback is invoked one or more times for each line of output. The end of a line
/// is signaled by a '\\n' character at the end of the buffer. Formatting newlines are never present
/// in the middle of a buffer, but values of properties might contain any UTF-8 characters.
///
/// @param refCon A pointer to client-defined data passed to the TextOutputProc.
///
/// @param buffer A string containing one line of output.
///
/// @param bufferSize The number of characters in the output buffer.
///
/// @return A success/fail status value. Any failure result aborts the output.
///
/// @see \c TXMPMeta::DumpObject()
typedef XMP_Status (* XMP_TextOutputProc) ( void * refCon,
XMP_StringPtr buffer,
XMP_StringLen bufferSize );
// -------------------------------------------------------------------------------------------------
/// The signature of a client-defined callback to check for a user request to abort a time-consuming
/// operation within XMPFiles.
///
/// @param arg A pointer to caller-defined data passed from the registration call.
///
/// @return True to abort the current operation, which results in an exception being thrown.
///
/// @see \c TXMPFiles::SetAbortProc()
typedef bool (* XMP_AbortProc) ( void * arg ); // Used by .
/// @}
// =================================================================================================
// Stuff with no better place to be
// ================================
/// XMP Toolkit version information
typedef struct XMP_VersionInfo {
/// The primary release number, the "1" in version "1.2.3".
XMP_Uns8 major;
/// The secondary release number, the "2" in version "1.2.3".
XMP_Uns8 minor;
/// The tertiary release number, the "3" in version "1.2.3".
XMP_Uns8 micro;
/// A 0/1 boolean value, true if this is a debug build.
XMP_Bool isDebug;
/// A rolling build number, monotonically increasing in a release.
XMP_Uns32 build;
/// Individual feature implementation flags.
XMP_Uns32 flags;
/// A comprehensive version information string.
XMP_StringPtr message;
} XMP_VersionInfo;
// =================================================================================================
#if __cplusplus
} // extern "C"
#endif
} //namespace
#endif // __XMP_Const_h__
diff --git a/core/libs/facesengine/dnnface/dnn_base/array_kernel.h b/core/libs/facesengine/dnnface/dnn_base/array_kernel.h
index 724b1258d1..ecef1d0240 100644
--- a/core/libs/facesengine/dnnface/dnn_base/array_kernel.h
+++ b/core/libs/facesengine/dnnface/dnn_base/array_kernel.h
@@ -1,771 +1,771 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-08-08
* Description : Base functions for dnn module, can be used for face recognition,
* all codes are ported from dlib library (http://dlib.net/)
*
* Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DLIB_ARRAY_KERNEl_2_
#define DLIB_ARRAY_KERNEl_2_
#include "enumerable.h"
#include "algs.h"
#include "serialize.h"
#include "dnn_sort.h"
#include "is_kind.h"
template <
typename T,
typename mem_manager = default_memory_manager
>
class array : public enumerable<T>
{
/*!
INITIAL VALUE
- array_size == 0
- max_array_size == 0
- array_elements == 0
- pos == 0
- last_pos == 0
- _at_start == true
CONVENTION
- array_size == size()
- max_array_size == max_size()
- if (max_array_size > 0)
- array_elements == pointer to max_array_size elements of type T
- else
- array_elements == 0
- if (array_size > 0)
- last_pos == array_elements + array_size - 1
- else
- last_pos == 0
- at_start() == _at_start
- current_element_valid() == pos != 0
- if (current_element_valid()) then
- *pos == element()
!*/
public:
// These typedefs are here for backwards compatibility with old versions of dlib.
typedef array kernel_1a;
typedef array kernel_1a_c;
typedef array kernel_2a;
typedef array kernel_2a_c;
typedef array sort_1a;
typedef array sort_1a_c;
typedef array sort_1b;
typedef array sort_1b_c;
typedef array sort_2a;
typedef array sort_2a_c;
typedef array sort_2b;
typedef array sort_2b_c;
typedef array expand_1a;
typedef array expand_1a_c;
typedef array expand_1b;
typedef array expand_1b_c;
typedef array expand_1c;
typedef array expand_1c_c;
typedef array expand_1d;
typedef array expand_1d_c;
typedef T type;
typedef T value_type;
typedef mem_manager mem_manager_type;
array (
) :
array_size(0),
max_array_size(0),
array_elements(0),
pos(0),
last_pos(0),
_at_start(true)
{}
array(
array&& item
) : array()
{
swap(item);
}
array& operator=(
array&& item
)
{
swap(item);
return *this;
}
explicit array (
unsigned long new_size
) :
array_size(0),
max_array_size(0),
array_elements(0),
pos(0),
last_pos(0),
_at_start(true)
{
resize(new_size);
}
~array (
);
void clear (
);
inline const T& operator[] (
unsigned long pos
) const;
inline T& operator[] (
unsigned long pos
);
void set_size (
unsigned long size
);
inline unsigned long max_size(
) const;
void set_max_size(
unsigned long max
);
void swap (
array& item
);
// functions from the enumerable interface
inline unsigned long size (
) const;
inline bool at_start (
) const;
inline void reset (
) const;
bool current_element_valid (
) const;
inline const T& element (
) const;
inline T& element (
);
bool move_next (
) const;
void sort (
);
void resize (
unsigned long new_size
);
const T& back (
) const;
T& back (
);
void pop_back (
);
void pop_back (
T& item
);
void push_back (
T& item
);
typedef T* iterator;
typedef const T* const_iterator;
iterator begin() { return array_elements; }
const_iterator begin() const { return array_elements; }
iterator end() { return array_elements+array_size; }
const_iterator end() const { return array_elements+array_size; }
private:
typename mem_manager::template rebind<T>::other pool;
// data members
unsigned long array_size;
unsigned long max_array_size;
T* array_elements;
mutable T* pos;
T* last_pos;
mutable bool _at_start;
// restricted functions
array(array<T>&); // copy constructor
array<T>& operator=(array<T>&); // assignment operator
};
template <
typename T,
typename mem_manager
>
inline void swap (
array<T,mem_manager>& a,
array<T,mem_manager>& b
) { a.swap(b); }
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void serialize (
const array<T,mem_manager>& item,
std::ostream& out
)
{
try
{
serialize(item.max_size(),out);
serialize(item.size(),out);
for (unsigned long i = 0; i < item.size(); ++i)
serialize(item[i],out);
}
catch (serialization_error e)
{
throw serialization_error(e.info + "\n while serializing object of type array");
}
}
template <
typename T,
typename mem_manager
>
void deserialize (
array<T,mem_manager>& item,
std::istream& in
)
{
try
{
unsigned long max_size, size;
deserialize(max_size,in);
deserialize(size,in);
item.set_max_size(max_size);
item.set_size(size);
for (unsigned long i = 0; i < size; ++i)
deserialize(item[i],in);
}
catch (serialization_error e)
{
item.clear();
throw serialization_error(e.info + "\n while deserializing object of type array");
}
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
array<T,mem_manager>::
~array (
)
{
if (array_elements)
{
pool.deallocate_array(array_elements);
}
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::
clear (
)
{
reset();
last_pos = 0;
array_size = 0;
if (array_elements)
{
pool.deallocate_array(array_elements);
}
array_elements = 0;
max_array_size = 0;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
const T& array<T,mem_manager>::
operator[] (
unsigned long pos
) const
{
// make sure requires clause is not broken
DLIB_ASSERT( pos < this->size() ,
"\tconst T& array::operator[]"
<< "\n\tpos must < size()"
<< "\n\tpos: " << pos
<< "\n\tsize(): " << this->size()
<< "\n\tthis: " << this
);
return array_elements[pos];
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
T& array<T,mem_manager>::
operator[] (
unsigned long pos
)
{
// make sure requires clause is not broken
DLIB_ASSERT( pos < this->size() ,
"\tT& array::operator[]"
<< "\n\tpos must be < size()"
<< "\n\tpos: " << pos
<< "\n\tsize(): " << this->size()
<< "\n\tthis: " << this
);
return array_elements[pos];
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::
set_size (
unsigned long size
)
{
// make sure requires clause is not broken
DLIB_CASSERT(( size <= this->max_size() ),
"\tvoid array::set_size"
<< "\n\tsize must be <= max_size()"
<< "\n\tsize: " << size
<< "\n\tmax size: " << this->max_size()
<< "\n\tthis: " << this
);
reset();
array_size = size;
if (size > 0)
last_pos = array_elements + size - 1;
else
last_pos = 0;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
unsigned long array<T,mem_manager>::
size (
) const
{
return array_size;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::
set_max_size(
unsigned long max
)
{
reset();
array_size = 0;
last_pos = 0;
if (max != 0)
{
// if new max size is different
if (max != max_array_size)
{
if (array_elements)
{
pool.deallocate_array(array_elements);
}
- // try to get more memroy
+ // try to get more memory
try { array_elements = pool.allocate_array(max); }
catch (...) { array_elements = 0; max_array_size = 0; throw; }
max_array_size = max;
}
}
// if the array is being made to be zero
else
{
if (array_elements)
pool.deallocate_array(array_elements);
max_array_size = 0;
array_elements = 0;
}
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
unsigned long array<T,mem_manager>::
max_size (
) const
{
return max_array_size;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::swap(array<T,mem_manager>& item)
{
unsigned long array_size_temp = item.array_size;
unsigned long max_array_size_temp = item.max_array_size;
T* array_elements_temp = item.array_elements;
item.array_size = array_size;
item.max_array_size = max_array_size;
item.array_elements = array_elements;
array_size = array_size_temp;
max_array_size = max_array_size_temp;
array_elements = array_elements_temp;
exchange(_at_start,item._at_start);
exchange(pos,item.pos);
exchange(last_pos,item.last_pos);
pool.swap(item.pool);
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// enumerable function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
bool array<T,mem_manager>::at_start() const
{
return _at_start;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::reset () const
{
_at_start = true;
pos = 0;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
bool array<T,mem_manager>::current_element_valid () const
{
return pos != 0;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
const T& array<T,mem_manager>::element () const
{
// make sure requires clause is not broken
DLIB_ASSERT(this->current_element_valid(),
"\tconst T& array::element()"
<< "\n\tThe current element must be valid if you are to access it."
<< "\n\tthis: " << this
);
return *pos;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
T& array<T,mem_manager>::element()
{
// make sure requires clause is not broken
DLIB_ASSERT(this->current_element_valid(),
"\tT& array::element()"
<< "\n\tThe current element must be valid if you are to access it."
<< "\n\tthis: " << this
);
return *pos;
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
bool array<T,mem_manager>::move_next() const
{
if (!_at_start)
{
if (pos < last_pos)
{
++pos;
return true;
}
else
{
pos = 0;
return false;
}
}
else
{
_at_start = false;
if (array_size > 0)
{
pos = array_elements;
return true;
}
else
{
return false;
}
}
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// Yet more functions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::sort()
{
if (this->size() > 1)
{
// call the quick sort function for arrays that is in algs.h
qsort_array(*this,0,this->size()-1);
}
this->reset();
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::resize(unsigned long new_size)
{
if (this->max_size() < new_size)
{
array temp;
temp.set_max_size(new_size);
temp.set_size(new_size);
for (unsigned long i = 0; i < this->size(); ++i)
{
exchange((*this)[i],temp[i]);
}
temp.swap(*this);
}
else
{
this->set_size(new_size);
}
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
T& array<T,mem_manager>::back()
{
// make sure requires clause is not broken
DLIB_ASSERT( this->size() > 0 ,
"\tT& array::back()"
<< "\n\tsize() must be bigger than 0"
<< "\n\tsize(): " << this->size()
<< "\n\tthis: " << this
);
return (*this)[this->size()-1];
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
const T& array<T,mem_manager>::back() const
{
// make sure requires clause is not broken
DLIB_ASSERT( this->size() > 0 ,
"\tconst T& array::back()"
<< "\n\tsize() must be bigger than 0"
<< "\n\tsize(): " << this->size()
<< "\n\tthis: " << this
);
return (*this)[this->size()-1];
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::pop_back(T& item)
{
// make sure requires clause is not broken
DLIB_ASSERT( this->size() > 0 ,
"\tvoid array::pop_back()"
<< "\n\tsize() must be bigger than 0"
<< "\n\tsize(): " << this->size()
<< "\n\tthis: " << this
);
exchange(item,(*this)[this->size()-1]);
this->set_size(this->size()-1);
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::pop_back()
{
// make sure requires clause is not broken
DLIB_ASSERT( this->size() > 0 ,
"\tvoid array::pop_back()"
<< "\n\tsize() must be bigger than 0"
<< "\n\tsize(): " << this->size()
<< "\n\tthis: " << this
);
this->set_size(this->size()-1);
}
// ----------------------------------------------------------------------------------------
template <
typename T,
typename mem_manager
>
void array<T,mem_manager>::push_back(T& item)
{
if (this->max_size() == this->size())
{
// double the size of the array
array temp;
temp.set_max_size(this->size()*2 + 1);
temp.set_size(this->size()+1);
for (unsigned long i = 0; i < this->size(); ++i)
{
exchange((*this)[i],temp[i]);
}
exchange(item,temp[temp.size()-1]);
temp.swap(*this);
}
else
{
this->set_size(this->size()+1);
exchange(item,(*this)[this->size()-1]);
}
}
#endif // DLIB_ARRAY_KERNEl_2_
diff --git a/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.cpp b/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.cpp
index 995784ea1c..9c5a93f443 100644
--- a/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.cpp
+++ b/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.cpp
@@ -1,424 +1,424 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-08-08
* Description : Base functions for dnn module, can be used for face recognition,
* all codes are ported from dlib library (http://dlib.net/)
*
* Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DLIB_BASE64_KERNEL_1_CPp_
#define DLIB_BASE64_KERNEL_1_CPp_
#include "base64_kernel_1.h"
#include <iostream>
#include <sstream>
#include <climits>
// ----------------------------------------------------------------------------------------
base64::line_ending_type base64::
line_ending (
) const
{
return eol_style;
}
// ----------------------------------------------------------------------------------------
void base64::
set_line_ending (
line_ending_type eol_style_
)
{
eol_style = eol_style_;
}
// ----------------------------------------------------------------------------------------
base64::
base64 (
) :
encode_table(0),
decode_table(0),
bad_value(100),
eol_style(LF)
{
try
{
encode_table = new char[64];
decode_table = new unsigned char[UCHAR_MAX];
}
catch (...)
{
if (encode_table) delete [] encode_table;
if (decode_table) delete [] decode_table;
throw;
}
// now set up the tables with the right stuff
encode_table[0] = 'A';
encode_table[17] = 'R';
encode_table[34] = 'i';
encode_table[51] = 'z';
encode_table[1] = 'B';
encode_table[18] = 'S';
encode_table[35] = 'j';
encode_table[52] = '0';
encode_table[2] = 'C';
encode_table[19] = 'T';
encode_table[36] = 'k';
encode_table[53] = '1';
encode_table[3] = 'D';
encode_table[20] = 'U';
encode_table[37] = 'l';
encode_table[54] = '2';
encode_table[4] = 'E';
encode_table[21] = 'V';
encode_table[38] = 'm';
encode_table[55] = '3';
encode_table[5] = 'F';
encode_table[22] = 'W';
encode_table[39] = 'n';
encode_table[56] = '4';
encode_table[6] = 'G';
encode_table[23] = 'X';
encode_table[40] = 'o';
encode_table[57] = '5';
encode_table[7] = 'H';
encode_table[24] = 'Y';
encode_table[41] = 'p';
encode_table[58] = '6';
encode_table[8] = 'I';
encode_table[25] = 'Z';
encode_table[42] = 'q';
encode_table[59] = '7';
encode_table[9] = 'J';
encode_table[26] = 'a';
encode_table[43] = 'r';
encode_table[60] = '8';
encode_table[10] = 'K';
encode_table[27] = 'b';
encode_table[44] = 's';
encode_table[61] = '9';
encode_table[11] = 'L';
encode_table[28] = 'c';
encode_table[45] = 't';
encode_table[62] = '+';
encode_table[12] = 'M';
encode_table[29] = 'd';
encode_table[46] = 'u';
encode_table[63] = '/';
encode_table[13] = 'N';
encode_table[30] = 'e';
encode_table[47] = 'v';
encode_table[14] = 'O';
encode_table[31] = 'f';
encode_table[48] = 'w';
encode_table[15] = 'P';
encode_table[32] = 'g';
encode_table[49] = 'x';
encode_table[16] = 'Q';
encode_table[33] = 'h';
encode_table[50] = 'y';
// we can now fill out the decode_table by using the encode_table
for (int i = 0; i < UCHAR_MAX; ++i)
{
decode_table[i] = bad_value;
}
for (unsigned char i = 0; i < 64; ++i)
{
decode_table[(unsigned char)encode_table[i]] = i;
}
}
// ----------------------------------------------------------------------------------------
base64::
~base64 (
)
{
delete [] encode_table;
delete [] decode_table;
}
// ----------------------------------------------------------------------------------------
void base64::
encode (
std::istream& in_,
std::ostream& out_
) const
{
using namespace std;
streambuf& in = *in_.rdbuf();
streambuf& out = *out_.rdbuf();
unsigned char inbuf[3];
unsigned char outbuf[4];
streamsize status = in.sgetn(reinterpret_cast<char*>(&inbuf),3);
unsigned char c1, c2, c3, c4, c5, c6;
int counter = 19;
// while we haven't hit the end of the input stream
while (status != 0)
{
if (counter == 0)
{
counter = 19;
// write a newline
char ch;
switch (eol_style)
{
case CR:
ch = '\r';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
break;
case LF:
ch = '\n';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
break;
case CRLF:
ch = '\r';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
ch = '\n';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
break;
default:
DLIB_CASSERT(false,"this should never happen");
}
}
--counter;
if (status == 3)
{
// encode the bytes in inbuf to base64 and write them to the output stream
c1 = inbuf[0]&0xfc;
c2 = inbuf[0]&0x03;
c3 = inbuf[1]&0xf0;
c4 = inbuf[1]&0x0f;
c5 = inbuf[2]&0xc0;
c6 = inbuf[2]&0x3f;
outbuf[0] = c1>>2;
outbuf[1] = (c2<<4)|(c3>>4);
outbuf[2] = (c4<<2)|(c5>>6);
outbuf[3] = c6;
outbuf[0] = encode_table[outbuf[0]];
outbuf[1] = encode_table[outbuf[1]];
outbuf[2] = encode_table[outbuf[2]];
outbuf[3] = encode_table[outbuf[3]];
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
// get 3 more input bytes
status = in.sgetn(reinterpret_cast<char*>(&inbuf),3);
continue;
}
else if (status == 2)
{
// we are at the end of the input stream and need to add some padding
// encode the bytes in inbuf to base64 and write them to the output stream
c1 = inbuf[0]&0xfc;
c2 = inbuf[0]&0x03;
c3 = inbuf[1]&0xf0;
c4 = inbuf[1]&0x0f;
c5 = 0;
outbuf[0] = c1>>2;
outbuf[1] = (c2<<4)|(c3>>4);
outbuf[2] = (c4<<2)|(c5>>6);
outbuf[3] = '=';
outbuf[0] = encode_table[outbuf[0]];
outbuf[1] = encode_table[outbuf[1]];
outbuf[2] = encode_table[outbuf[2]];
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
break;
}
else // in this case status must be 1
{
// we are at the end of the input stream and need to add some padding
// encode the bytes in inbuf to base64 and write them to the output stream
c1 = inbuf[0]&0xfc;
c2 = inbuf[0]&0x03;
c3 = 0;
outbuf[0] = c1>>2;
outbuf[1] = (c2<<4)|(c3>>4);
outbuf[2] = '=';
outbuf[3] = '=';
outbuf[0] = encode_table[outbuf[0]];
outbuf[1] = encode_table[outbuf[1]];
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
break;
}
} // while (status != 0)
// make sure the stream buffer flushes to its I/O channel
out.pubsync();
}
// ----------------------------------------------------------------------------------------
void base64::
decode (
std::istream& in_,
std::ostream& out_
) const
{
using namespace std;
streambuf& in = *in_.rdbuf();
streambuf& out = *out_.rdbuf();
unsigned char inbuf[4];
unsigned char outbuf[3];
int inbuf_pos = 0;
streamsize status = in.sgetn(reinterpret_cast<char*>(inbuf),1);
// only count this character if it isn't some kind of filler
if (status == 1 && decode_table[inbuf[0]] != bad_value )
++inbuf_pos;
unsigned char c1, c2, c3, c4, c5, c6;
streamsize outsize;
// while we haven't hit the end of the input stream
while (status != 0)
{
// if we have 4 valid characters
if (inbuf_pos == 4)
{
inbuf_pos = 0;
// this might be the end of the encoded data so we need to figure out if
// there was any padding applied.
outsize = 3;
if (inbuf[3] == '=')
{
if (inbuf[2] == '=')
outsize = 1;
else
outsize = 2;
}
// decode the incoming characters
inbuf[0] = decode_table[inbuf[0]];
inbuf[1] = decode_table[inbuf[1]];
inbuf[2] = decode_table[inbuf[2]];
inbuf[3] = decode_table[inbuf[3]];
// now pack these guys into bytes rather than 6 bit chunks
c1 = inbuf[0]<<2;
c2 = inbuf[1]>>4;
c3 = inbuf[1]<<4;
c4 = inbuf[2]>>2;
c5 = inbuf[2]<<6;
c6 = inbuf[3];
outbuf[0] = c1|c2;
outbuf[1] = c3|c4;
outbuf[2] = c5|c6;
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),outsize)!=outsize)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
}
// get more input characters
status = in.sgetn(reinterpret_cast<char*>(inbuf + inbuf_pos),1);
// only count this character if it isn't some kind of filler
if ((decode_table[inbuf[inbuf_pos]] != bad_value || inbuf[inbuf_pos] == '=') &&
status != 0)
++inbuf_pos;
} // while (status != 0)
if (inbuf_pos != 0)
{
ostringstream sout;
sout << inbuf_pos << " extra characters were found at the end of the encoded data."
<< " This may indicate that the data stream has been truncated.";
// this happens if we hit EOF in the middle of decoding a 24bit block.
throw decode_error(sout.str());
}
// make sure the stream buffer flushes to its I/O channel
out.pubsync();
}
// ----------------------------------------------------------------------------------------
#endif // DLIB_BASE64_KERNEL_1_CPp_
diff --git a/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.h b/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.h
index baeb86793d..3c718f3d87 100644
--- a/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.h
+++ b/core/libs/facesengine/dnnface/dnn_base/base64_kernel_1.h
@@ -1,452 +1,452 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-08-08
* Description : Base functions for dnn module, can be used for face recognition,
* all codes are ported from dlib library (http://dlib.net/)
*
* Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DLIB_BASE64_KERNEl_1_
#define DLIB_BASE64_KERNEl_1_
#include "algs.h"
#include <iosfwd>
class base64
{
/*!
INITIAL VALUE
- bad_value == 100
- encode_table == a pointer to an array of 64 chars
- where x is a 6 bit value the following is true:
- encode_table[x] == the base64 encoding of x
- decode_table == a pointer to an array of UCHAR_MAX chars
- where x is any char value:
- if (x is a valid character in the base64 coding scheme) then
- decode_table[x] == the 6 bit value that x encodes
- else
- decode_table[x] == bad_value
CONVENTION
- The state of this object never changes so just refer to its
initial value.
!*/
public:
// this is here for backwards compatibility with older versions of dlib.
typedef base64 kernel_1a;
class decode_error : public error { public:
decode_error( const std::string& e) : error(e) {}};
base64 (
) :
encode_table(0),
decode_table(0),
bad_value(100),
eol_style(LF)
{
try
{
encode_table = new char[64];
decode_table = new unsigned char[UCHAR_MAX];
}
catch (...)
{
if (encode_table) delete [] encode_table;
if (decode_table) delete [] decode_table;
throw;
}
// now set up the tables with the right stuff
encode_table[0] = 'A';
encode_table[17] = 'R';
encode_table[34] = 'i';
encode_table[51] = 'z';
encode_table[1] = 'B';
encode_table[18] = 'S';
encode_table[35] = 'j';
encode_table[52] = '0';
encode_table[2] = 'C';
encode_table[19] = 'T';
encode_table[36] = 'k';
encode_table[53] = '1';
encode_table[3] = 'D';
encode_table[20] = 'U';
encode_table[37] = 'l';
encode_table[54] = '2';
encode_table[4] = 'E';
encode_table[21] = 'V';
encode_table[38] = 'm';
encode_table[55] = '3';
encode_table[5] = 'F';
encode_table[22] = 'W';
encode_table[39] = 'n';
encode_table[56] = '4';
encode_table[6] = 'G';
encode_table[23] = 'X';
encode_table[40] = 'o';
encode_table[57] = '5';
encode_table[7] = 'H';
encode_table[24] = 'Y';
encode_table[41] = 'p';
encode_table[58] = '6';
encode_table[8] = 'I';
encode_table[25] = 'Z';
encode_table[42] = 'q';
encode_table[59] = '7';
encode_table[9] = 'J';
encode_table[26] = 'a';
encode_table[43] = 'r';
encode_table[60] = '8';
encode_table[10] = 'K';
encode_table[27] = 'b';
encode_table[44] = 's';
encode_table[61] = '9';
encode_table[11] = 'L';
encode_table[28] = 'c';
encode_table[45] = 't';
encode_table[62] = '+';
encode_table[12] = 'M';
encode_table[29] = 'd';
encode_table[46] = 'u';
encode_table[63] = '/';
encode_table[13] = 'N';
encode_table[30] = 'e';
encode_table[47] = 'v';
encode_table[14] = 'O';
encode_table[31] = 'f';
encode_table[48] = 'w';
encode_table[15] = 'P';
encode_table[32] = 'g';
encode_table[49] = 'x';
encode_table[16] = 'Q';
encode_table[33] = 'h';
encode_table[50] = 'y';
// we can now fill out the decode_table by using the encode_table
for (int i = 0; i < UCHAR_MAX; ++i)
{
decode_table[i] = bad_value;
}
for (unsigned char i = 0; i < 64; ++i)
{
decode_table[(unsigned char)encode_table[i]] = i;
}
};
//virtual ~base64 (
//);
~base64 ()
{
delete [] encode_table;
delete [] decode_table;
}
enum line_ending_type
{
CR, // i.e. "\r"
LF, // i.e. "\n"
CRLF // i.e. "\r\n"
};
line_ending_type line_ending (
) const
{
return eol_style;
};
void set_line_ending (
line_ending_type eol_style_
)
{
eol_style = eol_style_;
};
void encode (
std::istream& in_,
std::ostream& out_
) const
{
using namespace std;
streambuf& in = *in_.rdbuf();
streambuf& out = *out_.rdbuf();
unsigned char inbuf[3];
unsigned char outbuf[4];
streamsize status = in.sgetn(reinterpret_cast<char*>(&inbuf),3);
unsigned char c1, c2, c3, c4, c5, c6;
int counter = 19;
// while we haven't hit the end of the input stream
while (status != 0)
{
if (counter == 0)
{
counter = 19;
// write a newline
char ch;
switch (eol_style)
{
case CR:
ch = '\r';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
break;
case LF:
ch = '\n';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
break;
case CRLF:
ch = '\r';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
ch = '\n';
if (out.sputn(&ch,1)!=1)
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
break;
default:
DLIB_CASSERT(false,"this should never happen");
}
}
--counter;
if (status == 3)
{
// encode the bytes in inbuf to base64 and write them to the output stream
c1 = inbuf[0]&0xfc;
c2 = inbuf[0]&0x03;
c3 = inbuf[1]&0xf0;
c4 = inbuf[1]&0x0f;
c5 = inbuf[2]&0xc0;
c6 = inbuf[2]&0x3f;
outbuf[0] = c1>>2;
outbuf[1] = (c2<<4)|(c3>>4);
outbuf[2] = (c4<<2)|(c5>>6);
outbuf[3] = c6;
outbuf[0] = encode_table[outbuf[0]];
outbuf[1] = encode_table[outbuf[1]];
outbuf[2] = encode_table[outbuf[2]];
outbuf[3] = encode_table[outbuf[3]];
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
// get 3 more input bytes
status = in.sgetn(reinterpret_cast<char*>(&inbuf),3);
continue;
}
else if (status == 2)
{
// we are at the end of the input stream and need to add some padding
// encode the bytes in inbuf to base64 and write them to the output stream
c1 = inbuf[0]&0xfc;
c2 = inbuf[0]&0x03;
c3 = inbuf[1]&0xf0;
c4 = inbuf[1]&0x0f;
c5 = 0;
outbuf[0] = c1>>2;
outbuf[1] = (c2<<4)|(c3>>4);
outbuf[2] = (c4<<2)|(c5>>6);
outbuf[3] = '=';
outbuf[0] = encode_table[outbuf[0]];
outbuf[1] = encode_table[outbuf[1]];
outbuf[2] = encode_table[outbuf[2]];
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
break;
}
else // in this case status must be 1
{
// we are at the end of the input stream and need to add some padding
// encode the bytes in inbuf to base64 and write them to the output stream
c1 = inbuf[0]&0xfc;
c2 = inbuf[0]&0x03;
c3 = 0;
outbuf[0] = c1>>2;
outbuf[1] = (c2<<4)|(c3>>4);
outbuf[2] = '=';
outbuf[3] = '=';
outbuf[0] = encode_table[outbuf[0]];
outbuf[1] = encode_table[outbuf[1]];
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),4)!=4)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
break;
}
} // while (status != 0)
// make sure the stream buffer flushes to its I/O channel
out.pubsync();
};
void decode (
std::istream& in_,
std::ostream& out_
) const
{
using namespace std;
streambuf& in = *in_.rdbuf();
streambuf& out = *out_.rdbuf();
unsigned char inbuf[4];
unsigned char outbuf[3];
int inbuf_pos = 0;
streamsize status = in.sgetn(reinterpret_cast<char*>(inbuf),1);
// only count this character if it isn't some kind of filler
if (status == 1 && decode_table[inbuf[0]] != bad_value )
++inbuf_pos;
unsigned char c1, c2, c3, c4, c5, c6;
streamsize outsize;
// while we haven't hit the end of the input stream
while (status != 0)
{
// if we have 4 valid characters
if (inbuf_pos == 4)
{
inbuf_pos = 0;
// this might be the end of the encoded data so we need to figure out if
// there was any padding applied.
outsize = 3;
if (inbuf[3] == '=')
{
if (inbuf[2] == '=')
outsize = 1;
else
outsize = 2;
}
// decode the incoming characters
inbuf[0] = decode_table[inbuf[0]];
inbuf[1] = decode_table[inbuf[1]];
inbuf[2] = decode_table[inbuf[2]];
inbuf[3] = decode_table[inbuf[3]];
// now pack these guys into bytes rather than 6 bit chunks
c1 = inbuf[0]<<2;
c2 = inbuf[1]>>4;
c3 = inbuf[1]<<4;
c4 = inbuf[2]>>2;
c5 = inbuf[2]<<6;
c6 = inbuf[3];
outbuf[0] = c1|c2;
outbuf[1] = c3|c4;
outbuf[2] = c5|c6;
// write the encoded bytes to the output stream
if (out.sputn(reinterpret_cast<char*>(&outbuf),outsize)!=outsize)
{
- throw std::ios_base::failure("error occured in the base64 object");
+ throw std::ios_base::failure("error occurred in the base64 object");
}
}
// get more input characters
status = in.sgetn(reinterpret_cast<char*>(inbuf + inbuf_pos),1);
// only count this character if it isn't some kind of filler
if ((decode_table[inbuf[inbuf_pos]] != bad_value || inbuf[inbuf_pos] == '=') &&
status != 0)
++inbuf_pos;
} // while (status != 0)
if (inbuf_pos != 0)
{
ostringstream sout;
sout << inbuf_pos << " extra characters were found at the end of the encoded data."
<< " This may indicate that the data stream has been truncated.";
// this happens if we hit EOF in the middle of decoding a 24bit block.
throw decode_error(sout.str());
}
// make sure the stream buffer flushes to its I/O channel
out.pubsync();
};
private:
char* encode_table;
unsigned char* decode_table;
const unsigned char bad_value;
line_ending_type eol_style;
// restricted functions
base64(base64&); // copy constructor
base64& operator=(base64&); // assignment operator
};
#endif // DLIB_BASE64_KERNEl_1_
diff --git a/core/libs/facesengine/dnnface/dnn_base/entropy_decoder_model_kernel_5.h b/core/libs/facesengine/dnnface/dnn_base/entropy_decoder_model_kernel_5.h
index 98da22b4bb..193c8e8af3 100644
--- a/core/libs/facesengine/dnnface/dnn_base/entropy_decoder_model_kernel_5.h
+++ b/core/libs/facesengine/dnnface/dnn_base/entropy_decoder_model_kernel_5.h
@@ -1,812 +1,812 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-08-08
* Description : Base functions for dnn module, can be used for face recognition,
* all codes are ported from dlib library (http://dlib.net/)
*
* Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DLIB_ENTROPY_DECODER_MODEL_KERNEl_5_
#define DLIB_ENTROPY_DECODER_MODEL_KERNEl_5_
#include "algs.h"
#include "dnn_assert.h"
namespace edmk5
{
struct node
{
node* next;
node* child_context;
node* parent_context;
unsigned short symbol;
unsigned short count;
unsigned short total;
unsigned short escapes;
};
}
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
class entropy_decoder_model_kernel_5
{
/*!
REQUIREMENTS ON total_nodes
- 4096 < total_nodes
- this is the total number of nodes that we will use in the tree
REQUIREMENTS ON order
- 0 <= order
- this is the maximum depth-1 the tree will be allowed to go (note
that the root level is depth 0).
GENERAL NOTES
This implementation follows more or less the implementation
strategy laid out by Alistair Moffat in his paper
Implementing the PPM data compression scheme. Published in IEEE
Transactions on Communications, 38(11):1917-1921, 1990.
The escape method used will be method D.
This also uses Dmitry Shkarin's Information Inheritance scheme.
(described in "PPM: one step to practicality" and "Improving the
Efficiency of the PPM Algorithm")
INITIAL VALUE
- root == pointer to an array of total_nodes nodes
- next_node == 1
- cur == root
- cur_order = 0
- root->next == 0
- root->parent_context == 0
- root->child_context == 0
- root->escapes == 0
- root->total == 0
- stack_size == 0
- exc_used == false
- for all i: exc[i] == 0
CONVENTION
- exc_used == something_is_excluded()
- pop() == stack[stack_size-1].n and stack[stack_size-1].nc
- is_excluded(symbol) == bit symbol&0x1F from exc[symbol>>5]
- &get_entropy_decoder() == coder
- root == pointer to an array of total_nodes nodes.
this is also the root of the tree.
- if (next_node < total_nodes) then
- next_node == the next node in root that has not yet been allocated
- root->next == 0
- root->parent_context == 0
- for every node in the tree:
{
- NOTATION:
- The "context" of a node is the string of symbols seen
when you go from the root of the tree down (down though
child context pointers) to the node, including the symbol at
the node itself. (note that the context of the root node
is "" or the empty string)
- A set of nodes is in the same "context set" if all the node's
contexts are of length n and all the node's contexts share
the same prefix of length n-1.
- The "child context set" of a node is a set of nodes with
contexts that are one symbol longer and prefixed by the node's
context. For example, if a node has a context "abc" then the
nodes for contexts "abca", "abcb", "abcc", etc. are all in
the child context set of the node.
- The "parent context" of a node is the context that is one
symbol shorter than the node's context and includes the
symbol in the node. So the parent context of a node with
context "abcd" would be the context "bcd".
- if (next != 0) then
- next == pointer to the next node in the same context set
- if (child_context != 0) then
- child_context == pointer to the first node of the child
context set for this node.
- escapes > 0
- if (parent_context != 0) then
- parent_context == pointer to the parent context of this node.
- else
- this node is the root node of the tree
- if (this is not the root node) then
- symbol == the symbol represented with this node
- count == the number of times this symbol has been seen in its
parent context.
- else
- the root doesn't have a symbol. i.e. the context for the
root node is "" or the empty string.
- total == The sum of the counts of all the nodes
in the child context set + escapes.
- escapes == the escape count for the context represented
by the node.
- count > 0
}
- cur_order < order
- cur_order == the depth of the node cur in the tree.
(note that the root node has depth 0)
- cur == pointer to the node in the tree who's context matches
the most recent symbols we have seen.
!*/
typedef edmk5::node node;
public:
typedef entropy_decoder entropy_decoder_type;
entropy_decoder_model_kernel_5 (
entropy_decoder& coder
);
virtual ~entropy_decoder_model_kernel_5 (
);
inline void clear(
);
inline void decode (
unsigned long& symbol
);
entropy_decoder& get_entropy_decoder (
) { return coder; }
static unsigned long get_alphabet_size (
) { return alphabet_size; }
private:
inline void push (
node* n,
node* nc
);
/*!
requires
- stack_size < order
ensures
- #pop(a,b): a == n && b == nc
!*/
inline void pop (
node*& n,
node*& nc
);
/*!
requires
- stack_size > 0
ensures
- returns the two nodes at the top of the stack
!*/
inline edmk5::node* allocate_node (
);
/*!
requires
- space_left() == true
ensures
- returns a pointer to a new node
!*/
inline bool space_left (
) const;
/*!
ensures
- returns true if there is at least 1 free node left.
- returns false otherwise
!*/
inline void exclude (
unsigned short symbol
);
/*!
ensures
- #is_excluded(symbol) == true
- #something_is_excluded() == true
!*/
inline bool is_excluded (
unsigned short symbol
);
/*!
ensures
- if (symbol has been excluded) then
- returns true
- else
- returns false
!*/
inline bool something_is_excluded (
);
/*!
ensures
- returns true if some symbol has been excluded.
returns false otherwise
!*/
inline void clear_exclusions (
);
/*!
ensures
- for all symbols #is_excluded(symbol) == false
- #something_is_excluded() == false
!*/
inline void scale_counts (
node* n
);
/*!
ensures
- divides all the counts in the child context set of n by 2.
- none of the nodes in the child context set will have a count of 0
!*/
struct nodes
{
node* n;
node* nc;
};
entropy_decoder& coder;
unsigned long next_node;
node* root;
node* cur;
unsigned long cur_order;
unsigned long exc[alphabet_size/32+1];
nodes stack[order+1];
unsigned long stack_size;
bool exc_used;
// restricted functions
entropy_decoder_model_kernel_5(entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>&); // copy constructor
entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>& operator=(entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>&); // assignment operator
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
entropy_decoder_model_kernel_5 (
entropy_decoder& coder_
) :
coder(coder_),
next_node(1),
cur_order(0),
stack_size(0)
{
COMPILE_TIME_ASSERT( 1 < alphabet_size && alphabet_size < 65535);
COMPILE_TIME_ASSERT( 4096 < total_nodes );
root = new node[total_nodes];
cur = root;
root->child_context = 0;
root->escapes = 0;
root->next = 0;
root->parent_context = 0;
root->total = 0;
clear_exclusions();
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
~entropy_decoder_model_kernel_5 (
)
{
delete [] root;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
clear(
)
{
next_node = 1;
root->child_context = 0;
root->escapes = 0;
root->total = 0;
cur = root;
cur_order = 0;
stack_size = 0;
clear_exclusions();
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
decode (
unsigned long& symbol
)
{
node* temp = cur;
cur = 0;
unsigned long low_count, high_count, total_count;
unsigned long target;
node* new_node = 0;
// local_order will track the level of temp in the tree
unsigned long local_order = cur_order;
unsigned short c; // c == t(a|sk)
unsigned short t; // t == T(sk)
if (something_is_excluded())
clear_exclusions();
while (true)
{
high_count = 0;
if (space_left())
{
total_count = temp->total;
if (total_count > 0)
{
// check if we need to scale the counts
if (total_count > 10000)
{
scale_counts(temp);
total_count = temp->total;
}
if (something_is_excluded())
{
node* n = temp->child_context;
total_count = temp->escapes;
while (true)
{
if (is_excluded(n->symbol) == false)
{
total_count += n->count;
}
if (n->next == 0)
break;
n = n->next;
}
}
target = coder.get_target(total_count);
// find either the symbol we are looking for or the
// end of the context set
node* n = temp->child_context;
node* last = 0;
while (true)
{
if (is_excluded(n->symbol) == false)
{
high_count += n->count;
exclude(n->symbol);
}
if (high_count > target || n->next == 0)
break;
last = n;
n = n->next;
}
// if we found the symbol
if (high_count > target)
{
low_count = high_count - n->count;
if (new_node != 0)
{
new_node->parent_context = n;
}
symbol = n->symbol;
coder.decode(low_count,high_count);
c = n->count += 8;
t = temp->total += 8;
// move this node to the front
if (last)
{
last->next = n->next;
n->next = temp->child_context;
temp->child_context = n;
}
if (cur == 0)
{
if (local_order < order)
{
cur_order = local_order+1;
cur = n;
}
else
{
cur = n->parent_context;
cur_order = local_order;
}
}
break;
}
// if we hit the end of the context set without finding the symbol
else
{
if (new_node != 0)
{
new_node->parent_context = allocate_node();
new_node = new_node->parent_context;
}
else
{
new_node = allocate_node();
}
n->next = new_node;
// get the escape code
coder.decode(high_count,total_count);
}
}
else // if (total_count == 0)
{
// this means that temp->child_context == 0 so we should make
// a new node here.
if (new_node != 0)
{
new_node->parent_context = allocate_node();
new_node = new_node->parent_context;
}
else
{
new_node = allocate_node();
}
temp->child_context = new_node;
}
if (cur == 0 && local_order < order)
{
cur = new_node;
cur_order = local_order+1;
}
// fill out the new node
new_node->child_context = 0;
new_node->escapes = 0;
new_node->next = 0;
push(new_node,temp);
new_node->total = 0;
if (temp != root)
{
temp = temp->parent_context;
--local_order;
continue;
}
t = 2056;
c = 8;
// since this is the root we are going to the order-(-1) context
// so we can just take care of that here.
target = coder.get_target(alphabet_size);
new_node->parent_context = root;
coder.decode(target,target+1);
symbol = target;
if (cur == 0)
{
cur = root;
cur_order = 0;
}
break;
}
else
{
// there isn't enough space so we should rebuild the tree
clear();
temp = cur;
local_order = cur_order;
cur = 0;
new_node = 0;
}
} // while (true)
// initialize the counts and symbol for any new nodes we have added
// to the tree.
node* n, *nc;
while (stack_size > 0)
{
pop(n,nc);
n->symbol = static_cast<unsigned short>(symbol);
- // if nc is not a determnistic context
+ // if nc is not a deterministic context
if (nc->total)
{
unsigned long temp2 = t-c+nc->total - nc->escapes - nc->escapes;
unsigned long temp = nc->total;
temp *= c;
temp /= (temp2|1); // this oring by 1 is just to make sure that temp2 is never zero
temp += 2;
if (temp > 50000) temp = 50000;
n->count = static_cast<unsigned short>(temp);
nc->escapes += 4;
nc->total += static_cast<unsigned short>(temp) + 4;
}
else
{
n->count = 3 + 5*(c)/(t-c);
nc->escapes = 4;
nc->total = n->count + 4;
}
while (nc->total > 10000)
{
scale_counts(nc);
}
}
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// private member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
edmk5::node* entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
allocate_node (
)
{
node* temp;
temp = root + next_node;
++next_node;
return temp;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
bool entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
space_left (
) const
{
return (next_node < total_nodes);
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
exclude (
unsigned short symbol
)
{
exc_used = true;
unsigned long temp = 1;
temp <<= symbol&0x1F;
exc[symbol>>5] |= temp;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
bool entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
is_excluded (
unsigned short symbol
)
{
unsigned long temp = 1;
temp <<= symbol&0x1F;
return ((exc[symbol>>5]&temp) != 0);
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
clear_exclusions (
)
{
exc_used = false;
for (unsigned long i = 0; i < alphabet_size/32+1; ++i)
{
exc[i] = 0;
}
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
push (
node* n,
node* nc
)
{
stack[stack_size].n = n;
stack[stack_size].nc = nc;
++stack_size;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
pop (
node*& n,
node*& nc
)
{
--stack_size;
n = stack[stack_size].n;
nc = stack[stack_size].nc;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
bool entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
something_is_excluded (
)
{
return exc_used;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_decoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_decoder_model_kernel_5<alphabet_size,entropy_decoder,total_nodes,order>::
scale_counts (
node* temp
)
{
if (temp->escapes > 1)
temp->escapes >>= 1;
temp->total = temp->escapes;
node* n = temp->child_context;
while (n != 0)
{
if (n->count > 1)
n->count >>= 1;
temp->total += n->count;
n = n->next;
}
}
#endif // DLIB_ENTROPY_DECODER_MODEL_KERNEl_5_
diff --git a/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_kernel_2.cpp b/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_kernel_2.cpp
index 2f9fd4db70..2eb971f562 100644
--- a/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_kernel_2.cpp
+++ b/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_kernel_2.cpp
@@ -1,256 +1,256 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-08-08
* Description : Base functions for dnn module, can be used for face recognition,
* all codes are ported from dlib library (http://dlib.net/)
*
* Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DLIB_ENTROPY_ENCODER_KERNEL_2_CPp_
#define DLIB_ENTROPY_ENCODER_KERNEL_2_CPp_
#include "entropy_encoder_kernel_2.h"
#include <iostream>
#include <streambuf>
// ----------------------------------------------------------------------------------------
entropy_encoder_kernel_2::
entropy_encoder_kernel_2(
) :
initial_low(0x00000001),
initial_high(0xffffffff),
out(0),
low(initial_low),
high(initial_high)
{
streambuf = 0;
}
// ----------------------------------------------------------------------------------------
entropy_encoder_kernel_2::
~entropy_encoder_kernel_2 (
)
{
try {
if (out != 0)
{
flush();
}
} catch (...) {}
}
// ----------------------------------------------------------------------------------------
void entropy_encoder_kernel_2::
clear(
)
{
if (out != 0)
{
flush();
}
out = 0;
}
// ----------------------------------------------------------------------------------------
void entropy_encoder_kernel_2::
set_stream (
std::ostream& out_
)
{
if (out != 0)
{
// if a stream is currently set then flush the buffers to it before
// we switch to the new stream
flush();
}
out = &out_;
streambuf = out_.rdbuf();
// reset the encoder state
low = initial_low;
high = initial_high;
}
// ----------------------------------------------------------------------------------------
bool entropy_encoder_kernel_2::
stream_is_set (
) const
{
if (out != 0)
return true;
else
return false;
}
// ----------------------------------------------------------------------------------------
std::ostream& entropy_encoder_kernel_2::
get_stream (
) const
{
return *out;
}
// ----------------------------------------------------------------------------------------
void entropy_encoder_kernel_2::
encode (
uint32 low_count,
uint32 high_count,
uint32 total
)
{
// note that we must add one because of the convention that
// high == the real upper range minus 1
uint32 r = (high-low+1)/total;
// note that we must subtract 1 to preserve the convention that
// high == the real upper range - 1
high = low + r*high_count-1;
low = low + r*low_count;
while (true )
{
// if high and low don't have the same 8 high order bits
if ((high&0xFF000000) != (low&0xFF000000))
{
// if the distance between high and low is small and there aren't
// any bits we can roll off then force high and low to have common high
// order bits.
if ((high-low < 0x10000))
{
if (high-low > 0x1000)
{
high>>=1;
low>>=1;
high = low = high+low;
high += 0xFF;
low -= 0xFF;
}
else /**/
{
high>>=1;
low>>=1;
high = low = high+low;
}
}
else
{
// there are no bits to roll off and high and low are not
// too close so just quit the loop
break;
}
}
// else if there are 8 bits we can roll off
else
{
// write the 8 high order bits from low into buf
unsigned char buf = static_cast<unsigned char>(low>>24);
// roll off the bits we just wrote to buf
high <<= 8;
low <<= 8;
high |= 0xFF; // note that it is ok to add 0xFF to high here because
// of the convention that high == real upper range - 1.
// so that means that if we want to shift the upper range
// left by one then we must shift a one into high also
// since real upper range == high + 0.999999999...
// make sure low is never zero
if (low == 0)
low = 1;
// write buf to the output stream
if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
{
- throw std::ios_base::failure("error occured in the entropy_encoder object");
+ throw std::ios_base::failure("error occurred in the entropy_encoder object");
}
}
} // while (true)
}
// ----------------------------------------------------------------------------------------
void entropy_encoder_kernel_2::
flush (
)
{
// flush low to the output stream
unsigned char buf;
buf = static_cast<unsigned char>((low >> 24)&0xFF);
if (streambuf->sputn(reinterpret_cast<char*>(&buf),1) == 0)
- throw std::ios_base::failure("error occured in the entropy_encoder object");
+ throw std::ios_base::failure("error occurred in the entropy_encoder object");
buf = static_cast<unsigned char>((low >> 16)&0xFF);
if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
- throw std::ios_base::failure("error occured in the entropy_encoder object");
+ throw std::ios_base::failure("error occurred in the entropy_encoder object");
buf = static_cast<unsigned char>((low >> 8)&0xFF);
if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
- throw std::ios_base::failure("error occured in the entropy_encoder object");
+ throw std::ios_base::failure("error occurred in the entropy_encoder object");
buf = static_cast<unsigned char>((low)&0xFF);
if (streambuf->sputn(reinterpret_cast<char*>(&buf),1)==0)
- throw std::ios_base::failure("error occured in the entropy_encoder object");
+ throw std::ios_base::failure("error occurred in the entropy_encoder object");
// make sure the stream buffer flushes to its I/O channel
streambuf->pubsync();
// reset the encoder state
low = initial_low;
high = initial_high;
}
// ----------------------------------------------------------------------------------------
#endif // DLIB_ENTROPY_ENCODER_KERNEL_2_CPp_
diff --git a/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_model_kernel_5.h b/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_model_kernel_5.h
index daf2187db1..0e7aef91bc 100644
--- a/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_model_kernel_5.h
+++ b/core/libs/facesengine/dnnface/dnn_base/entropy_encoder_model_kernel_5.h
@@ -1,838 +1,838 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-08-08
* Description : Base functions for dnn module, can be used for face recognition,
* all codes are ported from dlib library (http://dlib.net/)
*
* Copyright (C) 2006-2016 by Davis E. King <davis at dlib dot net>
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DLIB_ENTROPY_ENCODER_MODEL_KERNEl_5_
#define DLIB_ENTROPY_ENCODER_MODEL_KERNEl_5_
#include "algs.h"
#include "dnn_assert.h"
namespace eemk5
{
struct node
{
node* next;
node* child_context;
node* parent_context;
unsigned short symbol;
unsigned short count;
unsigned short total;
unsigned short escapes;
};
}
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
class entropy_encoder_model_kernel_5
{
/*!
REQUIREMENTS ON total_nodes
- 4096 < total_nodes
- this is the total number of nodes that we will use in the tree
REQUIREMENTS ON order
- 0 <= order
- this is the maximum depth-1 the tree will be allowed to go (note
that the root level is depth 0).
GENERAL NOTES
This implementation follows more or less the implementation
strategy laid out by Alistair Moffat in his paper
Implementing the PPM data compression scheme. Published in IEEE
Transactions on Communications, 38(11):1917-1921, 1990.
The escape method used will be method D.
This also uses Dmitry Shkarin's Information Inheritance scheme.
(described in "PPM: one step to practicality" and "Improving the
Efficiency of the PPM Algorithm")
INITIAL VALUE
- root == pointer to an array of total_nodes nodes
- next_node == 1
- cur == root
- cur_order = 0
- root->next == 0
- root->parent_context == 0
- root->child_context == 0
- root->escapes == 0
- root->total == 0
- stack_size == 0
- exc_used == false
- for all i: exc[i] == 0
CONVENTION
- pop() == stack[stack_size-1].n and stack[stack_size-1].nc
- exc_used == something_is_excluded()
- is_excluded(symbol) == bit symbol&0x1F from exc[symbol>>5]
- &get_entropy_encoder() == coder
- root == pointer to an array of total_nodes nodes.
this is also the root of the tree.
- if (next_node < total_nodes) then
- next_node == the next node in root that has not yet been allocated
- root->next == 0
- root->parent_context == 0
- for every node in the tree:
{
- NOTATION:
- The "context" of a node is the string of symbols seen
when you go from the root of the tree down (down though
child context pointers) to the node, including the symbol at
the node itself. (note that the context of the root node
is "" or the empty string)
- A set of nodes is in the same "context set" if all the node's
contexts are of length n and all the node's contexts share
the same prefix of length n-1.
- The "child context set" of a node is a set of nodes with
contexts that are one symbol longer and prefixed by the node's
context. For example, if a node has a context "abc" then the
nodes for contexts "abca", "abcb", "abcc", etc. are all in
the child context set of the node.
- The "parent context" of a node is the context that is one
symbol shorter than the node's context and includes the
symbol in the node. So the parent context of a node with
context "abcd" would be the context "bcd".
- if (next != 0) then
- next == pointer to the next node in the same context set
- if (child_context != 0) then
- child_context == pointer to the first node of the child
context set for this node.
- escapes > 0
- if (parent_context != 0) then
- parent_context == pointer to the parent context of this node.
- else
- this node is the root node of the tree
- if (this is not the root node) then
- symbol == the symbol represented with this node
- count == the number of times this symbol has been seen in its
parent context.
- else
- the root doesn't have a symbol. i.e. the context for the
root node is "" or the empty string.
- total == The sum of the counts of all the nodes
in the child context set + escapes.
- escapes == the escape count for the context represented
by the node.
- count > 0
}
- cur_order < order
- cur_order == the depth of the node cur in the tree.
(note that the root node has depth 0)
- cur == pointer to the node in the tree who's context matches
the most recent symbols we have seen.
!*/
typedef eemk5::node node;
public:
typedef entropy_encoder entropy_encoder_type;
entropy_encoder_model_kernel_5 (
entropy_encoder& coder
);
virtual ~entropy_encoder_model_kernel_5 (
);
inline void clear(
);
inline void encode (
unsigned long symbol
);
entropy_encoder& get_entropy_encoder (
) { return coder; }
static unsigned long get_alphabet_size (
) { return alphabet_size; }
private:
inline eemk5::node* allocate_node (
);
/*!
requires
- space_left() == true
ensures
- returns a pointer to a new node
!*/
inline bool space_left (
) const;
/*!
ensures
- returns true if there is at least 1 free node left.
- returns false otherwise
!*/
inline void exclude (
unsigned short symbol
);
/*!
ensures
- #is_excluded(symbol) == true
- #something_is_excluded() == true
!*/
inline bool something_is_excluded (
);
/*!
ensures
- returns true if some symbol has been excluded.
returns false otherwise
!*/
inline bool is_excluded (
unsigned short symbol
);
/*!
ensures
- if (symbol has been excluded) then
- returns true
- else
- returns false
!*/
inline void clear_exclusions (
);
/*!
ensures
- for all symbols #is_excluded(symbol) == false
- #something_is_excluded() == true
!*/
inline void scale_counts (
node* n
);
/*!
ensures
- divides all the counts in the child context set of n by 2.
- none of the nodes in the child context set will have a count of 0
!*/
inline void push (
node* n,
node* nc
);
/*!
requires
- stack_size < order
ensures
- #pop(a,b): a == n && b == nc
!*/
inline void pop (
node*& n,
node*& nc
);
/*!
requires
- stack_size > 0
ensures
- returns the two nodes at the top of the stack
!*/
struct nodes
{
node* n;
node* nc;
};
unsigned long next_node;
entropy_encoder& coder;
node* root;
node* cur;
unsigned long cur_order;
unsigned long exc[alphabet_size/32+1];
bool exc_used;
nodes stack[order+1];
unsigned long stack_size;
// restricted functions
entropy_encoder_model_kernel_5(entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>&); // copy constructor
entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>& operator=(entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>&); // assignment operator
};
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
entropy_encoder_model_kernel_5 (
entropy_encoder& coder_
) :
next_node(1),
coder(coder_),
cur_order(0),
stack_size(0)
{
COMPILE_TIME_ASSERT( 1 < alphabet_size && alphabet_size < 65535 );
COMPILE_TIME_ASSERT( 4096 < total_nodes );
root = new node[total_nodes];
cur = root;
root->child_context = 0;
root->escapes = 0;
root->next = 0;
root->parent_context = 0;
root->total = 0;
clear_exclusions();
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
~entropy_encoder_model_kernel_5 (
)
{
delete [] root;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
clear(
)
{
next_node = 1;
root->child_context = 0;
root->escapes = 0;
root->total = 0;
cur = root;
cur_order = 0;
stack_size = 0;
clear_exclusions();
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
encode (
unsigned long sym
)
{
unsigned short symbol = static_cast<unsigned short>(sym);
node* temp = cur;
cur = 0;
unsigned short low_count, high_count, total_count;
node* new_node = 0;
// local_order will track the level of temp in the tree
unsigned long local_order = cur_order;
unsigned short c; // c == t(a|sk)
unsigned short t; // t == T(sk)
if (something_is_excluded())
clear_exclusions();
while (true)
{
low_count = 0;
high_count = 0;
if (space_left())
{
total_count = temp->total;
if (total_count > 0)
{
// check if we need to scale the counts
if (total_count > 10000)
{
scale_counts(temp);
total_count = temp->total;
}
// find the symbol we are looking for and put a pointer to it
// into found_symbol. If it isn't found then found_symbol == 0.
// also, low_count and high_count will be correctly set.
node* n = temp->child_context;
node* found_symbol = 0;
node* last = 0;
if (something_is_excluded())
{
node* templast = 0;
while (true)
{
if (is_excluded(n->symbol) == false)
{
exclude(n->symbol);
if (found_symbol == 0)
{
high_count += n->count;
if (n->symbol == symbol)
{
found_symbol = n;
last = templast;
low_count = high_count - n->count;
}
}
}
else
{
total_count -= n->count;
}
if (n->next == 0)
break;
templast = n;
n = n->next;
}
}
else
{
while (true)
{
high_count += n->count;
exclude(n->symbol);
if (n->symbol == symbol)
{
found_symbol = n;
low_count = high_count - n->count;
break;
}
if (n->next == 0)
break;
last = n;
n = n->next;
}
}
// if we found the symbol
if (found_symbol)
{
n = found_symbol;
if (new_node != 0)
{
new_node->parent_context = found_symbol;
}
coder.encode(low_count,high_count,total_count);
c = n->count += 8;
t = temp->total += 8;
// move this node to the front
if (last)
{
last->next = n->next;
n->next = temp->child_context;
temp->child_context = n;
}
if (cur == 0)
{
if (local_order >= order)
{
cur = n->parent_context;
cur_order = local_order;
}
else
{
cur_order = local_order+1;
cur = n;
}
}
break;
}
// if we hit the end of the context set without finding the symbol
else
{
// finish excluding all the symbols
while (n->next)
{
exclude(n->symbol);
n = n->next;
}
if (new_node != 0)
{
new_node->parent_context = allocate_node();
new_node = new_node->parent_context;
}
else
{
new_node = allocate_node();
}
n->next = new_node;
// write an escape to a lower context
coder.encode(high_count,total_count,total_count);
}
}
else // if (total_count == 0)
{
// this means that temp->child_context == 0 so we should make
// a new node here.
if (new_node != 0)
{
new_node->parent_context = allocate_node();
new_node = new_node->parent_context;
}
else
{
new_node = allocate_node();
}
temp->child_context = new_node;
}
if (cur == 0 && local_order < order)
{
cur = new_node;
cur_order = local_order+1;
}
// fill out the new node
new_node->child_context = 0;
new_node->escapes = 0;
new_node->next = 0;
new_node->total = 0;
push(new_node,temp);
if (temp != root)
{
temp = temp->parent_context;
--local_order;
continue;
}
t = 2056;
c = 8;
// since this is the root we are going to the order-(-1) context
// so we can just take care of that here.
new_node->parent_context = root;
coder.encode(symbol,symbol+1,alphabet_size);
if (cur == 0)
{
cur = root;
cur_order = 0;
}
break;
}
else
{
// there isn't enough space so we should throw away the tree
clear();
temp = cur;
local_order = cur_order;
cur = 0;
new_node = 0;
}
} // while (true)
// initialize the counts and symbol for any new nodes we have added
// to the tree.
node* n, *nc;
while (stack_size > 0)
{
pop(n,nc);
n->symbol = static_cast<unsigned short>(symbol);
- // if nc is not a determnistic context
+ // if nc is not a deterministic context
if (nc->total)
{
unsigned long temp2 = t-c+nc->total - nc->escapes - nc->escapes;
unsigned long temp = nc->total;
temp *= c;
temp /= (temp2|1); // this oring by 1 is just to make sure that temp2 is never zero
temp += 2;
if (temp > 50000) temp = 50000;
n->count = static_cast<unsigned short>(temp);
nc->escapes += 4;
nc->total += static_cast<unsigned short>(temp) + 4;
}
else
{
n->count = 3 + 5*(c)/(t-c);
nc->escapes = 4;
nc->total = n->count + 4;
}
while (nc->total > 10000)
{
scale_counts(nc);
}
}
}
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
// private member function definitions
// ----------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
eemk5::node* entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
allocate_node (
)
{
node* temp;
temp = root + next_node;
++next_node;
return temp;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
bool entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
space_left (
) const
{
return (next_node < total_nodes);
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
exclude (
unsigned short symbol
)
{
exc_used = true;
unsigned long temp = 1;
temp <<= symbol&0x1F;
exc[symbol>>5] |= temp;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
bool entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
is_excluded (
unsigned short symbol
)
{
unsigned long temp = 1;
temp <<= symbol&0x1F;
return ((exc[symbol>>5]&temp) != 0);
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
clear_exclusions (
)
{
exc_used = false;
for (unsigned long i = 0; i < alphabet_size/32+1; ++i)
{
exc[i] = 0;
}
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
bool entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
something_is_excluded (
)
{
return exc_used;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
push (
node* n,
node* nc
)
{
stack[stack_size].n = n;
stack[stack_size].nc = nc;
++stack_size;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
pop (
node*& n,
node*& nc
)
{
--stack_size;
n = stack[stack_size].n;
nc = stack[stack_size].nc;
}
// ----------------------------------------------------------------------------------------
template <
unsigned long alphabet_size,
typename entropy_encoder,
unsigned long total_nodes,
unsigned long order
>
void entropy_encoder_model_kernel_5<alphabet_size,entropy_encoder,total_nodes,order>::
scale_counts (
node* temp
)
{
if (temp->escapes > 1)
temp->escapes >>= 1;
temp->total = temp->escapes;
node* n = temp->child_context;
while (n != 0)
{
if (n->count > 1)
n->count >>= 1;
temp->total += n->count;
n = n->next;
}
}
// ----------------------------------------------------------------------------------------
#endif // DLIB_ENTROPY_ENCODER_MODEL_KERNEl_5_
diff --git a/core/libs/facesengine/recognition-dlib-dnn/dnnfacemodel.cpp b/core/libs/facesengine/recognition-dlib-dnn/dnnfacemodel.cpp
index 66d7f4dd99..48ba8de513 100644
--- a/core/libs/facesengine/recognition-dlib-dnn/dnnfacemodel.cpp
+++ b/core/libs/facesengine/recognition-dlib-dnn/dnnfacemodel.cpp
@@ -1,159 +1,159 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-05-22
* Description : Face recognition using deep learning
*
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dnnfacemodel.h"
// Qt includes
#include <QList>
// local includes
#include "digikam_debug.h"
namespace Digikam
{
DNNFaceVecMetadata::DNNFaceVecMetadata()
: databaseId(-1),
identity(0),
storageStatus(Created)
{
}
DNNFaceVecMetadata::~DNNFaceVecMetadata()
{
}
// ------------------------------------------------------------------------------------
DNNFaceModel::DNNFaceModel()
: cv::Ptr<DNNFaceRecognizer>(DNNFaceRecognizer::create())/*,
databaseId(0)*/
{
ptr()->setThreshold(0.6);
}
DNNFaceModel::~DNNFaceModel()
{
}
DNNFaceRecognizer* DNNFaceModel::ptr()
{
DNNFaceRecognizer* const ptr = cv::Ptr<DNNFaceRecognizer>::operator Digikam::DNNFaceRecognizer*();
if (!ptr)
qCWarning(DIGIKAM_FACESENGINE_LOG) << "DNNFaceRecognizer pointer is null";
return ptr;
}
const DNNFaceRecognizer* DNNFaceModel::ptr() const
{
const DNNFaceRecognizer* const ptr = cv::Ptr<DNNFaceRecognizer>::operator Digikam::DNNFaceRecognizer*();
if (!ptr)
qCWarning(DIGIKAM_FACESENGINE_LOG) << "DNNFaceRecognizer pointer is null";
return ptr;
}
std::vector<std::vector<float> > DNNFaceModel::getSrc() const
{
return ptr()->getSrc();
}
void DNNFaceModel::setSrc(std::vector<std::vector<float> > new_src)
{
ptr()->setSrc(new_src);
}
cv::Mat DNNFaceModel::getLabels() const
{
return ptr()->getLabels();
}
void DNNFaceModel::setLabels(cv::Mat new_labels)
{
ptr()->setLabels(new_labels);
}
/*
OpenCVMatData DNNFaceModel::matData(int index) const
{
return OpenCVMatData(ptr()->getSrc().at(index));
}
*/
QList<DNNFaceVecMetadata> DNNFaceModel::matMetadata() const
{
return m_vecMetadata;
}
void DNNFaceModel::setWrittenToDatabase(int index, int id)
{
m_vecMetadata[index].databaseId = id;
m_vecMetadata[index].storageStatus = DNNFaceVecMetadata::InDatabase;
}
void DNNFaceModel::setMats(const QList<std::vector<float> >& mats, const QList<DNNFaceVecMetadata>& matMetadata)
{
/*
* Does not work with standard OpenCV, as these two params are declared read-only in OpenCV.
* One reason why we copied the code.
*/
std::vector<std::vector<float> > newSrc;
cv::Mat newLabels;
newSrc.reserve(mats.size());
newLabels.reserve(matMetadata.size());
foreach (const std::vector<float>& mat, mats)
{
newSrc.push_back(mat);
}
m_vecMetadata.clear();
foreach (const DNNFaceVecMetadata& metadata, matMetadata)
{
newLabels.push_back(metadata.identity);
m_vecMetadata << metadata;
}
std::vector<std::vector<float> > currentSrcs = ptr()->getSrc();
cv::Mat currentLabels = ptr()->getLabels();
currentSrcs.insert(currentSrcs.end(), newSrc.begin(), newSrc.end());
currentLabels.push_back(newLabels);
//ptr()->setSrc(currentSrcs);
//ptr()->setLabels(currentLabels);
- //make sure that there exits traing data
+ //make sure that there exits training data
if (currentSrcs.size() > 0)
{
ptr()->train(currentSrcs, currentLabels);
}
}
} // namespace Digikam
diff --git a/core/libs/facesengine/recognition-opencv-eigenfaces/eigenfacemodel.cpp b/core/libs/facesengine/recognition-opencv-eigenfaces/eigenfacemodel.cpp
index d4b1bed631..77c1a5c6ee 100644
--- a/core/libs/facesengine/recognition-opencv-eigenfaces/eigenfacemodel.cpp
+++ b/core/libs/facesengine/recognition-opencv-eigenfaces/eigenfacemodel.cpp
@@ -1,178 +1,178 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-05-22
* Description : Face Recognition based on Eigenfaces
* http://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#eigenfaces
* Turk, Matthew A and Pentland, Alex P. "Face recognition using eigenfaces."
* Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.},
* {IEEE} Computer Society Conference on 1991.
*
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "eigenfacemodel.h"
// Qt includes
#include <QList>
// local includes
#include "digikam_debug.h"
namespace Digikam
{
EigenFaceMatMetadata::EigenFaceMatMetadata()
: databaseId(-1),
identity(0),
storageStatus(Created)
{
}
EigenFaceMatMetadata::~EigenFaceMatMetadata()
{
}
// ------------------------------------------------------------------------------------
EigenFaceModel::EigenFaceModel()
: cv::Ptr<EigenFaceRecognizer>(EigenFaceRecognizer::create())
/*, databaseId(0)*/
{
ptr()->setThreshold(20000.0);
}
EigenFaceModel::~EigenFaceModel()
{
}
EigenFaceRecognizer* EigenFaceModel::ptr()
{
EigenFaceRecognizer* const ptr = cv::Ptr<EigenFaceRecognizer>::operator Digikam::EigenFaceRecognizer*();
if (!ptr)
qCWarning(DIGIKAM_FACESENGINE_LOG) << "EigenFaceRecognizer pointer is null";
return ptr;
}
const EigenFaceRecognizer* EigenFaceModel::ptr() const
{
const EigenFaceRecognizer* const ptr = cv::Ptr<EigenFaceRecognizer>::operator Digikam::EigenFaceRecognizer*();
if (!ptr)
qCWarning(DIGIKAM_FACESENGINE_LOG) << "EigenFaceRecognizer pointer is null";
return ptr;
}
std::vector<cv::Mat> EigenFaceModel::getSrc() const
{
return ptr()->getSrc();
}
void EigenFaceModel::setSrc(std::vector<cv::Mat> new_src)
{
ptr()->setSrc(new_src);
}
cv::Mat EigenFaceModel::getLabels() const
{
return ptr()->getLabels();
}
void EigenFaceModel::setLabels(cv::Mat new_labels)
{
ptr()->setLabels(new_labels);
}
OpenCVMatData EigenFaceModel::matData(int index) const
{
return OpenCVMatData(ptr()->getSrc().at(index));
}
QList<EigenFaceMatMetadata> EigenFaceModel::matMetadata() const
{
return m_matMetadata;
}
void EigenFaceModel::setWrittenToDatabase(int index, int id)
{
m_matMetadata[index].databaseId = id;
m_matMetadata[index].storageStatus = EigenFaceMatMetadata::InDatabase;
}
void EigenFaceModel::setMats(const QList<OpenCVMatData>& mats, const QList<EigenFaceMatMetadata>& matMetadata)
{
/*
* Does not work with standard OpenCV, as these two params are declared read-only in OpenCV.
* One reason why we copied the code.
*/
std::vector<cv::Mat> newSrc;
cv::Mat newLabels;
newSrc.reserve(mats.size());
newLabels.reserve(matMetadata.size());
foreach (const OpenCVMatData& mat, mats)
{
newSrc.push_back(mat.toMat());
}
m_matMetadata.clear();
foreach (const EigenFaceMatMetadata& metadata, matMetadata)
{
newLabels.push_back(metadata.identity);
m_matMetadata << metadata;
}
std::vector<cv::Mat> currentSrcs = ptr()->getSrc();
cv::Mat currentLabels = ptr()->getLabels();
currentSrcs.insert(currentSrcs.end(), newSrc.begin(), newSrc.end());
currentLabels.push_back(newLabels);
//ptr()->setSrc(currentSrcs);
//ptr()->setLabels(currentLabels);
- //make sure that there exits traing data
+ //make sure that there exits training data
if (currentSrcs.size()>0)
{
ptr()->train(currentSrcs, currentLabels);
}
}
void EigenFaceModel::update(const std::vector<cv::Mat>& images, const std::vector<int>& labels, const QString& context)
{
ptr()->update(images, labels);
// Update local information
// We assume new labels are simply appended
cv::Mat currentLabels = ptr()->getLabels();
for (int i = m_matMetadata.size() ; i < currentLabels.rows ; i++)
{
EigenFaceMatMetadata metadata;
metadata.storageStatus = EigenFaceMatMetadata::Created;
metadata.identity = currentLabels.at<int>(i);
metadata.context = context;
m_matMetadata << metadata;
}
}
} // namespace Digikam
diff --git a/core/libs/facesengine/recognition-opencv-eigenfaces/facerec_eigenborrowed.cpp b/core/libs/facesengine/recognition-opencv-eigenfaces/facerec_eigenborrowed.cpp
index 31c86016c2..3a190c4df0 100644
--- a/core/libs/facesengine/recognition-opencv-eigenfaces/facerec_eigenborrowed.cpp
+++ b/core/libs/facesengine/recognition-opencv-eigenfaces/facerec_eigenborrowed.cpp
@@ -1,234 +1,234 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-05-22
* Description : Face Recognition based on Eigenfaces
* http://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#eigenfaces
* Turk, Matthew A and Pentland, Alex P. "Face recognition using eigenfaces."
* Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.},
* {IEEE} Computer Society Conference on 1991.
*
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "facerec_eigenborrowed.h"
// C++ includes
#include <set>
#include <limits>
#include <vector>
// Local includes
#include "digikam_debug.h"
using namespace cv;
namespace Digikam
{
//------------------------------------------------------------------------------
// Eigenfaces
//------------------------------------------------------------------------------
inline Mat asRowMatrix(std::vector<Mat> src, int rtype, double alpha=1, double beta=0)
{
// number of samples
size_t n = src.size();
// return empty matrix if no matrices given
if (n == 0)
return Mat();
// dimensionality of (reshaped) samples
size_t d = src[0].total();
// create data matrix
Mat data((int)n, (int)d, rtype);
// now copy data
for (unsigned int i = 0 ; i < n ; i++)
{
Mat xi = data.row(i);
// make reshape happy by cloning for non-continuous matrices
if (src[i].isContinuous())
{
src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta);
}
else
{
src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
}
}
return data;
}
void EigenFaceRecognizer::train(InputArrayOfArrays _in_src, InputArray _inm_labels)
{
this->train(_in_src, _inm_labels, false);
}
void EigenFaceRecognizer::update(InputArrayOfArrays _in_src, InputArray _inm_labels)
{
// got no data, just return
if (_in_src.total() == 0)
return;
this->train(_in_src, _inm_labels, true);
}
void EigenFaceRecognizer::train(InputArrayOfArrays _in_src, InputArray _inm_labels, bool preserveData)
{
if (_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECTOR_VECTOR)
{
String error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< std::vector<...> >).";
CV_Error(CV_StsBadArg, error_message);
}
if (_in_src.total() == 0)
{
String error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
CV_Error(CV_StsUnsupportedFormat, error_message);
}
else if (_inm_labels.getMat().type() != CV_32SC1)
{
String error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _inm_labels.type());
CV_Error(CV_StsUnsupportedFormat, error_message);
}
// get the vector of matrices
std::vector<Mat> src;
_in_src.getMatVector(src);
// get the label matrix
Mat labels = _inm_labels.getMat();
// check if data is well- aligned
if (labels.total() != src.size())
{
String error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%d, len(labels)=%d.", src.size(), m_labels.total());
CV_Error(CV_StsBadArg, error_message);
}
// if this model should be trained without preserving old data, delete old model data
if (!preserveData)
{
m_labels.release();
m_src.clear();
}
// append labels to m_labels matrix
for (size_t labelIdx = 0 ; labelIdx < labels.total() ; labelIdx++)
{
m_labels.push_back(labels.at<int>((int)labelIdx));
m_src.push_back(src[(int)labelIdx]);
}
// observations in row
Mat data = asRowMatrix(m_src, CV_64FC1);
// number of samples
int n = data.rows;
// clear existing model data
m_projections.clear();
// clip number of components to be valid
m_num_components = n;
// perform the PCA
PCA pca(data, Mat(), PCA::DATA_AS_ROW, m_num_components);
// copy the PCA results
m_mean = pca.mean.reshape(1, 1); // store the mean vector
transpose(pca.eigenvectors, m_eigenvectors); // eigenvectors by column
// save projections
for (int sampleIdx = 0 ; sampleIdx < data.rows ; sampleIdx++)
{
Mat p = LDA::subspaceProject(m_eigenvectors, m_mean, data.row(sampleIdx));
m_projections.push_back(p);
}
}
void EigenFaceRecognizer::predict(cv::InputArray _src, cv::Ptr<cv::face::PredictCollector> collector) const
{
qCWarning(DIGIKAM_FACESENGINE_LOG) << "Predicting face image";
if (m_projections.empty())
{
// throw error if no data (or simply return -1?)
String error_message = "This Eigenfaces model is not computed yet. Did you call the train method?";
CV_Error(CV_StsBadArg, error_message);
}
Mat src = _src.getMat(); //254*254
- // make sure the size of input image is the same as traing image
+ // make sure the size of input image is the same as training image
if (m_src.size() >= 1 && (src.rows != m_src[0].rows || src.cols != m_src[0].cols))
{
resize(src, src, Size(m_src[0].rows, m_src[0].cols), 0, 0, INTER_LINEAR);
}
collector->init(0); // here need to confirm
Mat q = LDA::subspaceProject(m_eigenvectors, m_mean, src.reshape(1, 1));
// find nearest neighbor
for (size_t sampleIdx = 0 ; sampleIdx < m_projections.size() ; sampleIdx++)
{
double dist = norm(m_projections[sampleIdx], q, NORM_L2);
int label = m_labels.at<int>((int) sampleIdx);
if (!collector->collect(label, dist))
{
return;
}
}
}
// Static method ----------------------------------------------------
Ptr<EigenFaceRecognizer> EigenFaceRecognizer::create(double threshold)
{
Ptr<EigenFaceRecognizer> ptr;
EigenFaceRecognizer* const fr = new EigenFaceRecognizer(threshold);
if (!fr)
{
qCWarning(DIGIKAM_FACESENGINE_LOG) << "Cannot create EigenFaceRecognizer instance";
return ptr;
}
ptr = Ptr<EigenFaceRecognizer>(fr);
if (ptr.empty())
{
qCWarning(DIGIKAM_FACESENGINE_LOG) << "EigenFaceRecognizer instance is empty";
}
return ptr;
}
} // namespace Digikam
diff --git a/core/libs/facesengine/recognition-opencv-fisherfaces/facerec_fisherborrowed.cpp b/core/libs/facesengine/recognition-opencv-fisherfaces/facerec_fisherborrowed.cpp
index e6fd819e7d..0bbe1ab580 100644
--- a/core/libs/facesengine/recognition-opencv-fisherfaces/facerec_fisherborrowed.cpp
+++ b/core/libs/facesengine/recognition-opencv-fisherfaces/facerec_fisherborrowed.cpp
@@ -1,283 +1,283 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-06-10
* Description : Face Recognition based on Fisherfaces
* http://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces
* Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces."
* Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.},
* {IEEE} Computer Society Conference on 1991.
*
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "facerec_fisherborrowed.h"
// C++ includes
#include <set>
#include <limits>
#include <vector>
// Local includes
#include "digikam_debug.h"
using namespace cv;
namespace Digikam
{
//------------------------------------------------------------------------------
// Fisherfaces
//------------------------------------------------------------------------------
inline Mat asRowMatrix(std::vector<Mat> src, int rtype, double alpha=1, double beta=0)
{
// number of samples
size_t n = src.size();
// return empty matrix if no matrices given
if (n == 0)
return Mat();
// dimensionality of (reshaped) samples
size_t d = src[0].total();
// create data matrix
Mat data((int)n, (int)d, rtype);
// now copy data
for (unsigned int i = 0 ; i < n ; i++)
{
Mat xi = data.row(i);
// make reshape happy by cloning for non-continuous matrices
if (src[i].isContinuous())
{
src[i].reshape(1, 1).convertTo(xi, rtype, alpha, beta);
}
else
{
src[i].clone().reshape(1, 1).convertTo(xi, rtype, alpha, beta);
}
}
return data;
}
// Removes duplicate elements in a given vector.
template<typename _Tp>
inline std::vector<_Tp> remove_dups(const std::vector<_Tp>& src)
{
typedef typename std::set<_Tp>::const_iterator constSetIterator;
typedef typename std::vector<_Tp>::const_iterator constVecIterator;
std::set<_Tp> set_elems;
for (constVecIterator it = src.begin() ; it != src.end() ; ++it)
set_elems.insert(*it);
std::vector<_Tp> elems;
for (constSetIterator it = set_elems.begin() ; it != set_elems.end() ; ++it)
elems.push_back(*it);
return elems;
}
void FisherFaceRecognizer::train(InputArrayOfArrays _in_src, InputArray _inm_labels)
{
this->train(_in_src, _inm_labels, false);
}
void FisherFaceRecognizer::update(InputArrayOfArrays _in_src, InputArray _inm_labels)
{
// got no data, just return
if (_in_src.total() == 0)
return;
this->train(_in_src, _inm_labels, true);
}
void FisherFaceRecognizer::train(InputArrayOfArrays _in_src, InputArray _inm_labels, bool preserveData)
{
if (_in_src.kind() != _InputArray::STD_VECTOR_MAT && _in_src.kind() != _InputArray::STD_VECTOR_VECTOR)
{
String error_message = "The images are expected as InputArray::STD_VECTOR_MAT (a std::vector<Mat>) or _InputArray::STD_VECTOR_VECTOR (a std::vector< std::vector<...> >).";
CV_Error(CV_StsBadArg, error_message);
}
if (_in_src.total() == 0)
{
String error_message = format("Empty training data was given. You'll need more than one sample to learn a model.");
CV_Error(CV_StsUnsupportedFormat, error_message);
}
else if (_inm_labels.getMat().type() != CV_32SC1)
{
String error_message = format("Labels must be given as integer (CV_32SC1). Expected %d, but was %d.", CV_32SC1, _inm_labels.type());
CV_Error(CV_StsUnsupportedFormat, error_message);
}
// get the vector of matrices
std::vector<Mat> src;
_in_src.getMatVector(src);
// get the label matrix
Mat labels = _inm_labels.getMat();
// check if data is well- aligned
if (labels.total() != src.size())
{
String error_message = format("The number of samples (src) must equal the number of labels (labels). Was len(samples)=%d, len(labels)=%d.", src.size(), m_labels.total());
CV_Error(CV_StsBadArg, error_message);
}
// if this model should be trained without preserving old data, delete old model data
if (!preserveData)
{
m_labels.release();
m_src.clear();
}
// append labels to m_labels matrix
for (size_t labelIdx = 0; labelIdx < labels.total(); labelIdx++)
{
m_labels.push_back(labels.at<int>((int)labelIdx));
m_src.push_back(src[(int)labelIdx]);
}
// observations in row
Mat data = asRowMatrix(m_src, CV_64FC1);
// number of samples
int n = data.rows;
/*
LDA needs more than one class
We have to check the labels first
*/
bool label_flag = false;
for (int i = 1 ; i < m_labels.rows ; i++)
{
if (m_labels.at<int>(i, 0)!=m_labels.at<int>(i-1, 0))
{
label_flag = true;
break;
}
}
if (!label_flag)
{
String error_message = format("The labels should contain more than one types.");
CV_Error(CV_StsBadArg, error_message);
}
// clear existing model data
m_projections.clear();
std::vector<int> ll;
for (unsigned int i = 0 ; i < m_labels.total() ; i++)
{
ll.push_back(m_labels.at<int>(i));
}
// get the number of unique classes
int C = (int) remove_dups(ll).size();
// clip number of components to be valid
m_num_components = (C-1);
// perform the PCA
PCA pca(data, Mat(), PCA::DATA_AS_ROW, (n-C));
LDA lda(pca.project(data),m_labels, m_num_components);
// Now calculate the projection matrix as pca.eigenvectors * lda.eigenvectors.
// Note: OpenCV stores the eigenvectors by row, so we need to transpose it!
gemm(pca.eigenvectors, lda.eigenvectors(), 1.0, Mat(), 0.0, m_eigenvectors, GEMM_1_T);
// store the projections of the original data
for (int sampleIdx = 0 ; sampleIdx < data.rows ; sampleIdx++)
{
Mat p = LDA::subspaceProject(m_eigenvectors, m_mean, data.row(sampleIdx));
m_projections.push_back(p);
}
}
void FisherFaceRecognizer::predict(cv::InputArray _src, cv::Ptr<cv::face::PredictCollector> collector) const
{
qCWarning(DIGIKAM_FACESENGINE_LOG) << "Predicting face image using fisherfaces";
if (m_projections.empty())
{
// throw error if no data (or simply return -1?)
String error_message = "This Fisherfaces model is not computed yet. Did you call the train method?";
CV_Error(CV_StsBadArg, error_message);
}
Mat src = _src.getMat();//254*254
- //make sure the size of input image is the same as traing image
+ //make sure the size of input image is the same as training image
if (m_src.size() >= 1 && (src.rows != m_src[0].rows || src.cols != m_src[0].cols))
{
//resize(src, src, Size(m_src[0].rows, m_src[0].cols), (0, 0), (0, 0), INTER_LINEAR);
resize(src, src, Size(m_src[0].rows, m_src[0].cols));
}
collector->init(0);//here need to confirm
Mat q = LDA::subspaceProject(m_eigenvectors, m_mean, src.reshape(1, 1));
//find nearest neighbor
for (size_t sampleIdx = 0 ; sampleIdx < m_projections.size() ; sampleIdx++)
{
double dist = norm(m_projections[sampleIdx], q, NORM_L2);
int label = m_labels.at<int>((int) sampleIdx);
if (!collector->collect(label, dist))
{
return;
}
}
}
// Static method ----------------------------------------------------
Ptr<FisherFaceRecognizer> FisherFaceRecognizer::create(double threshold)
{
Ptr<FisherFaceRecognizer> ptr;
FisherFaceRecognizer* const fr = new FisherFaceRecognizer(threshold);
if (!fr)
{
qCWarning(DIGIKAM_FACESENGINE_LOG) << "Cannot create FisherFaceRecognizer instance";
return ptr;
}
ptr = Ptr<FisherFaceRecognizer>(fr);
if (ptr.empty())
{
qCWarning(DIGIKAM_FACESENGINE_LOG) << "FisherFaceRecognizer instance is empty";
}
return ptr;
}
} // namespace Digikam
diff --git a/core/libs/facesengine/recognition-opencv-fisherfaces/fisherfacemodel.cpp b/core/libs/facesengine/recognition-opencv-fisherfaces/fisherfacemodel.cpp
index a43e61068f..ee93077e4f 100644
--- a/core/libs/facesengine/recognition-opencv-fisherfaces/fisherfacemodel.cpp
+++ b/core/libs/facesengine/recognition-opencv-fisherfaces/fisherfacemodel.cpp
@@ -1,178 +1,178 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2017-06-10
* Description : Face Recognition based on Fisherfaces
* http://docs.opencv.org/2.4/modules/contrib/doc/facerec/facerec_tutorial.html#Fisherfaces
* Turk, Matthew A and Pentland, Alex P. "Face recognition using Fisherfaces."
* Computer Vision and Pattern Recognition, 1991. Proceedings {CVPR'91.},
* {IEEE} Computer Society Conference on 1991.
*
* Copyright (C) 2017 by Yingjie Liu <yingjiewudi at gmail dot com>
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "fisherfacemodel.h"
// Qt includes
#include <QList>
// local includes
#include "digikam_debug.h"
namespace Digikam
{
FisherFaceMatMetadata::FisherFaceMatMetadata()
: databaseId(-1),
identity(0),
storageStatus(Created)
{
}
FisherFaceMatMetadata::~FisherFaceMatMetadata()
{
}
// ------------------------------------------------------------------------------------
FisherFaceModel::FisherFaceModel()
: cv::Ptr<FisherFaceRecognizer>(FisherFaceRecognizer::create())/*,
databaseId(0)*/
{
ptr()->setThreshold(25000.0);
}
FisherFaceModel::~FisherFaceModel()
{
}
FisherFaceRecognizer* FisherFaceModel::ptr()
{
FisherFaceRecognizer* const ptr = cv::Ptr<FisherFaceRecognizer>::operator Digikam::FisherFaceRecognizer*();
if (!ptr)
qCWarning(DIGIKAM_FACESENGINE_LOG) << "FisherFaceRecognizer pointer is null";
return ptr;
}
const FisherFaceRecognizer* FisherFaceModel::ptr() const
{
const FisherFaceRecognizer* const ptr = cv::Ptr<FisherFaceRecognizer>::operator Digikam::FisherFaceRecognizer*();
if (!ptr)
qCWarning(DIGIKAM_FACESENGINE_LOG) << "FisherFaceRecognizer pointer is null";
return ptr;
}
std::vector<cv::Mat> FisherFaceModel::getSrc() const
{
return ptr()->getSrc();
}
void FisherFaceModel::setSrc(std::vector<cv::Mat> new_src)
{
ptr()->setSrc(new_src);
}
cv::Mat FisherFaceModel::getLabels() const
{
return ptr()->getLabels();
}
void FisherFaceModel::setLabels(cv::Mat new_labels)
{
ptr()->setLabels(new_labels);
}
OpenCVMatData FisherFaceModel::matData(int index) const
{
return OpenCVMatData(ptr()->getSrc().at(index));
}
QList<FisherFaceMatMetadata> FisherFaceModel::matMetadata() const
{
return m_matMetadata;
}
void FisherFaceModel::setWrittenToDatabase(int index, int id)
{
m_matMetadata[index].databaseId = id;
m_matMetadata[index].storageStatus = FisherFaceMatMetadata::InDatabase;
}
void FisherFaceModel::setMats(const QList<OpenCVMatData>& mats, const QList<FisherFaceMatMetadata>& matMetadata)
{
/*
* Does not work with standard OpenCV, as these two params are declared read-only in OpenCV.
* One reason why we copied the code.
*/
std::vector<cv::Mat> newSrc;
cv::Mat newLabels;
newSrc.reserve(mats.size());
newLabels.reserve(matMetadata.size());
foreach (const OpenCVMatData& mat, mats)
{
newSrc.push_back(mat.toMat());
}
m_matMetadata.clear();
foreach (const FisherFaceMatMetadata& metadata, matMetadata)
{
newLabels.push_back(metadata.identity);
m_matMetadata << metadata;
}
std::vector<cv::Mat> currentSrcs = ptr()->getSrc();
cv::Mat currentLabels = ptr()->getLabels();
currentSrcs.insert(currentSrcs.end(), newSrc.begin(), newSrc.end());
currentLabels.push_back(newLabels);
//ptr()->setSrc(currentSrcs);
//ptr()->setLabels(currentLabels);
- //make sure that there exits traing data
+ //make sure that there exits training data
if (currentSrcs.size()>0)
{
ptr()->train(currentSrcs, currentLabels);
}
}
void FisherFaceModel::update(const std::vector<cv::Mat>& images, const std::vector<int>& labels, const QString& context)
{
ptr()->update(images, labels);
// Update local information
// We assume new labels are simply appended
cv::Mat currentLabels = ptr()->getLabels();
for (int i = m_matMetadata.size() ; i < currentLabels.rows ; i++)
{
FisherFaceMatMetadata metadata;
metadata.storageStatus = FisherFaceMatMetadata::Created;
metadata.identity = currentLabels.at<int>(i);
metadata.context = context;
m_matMetadata << metadata;
}
}
} // namespace Digikam
diff --git a/core/libs/facesengine/recognitiondatabase.h b/core/libs/facesengine/recognitiondatabase.h
index e5f97aa13c..bdde2b0942 100644
--- a/core/libs/facesengine/recognitiondatabase.h
+++ b/core/libs/facesengine/recognitiondatabase.h
@@ -1,239 +1,239 @@
/* ============================================================
*
* This file is a part of digiKam
*
* Date : 2010-06-16
* Description : The recognition database wrapper
*
* Copyright (C) 2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010 by Aditya Bhatt <adityabhatt1991 at gmail dot com>
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_FACESENGINE_RECOGNITIONDATABASE_H
#define DIGIKAM_FACESENGINE_RECOGNITIONDATABASE_H
// Qt includes
#include <QExplicitlySharedDataPointer>
#include <QImage>
#include <QList>
#include <QMap>
#include <QVariant>
// Local includes
#include "digikam_export.h"
#include "identity.h"
#include "dataproviders.h"
namespace Digikam
{
/**
* Performs face recognition.
* Persistent data about identities and training data will be stored
* under a given or the default configuration path.
*
* The class guarantees
* - deferred creation: The backend is created only when used first.
* - only one instance per configuration path is created
* - an instance of this class is thread-safe
* (this class is also reentrant, for different objects and paths)
*/
class DIGIKAM_DATABASE_EXPORT RecognitionDatabase
{
public:
enum TrainingCostHint
{
/// Training is so cheap that new photos for training can be passed any time
TrainingIsCheap,
/// Training is significantly optimized if new images are received in batches
/// instead training single images multiple times
TrainingLikesBatches,
/// Training is a computing intensive operation.
/// By choice of the application, it may be manually triggered by the user.
TrainingIsExpensive
};
// for recognize algorithm option
enum RecognizeAlgorithm
{
LBP,
EigenFace,
FisherFace,
DNN // Default one as it must be the best and the most advanced : Deep Neural Networks.
};
public:
explicit RecognitionDatabase();
~RecognitionDatabase();
RecognitionDatabase(const RecognitionDatabase&);
// ------------ Identity management --------------
/// For the documentation of standard attributes, see identity.h
/**
* Returns all identities known to the database
*/
QList<Identity> allIdentities() const;
Identity identity(int id) const;
/**
* Finds the first identity with matching attribute - value.
* Returns a null identity if no match is found or attribute is empty.
*/
Identity findIdentity(const QString& attribute, const QString& value) const;
/**
* Finds the identity matching the given attributes.
* Attributes are first checked with knowledge of their meaning.
* Secondly, all unknown attributes are used.
* Returns a null Identity if no match is possible or the map is empty.
*/
Identity findIdentity(const QMap<QString, QString>& attributes) const;
/**
* Adds a new identity with the specified attributes.
* Please note that a UUID is automatically generated.
*/
Identity addIdentity(const QMap<QString, QString>& attributes);
/**
* Adds or sets, resp., the attributes of an identity.
*/
void addIdentityAttributes(int id, const QMap<QString, QString>& attributes);
void addIdentityAttribute(int id, const QString& attribute, const QString& value);
void setIdentityAttributes(int id, const QMap<QString, QString>& attributes);
// ------------ backend parameters --------------
/**
* A textual, informative identifier of the backend in use.
*/
QString backendIdentifier() const;
/**
* Tunes backend parameters.
* Available parameters:
* "accuracy", synonymous: "threshold", range: 0-1, type: float
- * Determines recognition threshold, 0->accept very unsecure recognitions, 1-> be very sure about a recognition.
+ * Determines recognition threshold, 0->accept very insecure recognitions, 1-> be very sure about a recognition.
*/
void setParameter(const QString& parameter, const QVariant& value);
void setParameters(const QVariantMap& parameters);
QVariantMap parameters() const;
// ------------ Recognition, clustering and training --------------
/**
* Returns the recommended size if you want to scale face images for recognition.
* Larger images can be passed, but may be downscaled.
*/
int recommendedImageSize(const QSize& availableSize = QSize()) const;
/**
* Set the face recognition algorithm type
*/
void activeFaceRecognizer(RecognizeAlgorithm algorithmType);
/**
* Performs recognition.
* The face details to be recognized are passed by the provider.
* For each entry in the provider, in 1-to-1 mapping,
* a recognized identity or the null identity is returned.
*/
QList<Identity> recognizeFaces(ImageListProvider* const images);
QList<Identity> recognizeFaces(const QList<QImage>& images);
Identity recognizeFace(const QImage& image);
/**
* Gives a hint about the complexity of training for the current backend.
*/
TrainingCostHint trainingCostHint() const;
/**
* Performs training.
* The identities which have new images to be trained are given.
* An empty list means that all identities are checked.
*
* All needed data will be queried from the provider.
*
* An identifier for the current training context is given,
* which can identify the application or group of collections.
* (It is assumed that training from different contexts is based on
* non-overlapping collections of images. Keep it always constant for your app.)
*/
void train(const QList<Identity>& identitiesToBeTrained, TrainingDataProvider* const data,
const QString& trainingContext);
void train(const Identity& identityToBeTrained, TrainingDataProvider* const data,
const QString& trainingContext);
/**
* Performs training by using image data directly.
*
* These are convenience functions for simple setups.
* If you want good performance and/or a more versatile implementation, be sure to
* implement your own TrainingDataProvider and use one of the above functions.
*/
void train(const Identity& identityToBeTrained, const QImage& image,
const QString& trainingContext);
void train(const Identity& identityToBeTrained, const QList<QImage>& images,
const QString& trainingContext);
/**
* Deletes the training data for all identities,
* leaving the identities as such in the database.
*/
void clearAllTraining(const QString& trainingContext = QString());
/**
* Deletes the training data for the given identity,
* leaving the identity as such in the database.
*/
void clearTraining(const QList<Identity>& identitiesToClean, const QString& trainingContext = QString());
/**
* Deletes an identity from the database.
*/
void deleteIdentity(const Identity& identityToBeDeleted);
/**
* Checks the integrity and returns true if everything is fine.
*/
bool integrityCheck();
/**
* Shrinks the database.
*/
void vacuum();
public:
// Declared as public to please with Clang compiler, due to use as argument with static methods.
class Private;
private:
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_FACESENGINE_RECOGNITIONDATABASE_H
diff --git a/core/libs/fileactionmanager/metadatahub.h b/core/libs/fileactionmanager/metadatahub.h
index 55901c119c..59b3d04b03 100644
--- a/core/libs/fileactionmanager/metadatahub.h
+++ b/core/libs/fileactionmanager/metadatahub.h
@@ -1,286 +1,286 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-01-05
* Description : Metadata handling
*
* Copyright (C) 2007-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2007-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_METADATA_HUB_H
#define DIGIKAM_METADATA_HUB_H
// Qt includes
#include <QList>
#include <QStringList>
#include <QDateTime>
#include <QMap>
// Local includes
#include "metadatasettings.h"
#include "captionvalues.h"
#include "dmetadata.h"
#include "dimg.h"
namespace Digikam
{
class ApplicationSettings;
class ImageInfo;
class Template;
class MetadataHub
{
public:
/**
The status enum describes the result of joining several metadata sets.
If only one set has been added, the status is always MetadataAvailable.
If no set has been added, the status is always MetadataInvalid
*/
enum Status
{
MetadataInvalid, /// not yet filled with any value
MetadataAvailable /// only one data set has been added, or a common value is available
};
enum WriteMode
{
/**
Write all available information
*/
FullWrite,
/**
Do a full write if and only if
- metadata fields changed
- the changed fields shall be written according to write settings
"Changed" in this context means changed by one of the set... methods,
the load() methods are ignored for this attribute.
This mode allows to avoid write operations when e.g. the user does not want
keywords to be written and only changes keywords.
*/
FullWriteIfChanged,
/**
Write only the changed parts.
Metadata fields which cannot be changed from MetadataHub (photographer ID etc.)
will never be written
*/
PartialWrite
};
enum WriteComponents{
WRITE_DATETIME = 1,
WRITE_TITLE = 2,
WRITE_COMMENTS = 4,
WRITE_PICKLABEL = 8,
WRITE_COLORLABEL= 16,
WRITE_RATING = 32,
WRITE_TEMPLATE = 64,
WRITE_TAGS = 128,
WRITE_ALL = 255
};
Q_DECLARE_FLAGS(WriteComponent, WriteComponents)
public:
/**
Constructs a MetadataHub.
@param dbmode Determines if the database may be accessed or not. See the enum description above.
*/
MetadataHub();
virtual ~MetadataHub();
/// Copies by value - no sharing involved.
MetadataHub& operator=(const MetadataHub&);
MetadataHub(const MetadataHub&);
/**
* Creates a copy (as always, by value) of this hub.
* Shall be reimplemented by subclasses to return an object of the correct type.
*/
virtual MetadataHub* clone() const;
void reset();
// --------------------------------------------------
/**
Add metadata information contained in the ImageInfo object.
This method (or in combination with the other load methods)
can be called multiple times on the same MetadataHub object.
In this case, the metadata will be combined.
*/
void load(const ImageInfo& info);
// /**
// Add metadata information from the DMetadata object
// */
// void load(const DMetadata& metadata);
// /**
// Load metadata information from the given file.
// (Uses DMetadata, QFileInfo)
// @returns True if the metadata could be loaded
// */
// bool load(const QString& filePath, const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
// --------------------------------------------------
/**
* @brief writeToMetadata - write to metadata using image info to retrieve tags and filepath
* use this method when multiple image infos are loaded in hub
* @param info - image info to retrieve current tags
* @param writeMode
* @param settings
- * @return true - if everything is succesfull
+ * @return true - if everything is successful
*/
bool writeToMetadata(const ImageInfo& info, WriteComponent writeMode = WRITE_ALL,
bool ignoreLazySync = false, const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
/**
Constructs a DMetadata object for given filePath,
calls the above method, writes the changes out to the file,
and notifies the ImageAttributesWatch.
WARNING: Do not use this method when multiple image infos are loaded
It will result in disjoint tags not being written
Use writeToMetadata(Image info ...) instead
@return Returns if the file has been touched
*/
bool write(const QString& filePath, WriteComponent writeMode = WRITE_ALL,
bool ignoreLazySync = false, const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
/**
Constructs a DMetadata object from the metadata stored in the given DImg object,
calls the above method, and changes the stored metadata in the DImg object.
@return Returns if the DImg object has been touched
*/
bool write(DImg& image, WriteComponent writeMode = WRITE_ALL,
bool ignoreLazySync = false, const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
/**
Will write only Tags to image. Used by TagsManager to write tags to image
Other metadata are not updated.
@return if tags were successfully written.
*/
bool writeTags(const QString& filePath, WriteComponent writeMode = WRITE_ALL,
const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
/**
* @brief writeTags - used to deduplicate code from writeTags and usual write, all write to tags
* operations must be done here
* @param metadata - DMetadata object that apply changes
* @param saveTags - save switch
* @return - if tags were successfully set
*/
bool writeTags(DMetadata& metadata, bool saveTags);
/**
* @brief cleanupTags - remove duplicates and obsolete tags before setting metadata
* @param toClean - tag list to be cleared and de-duplicated
* @return - clean tag list
*/
QStringList cleanupTags(const QStringList& toClean);
/**
With the currently applied changes, the given writeMode and settings,
returns if write(DMetadata), write(QString) or write(DImg) will actually
apply any changes.
*/
bool willWriteMetadata(Digikam::MetadataHub::WriteComponent writeMode = WRITE_ALL,
const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings()) const;
/**
* @brief writeToBaloo - write tags, comments and rating to KDE Nepomuk replacement: Baloo
* @param filePath - path to file to add comments, tags and rating
* @param settings - metadata settings to be set
*/
void writeToBaloo(const QString& filePath, const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
// --------------------------------------------------
/**
Dedicated method to set face rectangles from database
When called from outside the metadatahub and ImageInfo is cached,
method dimension() can return wrong values, QSize must be specified
manually
*/
void loadFaceTags(const ImageInfo& info, const QSize& size);
/**
Get face tag names and face tag regions.
This is used for metadata synchronization in Image Editor
*/
QMultiMap<QString, QVariant> getFaceTags();
/**
load the integer face tags.
This is used in face tag transformation
*/
QMultiMap<QString, QVariant> loadIntegerFaceTags(const ImageInfo& info);
/**
Set new face tags
*/
void setFaceTags(QMultiMap<QString, QVariant> newFaceTags, QSize size);
protected:
/**
Applies the set of metadata contained in this MetadataHub
to the given DMetadata object.
The MetadataSettingsContainer determine whether data is actually
set or not.
The following metadata fields may be set (depending on settings):
- Comment
- Date
- Rating
- Tags
- Photographer ID (data from settings)
- Credits (data from settings)
The data fields taken from this MetadataHub object are only set if
their status is MetadataAvailable.
If the status is MetadataInvalid or MetadataDisjoint, the respective
metadata field is not touched.
@return Returns true if the metadata object has been touched
*/
bool write(DMetadata& metadata, WriteComponent writeMode = WRITE_ALL,
const MetadataSettingsContainer& settings = MetadataSettings::instance()->settings());
void load(const QDateTime& dateTime,
const CaptionsMap& titles, const CaptionsMap& comment,
int colorLabel, int pickLabel,
int rating, const Template& t);
void loadTags(const QList<int>& loadedTagIds);
void loadTags(const QStringList& loadedTagPaths);
void notifyTagDeleted(int id);
virtual void applyChangeNotifications();
private:
class Private;
Private* const d;
};
} // namespace Digikam
Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::MetadataHub::WriteComponent)
#endif // DIGIKAM_METADATA_HUB_H
diff --git a/core/libs/imageproperties/imagedescedittab.cpp b/core/libs/imageproperties/imagedescedittab.cpp
index 05a73134e0..f1eb42d013 100644
--- a/core/libs/imageproperties/imagedescedittab.cpp
+++ b/core/libs/imageproperties/imagedescedittab.cpp
@@ -1,1495 +1,1495 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2003-03-09
* Description : Captions, Tags, and Rating properties editor
*
* Copyright (C) 2003-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2003-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
* Copyright (C) 2009-2011 by Johannes Wienke <languitar at semipol dot de>
* Copyright (C) 2015 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "imagedescedittab.h"
// Qt includes
#include <QTextEdit>
#include <QStyle>
#include <QGridLayout>
#include <QScrollArea>
#include <QSignalMapper>
#include <QTimer>
#include <QToolButton>
#include <QApplication>
#include <QPushButton>
#include <QMenu>
#include <QIcon>
#include <QCheckBox>
#include <QMessageBox>
#include <QPointer>
// Local includes
#include "digikam_debug.h"
#include "addtagslineedit.h"
#include "applicationsettings.h"
#include "albumthumbnailloader.h"
#include "captionedit.h"
#include "collectionscanner.h"
#include "coredbtransaction.h"
#include "dnotificationwrapper.h"
#include "ddatetimeedit.h"
#include "digikamapp.h"
#include "fileactionmngr.h"
#include "ratingwidget.h"
#include "scancontroller.h"
#include "tagcheckview.h"
#include "templateselector.h"
#include "templateviewer.h"
#include "imageattributeswatch.h"
#include "statusprogressbar.h"
#include "tagmodificationhelper.h"
#include "template.h"
#include "imageinfolist.h"
#include "imageinfo.h"
#include "colorlabelwidget.h"
#include "picklabelwidget.h"
#include "fileactionprogress.h"
#include "tagsmanager.h"
#include "searchtextbar.h"
#include "disjointmetadata.h"
#include "altlangstredit.h"
namespace Digikam
{
class Q_DECL_HIDDEN ImageDescEditTab::Private
{
public:
enum DescEditTab
{
DESCRIPTIONS=0,
TAGS,
INFOS
};
explicit Private()
{
modified = false;
ignoreImageAttributesWatch = false;
ignoreTagChanges = false;
togglingSearchSettings = false;
recentTagsBtn = 0;
titleEdit = 0;
captionsEdit = 0;
tagsSearchBar = 0;
dateTimeEdit = 0;
tagCheckView = 0;
ratingWidget = 0;
assignedTagsBtn = 0;
applyBtn = 0;
moreButton = 0;
revertBtn = 0;
openTagMngr = 0;
moreMenu = 0;
applyToAllVersionsButton = 0;
recentTagsMapper = 0;
newTagEdit = 0;
lastSelectedWidget = 0;
templateSelector = 0;
templateViewer = 0;
tabWidget = 0;
tagModel = 0;
tagCheckView = 0;
colorLabelSelector = 0;
pickLabelSelector = 0;
metadataChangeTimer = 0;
}
bool modified;
bool ignoreImageAttributesWatch;
bool ignoreTagChanges;
bool togglingSearchSettings;
QToolButton* recentTagsBtn;
QToolButton* assignedTagsBtn;
QToolButton* revertBtn;
QPushButton* openTagMngr;
QMenu* moreMenu;
QSignalMapper* recentTagsMapper;
QPushButton* applyBtn;
QPushButton* moreButton;
QPushButton* applyToAllVersionsButton;
QWidget* lastSelectedWidget;
AltLangStrEdit* titleEdit;
CaptionEdit* captionsEdit;
DDateTimeEdit* dateTimeEdit;
QTabWidget* tabWidget;
SearchTextBar* tagsSearchBar;
AddTagsLineEdit* newTagEdit;
ImageInfoList currInfos;
TagCheckView* tagCheckView;
TemplateSelector* templateSelector;
TemplateViewer* templateViewer;
RatingWidget* ratingWidget;
ColorLabelSelector* colorLabelSelector;
PickLabelSelector* pickLabelSelector;
DisjointMetadata hub;
TagModel* tagModel;
QTimer* metadataChangeTimer;
QList<int> metadataChangeIds;
};
ImageDescEditTab::ImageDescEditTab(QWidget* const parent)
: DVBox(parent),
d(new Private)
{
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
setContentsMargins(QMargins());
setSpacing(spacing);
d->tabWidget = new QTabWidget(this);
d->metadataChangeTimer = new QTimer(this);
d->metadataChangeTimer->setSingleShot(true);
d->metadataChangeTimer->setInterval(250);
// Captions/Date/Rating view -----------------------------------
QScrollArea* const sv = new QScrollArea(d->tabWidget);
sv->setFrameStyle(QFrame::NoFrame);
sv->setWidgetResizable(true);
QWidget* const captionTagsArea = new QWidget(sv->viewport());
QGridLayout* const grid1 = new QGridLayout(captionTagsArea);
sv->setWidget(captionTagsArea);
d->titleEdit = new AltLangStrEdit(captionTagsArea);
d->titleEdit->setTitle(i18n("Title:"));
d->titleEdit->setPlaceholderText(i18n("Enter title here."));
d->captionsEdit = new CaptionEdit(captionTagsArea);
DHBox* const dateBox = new DHBox(captionTagsArea);
new QLabel(i18n("Date:"), dateBox);
d->dateTimeEdit = new DDateTimeEdit(dateBox, QLatin1String("datepicker"));
DHBox* const pickBox = new DHBox(captionTagsArea);
new QLabel(i18n("Pick Label:"), pickBox);
d->pickLabelSelector = new PickLabelSelector(pickBox);
pickBox->layout()->setAlignment(d->pickLabelSelector, Qt::AlignVCenter|Qt::AlignRight);
DHBox* const colorBox = new DHBox(captionTagsArea);
new QLabel(i18n("Color Label:"), colorBox);
d->colorLabelSelector = new ColorLabelSelector(colorBox);
colorBox->layout()->setAlignment(d->colorLabelSelector, Qt::AlignVCenter|Qt::AlignRight);
DHBox* const rateBox = new DHBox(captionTagsArea);
new QLabel(i18n("Rating:"), rateBox);
d->ratingWidget = new RatingWidget(rateBox);
rateBox->layout()->setAlignment(d->ratingWidget, Qt::AlignVCenter|Qt::AlignRight);
// Buttons -----------------------------------------
DHBox* const applyButtonBox = new DHBox(this);
applyButtonBox->setSpacing(spacing);
d->applyBtn = new QPushButton(i18n("Apply"), applyButtonBox);
d->applyBtn->setIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply")));
d->applyBtn->setEnabled(false);
d->applyBtn->setToolTip( i18n("Apply all changes to images"));
//buttonsBox->setStretchFactor(d->applyBtn, 10);
DHBox* const buttonsBox = new DHBox(this);
buttonsBox->setSpacing(spacing);
d->revertBtn = new QToolButton(buttonsBox);
d->revertBtn->setIcon(QIcon::fromTheme(QLatin1String("document-revert")));
d->revertBtn->setToolTip( i18n("Revert all changes"));
d->revertBtn->setEnabled(false);
d->applyToAllVersionsButton = new QPushButton(i18n("Apply to all versions"), buttonsBox);
d->applyToAllVersionsButton->setIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply")));
d->applyToAllVersionsButton->setEnabled(false);
d->applyToAllVersionsButton->setToolTip(i18n("Apply all changes to all versions of this image"));
d->moreButton = new QPushButton(i18n("More"), buttonsBox);
d->moreMenu = new QMenu(captionTagsArea);
d->moreButton->setMenu(d->moreMenu);
// --------------------------------------------------
grid1->addWidget(d->titleEdit, 0, 0, 1, 2);
grid1->addWidget(d->captionsEdit, 1, 0, 1, 2);
grid1->addWidget(dateBox, 2, 0, 1, 2);
grid1->addWidget(pickBox, 3, 0, 1, 2);
grid1->addWidget(colorBox, 4, 0, 1, 2);
grid1->addWidget(rateBox, 5, 0, 1, 2);
grid1->setRowStretch(1, 10);
grid1->setContentsMargins(spacing, spacing, spacing, spacing);
grid1->setSpacing(spacing);
d->tabWidget->insertTab(Private::DESCRIPTIONS, sv, i18n("Description"));
// Tags view ---------------------------------------------------
QScrollArea* const sv3 = new QScrollArea(d->tabWidget);
sv3->setFrameStyle(QFrame::NoFrame);
sv3->setWidgetResizable(true);
QWidget* const tagsArea = new QWidget(sv3->viewport());
QGridLayout* const grid3 = new QGridLayout(tagsArea);
sv3->setWidget(tagsArea);
d->tagModel = new TagModel(AbstractAlbumModel::IncludeRootAlbum, this);
d->tagModel->setCheckable(true);
d->tagModel->setRootCheckable(false);
d->tagCheckView = new TagCheckView(tagsArea, d->tagModel);
d->tagCheckView->setCheckNewTags(true);
d->openTagMngr = new QPushButton( i18n("Open Tag Manager"));
d->newTagEdit = new AddTagsLineEdit(tagsArea);
d->newTagEdit->setSupportingTagModel(d->tagModel);
d->newTagEdit->setTagTreeView(d->tagCheckView);
//, "ImageDescEditTabNewTagEdit",
//d->newTagEdit->setCaseSensitive(false);
d->newTagEdit->setPlaceholderText(i18n("Enter tag here."));
d->newTagEdit->setWhatsThis(i18n("Enter the text used to create tags here. "
"'/' can be used to create a hierarchy of tags. "
"',' can be used to create more than one hierarchy at the same time."));
DHBox* const tagsSearch = new DHBox(tagsArea);
tagsSearch->setSpacing(spacing);
d->tagsSearchBar = new SearchTextBar(tagsSearch, QLatin1String("ImageDescEditTabTagsSearchBar"));
d->tagsSearchBar->setModel(d->tagCheckView->filteredModel(),
AbstractAlbumModel::AlbumIdRole, AbstractAlbumModel::AlbumTitleRole);
d->tagsSearchBar->setFilterModel(d->tagCheckView->albumFilterModel());
d->assignedTagsBtn = new QToolButton(tagsSearch);
d->assignedTagsBtn->setToolTip( i18n("Tags already assigned"));
d->assignedTagsBtn->setIcon(QIcon::fromTheme(QLatin1String("tag-assigned")));
d->assignedTagsBtn->setCheckable(true);
d->recentTagsBtn = new QToolButton(tagsSearch);
QMenu* const recentTagsMenu = new QMenu(d->recentTagsBtn);
d->recentTagsBtn->setToolTip( i18n("Recent Tags"));
d->recentTagsBtn->setIcon(QIcon::fromTheme(QLatin1String("tag-recents")));
d->recentTagsBtn->setIconSize(QSize(16, 16));
d->recentTagsBtn->setMenu(recentTagsMenu);
d->recentTagsBtn->setPopupMode(QToolButton::InstantPopup);
d->recentTagsMapper = new QSignalMapper(this);
grid3->addWidget(d->openTagMngr, 0, 0, 1, 2);
grid3->addWidget(d->newTagEdit, 1, 0, 1, 2);
grid3->addWidget(d->tagCheckView, 2, 0, 1, 2);
grid3->addWidget(tagsSearch, 3, 0, 1, 2);
grid3->setRowStretch(1, 10);
d->tabWidget->insertTab(Private::TAGS, sv3, i18n("Tags"));
- // Information Managament View --------------------------------------
+ // Information Management View --------------------------------------
QScrollArea* const sv2 = new QScrollArea(d->tabWidget);
sv2->setFrameStyle(QFrame::NoFrame);
sv2->setWidgetResizable(true);
QWidget* const infoArea = new QWidget(sv2->viewport());
QGridLayout* const grid2 = new QGridLayout(infoArea);
sv2->setWidget(infoArea);
d->templateSelector = new TemplateSelector(infoArea);
d->templateViewer = new TemplateViewer(infoArea);
d->templateViewer->setObjectName(QLatin1String("ImageDescEditTab Expander"));
grid2->addWidget(d->templateSelector, 0, 0, 1, 2);
grid2->addWidget(d->templateViewer, 1, 0, 1, 2);
grid2->setRowStretch(1, 10);
grid2->setContentsMargins(spacing, spacing, spacing, spacing);
grid2->setSpacing(spacing);
d->tabWidget->insertTab(Private::INFOS, sv2, i18n("Information"));
// --------------------------------------------------
connect(d->openTagMngr, SIGNAL(clicked()),
this, SLOT(slotOpenTagsManager()));
connect(d->tagCheckView->checkableModel(), SIGNAL(checkStateChanged(Album*,Qt::CheckState)),
this, SLOT(slotTagStateChanged(Album*,Qt::CheckState)));
connect(d->titleEdit, SIGNAL(signalModified(QString,QString)),
this, SLOT(slotTitleChanged()));
connect(d->titleEdit, SIGNAL(signalValueAdded(QString,QString)),
this, SLOT(slotTitleChanged()));
connect(d->titleEdit, SIGNAL(signalValueDeleted(QString)),
this, SLOT(slotTitleChanged()));
connect(d->captionsEdit, SIGNAL(signalModified()),
this, SLOT(slotCommentChanged()));
connect(d->dateTimeEdit, SIGNAL(dateTimeChanged(QDateTime)),
this, SLOT(slotDateTimeChanged(QDateTime)));
connect(d->pickLabelSelector, SIGNAL(signalPickLabelChanged(int)),
this, SLOT(slotPickLabelChanged(int)));
connect(d->colorLabelSelector, SIGNAL(signalColorLabelChanged(int)),
this, SLOT(slotColorLabelChanged(int)));
connect(d->ratingWidget, SIGNAL(signalRatingChanged(int)),
this, SLOT(slotRatingChanged(int)));
connect(d->templateSelector, SIGNAL(signalTemplateSelected()),
this, SLOT(slotTemplateSelected()));
connect(d->tagsSearchBar, SIGNAL(signalSearchTextSettings(SearchTextSettings)),
this, SLOT(slotTagsSearchChanged(SearchTextSettings)));
connect(d->assignedTagsBtn, SIGNAL(toggled(bool)),
this, SLOT(slotAssignedTagsToggled(bool)));
connect(d->newTagEdit, SIGNAL(taggingActionActivated(TaggingAction)),
this, SLOT(slotTaggingActionActivated(TaggingAction)));
connect(d->applyBtn, SIGNAL(clicked()),
this, SLOT(slotApplyAllChanges()));
connect(d->applyToAllVersionsButton, SIGNAL(clicked()),
this, SLOT(slotApplyChangesToAllVersions()));
connect(d->revertBtn, SIGNAL(clicked()),
this, SLOT(slotRevertAllChanges()));
connect(d->moreMenu, SIGNAL(aboutToShow()),
this, SLOT(slotMoreMenu()));
connect(d->recentTagsMapper, SIGNAL(mapped(int)),
this, SLOT(slotRecentTagsMenuActivated(int)));
connect(d->metadataChangeTimer, SIGNAL(timeout()),
this, SLOT(slotReloadForMetadataChange()));
connect(this, SIGNAL(askToApplyChanges(QList<ImageInfo>,DisjointMetadata*)),
this, SLOT(slotAskToApplyChanges(QList<ImageInfo>,DisjointMetadata*)),
Qt::QueuedConnection);
// Initialize ---------------------------------------------
d->titleEdit->textEdit()->installEventFilter(this);
d->captionsEdit->textEdit()->installEventFilter(this);
d->dateTimeEdit->installEventFilter(this);
d->pickLabelSelector->installEventFilter(this);
d->colorLabelSelector->installEventFilter(this);
d->ratingWidget->installEventFilter(this);
// TODO update, what does this filter?
d->tagCheckView->installEventFilter(this);
updateRecentTags();
// Connect to attribute watch ------------------------------
ImageAttributesWatch* const watch = ImageAttributesWatch::instance();
connect(watch, SIGNAL(signalImageTagsChanged(qlonglong)),
this, SLOT(slotImageTagsChanged(qlonglong)));
connect(watch, SIGNAL(signalImagesChanged(int)),
this, SLOT(slotImagesChanged(int)));
connect(watch, SIGNAL(signalImageRatingChanged(qlonglong)),
this, SLOT(slotImageRatingChanged(qlonglong)));
connect(watch, SIGNAL(signalImageDateChanged(qlonglong)),
this, SLOT(slotImageDateChanged(qlonglong)));
connect(watch, SIGNAL(signalImageCaptionChanged(qlonglong)),
this, SLOT(slotImageCaptionChanged(qlonglong)));
}
ImageDescEditTab::~ImageDescEditTab()
{
delete d;
}
void ImageDescEditTab::readSettings(KConfigGroup& group)
{
d->tabWidget->setCurrentIndex(group.readEntry(QLatin1String("ImageDescEdit Tab"), (int)Private::DESCRIPTIONS));
d->titleEdit->setCurrentLanguageCode(group.readEntry(QLatin1String("ImageDescEditTab TitleLang"), QString()));
d->captionsEdit->setCurrentLanguageCode(group.readEntry(QLatin1String("ImageDescEditTab CaptionsLang"), QString()));
d->templateViewer->readSettings(group);
d->tagCheckView->setConfigGroup(group);
d->tagCheckView->setEntryPrefix(QLatin1String("ImageDescEditTab TagCheckView"));
d->tagCheckView->loadState();
d->tagsSearchBar->setConfigGroup(group);
d->tagsSearchBar->setEntryPrefix(QLatin1String("ImageDescEditTab SearchBar"));
d->tagsSearchBar->loadState();
}
void ImageDescEditTab::writeSettings(KConfigGroup& group)
{
group.writeEntry(QLatin1String("ImageDescEdit Tab"), d->tabWidget->currentIndex());
group.writeEntry(QLatin1String("ImageDescEditTab TitleLang"), d->titleEdit->currentLanguageCode());
group.writeEntry(QLatin1String("ImageDescEditTab CaptionsLang"), d->captionsEdit->currentLanguageCode());
d->templateViewer->writeSettings(group);
d->tagCheckView->saveState();
d->tagsSearchBar->saveState();
}
void ImageDescEditTab::setFocusToLastSelectedWidget()
{
if (d->lastSelectedWidget)
{
d->lastSelectedWidget->setFocus();
}
d->lastSelectedWidget = 0;
}
void ImageDescEditTab::setFocusToTagsView()
{
d->lastSelectedWidget = qobject_cast<QWidget*>(d->tagCheckView);
d->tagCheckView->setFocus();
d->tabWidget->setCurrentIndex(Private::TAGS);
}
void ImageDescEditTab::setFocusToNewTagEdit()
{
//select "Tags" tab and focus the NewTagLineEdit widget
d->tabWidget->setCurrentIndex(Private::TAGS);
d->newTagEdit->setFocus();
}
void ImageDescEditTab::setFocusToTitlesEdit()
{
d->tabWidget->setCurrentIndex(Private::DESCRIPTIONS);
d->titleEdit->textEdit()->setFocus();
}
void ImageDescEditTab::setFocusToCommentsEdit()
{
d->tabWidget->setCurrentIndex(Private::DESCRIPTIONS);
d->captionsEdit->textEdit()->setFocus();
}
void ImageDescEditTab::activateAssignedTagsButton()
{
d->tabWidget->setCurrentIndex(Private::TAGS);
d->assignedTagsBtn->click();
}
bool ImageDescEditTab::singleSelection() const
{
return (d->currInfos.count() == 1);
}
void ImageDescEditTab::slotChangingItems()
{
if (!d->modified)
{
return;
}
if (d->currInfos.isEmpty())
{
return;
}
if (!ApplicationSettings::instance()->getApplySidebarChangesDirectly())
{
// Open dialog via queued connection out-of-scope, see bug 302311
emit askToApplyChanges(d->currInfos, new DisjointMetadata(d->hub));
reset();
}
else
{
slotApplyAllChanges();
}
}
void ImageDescEditTab::slotAskToApplyChanges(const QList<ImageInfo>& infos, DisjointMetadata* hub)
{
int changedFields = 0;
if (hub->titlesChanged())
{
++changedFields;
}
if (hub->commentsChanged())
{
++changedFields;
}
if (hub->dateTimeChanged())
{
++changedFields;
}
if (hub->ratingChanged())
{
++changedFields;
}
if (hub->pickLabelChanged())
{
++changedFields;
}
if (hub->colorLabelChanged())
{
++changedFields;
}
if (hub->tagsChanged())
{
++changedFields;
}
QString text;
if (changedFields == 1)
{
if (hub->commentsChanged())
text = i18np("You have edited the image caption. ",
"You have edited the captions of %1 images. ",
infos.count());
else if (hub->titlesChanged())
text = i18np("You have edited the image title. ",
"You have edited the titles of %1 images. ",
infos.count());
else if (hub->dateTimeChanged())
text = i18np("You have edited the date of the image. ",
"You have edited the date of %1 images. ",
infos.count());
else if (hub->pickLabelChanged())
text = i18np("You have edited the pick label of the image. ",
"You have edited the pick label of %1 images. ",
infos.count());
else if (hub->colorLabelChanged())
text = i18np("You have edited the color label of the image. ",
"You have edited the color label of %1 images. ",
infos.count());
else if (hub->ratingChanged())
text = i18np("You have edited the rating of the image. ",
"You have edited the rating of %1 images. ",
infos.count());
else if (hub->tagsChanged())
text = i18np("You have edited the tags of the image. ",
"You have edited the tags of %1 images. ",
infos.count());
text += i18n("Do you want to apply your changes?");
}
else
{
text = i18np("<p>You have edited the metadata of the image: </p><p><ul>",
"<p>You have edited the metadata of %1 images: </p><p><ul>",
infos.count());
if (hub->titlesChanged())
{
text += i18n("<li>title</li>");
}
if (hub->commentsChanged())
{
text += i18n("<li>caption</li>");
}
if (hub->dateTimeChanged())
{
text += i18n("<li>date</li>");
}
if (hub->pickLabelChanged())
{
text += i18n("<li>pick label</li>");
}
if (hub->colorLabelChanged())
{
text += i18n("<li>color label</li>");
}
if (hub->ratingChanged())
{
text += i18n("<li>rating</li>");
}
if (hub->tagsChanged())
{
text += i18n("<li>tags</li>");
}
text += QLatin1String("</ul></p>");
text += i18n("<p>Do you want to apply your changes?</p>");
}
QCheckBox* const alwaysCBox = new QCheckBox(i18n("Always apply changes without confirmation"));
QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Information,
i18n("Apply changes?"),
text,
QMessageBox::Yes | QMessageBox::No,
qApp->activeWindow());
msgBox->setCheckBox(alwaysCBox);
msgBox->setDefaultButton(QMessageBox::No);
msgBox->setEscapeButton(QMessageBox::No);
// Pop-up a message in desktop notification manager
DNotificationWrapper(QString(), i18n("Apply changes?"),
DigikamApp::instance(), DigikamApp::instance()->windowTitle());
int returnCode = msgBox->exec();
bool alwaysApply = msgBox->checkBox()->isChecked();
delete msgBox;
if (alwaysApply)
{
ApplicationSettings::instance()->setApplySidebarChangesDirectly(true);
}
if (returnCode == QMessageBox::No)
{
delete hub;
return;
}
// otherwise apply:
FileActionMngr::instance()->applyMetadata(infos, hub);
}
void ImageDescEditTab::reset()
{
d->modified = false;
d->hub.resetChanged();
d->applyBtn->setEnabled(false);
d->revertBtn->setEnabled(false);
d->applyToAllVersionsButton->setEnabled(false);
}
void ImageDescEditTab::slotApplyAllChanges()
{
if (!d->modified)
{
return;
}
if (d->currInfos.isEmpty())
{
return;
}
FileActionMngr::instance()->applyMetadata(d->currInfos, d->hub);
reset();
}
void ImageDescEditTab::slotRevertAllChanges()
{
if (!d->modified)
{
return;
}
if (d->currInfos.isEmpty())
{
return;
}
setInfos(d->currInfos);
}
void ImageDescEditTab::setItem(const ImageInfo& info)
{
slotChangingItems();
ImageInfoList list;
if (!info.isNull())
{
list << info;
}
setInfos(list);
}
void ImageDescEditTab::setItems(const ImageInfoList& infos)
{
slotChangingItems();
setInfos(infos);
}
void ImageDescEditTab::setInfos(const ImageInfoList& infos)
{
if (infos.isEmpty())
{
d->hub = DisjointMetadata();
d->captionsEdit->blockSignals(true);
d->captionsEdit->reset();
d->captionsEdit->blockSignals(false);
d->titleEdit->blockSignals(true);
d->titleEdit->reset();
d->titleEdit->blockSignals(false);
d->currInfos.clear();
resetMetadataChangeInfo();
setEnabled(false);
return;
}
setEnabled(true);
d->currInfos = infos;
d->modified = false;
resetMetadataChangeInfo();
d->hub = DisjointMetadata();
d->applyBtn->setEnabled(false);
d->revertBtn->setEnabled(false);
foreach(const ImageInfo& info, d->currInfos)
{
d->hub.load(info);
}
updateComments();
updatePickLabel();
updateColorLabel();
updateRating();
updateDate();
updateTemplate();
updateTagsView();
updateRecentTags();
setFocusToLastSelectedWidget();
}
void ImageDescEditTab::slotReadFromFileMetadataToDatabase()
{
initProgressIndicator();
emit signalProgressMessageChanged(i18n("Reading metadata from files. Please wait..."));
d->ignoreImageAttributesWatch = true;
int i = 0;
ScanController::instance()->suspendCollectionScan();
CollectionScanner scanner;
foreach(const ImageInfo& info, d->currInfos)
{
scanner.scanFile(info, CollectionScanner::Rescan);
emit signalProgressValueChanged(i++/(float)d->currInfos.count());
qApp->processEvents();
}
ScanController::instance()->resumeCollectionScan();
d->ignoreImageAttributesWatch = false;
emit signalProgressFinished();
// reload everything
setInfos(d->currInfos);
}
void ImageDescEditTab::slotWriteToFileMetadataFromDatabase()
{
initProgressIndicator();
emit signalProgressMessageChanged(i18n("Writing metadata to files. Please wait..."));
int i = 0;
foreach(const ImageInfo& info, d->currInfos)
{
MetadataHub fileHub;
// read in from database
fileHub.load(info);
// write out to file DMetadata
fileHub.write(info.filePath());
emit signalProgressValueChanged(i++/(float)d->currInfos.count());
qApp->processEvents();
}
emit signalProgressFinished();
}
bool ImageDescEditTab::eventFilter(QObject* o, QEvent* e)
{
if ( e->type() == QEvent::KeyPress )
{
QKeyEvent* const k = static_cast<QKeyEvent*>(e);
if (k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return)
{
if (k->modifiers() == Qt::ControlModifier)
{
d->lastSelectedWidget = qobject_cast<QWidget*>(o);
emit signalNextItem();
return true;
}
else if (k->modifiers() == Qt::ShiftModifier)
{
d->lastSelectedWidget = qobject_cast<QWidget*>(o);
emit signalPrevItem();
return true;
}
}
if (k->key() == Qt::Key_PageUp)
{
d->lastSelectedWidget = qobject_cast<QWidget*>(o);
emit signalPrevItem();
return true;
}
if (k->key() == Qt::Key_PageDown)
{
d->lastSelectedWidget = qobject_cast<QWidget*>(o);
emit signalNextItem();
return true;
}
}
return DVBox::eventFilter(o, e);
}
void ImageDescEditTab::populateTags()
{
// TODO update, this wont work... crashes
//KConfigGroup group;
//d->tagCheckView->loadViewState(group);
}
void ImageDescEditTab::slotTagStateChanged(Album* album, Qt::CheckState checkState)
{
TAlbum* const tag = dynamic_cast<TAlbum*>(album);
if (!tag || d->ignoreTagChanges)
{
return;
}
switch (checkState)
{
case Qt::Checked:
d->hub.setTag(tag->id());
break;
default:
d->hub.setTag(tag->id(), DisjointMetadata::MetadataInvalid);
break;
}
slotModified();
}
void ImageDescEditTab::slotCommentChanged()
{
d->hub.setComments(d->captionsEdit->values());
setMetadataWidgetStatus(d->hub.commentsStatus(), d->captionsEdit);
slotModified();
}
void ImageDescEditTab::slotTitleChanged()
{
CaptionsMap titles;
titles.fromAltLangMap(d->titleEdit->values());
d->hub.setTitles(titles);
setMetadataWidgetStatus(d->hub.titlesStatus(), d->titleEdit);
slotModified();
}
void ImageDescEditTab::slotDateTimeChanged(const QDateTime& dateTime)
{
d->hub.setDateTime(dateTime);
setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit);
slotModified();
}
void ImageDescEditTab::slotTemplateSelected()
{
d->hub.setMetadataTemplate(d->templateSelector->getTemplate());
d->templateViewer->setTemplate(d->templateSelector->getTemplate());
setMetadataWidgetStatus(d->hub.templateStatus(), d->templateSelector);
slotModified();
}
void ImageDescEditTab::slotPickLabelChanged(int pickId)
{
d->hub.setPickLabel(pickId);
// no handling for MetadataDisjoint needed for pick label,
// we set it to 0 when disjoint, see below
slotModified();
}
void ImageDescEditTab::slotColorLabelChanged(int colorId)
{
d->hub.setColorLabel(colorId);
// no handling for MetadataDisjoint needed for color label,
// we set it to 0 when disjoint, see below
slotModified();
}
void ImageDescEditTab::slotRatingChanged(int rating)
{
d->hub.setRating(rating);
// no handling for MetadataDisjoint needed for rating,
// we set it to 0 when disjoint, see below
slotModified();
}
void ImageDescEditTab::slotModified()
{
d->modified = true;
d->applyBtn->setEnabled(true);
d->revertBtn->setEnabled(true);
if(d->currInfos.size() == 1)
{
d->applyToAllVersionsButton->setEnabled(true);
}
}
void ImageDescEditTab::slotCreateNewTag()
{
if (d->newTagEdit->text().isEmpty())
{
return;
}
TAlbum* const created = d->tagCheckView->tagModificationHelper()->
slotTagNew(d->tagCheckView->currentAlbum(), d->newTagEdit->text());
if (created)
{
//d->tagCheckView->slotSelectAlbum(created);
d->newTagEdit->clear();
}
}
void ImageDescEditTab::slotTaggingActionActivated(const TaggingAction& action)
{
TAlbum* assigned = 0;
if (action.shallAssignTag())
{
assigned = AlbumManager::instance()->findTAlbum(action.tagId());
if (assigned)
{
d->tagModel->setChecked(assigned, true);
}
}
else if (action.shallCreateNewTag())
{
TAlbum* const parent = AlbumManager::instance()->findTAlbum(action.parentTagId());
// tag is assigned automatically
assigned = d->tagCheckView->tagModificationHelper()->slotTagNew(parent, action.newTagName());
}
if (assigned)
{
d->tagCheckView->scrollTo(d->tagCheckView->albumFilterModel()->indexForAlbum(assigned));
QTimer::singleShot(0, d->newTagEdit, SLOT(clear()));
}
}
void ImageDescEditTab::assignPickLabel(int pickId)
{
d->pickLabelSelector->setPickLabel((PickLabel)pickId);
}
void ImageDescEditTab::assignColorLabel(int colorId)
{
d->colorLabelSelector->setColorLabel((ColorLabel)colorId);
}
void ImageDescEditTab::assignRating(int rating)
{
d->ratingWidget->setRating(rating);
}
void ImageDescEditTab::setTagState(TAlbum* const tag, DisjointMetadata::Status status)
{
if (!tag)
{
return;
}
switch (status)
{
case DisjointMetadata::MetadataDisjoint:
d->tagModel->setCheckState(tag, Qt::PartiallyChecked);
break;
case DisjointMetadata::MetadataAvailable:
d->tagModel->setChecked(tag, true);
break;
case DisjointMetadata::MetadataInvalid:
d->tagModel->setChecked(tag, false);
break;
default:
qCWarning(DIGIKAM_GENERAL_LOG) << "Untreated tag status enum value " << status;
d->tagModel->setCheckState(tag, Qt::PartiallyChecked);
break;
}
}
void ImageDescEditTab::updateTagsView()
{
// avoid that the automatic tag toggling handles these calls and
// modification is indicated to this widget
TagCheckView::ToggleAutoTags toggle = d->tagCheckView->getToggleAutoTags();
d->tagCheckView->setToggleAutoTags(TagCheckView::NoToggleAuto);
d->ignoreTagChanges = true;
// first reset the tags completely
d->tagModel->resetAllCheckedAlbums();
// then update checked state for all tags of the currently selected images
const QMap<int, DisjointMetadata::Status> hubMap = d->hub.tags();
for (QMap<int, DisjointMetadata::Status>::const_iterator it = hubMap.begin(); it != hubMap.end(); ++it)
{
TAlbum* tag = AlbumManager::instance()->findTAlbum(it.key());
setTagState(tag, it.value());
}
d->ignoreTagChanges = false;
d->tagCheckView->setToggleAutoTags(toggle);
// The condition is a temporary fix not to destroy name filtering on image change.
// See comments in these methods.
if (d->assignedTagsBtn->isChecked())
{
slotAssignedTagsToggled(d->assignedTagsBtn->isChecked());
}
}
void ImageDescEditTab::updateComments()
{
d->captionsEdit->blockSignals(true);
d->captionsEdit->setValues(d->hub.comments());
setMetadataWidgetStatus(d->hub.commentsStatus(), d->captionsEdit);
d->captionsEdit->blockSignals(false);
d->titleEdit->blockSignals(true);
d->titleEdit->setValues(d->hub.titles().toAltLangMap());
setMetadataWidgetStatus(d->hub.titlesStatus(), d->titleEdit);
d->titleEdit->blockSignals(false);
}
void ImageDescEditTab::updatePickLabel()
{
d->pickLabelSelector->blockSignals(true);
if (d->hub.pickLabelStatus() == DisjointMetadata::MetadataDisjoint)
{
d->pickLabelSelector->setPickLabel(NoPickLabel);
}
else
{
d->pickLabelSelector->setPickLabel((PickLabel)d->hub.pickLabel());
}
d->pickLabelSelector->blockSignals(false);
}
void ImageDescEditTab::updateColorLabel()
{
d->colorLabelSelector->blockSignals(true);
if (d->hub.colorLabelStatus() == DisjointMetadata::MetadataDisjoint)
{
d->colorLabelSelector->setColorLabel(NoColorLabel);
}
else
{
d->colorLabelSelector->setColorLabel((ColorLabel)d->hub.colorLabel());
}
d->colorLabelSelector->blockSignals(false);
}
void ImageDescEditTab::updateRating()
{
d->ratingWidget->blockSignals(true);
if (d->hub.ratingStatus() == DisjointMetadata::MetadataDisjoint)
{
d->ratingWidget->setRating(0);
}
else
{
d->ratingWidget->setRating(d->hub.rating());
}
d->ratingWidget->blockSignals(false);
}
void ImageDescEditTab::updateDate()
{
d->dateTimeEdit->blockSignals(true);
d->dateTimeEdit->setDateTime(d->hub.dateTime());
setMetadataWidgetStatus(d->hub.dateTimeStatus(), d->dateTimeEdit);
d->dateTimeEdit->blockSignals(false);
}
void ImageDescEditTab::updateTemplate()
{
d->templateSelector->blockSignals(true);
d->templateSelector->setTemplate(d->hub.metadataTemplate());
d->templateViewer->setTemplate(d->hub.metadataTemplate());
setMetadataWidgetStatus(d->hub.templateStatus(), d->templateSelector);
d->templateSelector->blockSignals(false);
}
void ImageDescEditTab::setMetadataWidgetStatus(int status, QWidget* const widget)
{
if (status == DisjointMetadata::MetadataDisjoint)
{
// For text widgets: Set text color to color of disabled text
QPalette palette = widget->palette();
palette.setColor(QPalette::Text, palette.color(QPalette::Disabled, QPalette::Text));
widget->setPalette(palette);
}
else
{
widget->setPalette(QPalette());
}
}
void ImageDescEditTab::slotMoreMenu()
{
d->moreMenu->clear();
if (singleSelection())
{
d->moreMenu->addAction(i18n("Read metadata from file to database"), this, SLOT(slotReadFromFileMetadataToDatabase()));
QAction* const writeAction = d->moreMenu->addAction(i18n("Write metadata to each file"), this,
SLOT(slotWriteToFileMetadataFromDatabase()));
// we do not need a "Write to file" action here because the apply button will do just that
// if selection is a single file.
// Adding the option will confuse users: Does the apply button not write to file?
// Removing the option will confuse users: There is not option to write to file! (not visible in single selection)
// Disabling will confuse users: Why is it disabled?
writeAction->setEnabled(false);
}
else
{
// We need to make clear that this action is different from the Apply button,
// which saves the same changes to all files. These batch operations operate on each single file.
d->moreMenu->addAction(i18n("Read metadata from each file to database"), this, SLOT(slotReadFromFileMetadataToDatabase()));
d->moreMenu->addAction(i18n("Write metadata to each file"), this, SLOT(slotWriteToFileMetadataFromDatabase()));
}
}
void ImageDescEditTab::slotOpenTagsManager()
{
TagsManager* const tagMngr = TagsManager::instance();
tagMngr->show();
tagMngr->activateWindow();
tagMngr->raise();
}
void ImageDescEditTab::slotImagesChanged(int albumId)
{
if (d->ignoreImageAttributesWatch || d->modified)
{
return;
}
Album* const a = AlbumManager::instance()->findAlbum(albumId);
if (d->currInfos.isEmpty() || !a || a->isRoot() || a->type() != Album::TAG)
{
return;
}
setInfos(d->currInfos);
}
void ImageDescEditTab::slotImageTagsChanged(qlonglong imageId)
{
metadataChange(imageId);
}
void ImageDescEditTab::slotImageRatingChanged(qlonglong imageId)
{
metadataChange(imageId);
}
void ImageDescEditTab::slotImageCaptionChanged(qlonglong imageId)
{
metadataChange(imageId);
}
void ImageDescEditTab::slotImageDateChanged(qlonglong imageId)
{
metadataChange(imageId);
}
// private common code for above methods
void ImageDescEditTab::metadataChange(qlonglong imageId)
{
if (d->ignoreImageAttributesWatch || d->modified)
{
// Don't lose modifications
return;
}
d->metadataChangeIds << imageId;
d->metadataChangeTimer->start();
}
void ImageDescEditTab::resetMetadataChangeInfo()
{
d->metadataChangeTimer->stop();
d->metadataChangeIds.clear();
}
void ImageDescEditTab::slotReloadForMetadataChange()
{
// NOTE: What to do if d->modified? Reloading is no option.
// It may be a little change the user wants to ignore, or a large conflict.
if (d->currInfos.isEmpty() || d->modified)
{
resetMetadataChangeInfo();
return;
}
if (singleSelection())
{
if (d->metadataChangeIds.contains(d->currInfos.first().id()))
{
setInfos(d->currInfos);
}
}
else
{
// if image id is in our list, update
foreach(const ImageInfo& info, d->currInfos)
{
if (d->metadataChangeIds.contains(info.id()))
{
setInfos(d->currInfos);
break;
}
}
}
}
void ImageDescEditTab::updateRecentTags()
{
QMenu* const menu = dynamic_cast<QMenu*>(d->recentTagsBtn->menu());
if (!menu)
{
return;
}
menu->clear();
AlbumList recentTags = AlbumManager::instance()->getRecentlyAssignedTags();
if (recentTags.isEmpty())
{
QAction* const noTagsAction = menu->addAction(i18n("No Recently Assigned Tags"));
noTagsAction->setEnabled(false);
}
else
{
for (AlbumList::const_iterator it = recentTags.constBegin();
it != recentTags.constEnd(); ++it)
{
TAlbum* const album = static_cast<TAlbum*>(*it);
if (album)
{
AlbumThumbnailLoader* const loader = AlbumThumbnailLoader::instance();
QPixmap icon;
if (!loader->getTagThumbnail(album, icon))
{
if (icon.isNull())
{
icon = loader->getStandardTagIcon(album, AlbumThumbnailLoader::SmallerSize);
}
}
TAlbum* const parent = dynamic_cast<TAlbum*> (album->parent());
if (parent)
{
QString text = album->title() + QLatin1String(" (") + parent->prettyUrl() + QLatin1Char(')');
QAction* const action = menu->addAction(icon, text, d->recentTagsMapper, SLOT(map()));
d->recentTagsMapper->setMapping(action, album->id());
}
else
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Tag" << album
<< "do not have a valid parent";
}
}
}
}
}
void ImageDescEditTab::slotRecentTagsMenuActivated(int id)
{
AlbumManager* const albumMan = AlbumManager::instance();
if (id > 0)
{
TAlbum* const album = albumMan->findTAlbum(id);
if (album)
{
d->tagModel->setChecked(album, true);
}
}
}
void ImageDescEditTab::slotTagsSearchChanged(const SearchTextSettings& settings)
{
Q_UNUSED(settings);
// if we filter, we should reset the assignedTagsBtn again
if (d->assignedTagsBtn->isChecked() && !d->togglingSearchSettings)
{
d->togglingSearchSettings = true;
d->assignedTagsBtn->setChecked(false);
d->togglingSearchSettings = false;
}
}
void ImageDescEditTab::slotAssignedTagsToggled(bool t)
{
d->tagCheckView->checkableAlbumFilterModel()->setFilterChecked(t);
d->tagCheckView->checkableAlbumFilterModel()->setFilterPartiallyChecked(t);
d->tagCheckView->checkableAlbumFilterModel()->setFilterBehavior(t ? AlbumFilterModel::StrictFiltering : AlbumFilterModel::FullFiltering);
if (t)
{
// if we filter by assigned, we should initially clear the normal search
if (!d->togglingSearchSettings)
{
d->togglingSearchSettings = true;
d->tagsSearchBar->clear();
d->togglingSearchSettings = false;
}
// Only after above change, do this
d->tagCheckView->expandMatches(d->tagCheckView->rootIndex());
}
}
void ImageDescEditTab::slotApplyChangesToAllVersions()
{
if (!d->modified)
{
return;
}
if (d->currInfos.isEmpty())
{
return;
}
QSet<qlonglong> tmpSet;
QList<QPair<qlonglong, qlonglong> > relations;
foreach(const ImageInfo& info, d->currInfos)
{
// Collect all ids in all image's relations
relations.append(info.relationCloud());
}
if (relations.isEmpty())
{
slotApplyAllChanges();
return;
}
for (int i = 0; i < relations.size(); ++i)
{
// Use QSet to prevent duplicates
tmpSet.insert(relations.at(i).first);
tmpSet.insert(relations.at(i).second);
}
FileActionMngr::instance()->applyMetadata(ImageInfoList(tmpSet.toList()), d->hub);
d->modified = false;
d->hub.resetChanged();
d->applyBtn->setEnabled(false);
d->revertBtn->setEnabled(false);
d->applyToAllVersionsButton->setEnabled(false);
}
void ImageDescEditTab::initProgressIndicator()
{
if (!ProgressManager::instance()->findItembyId(QLatin1String("ImageDescEditTabProgress")))
{
FileActionProgress* const item = new FileActionProgress(QLatin1String("ImageDescEditTabProgress"));
connect(this, SIGNAL(signalProgressMessageChanged(QString)),
item, SLOT(slotProgressStatus(QString)));
connect(this, SIGNAL(signalProgressValueChanged(float)),
item, SLOT(slotProgressValue(float)));
connect(this, SIGNAL(signalProgressFinished()),
item, SLOT(slotCompleted()));
}
}
AddTagsLineEdit* ImageDescEditTab::getNewTagEdit() const
{
return d->newTagEdit;
}
} // namespace Digikam
diff --git a/core/libs/imageproperties/imagepropertiestab.h b/core/libs/imageproperties/imagepropertiestab.h
index 790b3314ea..2a2649e68b 100644
--- a/core/libs/imageproperties/imagepropertiestab.h
+++ b/core/libs/imageproperties/imagepropertiestab.h
@@ -1,133 +1,133 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-04-19
* Description : A tab to display general image information
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_IMAGE_PROPERTIES_TAB_H
#define DIGIKAM_IMAGE_PROPERTIES_TAB_H
// Qt includes
#include <QString>
#include <QColor>
#include <QUrl>
#include <QFileInfo>
// Local includes
#include "dexpanderbox.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT ImagePropertiesTab : public DExpanderBox
{
Q_OBJECT
public:
explicit ImagePropertiesTab(QWidget* const parent);
~ImagePropertiesTab();
void setCurrentURL(const QUrl& url=QUrl());
void setPhotoInfoDisable(const bool b);
void setVideoInfoDisable(const bool b);
void showOrHideCaptionAndTags();
void setFileModifiedDate(const QString& str);
void setFileSize(const QString& str);
void setFileOwner(const QString& str);
void setFilePermissions(const QString& str);
void setImageDimensions(const QString& str);
void setImageRatio(const QString& str);
void setImageMime(const QString& str);
void setImageBitDepth(const QString& str);
void setImageColorMode(const QString& str);
void setPhotoMake(const QString& str);
void setPhotoModel(const QString& str);
void setPhotoDateTime(const QString& str);
void setPhotoLens(const QString& str);
void setPhotoAperture(const QString& str);
void setPhotoFocalLength(const QString& str);
void setPhotoExposureTime(const QString& str);
void setPhotoSensitivity(const QString& str);
void setPhotoExposureMode(const QString& str);
void setPhotoFlash(const QString& str);
void setPhotoWhiteBalance(const QString& str);
void setVideoAspectRatio(const QString& str);
void setVideoAudioBitRate(const QString& str);
void setVideoAudioChannelType(const QString& str);
void setVideoAudioCodec(const QString& str);
void setVideoDuration(const QString& str);
void setVideoFrameRate(const QString& str);
void setVideoVideoCodec(const QString& str);
void setCaption(const QString& str);
void setPickLabel(int pickId);
void setColorLabel(int colorId);
void setRating(int rating);
void setTags(const QStringList& tagPaths, const QStringList& tagNames = QStringList());
/**
* Shortens the tag paths by sorting and then cutting identical paths from the second
* and following paths (only the first item gives the full path).
* If you want to retain information about which tag path is sorted where,
* you can optionally give a QVariant list. This list shall contain an identifier
* for the tag path at the same index and will be resorted as the returned list.
*/
static QStringList shortenedTagPaths(const QStringList& tagPaths, QList<QVariant>* identifiers = 0);
/** This methods shortens make an model camera info to prevent bloating GUI
* See bug #265231 for details.
*/
static void shortenedMakeInfo(QString& make);
static void shortenedModelInfo(QString& model);
- /** Write a string with apect ratio information formated
+ /** Write a string with aspect ratio information formatted
*/
static bool aspectRatioToString(int width, int height, QString& arString);
/** Return file permissions string.
*/
static QString permissionsString(const QFileInfo& fi);
/** Return human readable string of file size in bytes.
*/
static QString humanReadableBytesCount(qint64 bytes, bool si=false);
private:
static double doubleToHumanReadableFraction(double val, long* num, long* den, long maxden=2);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_IMAGE_PROPERTIES_TAB_H
diff --git a/core/libs/iojobs/iojobsthread.h b/core/libs/iojobs/iojobsthread.h
index e85ba6a2ea..a9a451a7b7 100644
--- a/core/libs/iojobs/iojobsthread.h
+++ b/core/libs/iojobs/iojobsthread.h
@@ -1,155 +1,155 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2015-06-15
* Description : IO Jobs thread for file system jobs
*
* Copyright (C) 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
* Copyright (C) 2018 by Maik Qualmann <metzpinguin at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_IO_JOBS_THREAD_H
#define DIGIKAM_IO_JOBS_THREAD_H
// Local includes
#include "actionthreadbase.h"
#include "digikam_export.h"
#include "dtrashiteminfo.h"
namespace Digikam
{
class IOJob;
class IOJobData;
class DIGIKAM_EXPORT IOJobsThread : public ActionThreadBase
{
Q_OBJECT
public:
explicit IOJobsThread(QObject* const parent);
~IOJobsThread();
/**
* @brief Starts a number of jobs to copy or move source files to destination
* @param data: IOJobsData container
*/
void copyOrMove(IOJobData* const data);
/**
* @brief Starts a number of jobs to delete multiple files
* @param data: IOJobsData container
*/
void deleteFiles(IOJobData* const data);
/**
* @brief Starts one job to rename a file to a new name
* @param data: IOJobsData container
*/
void renameFile(IOJobData* const data);
/**
* @brief Starts a job for listing trash items in a collection
* @param collectionPath
*/
void listDTrashItems(const QString& collectionPath);
/**
* @brief creates a job for every item to restore back to album
* @param items to restore
*/
void restoreDTrashItems(const DTrashItemInfoList& items);
/**
* @brief creates a job for every item to delete from collection trash
* @param items to delete
*/
void deleteDTrashItems(const DTrashItemInfoList& items);
/**
* @brief isCanceled
- * @return true if the thread was inturrupted
+ * @return true if the thread was interrupted
*/
bool isCanceled() const;
/**
* @brief hasErrors
* @return true if string list was not empty
*/
bool hasErrors() const;
/**
* @brief errorsList
* @return
*/
QStringList& errorsList() const;
/**
* @brief jobData
* @return
*/
IOJobData* jobData() const;
public Q_SLOTS:
/**
* @brief cancels thread execution
*/
void slotCancel();
Q_SIGNALS:
void finished();
void signalOneProccessed(const QUrl& url);
void signalRenameFailed(const QUrl& url);
void collectionTrashItemInfo(const DTrashItemInfo& trashItemInfo);
private:
/**
* @brief connects the job with signals/slots
* @param job to be connected
*/
void connectOneJob(IOJob* const j);
private Q_SLOTS:
/**
* @brief connected to all active jobs and checks if the job
* list has finished to report that thread is finished
*/
void slotOneJobFinished();
/**
* @brief A slot to receive the error from the job
* @param errString: string to be appended
*/
void slotError(const QString& errString);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_IO_JOBS_THREAD_H
diff --git a/core/libs/pgfutils/libpgf/PGFimage.cpp b/core/libs/pgfutils/libpgf/PGFimage.cpp
index fc0e5a3071..80f0912375 100644
--- a/core/libs/pgfutils/libpgf/PGFimage.cpp
+++ b/core/libs/pgfutils/libpgf/PGFimage.cpp
@@ -1,2732 +1,2732 @@
/*
* The Progressive Graphics File; http://www.libpgf.org
*
* $Date: 2007-02-03 13:04:21 +0100 (Sa, 03 Feb 2007) $
* $Revision: 280 $
*
* This file Copyright (C) 2006 xeraina GmbH, Switzerland
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//////////////////////////////////////////////////////////////////////
/// @file PGFimage.cpp
/// @brief PGF image class implementation
/// @author C. Stamm
#include "PGFimage.h"
#include "Decoder.h"
#include "Encoder.h"
#include "BitStream.h"
#include <cmath>
#include <cstring>
#define YUVoffset4 8 // 2^3
#define YUVoffset6 32 // 2^5
#define YUVoffset8 128 // 2^7
#define YUVoffset16 32768 // 2^15
//#define YUVoffset31 1073741824 // 2^30
//////////////////////////////////////////////////////////////////////
// global methods and variables
#ifdef NEXCEPTIONS
OSError _PGF_Error_;
OSError GetLastPGFError() {
OSError tmp = _PGF_Error_;
_PGF_Error_ = NoError;
return tmp;
}
#endif
#ifdef _DEBUG
// allows RGB and RGBA image visualization inside Visual Studio Debugger
struct DebugBGRImage {
int width, height, pitch;
BYTE *data;
} roiimage;
#endif
//////////////////////////////////////////////////////////////////////
// Standard constructor
CPGFImage::CPGFImage() {
Init();
}
//////////////////////////////////////////////////////////////////////
void CPGFImage::Init() {
// init pointers
m_decoder = nullptr;
m_encoder = nullptr;
m_levelLength = nullptr;
// init members
#ifdef __PGFROISUPPORT__
m_streamReinitialized = false;
#endif
m_currentLevel = 0;
m_quant = 0;
m_userDataPos = 0;
m_downsample = false;
m_favorSpeedOverSize = false;
m_useOMPinEncoder = true;
m_useOMPinDecoder = true;
m_cb = nullptr;
m_cbArg = nullptr;
m_progressMode = PM_Relative;
m_percent = 0;
m_userDataPolicy = UP_CacheAll;
// init preHeader
memcpy(m_preHeader.magic, PGFMagic, 3);
m_preHeader.version = PGFVersion;
m_preHeader.hSize = 0;
// init postHeader
m_postHeader.userData = nullptr;
m_postHeader.userDataLen = 0;
m_postHeader.cachedUserDataLen = 0;
// init channels
for (int i = 0; i < MaxChannels; i++) {
m_channel[i] = nullptr;
m_wtChannel[i] = nullptr;
}
// set image width and height
for (int i = 0; i < MaxChannels; i++) {
m_width[0] = 0;
m_height[0] = 0;
}
}
//////////////////////////////////////////////////////////////////////
// Destructor: Destroy internal data structures.
CPGFImage::~CPGFImage() {
m_currentLevel = -100; // unusual value used as marker in Destroy()
Destroy();
}
//////////////////////////////////////////////////////////////////////
// Destroy internal data structures. Object state after this is the same as after CPGFImage().
void CPGFImage::Destroy() {
for (int i = 0; i < m_header.channels; i++) {
delete m_wtChannel[i]; // also deletes m_channel
}
delete[] m_postHeader.userData;
delete[] m_levelLength;
delete m_decoder;
delete m_encoder;
if (m_currentLevel != -100) Init();
}
/////////////////////////////////////////////////////////////////////////////
// Open a PGF image at current stream position: read pre-header, header, levelLength, and ckeck image type.
// Precondition: The stream has been opened for reading.
// It might throw an IOException.
// @param stream A PGF stream
void CPGFImage::Open(CPGFStream *stream) {
ASSERT(stream);
// create decoder and read PGFPreHeader PGFHeader PGFPostHeader LevelLengths
m_decoder = new CDecoder(stream, m_preHeader, m_header, m_postHeader, m_levelLength,
m_userDataPos, m_useOMPinDecoder, m_userDataPolicy);
if (m_header.nLevels > MaxLevel) ReturnWithError(FormatCannotRead);
// set current level
m_currentLevel = m_header.nLevels;
// set image width and height
m_width[0] = m_header.width;
m_height[0] = m_header.height;
// complete header
if (!CompleteHeader()) ReturnWithError(FormatCannotRead);
// interpret quant parameter
if (m_header.quality > DownsampleThreshold &&
(m_header.mode == ImageModeRGBColor ||
m_header.mode == ImageModeRGBA ||
m_header.mode == ImageModeRGB48 ||
m_header.mode == ImageModeCMYKColor ||
m_header.mode == ImageModeCMYK64 ||
m_header.mode == ImageModeLabColor ||
m_header.mode == ImageModeLab48)) {
m_downsample = true;
m_quant = m_header.quality - 1;
} else {
m_downsample = false;
m_quant = m_header.quality;
}
// set channel dimensions (chrominance is subsampled by factor 2)
if (m_downsample) {
for (int i=1; i < m_header.channels; i++) {
m_width[i] = (m_width[0] + 1) >> 1;
m_height[i] = (m_height[0] + 1) >> 1;
}
} else {
for (int i=1; i < m_header.channels; i++) {
m_width[i] = m_width[0];
m_height[i] = m_height[0];
}
}
if (m_header.nLevels > 0) {
// init wavelet subbands
for (int i=0; i < m_header.channels; i++) {
m_wtChannel[i] = new CWaveletTransform(m_width[i], m_height[i], m_header.nLevels);
}
// used in Read when PM_Absolute
m_percent = pow(0.25, m_header.nLevels);
} else {
// very small image: we don't use DWT and encoding
// read channels
for (int c=0; c < m_header.channels; c++) {
const UINT32 size = m_width[c]*m_height[c];
m_channel[c] = new(std::nothrow) DataT[size];
if (!m_channel[c]) ReturnWithError(InsufficientMemory);
// read channel data from stream
for (UINT32 i=0; i < size; i++) {
int count = DataTSize;
stream->Read(&count, &m_channel[c][i]);
if (count != DataTSize) ReturnWithError(MissingData);
}
}
}
}
////////////////////////////////////////////////////////////
bool CPGFImage::CompleteHeader() {
// set current codec version
m_header.version = PGFVersionNumber(PGFMajorNumber, PGFYear, PGFWeek);
if (m_header.mode == ImageModeUnknown) {
// undefined mode
switch(m_header.bpp) {
case 1: m_header.mode = ImageModeBitmap; break;
case 8: m_header.mode = ImageModeGrayScale; break;
case 12: m_header.mode = ImageModeRGB12; break;
case 16: m_header.mode = ImageModeRGB16; break;
case 24: m_header.mode = ImageModeRGBColor; break;
case 32: m_header.mode = ImageModeRGBA; break;
case 48: m_header.mode = ImageModeRGB48; break;
default: m_header.mode = ImageModeRGBColor; break;
}
}
if (!m_header.bpp) {
// undefined bpp
switch(m_header.mode) {
case ImageModeBitmap:
m_header.bpp = 1;
break;
case ImageModeIndexedColor:
case ImageModeGrayScale:
m_header.bpp = 8;
break;
case ImageModeRGB12:
m_header.bpp = 12;
break;
case ImageModeRGB16:
case ImageModeGray16:
m_header.bpp = 16;
break;
case ImageModeRGBColor:
case ImageModeLabColor:
m_header.bpp = 24;
break;
case ImageModeRGBA:
case ImageModeCMYKColor:
case ImageModeGray32:
m_header.bpp = 32;
break;
case ImageModeRGB48:
case ImageModeLab48:
m_header.bpp = 48;
break;
case ImageModeCMYK64:
m_header.bpp = 64;
break;
default:
ASSERT(false);
m_header.bpp = 24;
}
}
if (m_header.mode == ImageModeRGBColor && m_header.bpp == 32) {
// change mode
m_header.mode = ImageModeRGBA;
}
if (m_header.mode == ImageModeBitmap && m_header.bpp != 1) return false;
if (m_header.mode == ImageModeIndexedColor && m_header.bpp != 8) return false;
if (m_header.mode == ImageModeGrayScale && m_header.bpp != 8) return false;
if (m_header.mode == ImageModeGray16 && m_header.bpp != 16) return false;
if (m_header.mode == ImageModeGray32 && m_header.bpp != 32) return false;
if (m_header.mode == ImageModeRGBColor && m_header.bpp != 24) return false;
if (m_header.mode == ImageModeRGBA && m_header.bpp != 32) return false;
if (m_header.mode == ImageModeRGB12 && m_header.bpp != 12) return false;
if (m_header.mode == ImageModeRGB16 && m_header.bpp != 16) return false;
if (m_header.mode == ImageModeRGB48 && m_header.bpp != 48) return false;
if (m_header.mode == ImageModeLabColor && m_header.bpp != 24) return false;
if (m_header.mode == ImageModeLab48 && m_header.bpp != 48) return false;
if (m_header.mode == ImageModeCMYKColor && m_header.bpp != 32) return false;
if (m_header.mode == ImageModeCMYK64 && m_header.bpp != 64) return false;
// set number of channels
if (!m_header.channels) {
switch(m_header.mode) {
case ImageModeBitmap:
case ImageModeIndexedColor:
case ImageModeGrayScale:
case ImageModeGray16:
case ImageModeGray32:
m_header.channels = 1;
break;
case ImageModeRGBColor:
case ImageModeRGB12:
case ImageModeRGB16:
case ImageModeRGB48:
case ImageModeLabColor:
case ImageModeLab48:
m_header.channels = 3;
break;
case ImageModeRGBA:
case ImageModeCMYKColor:
case ImageModeCMYK64:
m_header.channels = 4;
break;
default:
return false;
}
}
// store used bits per channel
UINT8 bpc = m_header.bpp/m_header.channels;
if (bpc > 31) bpc = 31;
if (!m_header.usedBitsPerChannel || m_header.usedBitsPerChannel > bpc) {
m_header.usedBitsPerChannel = bpc;
}
return true;
}
//////////////////////////////////////////////////////////////////////
/// Return user data and size of user data.
/// Precondition: The PGF image has been opened with a call of Open(...).
/// In an encoder scenario don't call this method before WriteHeader().
/// @param cachedSize [out] Size of returned user data in bytes.
/// @param pTotalSize [optional out] Pointer to return the size of user data stored in image header in bytes.
/// @return A pointer to user data or nullptr if there is no user data available.
const UINT8* CPGFImage::GetUserData(UINT32& cachedSize, UINT32* pTotalSize /*= nullptr*/) const {
cachedSize = m_postHeader.cachedUserDataLen;
if (pTotalSize) *pTotalSize = m_postHeader.userDataLen;
return m_postHeader.userData;
}
//////////////////////////////////////////////////////////////////////
/// After you've written a PGF image, you can call this method followed by GetBitmap/GetYUV
/// to get a quick reconstruction (coded -> decoded image).
/// It might throw an IOException.
/// @param level The image level of the resulting image in the internal image buffer.
void CPGFImage::Reconstruct(int level /*= 0*/) {
if (m_header.nLevels == 0) {
// image didn't use wavelet transform
if (level == 0) {
for (int i=0; i < m_header.channels; i++) {
ASSERT(m_wtChannel[i]);
m_channel[i] = m_wtChannel[i]->GetSubband(0, LL)->GetBuffer();
}
}
} else {
int currentLevel = m_header.nLevels;
#ifdef __PGFROISUPPORT__
if (ROIisSupported()) {
// enable ROI reading
SetROI(PGFRect(0, 0, m_header.width, m_header.height));
}
#endif
while (currentLevel > level) {
for (int i=0; i < m_header.channels; i++) {
ASSERT(m_wtChannel[i]);
// dequantize subbands
if (currentLevel == m_header.nLevels) {
// last level also has LL band
m_wtChannel[i]->GetSubband(currentLevel, LL)->Dequantize(m_quant);
}
m_wtChannel[i]->GetSubband(currentLevel, HL)->Dequantize(m_quant);
m_wtChannel[i]->GetSubband(currentLevel, LH)->Dequantize(m_quant);
m_wtChannel[i]->GetSubband(currentLevel, HH)->Dequantize(m_quant);
// inverse transform from m_wtChannel to m_channel
OSError err = m_wtChannel[i]->InverseTransform(currentLevel, &m_width[i], &m_height[i], &m_channel[i]);
if (err != NoError) ReturnWithError(err);
ASSERT(m_channel[i]);
}
currentLevel--;
}
}
}
//////////////////////////////////////////////////////////////////////
// Read and decode some levels of a PGF image at current stream position.
// A PGF image is structered in levels, numbered between 0 and Levels() - 1.
// Each level can be seen as a single image, containing the same content
// as all other levels, but in a different size (width, height).
// The image size at level i is double the size (width, height) of the image at level i+1.
// The image at level 0 contains the original size.
// Precondition: The PGF image has been opened with a call of Open(...).
// It might throw an IOException.
// @param level The image level of the resulting image in the internal image buffer.
// @param cb A pointer to a callback procedure. The procedure is called after reading a single level. If cb returns true, then it stops proceeding.
// @param data Data Pointer to C++ class container to host callback procedure.
void CPGFImage::Read(int level /*= 0*/, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) {
ASSERT((level >= 0 && level < m_header.nLevels) || m_header.nLevels == 0); // m_header.nLevels == 0: image didn't use wavelet transform
ASSERT(m_decoder);
#ifdef __PGFROISUPPORT__
if (ROIisSupported() && m_header.nLevels > 0) {
// new encoding scheme supporting ROI
PGFRect rect(0, 0, m_header.width, m_header.height);
Read(rect, level, cb, data);
return;
}
#endif
if (m_header.nLevels == 0) {
if (level == 0) {
// the data has already been read during open
// now update progress
if (cb) {
if ((*cb)(1.0, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
const int levelDiff = m_currentLevel - level;
double percent = (m_progressMode == PM_Relative) ? pow(0.25, levelDiff) : m_percent;
// encoding scheme without ROI
while (m_currentLevel > level) {
for (int i=0; i < m_header.channels; i++) {
CWaveletTransform* wtChannel = m_wtChannel[i];
ASSERT(wtChannel);
// decode file and write stream to m_wtChannel
if (m_currentLevel == m_header.nLevels) {
// last level also has LL band
wtChannel->GetSubband(m_currentLevel, LL)->PlaceTile(*m_decoder, m_quant);
}
if (m_preHeader.version & Version5) {
// since version 5
wtChannel->GetSubband(m_currentLevel, HL)->PlaceTile(*m_decoder, m_quant);
wtChannel->GetSubband(m_currentLevel, LH)->PlaceTile(*m_decoder, m_quant);
} else {
// until version 4
m_decoder->DecodeInterleaved(wtChannel, m_currentLevel, m_quant);
}
wtChannel->GetSubband(m_currentLevel, HH)->PlaceTile(*m_decoder, m_quant);
}
volatile OSError error = NoError; // volatile prevents optimizations
#ifdef LIBPGF_USE_OPENMP
#pragma omp parallel for default(shared)
#endif
for (int i=0; i < m_header.channels; i++) {
// inverse transform from m_wtChannel to m_channel
if (error == NoError) {
OSError err = m_wtChannel[i]->InverseTransform(m_currentLevel, &m_width[i], &m_height[i], &m_channel[i]);
if (err != NoError) error = err;
}
ASSERT(m_channel[i]);
}
if (error != NoError) ReturnWithError(error);
// set new level: must be done before refresh callback
m_currentLevel--;
// now we have to refresh the display
if (m_cb) m_cb(m_cbArg);
// now update progress
if (cb) {
percent *= 4;
if (m_progressMode == PM_Absolute) m_percent = percent;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
}
#ifdef __PGFROISUPPORT__
//////////////////////////////////////////////////////////////////////
/// Read and decode rectangular region of interest (ROI) of a PGF image at current stream position.
/// The origin of the coordinate axis is the top-left corner of the image.
/// All coordinates are measured in pixels.
/// It might throw an IOException.
/// @param rect [inout] Rectangular region of interest (ROI) at level 0. The rect might be cropped.
/// @param level The image level of the resulting image in the internal image buffer.
/// @param cb A pointer to a callback procedure. The procedure is called after reading a single level. If cb returns true, then it stops proceeding.
/// @param data Data Pointer to C++ class container to host callback procedure.
void CPGFImage::Read(PGFRect& rect, int level /*= 0*/, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) {
ASSERT((level >= 0 && level < m_header.nLevels) || m_header.nLevels == 0); // m_header.nLevels == 0: image didn't use wavelet transform
ASSERT(m_decoder);
if (m_header.nLevels == 0 || !ROIisSupported()) {
rect.left = rect.top = 0;
rect.right = m_header.width; rect.bottom = m_header.height;
Read(level, cb, data);
} else {
ASSERT(ROIisSupported());
// new encoding scheme supporting ROI
ASSERT(rect.left < m_header.width && rect.top < m_header.height);
// check rectangle
if (rect.right == 0 || rect.right > m_header.width) rect.right = m_header.width;
if (rect.bottom == 0 || rect.bottom > m_header.height) rect.bottom = m_header.height;
const int levelDiff = m_currentLevel - level;
double percent = (m_progressMode == PM_Relative) ? pow(0.25, levelDiff) : m_percent;
// check level difference
if (levelDiff <= 0) {
// it is a new read call, probably with a new ROI
m_currentLevel = m_header.nLevels;
m_decoder->SetStreamPosToData();
}
// enable ROI decoding and reading
SetROI(rect);
while (m_currentLevel > level) {
for (int i=0; i < m_header.channels; i++) {
CWaveletTransform* wtChannel = m_wtChannel[i];
ASSERT(wtChannel);
// get number of tiles and tile indices
const UINT32 nTiles = wtChannel->GetNofTiles(m_currentLevel); // independent of ROI
// decode file and write stream to m_wtChannel
if (m_currentLevel == m_header.nLevels) { // last level also has LL band
ASSERT(nTiles == 1);
m_decoder->GetNextMacroBlock();
wtChannel->GetSubband(m_currentLevel, LL)->PlaceTile(*m_decoder, m_quant);
}
for (UINT32 tileY=0; tileY < nTiles; tileY++) {
for (UINT32 tileX=0; tileX < nTiles; tileX++) {
// check relevance of tile
if (wtChannel->TileIsRelevant(m_currentLevel, tileX, tileY)) {
m_decoder->GetNextMacroBlock();
wtChannel->GetSubband(m_currentLevel, HL)->PlaceTile(*m_decoder, m_quant, true, tileX, tileY);
wtChannel->GetSubband(m_currentLevel, LH)->PlaceTile(*m_decoder, m_quant, true, tileX, tileY);
wtChannel->GetSubband(m_currentLevel, HH)->PlaceTile(*m_decoder, m_quant, true, tileX, tileY);
} else {
// skip tile
m_decoder->SkipTileBuffer();
}
}
}
}
volatile OSError error = NoError; // volatile prevents optimizations
#ifdef LIBPGF_USE_OPENMP
#pragma omp parallel for default(shared)
#endif
for (int i=0; i < m_header.channels; i++) {
// inverse transform from m_wtChannel to m_channel
if (error == NoError) {
OSError err = m_wtChannel[i]->InverseTransform(m_currentLevel, &m_width[i], &m_height[i], &m_channel[i]);
if (err != NoError) error = err;
}
ASSERT(m_channel[i]);
}
if (error != NoError) ReturnWithError(error);
// set new level: must be done before refresh callback
m_currentLevel--;
// now we have to refresh the display
if (m_cb) m_cb(m_cbArg);
// now update progress
if (cb) {
percent *= 4;
if (m_progressMode == PM_Absolute) m_percent = percent;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
}
//////////////////////////////////////////////////////////////////////
/// Return ROI of channel 0 at current level in pixels.
/// The returned rect is only valid after reading a ROI.
/// @return ROI in pixels
PGFRect CPGFImage::ComputeLevelROI() const {
if (m_currentLevel == 0) {
return m_roi;
} else {
const UINT32 rLeft = LevelSizeL(m_roi.left, m_currentLevel);
const UINT32 rRight = LevelSizeL(m_roi.right, m_currentLevel);
const UINT32 rTop = LevelSizeL(m_roi.top, m_currentLevel);
const UINT32 rBottom = LevelSizeL(m_roi.bottom, m_currentLevel);
return PGFRect(rLeft, rTop, rRight - rLeft, rBottom - rTop);
}
}
//////////////////////////////////////////////////////////////////////
/// Returns aligned ROI in pixels of current level of channel c
/// @param c A channel index
PGFRect CPGFImage::GetAlignedROI(int c /*= 0*/) const {
PGFRect roi(0, 0, m_width[c], m_height[c]);
if (ROIisSupported()) {
ASSERT(m_wtChannel[c]);
roi = m_wtChannel[c]->GetAlignedROI(m_currentLevel);
}
ASSERT(roi.Width() == m_width[c]);
ASSERT(roi.Height() == m_height[c]);
return roi;
}
//////////////////////////////////////////////////////////////////////
/// Compute ROIs for each channel and each level <= current level
/// Called inside of Read(rect, ...).
/// @param rect rectangular region of interest (ROI) at level 0
void CPGFImage::SetROI(PGFRect rect) {
ASSERT(m_decoder);
ASSERT(ROIisSupported());
ASSERT(m_wtChannel[0]);
// store ROI for a later call of GetBitmap
m_roi = rect;
// enable ROI decoding
m_decoder->SetROI();
// prepare wavelet channels for using ROI
m_wtChannel[0]->SetROI(rect);
if (m_downsample && m_header.channels > 1) {
// all further channels are downsampled, therefore downsample ROI
rect.left >>= 1;
rect.top >>= 1;
rect.right = (rect.right + 1) >> 1;
rect.bottom = (rect.bottom + 1) >> 1;
}
for (int i=1; i < m_header.channels; i++) {
ASSERT(m_wtChannel[i]);
m_wtChannel[i]->SetROI(rect);
}
}
#endif // __PGFROISUPPORT__
//////////////////////////////////////////////////////////////////////
/// Return the length of all encoded headers in bytes.
/// Precondition: The PGF image has been opened with a call of Open(...).
/// @return The length of all encoded headers in bytes
UINT32 CPGFImage::GetEncodedHeaderLength() const {
ASSERT(m_decoder);
return m_decoder->GetEncodedHeaderLength();
}
//////////////////////////////////////////////////////////////////////
/// Reads the encoded PGF header and copies it to a target buffer.
/// Precondition: The PGF image has been opened with a call of Open(...).
/// It might throw an IOException.
/// @param target The target buffer
/// @param targetLen The length of the target buffer in bytes
/// @return The number of bytes copied to the target buffer
UINT32 CPGFImage::ReadEncodedHeader(UINT8* target, UINT32 targetLen) const {
ASSERT(target);
ASSERT(targetLen > 0);
ASSERT(m_decoder);
// reset stream position
m_decoder->SetStreamPosToStart();
// compute number of bytes to read
UINT32 len = __min(targetLen, GetEncodedHeaderLength());
// read data
len = m_decoder->ReadEncodedData(target, len);
ASSERT(len >= 0 && len <= targetLen);
return len;
}
////////////////////////////////////////////////////////////////////
/// Reset stream position to start of PGF pre-header or start of data. Must not be called before Open() or before Write().
/// Use this method after Read() if you want to read the same image several times, e.g. reading different ROIs.
/// @param startOfData true: you want to read the same image several times. false: resets stream position to the initial position
void CPGFImage::ResetStreamPos(bool startOfData) {
if (startOfData) {
ASSERT(m_decoder);
m_decoder->SetStreamPosToData();
} else {
if (m_decoder) {
m_decoder->SetStreamPosToStart();
} else if (m_encoder) {
m_encoder->SetStreamPosToStart();
} else {
ASSERT(false);
}
}
}
//////////////////////////////////////////////////////////////////////
/// Reads the data of an encoded PGF level and copies it to a target buffer
/// without decoding.
/// Precondition: The PGF image has been opened with a call of Open(...).
/// It might throw an IOException.
/// @param level The image level
/// @param target The target buffer
/// @param targetLen The length of the target buffer in bytes
/// @return The number of bytes copied to the target buffer
UINT32 CPGFImage::ReadEncodedData(int level, UINT8* target, UINT32 targetLen) const {
ASSERT(level >= 0 && level < m_header.nLevels);
ASSERT(target);
ASSERT(targetLen > 0);
ASSERT(m_decoder);
// reset stream position
m_decoder->SetStreamPosToData();
// position stream
UINT64 offset = 0;
for (int i=m_header.nLevels - 1; i > level; i--) {
offset += m_levelLength[m_header.nLevels - 1 - i];
}
m_decoder->Skip(offset);
// compute number of bytes to read
UINT32 len = __min(targetLen, GetEncodedLevelLength(level));
// read data
len = m_decoder->ReadEncodedData(target, len);
ASSERT(len >= 0 && len <= targetLen);
return len;
}
//////////////////////////////////////////////////////////////////////
/// Set maximum intensity value for image modes with more than eight bits per channel.
/// Call this method after SetHeader, but before ImportBitmap.
/// @param maxValue The maximum intensity value.
void CPGFImage::SetMaxValue(UINT32 maxValue) {
const BYTE bpc = m_header.bpp/m_header.channels;
BYTE pot = 0;
while(maxValue > 0) {
pot++;
maxValue >>= 1;
}
// store bits per channel
if (pot > bpc) pot = bpc;
if (pot > 31) pot = 31;
m_header.usedBitsPerChannel = pot;
}
//////////////////////////////////////////////////////////////////////
/// Returns number of used bits per input/output image channel.
/// Precondition: header must be initialized.
/// @return number of used bits per input/output image channel.
BYTE CPGFImage::UsedBitsPerChannel() const {
const BYTE bpc = m_header.bpp/m_header.channels;
if (bpc > 8) {
return m_header.usedBitsPerChannel;
} else {
return bpc;
}
}
//////////////////////////////////////////////////////////////////////
/// Return major version
BYTE CPGFImage::CodecMajorVersion(BYTE version) {
if (version & Version7) return 7;
if (version & Version6) return 6;
if (version & Version5) return 5;
if (version & Version2) return 2;
return 1;
}
//////////////////////////////////////////////////////////////////
// Import an image from a specified image buffer.
// This method is usually called before Write(...) and after SetHeader(...).
// It might throw an IOException.
// The absolute value of pitch is the number of bytes of an image row.
// If pitch is negative, then buff points to the last row of a bottom-up image (first byte on last row).
// If pitch is positive, then buff points to the first row of a top-down image (first byte).
// The sequence of input channels in the input image buffer does not need to be the same as expected from PGF. In case of different sequences you have to
// provide a channelMap of size of expected channels (depending on image mode). For example, PGF expects in RGB color mode a channel sequence BGR.
// If your provided image buffer contains a channel sequence ARGB, then the channelMap looks like { 3, 2, 1 }.
// @param pitch The number of bytes of a row of the image buffer.
// @param buff An image buffer.
// @param bpp The number of bits per pixel used in image buffer.
// @param channelMap A integer array containing the mapping of input channel ordering to expected channel ordering.
// @param cb A pointer to a callback procedure. The procedure is called after each imported buffer row. If cb returns true, then it stops proceeding.
// @param data Data Pointer to C++ class container to host callback procedure.
void CPGFImage::ImportBitmap(int pitch, UINT8 *buff, BYTE bpp, int channelMap[] /*= nullptr */, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) {
ASSERT(buff);
ASSERT(m_channel[0]);
// color transform
RgbToYuv(pitch, buff, bpp, channelMap, cb, data);
if (m_downsample) {
// Subsampling of the chrominance and alpha channels
for (int i=1; i < m_header.channels; i++) {
Downsample(i);
}
}
}
/////////////////////////////////////////////////////////////////
// Bilinerar Subsampling of channel ch by a factor 2
// Called before Write()
void CPGFImage::Downsample(int ch) {
ASSERT(ch > 0);
const int w = m_width[0];
const int w2 = w/2;
const int h2 = m_height[0]/2;
const int oddW = w%2; // don't use bool -> problems with MaxSpeed optimization
const int oddH = m_height[0]%2; // "
int loPos = 0;
int hiPos = w;
int sampledPos = 0;
DataT* buff = m_channel[ch]; ASSERT(buff);
for (int i=0; i < h2; i++) {
for (int j=0; j < w2; j++) {
// compute average of pixel block
buff[sampledPos] = (buff[loPos] + buff[loPos + 1] + buff[hiPos] + buff[hiPos + 1]) >> 2;
loPos += 2; hiPos += 2;
sampledPos++;
}
if (oddW) {
buff[sampledPos] = (buff[loPos] + buff[hiPos]) >> 1;
loPos++; hiPos++;
sampledPos++;
}
loPos += w; hiPos += w;
}
if (oddH) {
for (int j=0; j < w2; j++) {
buff[sampledPos] = (buff[loPos] + buff[loPos+1]) >> 1;
loPos += 2; hiPos += 2;
sampledPos++;
}
if (oddW) {
buff[sampledPos] = buff[loPos];
}
}
// downsampled image has half width and half height
m_width[ch] = (m_width[ch] + 1)/2;
m_height[ch] = (m_height[ch] + 1)/2;
}
//////////////////////////////////////////////////////////////////////
void CPGFImage::ComputeLevels() {
const int maxThumbnailWidth = 20*FilterSize;
const int m = __min(m_header.width, m_header.height);
int s = m;
if (m_header.nLevels < 1 || m_header.nLevels > MaxLevel) {
m_header.nLevels = 1;
// compute a good value depending on the size of the image
while (s > maxThumbnailWidth) {
m_header.nLevels++;
s >>= 1;
}
}
int levels = m_header.nLevels; // we need a signed value during level reduction
// reduce number of levels if the image size is smaller than FilterSize*(2^levels)
s = FilterSize*(1 << levels); // must be at least the double filter size because of subsampling
while (m < s) {
levels--;
s >>= 1;
}
if (levels > MaxLevel) m_header.nLevels = MaxLevel;
else if (levels < 0) m_header.nLevels = 0;
else m_header.nLevels = (UINT8)levels;
// used in Write when PM_Absolute
m_percent = pow(0.25, m_header.nLevels);
ASSERT(0 <= m_header.nLevels && m_header.nLevels <= MaxLevel);
}
//////////////////////////////////////////////////////////////////////
/// Set PGF header and user data.
/// Precondition: The PGF image has been never opened with Open(...).
/// It might throw an IOException.
/// @param header A valid and already filled in PGF header structure
/// @param flags A combination of additional version flags. In case you use level-wise encoding then set flag = PGFROI.
/// @param userData A user-defined memory block containing any kind of cached metadata.
/// @param userDataLength The size of user-defined memory block in bytes
void CPGFImage::SetHeader(const PGFHeader& header, BYTE flags /*=0*/, const UINT8* userData /*= 0*/, UINT32 userDataLength /*= 0*/) {
ASSERT(!m_decoder); // current image must be closed
ASSERT(header.quality <= MaxQuality);
ASSERT(userDataLength <= MaxUserDataSize);
// init state
#ifdef __PGFROISUPPORT__
m_streamReinitialized = false;
#endif
// init preHeader
memcpy(m_preHeader.magic, PGFMagic, 3);
m_preHeader.version = PGFVersion | flags;
m_preHeader.hSize = HeaderSize;
// copy header
memcpy(&m_header, &header, HeaderSize);
// check quality
if (m_header.quality > MaxQuality) m_header.quality = MaxQuality;
// complete header
CompleteHeader();
// check and set number of levels
ComputeLevels();
// check for downsample
if (m_header.quality > DownsampleThreshold && (m_header.mode == ImageModeRGBColor ||
m_header.mode == ImageModeRGBA ||
m_header.mode == ImageModeRGB48 ||
m_header.mode == ImageModeCMYKColor ||
m_header.mode == ImageModeCMYK64 ||
m_header.mode == ImageModeLabColor ||
m_header.mode == ImageModeLab48)) {
m_downsample = true;
m_quant = m_header.quality - 1;
} else {
m_downsample = false;
m_quant = m_header.quality;
}
// update header size and copy user data
if (m_header.mode == ImageModeIndexedColor) {
// update header size
m_preHeader.hSize += ColorTableSize;
}
if (userDataLength && userData) {
if (userDataLength > MaxUserDataSize) userDataLength = MaxUserDataSize;
m_postHeader.userData = new(std::nothrow) UINT8[userDataLength];
if (!m_postHeader.userData) ReturnWithError(InsufficientMemory);
m_postHeader.userDataLen = m_postHeader.cachedUserDataLen = userDataLength;
memcpy(m_postHeader.userData, userData, userDataLength);
// update header size
m_preHeader.hSize += userDataLength;
}
// allocate channels
for (int i=0; i < m_header.channels; i++) {
// set current width and height
m_width[i] = m_header.width;
m_height[i] = m_header.height;
// allocate channels
ASSERT(!m_channel[i]);
m_channel[i] = new(std::nothrow) DataT[m_header.width*m_header.height];
if (!m_channel[i]) {
if (i) i--;
while(i) {
delete[] m_channel[i]; m_channel[i] = 0;
i--;
}
ReturnWithError(InsufficientMemory);
}
}
}
//////////////////////////////////////////////////////////////////
/// Create wavelet transform channels and encoder. Write header at current stream position.
/// Performs forward FWT.
/// Call this method before your first call of Write(int level) or WriteImage(), but after SetHeader().
/// This method is called inside of Write(stream, ...).
/// It might throw an IOException.
/// @param stream A PGF stream
/// @return The number of bytes written into stream.
UINT32 CPGFImage::WriteHeader(CPGFStream* stream) {
ASSERT(m_header.nLevels <= MaxLevel);
ASSERT(m_header.quality <= MaxQuality); // quality is already initialized
if (m_header.nLevels > 0) {
volatile OSError error = NoError; // volatile prevents optimizations
// create new wt channels
#ifdef LIBPGF_USE_OPENMP
#pragma omp parallel for default(shared)
#endif
for (int i=0; i < m_header.channels; i++) {
DataT *temp = nullptr;
if (error == NoError) {
if (m_wtChannel[i]) {
ASSERT(m_channel[i]);
// copy m_channel to temp
int size = m_height[i]*m_width[i];
temp = new(std::nothrow) DataT[size];
if (temp) {
memcpy(temp, m_channel[i], size*DataTSize);
delete m_wtChannel[i]; // also deletes m_channel
m_channel[i] = nullptr;
} else {
error = InsufficientMemory;
}
}
if (error == NoError) {
if (temp) {
ASSERT(!m_channel[i]);
m_channel[i] = temp;
}
m_wtChannel[i] = new CWaveletTransform(m_width[i], m_height[i], m_header.nLevels, m_channel[i]);
if (m_wtChannel[i]) {
#ifdef __PGFROISUPPORT__
m_wtChannel[i]->SetROI(PGFRect(0, 0, m_width[i], m_height[i]));
#endif
// wavelet subband decomposition
for (int l=0; error == NoError && l < m_header.nLevels; l++) {
OSError err = m_wtChannel[i]->ForwardTransform(l, m_quant);
if (err != NoError) error = err;
}
} else {
delete[] m_channel[i];
error = InsufficientMemory;
}
}
}
}
if (error != NoError) {
// free already allocated memory
for (int i=0; i < m_header.channels; i++) {
delete m_wtChannel[i];
}
ReturnWithError(error);
}
m_currentLevel = m_header.nLevels;
// create encoder, write headers and user data, but not level-length area
m_encoder = new CEncoder(stream, m_preHeader, m_header, m_postHeader, m_userDataPos, m_useOMPinEncoder);
if (m_favorSpeedOverSize) m_encoder->FavorSpeedOverSize();
#ifdef __PGFROISUPPORT__
if (ROIisSupported()) {
// new encoding scheme supporting ROI
m_encoder->SetROI();
}
#endif
} else {
// very small image: we don't use DWT and encoding
// create encoder, write headers and user data, but not level-length area
m_encoder = new CEncoder(stream, m_preHeader, m_header, m_postHeader, m_userDataPos, m_useOMPinEncoder);
}
INT64 nBytes = m_encoder->ComputeHeaderLength();
return (nBytes > 0) ? (UINT32)nBytes : 0;
}
//////////////////////////////////////////////////////////////////
// Encode and write next level of a PGF image at current stream position.
// A PGF image is structered in levels, numbered between 0 and Levels() - 1.
// Each level can be seen as a single image, containing the same content
// as all other levels, but in a different size (width, height).
// The image size at level i is double the size (width, height) of the image at level i+1.
// The image at level 0 contains the original size.
// It might throw an IOException.
void CPGFImage::WriteLevel() {
ASSERT(m_encoder);
ASSERT(m_currentLevel > 0);
ASSERT(m_header.nLevels > 0);
#ifdef __PGFROISUPPORT__
if (ROIisSupported()) {
const int lastChannel = m_header.channels - 1;
for (int i=0; i < m_header.channels; i++) {
// get number of tiles and tile indices
const UINT32 nTiles = m_wtChannel[i]->GetNofTiles(m_currentLevel);
const UINT32 lastTile = nTiles - 1;
if (m_currentLevel == m_header.nLevels) {
// last level also has LL band
ASSERT(nTiles == 1);
m_wtChannel[i]->GetSubband(m_currentLevel, LL)->ExtractTile(*m_encoder);
m_encoder->EncodeTileBuffer(); // encode macro block with tile-end = true
}
for (UINT32 tileY=0; tileY < nTiles; tileY++) {
for (UINT32 tileX=0; tileX < nTiles; tileX++) {
// extract tile to macro block and encode already filled macro blocks with tile-end = false
m_wtChannel[i]->GetSubband(m_currentLevel, HL)->ExtractTile(*m_encoder, true, tileX, tileY);
m_wtChannel[i]->GetSubband(m_currentLevel, LH)->ExtractTile(*m_encoder, true, tileX, tileY);
m_wtChannel[i]->GetSubband(m_currentLevel, HH)->ExtractTile(*m_encoder, true, tileX, tileY);
if (i == lastChannel && tileY == lastTile && tileX == lastTile) {
// all necessary data are buffered. next call of EncodeTileBuffer will write the last piece of data of the current level.
m_encoder->SetEncodedLevel(--m_currentLevel);
}
m_encoder->EncodeTileBuffer(); // encode last macro block with tile-end = true
}
}
}
} else
#endif
{
for (int i=0; i < m_header.channels; i++) {
ASSERT(m_wtChannel[i]);
if (m_currentLevel == m_header.nLevels) {
// last level also has LL band
m_wtChannel[i]->GetSubband(m_currentLevel, LL)->ExtractTile(*m_encoder);
}
//encoder.EncodeInterleaved(m_wtChannel[i], m_currentLevel, m_quant); // until version 4
m_wtChannel[i]->GetSubband(m_currentLevel, HL)->ExtractTile(*m_encoder); // since version 5
m_wtChannel[i]->GetSubband(m_currentLevel, LH)->ExtractTile(*m_encoder); // since version 5
m_wtChannel[i]->GetSubband(m_currentLevel, HH)->ExtractTile(*m_encoder);
}
// all necessary data are buffered. next call of EncodeBuffer will write the last piece of data of the current level.
m_encoder->SetEncodedLevel(--m_currentLevel);
}
}
//////////////////////////////////////////////////////////////////////
// Return written levelLength bytes
UINT32 CPGFImage::UpdatePostHeaderSize() {
ASSERT(m_encoder);
INT64 offset = m_encoder->ComputeOffset(); ASSERT(offset >= 0);
if (offset > 0) {
// update post-header size and rewrite pre-header
m_preHeader.hSize += (UINT32)offset;
m_encoder->UpdatePostHeaderSize(m_preHeader);
}
// write dummy levelLength into stream
return m_encoder->WriteLevelLength(m_levelLength);
}
//////////////////////////////////////////////////////////////////////
/// Encode and write an image at current stream position.
/// Call this method after WriteHeader().
/// In case you want to write uncached metadata,
/// then do that after WriteHeader() and before WriteImage().
/// This method is called inside of Write(stream, ...).
/// It might throw an IOException.
/// @param stream A PGF stream
/// @param cb A pointer to a callback procedure. The procedure is called after writing a single level. If cb returns true, then it stops proceeding.
/// @param data Data Pointer to C++ class container to host callback procedure.
/// @return The number of bytes written into stream.
UINT32 CPGFImage::WriteImage(CPGFStream* stream, CallbackPtr cb /*= nullptr*/, void *data /*= nullptr*/) {
ASSERT(stream);
ASSERT(m_preHeader.hSize);
int levels = m_header.nLevels;
double percent = pow(0.25, levels);
// update post-header size, rewrite pre-header, and write dummy levelLength
UINT32 nWrittenBytes = UpdatePostHeaderSize();
if (levels == 0) {
// for very small images: write channels uncoded
for (int c=0; c < m_header.channels; c++) {
const UINT32 size = m_width[c]*m_height[c];
// write channel data into stream
for (UINT32 i=0; i < size; i++) {
int count = DataTSize;
stream->Write(&count, &m_channel[c][i]);
}
}
// now update progress
if (cb) {
if ((*cb)(1, true, data)) ReturnWithError(EscapePressed);
}
} else {
// encode quantized wavelet coefficients and write to PGF file
// encode subbands, higher levels first
// color channels are interleaved
// encode all levels
for (m_currentLevel = levels; m_currentLevel > 0; ) {
WriteLevel(); // decrements m_currentLevel
// now update progress
if (cb) {
percent *= 4;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
// flush encoder and write level lengths
m_encoder->Flush();
}
// update level lengths
nWrittenBytes += m_encoder->UpdateLevelLength(); // return written image bytes
// delete encoder
delete m_encoder; m_encoder = nullptr;
ASSERT(!m_encoder);
return nWrittenBytes;
}
//////////////////////////////////////////////////////////////////
/// Encode and write an entire PGF image (header and image) at current stream position.
/// A PGF image is structered in levels, numbered between 0 and Levels() - 1.
/// Each level can be seen as a single image, containing the same content
/// as all other levels, but in a different size (width, height).
/// The image size at level i is double the size (width, height) of the image at level i+1.
/// The image at level 0 contains the original size.
/// Precondition: the PGF image contains a valid header (see also SetHeader(...)).
/// It might throw an IOException.
/// @param stream A PGF stream
/// @param nWrittenBytes [in-out] The number of bytes written into stream are added to the input value.
/// @param cb A pointer to a callback procedure. The procedure is called after writing a single level. If cb returns true, then it stops proceeding.
/// @param data Data Pointer to C++ class container to host callback procedure.
void CPGFImage::Write(CPGFStream* stream, UINT32* nWrittenBytes /*= nullptr*/, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) {
ASSERT(stream);
ASSERT(m_preHeader.hSize);
// create wavelet transform channels and encoder
UINT32 nBytes = WriteHeader(stream);
// write image
nBytes += WriteImage(stream, cb, data);
// return written bytes
if (nWrittenBytes) *nWrittenBytes += nBytes;
}
#ifdef __PGFROISUPPORT__
//////////////////////////////////////////////////////////////////
// Encode and write down to given level at current stream position.
// A PGF image is structered in levels, numbered between 0 and Levels() - 1.
// Each level can be seen as a single image, containing the same content
// as all other levels, but in a different size (width, height).
// The image size at level i is double the size (width, height) of the image at level i+1.
// The image at level 0 contains the original size.
// Precondition: the PGF image contains a valid header (see also SetHeader(...)) and WriteHeader() has been called before.
// The ROI encoding scheme is used.
// It might throw an IOException.
// @param level The image level of the resulting image in the internal image buffer.
// @param cb A pointer to a callback procedure. The procedure is called after writing a single level. If cb returns true, then it stops proceeding.
// @param data Data Pointer to C++ class container to host callback procedure.
// @return The number of bytes written into stream.
UINT32 CPGFImage::Write(int level, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) {
ASSERT(m_header.nLevels > 0);
ASSERT(0 <= level && level < m_header.nLevels);
ASSERT(m_encoder);
ASSERT(ROIisSupported());
const int levelDiff = m_currentLevel - level;
double percent = (m_progressMode == PM_Relative) ? pow(0.25, levelDiff) : m_percent;
UINT32 nWrittenBytes = 0;
if (m_currentLevel == m_header.nLevels) {
// update post-header size, rewrite pre-header, and write dummy levelLength
nWrittenBytes = UpdatePostHeaderSize();
} else {
// prepare for next level: save current file position, because the stream might have been reinitialized
if (m_encoder->ComputeBufferLength()) {
m_streamReinitialized = true;
}
}
// encoding scheme with ROI
while (m_currentLevel > level) {
WriteLevel(); // decrements m_currentLevel
if (m_levelLength) {
nWrittenBytes += m_levelLength[m_header.nLevels - m_currentLevel - 1];
}
// now update progress
if (cb) {
percent *= 4;
if (m_progressMode == PM_Absolute) m_percent = percent;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
// automatically closing
if (m_currentLevel == 0) {
if (!m_streamReinitialized) {
// don't write level lengths, if the stream position changed inbetween two Write operations
m_encoder->UpdateLevelLength();
}
// delete encoder
delete m_encoder; m_encoder = nullptr;
}
return nWrittenBytes;
}
#endif // __PGFROISUPPORT__
//////////////////////////////////////////////////////////////////
// Check for valid import image mode.
// @param mode Image mode
// @return True if an image of given mode can be imported with ImportBitmap(...)
bool CPGFImage::ImportIsSupported(BYTE mode) {
size_t size = DataTSize;
if (size >= 2) {
switch(mode) {
case ImageModeBitmap:
case ImageModeIndexedColor:
case ImageModeGrayScale:
case ImageModeRGBColor:
case ImageModeCMYKColor:
case ImageModeHSLColor:
case ImageModeHSBColor:
//case ImageModeDuotone:
case ImageModeLabColor:
case ImageModeRGB12:
case ImageModeRGB16:
case ImageModeRGBA:
return true;
}
}
if (size >= 3) {
switch(mode) {
case ImageModeGray16:
case ImageModeRGB48:
case ImageModeLab48:
case ImageModeCMYK64:
//case ImageModeDuotone16:
return true;
}
}
if (size >=4) {
switch(mode) {
case ImageModeGray32:
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////
/// Retrieves red, green, blue (RGB) color values from a range of entries in the palette of the DIB section.
/// It might throw an IOException.
/// @param iFirstColor The color table index of the first entry to retrieve.
/// @param nColors The number of color table entries to retrieve.
/// @param prgbColors A pointer to the array of RGBQUAD structures to retrieve the color table entries.
void CPGFImage::GetColorTable(UINT32 iFirstColor, UINT32 nColors, RGBQUAD* prgbColors) const {
if (iFirstColor + nColors > ColorTableLen) ReturnWithError(ColorTableError);
for (UINT32 i=iFirstColor, j=0; j < nColors; i++, j++) {
prgbColors[j] = m_postHeader.clut[i];
}
}
//////////////////////////////////////////////////////////////////////
/// Sets the red, green, blue (RGB) color values for a range of entries in the palette (clut).
/// It might throw an IOException.
/// @param iFirstColor The color table index of the first entry to set.
/// @param nColors The number of color table entries to set.
/// @param prgbColors A pointer to the array of RGBQUAD structures to set the color table entries.
void CPGFImage::SetColorTable(UINT32 iFirstColor, UINT32 nColors, const RGBQUAD* prgbColors) {
if (iFirstColor + nColors > ColorTableLen) ReturnWithError(ColorTableError);
for (UINT32 i=iFirstColor, j=0; j < nColors; i++, j++) {
m_postHeader.clut[i] = prgbColors[j];
}
}
//////////////////////////////////////////////////////////////////
-// Buffer transform from interleaved to channel seperated format
+// Buffer transform from interleaved to channel separated format
// the absolute value of pitch is the number of bytes of an image row
// if pitch is negative, then buff points to the last row of a bottom-up image (first byte on last row)
// if pitch is positive, then buff points to the first row of a top-down image (first byte)
// bpp is the number of bits per pixel used in image buffer buff
//
// RGB is transformed into YUV format (ordering of buffer data is BGR[A])
// Y = (R + 2*G + B)/4 -128
// U = R - G
// V = B - G
//
// Since PGF Codec version 2.0 images are stored in top-down direction
//
// The sequence of input channels in the input image buffer does not need to be the same as expected from PGF. In case of different sequences you have to
// provide a channelMap of size of expected channels (depending on image mode). For example, PGF expects in RGB color mode a channel sequence BGR.
// If your provided image buffer contains a channel sequence ARGB, then the channelMap looks like { 3, 2, 1 }.
void CPGFImage::RgbToYuv(int pitch, UINT8* buff, BYTE bpp, int channelMap[], CallbackPtr cb, void *data /*=nullptr*/) {
ASSERT(buff);
UINT32 yPos = 0, cnt = 0;
double percent = 0;
const double dP = 1.0/m_header.height;
int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
if (channelMap == nullptr) channelMap = defMap;
switch(m_header.mode) {
case ImageModeBitmap:
{
ASSERT(m_header.channels == 1);
ASSERT(m_header.bpp == 1);
ASSERT(bpp == 1);
const UINT32 w = m_header.width;
const UINT32 w2 = (m_header.width + 7)/8;
DataT* y = m_channel[0]; ASSERT(y);
// new unpacked version since version 7
for (UINT32 h = 0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 j = 0; j < w2; j++) {
UINT8 byte = buff[j];
for (int k = 0; k < 8; k++) {
UINT8 bit = (byte & 0x80) >> 7;
if (cnt < w) y[yPos++] = bit;
byte <<= 1;
cnt++;
}
}
buff += pitch;
}
/* old version: packed values: 8 pixels in 1 byte
for (UINT32 h = 0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
for (UINT32 j = 0; j < w2; j++) {
y[yPos++] = buff[j] - YUVoffset8;
}
// version 5 and 6
// for (UINT32 j = w2; j < w; j++) {
// y[yPos++] = YUVoffset8;
//}
buff += pitch;
}
*/
}
break;
case ImageModeIndexedColor:
case ImageModeGrayScale:
case ImageModeHSLColor:
case ImageModeHSBColor:
case ImageModeLabColor:
{
ASSERT(m_header.channels >= 1);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
const int channels = bpp/8; ASSERT(channels >= m_header.channels);
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
for (int c=0; c < m_header.channels; c++) {
m_channel[c][yPos] = buff[cnt + channelMap[c]] - YUVoffset8;
}
cnt += channels;
yPos++;
}
buff += pitch;
}
}
break;
case ImageModeGray16:
case ImageModeLab48:
{
ASSERT(m_header.channels >= 1);
ASSERT(m_header.bpp == m_header.channels*16);
ASSERT(bpp%16 == 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
const int pitch16 = pitch/2;
const int channels = bpp/16; ASSERT(channels >= m_header.channels);
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
for (int c=0; c < m_header.channels; c++) {
m_channel[c][yPos] = (buff16[cnt + channelMap[c]] >> shift) - yuvOffset16;
}
cnt += channels;
yPos++;
}
buff16 += pitch16;
}
}
break;
case ImageModeRGBColor:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
const int channels = bpp/8; ASSERT(channels >= m_header.channels);
UINT8 b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
b = buff[cnt + channelMap[0]];
g = buff[cnt + channelMap[1]];
r = buff[cnt + channelMap[2]];
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset8;
u[yPos] = r - g;
v[yPos] = b - g;
yPos++;
cnt += channels;
}
buff += pitch;
}
}
break;
case ImageModeRGB48:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*16);
ASSERT(bpp%16 == 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
const int pitch16 = pitch/2;
const int channels = bpp/16; ASSERT(channels >= m_header.channels);
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
b = buff16[cnt + channelMap[0]] >> shift;
g = buff16[cnt + channelMap[1]] >> shift;
r = buff16[cnt + channelMap[2]] >> shift;
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - yuvOffset16;
u[yPos] = r - g;
v[yPos] = b - g;
yPos++;
cnt += channels;
}
buff16 += pitch16;
}
}
break;
case ImageModeRGBA:
case ImageModeCMYKColor:
{
ASSERT(m_header.channels == 4);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
const int channels = bpp/8; ASSERT(channels >= m_header.channels);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
UINT8 b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
b = buff[cnt + channelMap[0]];
g = buff[cnt + channelMap[1]];
r = buff[cnt + channelMap[2]];
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset8;
u[yPos] = r - g;
v[yPos] = b - g;
a[yPos++] = buff[cnt + channelMap[3]] - YUVoffset8;
cnt += channels;
}
buff += pitch;
}
}
break;
case ImageModeCMYK64:
{
ASSERT(m_header.channels == 4);
ASSERT(m_header.bpp == m_header.channels*16);
ASSERT(bpp%16 == 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
const int pitch16 = pitch/2;
const int channels = bpp/16; ASSERT(channels >= m_header.channels);
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
UINT16 b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
b = buff16[cnt + channelMap[0]] >> shift;
g = buff16[cnt + channelMap[1]] >> shift;
r = buff16[cnt + channelMap[2]] >> shift;
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - yuvOffset16;
u[yPos] = r - g;
v[yPos] = b - g;
a[yPos++] = (buff16[cnt + channelMap[3]] >> shift) - yuvOffset16;
cnt += channels;
}
buff16 += pitch16;
}
}
break;
#ifdef __PGF32SUPPORT__
case ImageModeGray32:
{
ASSERT(m_header.channels == 1);
ASSERT(m_header.bpp == 32);
ASSERT(bpp == 32);
ASSERT(DataTSize == sizeof(UINT32));
DataT* y = m_channel[0]; ASSERT(y);
UINT32 *buff32 = reinterpret_cast<UINT32*>(buff);
const int pitch32 = pitch/4;
const int shift = 31 - UsedBitsPerChannel(); ASSERT(shift >= 0);
const DataT yuvOffset31 = 1 << (UsedBitsPerChannel() - 1);
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
for (UINT32 w=0; w < m_header.width; w++) {
y[yPos++] = (buff32[w] >> shift) - yuvOffset31;
}
buff32 += pitch32;
}
}
break;
#endif
case ImageModeRGB12:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*4);
ASSERT(bpp == m_header.channels*4);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT8 rgb = 0, b, g, r;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
if (w%2 == 0) {
// even pixel position
rgb = buff[cnt];
b = rgb & 0x0F;
g = (rgb & 0xF0) >> 4;
cnt++;
rgb = buff[cnt];
r = rgb & 0x0F;
} else {
// odd pixel position
b = (rgb & 0xF0) >> 4;
cnt++;
rgb = buff[cnt];
g = rgb & 0x0F;
r = (rgb & 0xF0) >> 4;
cnt++;
}
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset4;
u[yPos] = r - g;
v[yPos] = b - g;
yPos++;
}
buff += pitch;
}
}
break;
case ImageModeRGB16:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == 16);
ASSERT(bpp == 16);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
UINT16 rgb, b, g, r;
const int pitch16 = pitch/2;
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
for (UINT32 w=0; w < m_header.width; w++) {
rgb = buff16[w];
r = (rgb & 0xF800) >> 10; // highest 5 bits
g = (rgb & 0x07E0) >> 5; // middle 6 bits
b = (rgb & 0x001F) << 1; // lowest 5 bits
// Yuv
y[yPos] = ((b + (g << 1) + r) >> 2) - YUVoffset6;
u[yPos] = r - g;
v[yPos] = b - g;
yPos++;
}
buff16 += pitch16;
}
}
break;
default:
ASSERT(false);
}
}
//////////////////////////////////////////////////////////////////
// Get image data in interleaved format: (ordering of RGB data is BGR[A])
// Upsampling, YUV to RGB transform and interleaving are done here to reduce the number
// of passes over the data.
// The absolute value of pitch is the number of bytes of an image row of the given image buffer.
// If pitch is negative, then the image buffer must point to the last row of a bottom-up image (first byte on last row).
// if pitch is positive, then the image buffer must point to the first row of a top-down image (first byte).
// The sequence of output channels in the output image buffer does not need to be the same as provided by PGF. In case of different sequences you have to
// provide a channelMap of size of expected channels (depending on image mode). For example, PGF provides a channel sequence BGR in RGB color mode.
// If your provided image buffer expects a channel sequence ARGB, then the channelMap looks like { 3, 2, 1 }.
// It might throw an IOException.
// @param pitch The number of bytes of a row of the image buffer.
// @param buff An image buffer.
// @param bpp The number of bits per pixel used in image buffer.
// @param channelMap A integer array containing the mapping of PGF channel ordering to expected channel ordering.
// @param cb A pointer to a callback procedure. The procedure is called after each copied buffer row. If cb returns true, then it stops proceeding.
// @param data Data Pointer to C++ class container to host callback procedure.
void CPGFImage::GetBitmap(int pitch, UINT8* buff, BYTE bpp, int channelMap[] /*= nullptr */, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) const {
ASSERT(buff);
UINT32 w = m_width[0]; // width of decoded image
UINT32 h = m_height[0]; // height of decoded image
UINT32 yw = w; // y-channel width
UINT32 uw = m_width[1]; // u-channel width
UINT32 roiOffsetX = 0;
UINT32 roiOffsetY = 0;
UINT32 yOffset = 0;
UINT32 uOffset = 0;
#ifdef __PGFROISUPPORT__
const PGFRect& roi = GetAlignedROI(); // in pixels, roi is usually larger than levelRoi
ASSERT(w == roi.Width() && h == roi.Height());
const PGFRect levelRoi = ComputeLevelROI();
ASSERT(roi.left <= levelRoi.left && levelRoi.right <= roi.right);
ASSERT(roi.top <= levelRoi.top && levelRoi.bottom <= roi.bottom);
if (ROIisSupported() && (levelRoi.Width() < w || levelRoi.Height() < h)) {
// ROI is used
w = levelRoi.Width();
h = levelRoi.Height();
roiOffsetX = levelRoi.left - roi.left;
roiOffsetY = levelRoi.top - roi.top;
yOffset = roiOffsetX + roiOffsetY*yw;
if (m_downsample) {
const PGFRect& downsampledRoi = GetAlignedROI(1);
uOffset = levelRoi.left/2 - downsampledRoi.left + (levelRoi.top/2 - downsampledRoi.top)*m_width[1];
} else {
uOffset = yOffset;
}
}
#endif
const double dP = 1.0/h;
int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
if (channelMap == nullptr) channelMap = defMap;
DataT uAvg, vAvg;
double percent = 0;
UINT32 i, j;
switch(m_header.mode) {
case ImageModeBitmap:
{
ASSERT(m_header.channels == 1);
ASSERT(m_header.bpp == 1);
ASSERT(bpp == 1);
const UINT32 w2 = (w + 7)/8;
DataT* y = m_channel[0]; ASSERT(y);
if (m_preHeader.version & Version7) {
// new unpacked version has a little better compression ratio
// since version 7
for (i = 0; i < h; i++) {
UINT32 cnt = 0;
for (j = 0; j < w2; j++) {
UINT8 byte = 0;
for (int k = 0; k < 8; k++) {
byte <<= 1;
UINT8 bit = 0;
if (cnt < w) {
bit = y[yOffset + cnt] & 1;
}
byte |= bit;
cnt++;
}
buff[j] = byte;
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
// old versions
// packed pixels: 8 pixel in 1 byte of channel[0]
if (!(m_preHeader.version & Version5)) yw = w2; // not version 5 or 6
yOffset = roiOffsetX/8 + roiOffsetY*yw; // 1 byte in y contains 8 pixel values
for (i = 0; i < h; i++) {
for (j = 0; j < w2; j++) {
buff[j] = Clamp8(y[yOffset + j] + YUVoffset8);
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
case ImageModeIndexedColor:
case ImageModeGrayScale:
case ImageModeHSLColor:
case ImageModeHSBColor:
{
ASSERT(m_header.channels >= 1);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
UINT32 cnt, channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
for (UINT32 c=0; c < m_header.channels; c++) {
buff[cnt + channelMap[c]] = Clamp8(m_channel[c][yPos] + YUVoffset8);
}
cnt += channels;
yPos++;
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
break;
}
case ImageModeGray16:
{
ASSERT(m_header.channels >= 1);
ASSERT(m_header.bpp == m_header.channels*16);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
UINT32 cnt, channels;
if (bpp%16 == 0) {
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
int pitch16 = pitch/2;
channels = bpp/16; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
for (UINT32 c=0; c < m_header.channels; c++) {
buff16[cnt + channelMap[c]] = Clamp16((m_channel[c][yPos] + yuvOffset16) << shift);
}
cnt += channels;
yPos++;
}
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
ASSERT(bpp%8 == 0);
const int shift = __max(0, UsedBitsPerChannel() - 8);
channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
for (UINT32 c=0; c < m_header.channels; c++) {
buff[cnt + channelMap[c]] = Clamp8((m_channel[c][yPos] + yuvOffset16) >> shift);
}
cnt += channels;
yPos++;
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
case ImageModeRGBColor:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
ASSERT(bpp >= m_header.bpp);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT8 *buffg = &buff[channelMap[1]],
*buffr = &buff[channelMap[2]],
*buffb = &buff[channelMap[0]];
UINT8 g;
UINT32 cnt, channels = bpp/8;
if (m_downsample) {
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
// u and v are downsampled
uAvg = u[uPos];
vAvg = v[uPos];
// Yuv
buffg[cnt] = g = Clamp8(y[yPos] + YUVoffset8 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
buffr[cnt] = Clamp8(uAvg + g);
buffb[cnt] = Clamp8(vAvg + g);
cnt += channels;
if (j & 1) uPos++;
yPos++;
}
if (i & 1) uOffset += uw;
yOffset += yw;
buffb += pitch;
buffg += pitch;
buffr += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
for (i=0; i < h; i++) {
cnt = 0;
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
uAvg = u[yPos];
vAvg = v[yPos];
// Yuv
buffg[cnt] = g = Clamp8(y[yPos] + YUVoffset8 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
buffr[cnt] = Clamp8(uAvg + g);
buffb[cnt] = Clamp8(vAvg + g);
cnt += channels;
yPos++;
}
yOffset += yw;
buffb += pitch;
buffg += pitch;
buffr += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
case ImageModeRGB48:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == 48);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT32 cnt, channels;
DataT g;
if (bpp >= 48 && bpp%16 == 0) {
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
int pitch16 = pitch/2;
channels = bpp/16; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = u[uPos];
vAvg = v[uPos];
// Yuv
g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
buff16[cnt + channelMap[1]] = Clamp16(g << shift);
buff16[cnt + channelMap[2]] = Clamp16((uAvg + g) << shift);
buff16[cnt + channelMap[0]] = Clamp16((vAvg + g) << shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
ASSERT(bpp%8 == 0);
const int shift = __max(0, UsedBitsPerChannel() - 8);
channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = u[uPos];
vAvg = v[uPos];
// Yuv
g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
buff[cnt + channelMap[1]] = Clamp8(g >> shift);
buff[cnt + channelMap[2]] = Clamp8((uAvg + g) >> shift);
buff[cnt + channelMap[0]] = Clamp8((vAvg + g) >> shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
case ImageModeLabColor:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
DataT* l = m_channel[0]; ASSERT(l);
DataT* a = m_channel[1]; ASSERT(a);
DataT* b = m_channel[2]; ASSERT(b);
UINT32 cnt, channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = a[uPos];
vAvg = b[uPos];
buff[cnt + channelMap[0]] = Clamp8(l[yPos] + YUVoffset8);
buff[cnt + channelMap[1]] = Clamp8(uAvg + YUVoffset8);
buff[cnt + channelMap[2]] = Clamp8(vAvg + YUVoffset8);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
break;
}
case ImageModeLab48:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*16);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
DataT* l = m_channel[0]; ASSERT(l);
DataT* a = m_channel[1]; ASSERT(a);
DataT* b = m_channel[2]; ASSERT(b);
UINT32 cnt, channels;
if (bpp%16 == 0) {
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
int pitch16 = pitch/2;
channels = bpp/16; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = a[uPos];
vAvg = b[uPos];
buff16[cnt + channelMap[0]] = Clamp16((l[yPos] + yuvOffset16) << shift);
buff16[cnt + channelMap[1]] = Clamp16((uAvg + yuvOffset16) << shift);
buff16[cnt + channelMap[2]] = Clamp16((vAvg + yuvOffset16) << shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
ASSERT(bpp%8 == 0);
const int shift = __max(0, UsedBitsPerChannel() - 8);
channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = a[uPos];
vAvg = b[uPos];
buff[cnt + channelMap[0]] = Clamp8((l[yPos] + yuvOffset16) >> shift);
buff[cnt + channelMap[1]] = Clamp8((uAvg + yuvOffset16) >> shift);
buff[cnt + channelMap[2]] = Clamp8((vAvg + yuvOffset16) >> shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
case ImageModeRGBA:
case ImageModeCMYKColor:
{
ASSERT(m_header.channels == 4);
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%8 == 0);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
UINT8 g, aAvg;
UINT32 cnt, channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = u[uPos];
vAvg = v[uPos];
aAvg = Clamp8(a[uPos] + YUVoffset8);
// Yuv
buff[cnt + channelMap[1]] = g = Clamp8(y[yPos] + YUVoffset8 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
buff[cnt + channelMap[2]] = Clamp8(uAvg + g);
buff[cnt + channelMap[0]] = Clamp8(vAvg + g);
buff[cnt + channelMap[3]] = aAvg;
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
break;
}
case ImageModeCMYK64:
{
ASSERT(m_header.channels == 4);
ASSERT(m_header.bpp == 64);
const DataT yuvOffset16 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
DataT g, aAvg;
UINT32 cnt, channels;
if (bpp%16 == 0) {
const int shift = 16 - UsedBitsPerChannel(); ASSERT(shift >= 0);
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
int pitch16 = pitch/2;
channels = bpp/16; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = u[uPos];
vAvg = v[uPos];
aAvg = a[uPos] + yuvOffset16;
// Yuv
g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
buff16[cnt + channelMap[1]] = Clamp16(g << shift);
buff16[cnt + channelMap[2]] = Clamp16((uAvg + g) << shift);
buff16[cnt + channelMap[0]] = Clamp16((vAvg + g) << shift);
buff16[cnt + channelMap[3]] = Clamp16(aAvg << shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
ASSERT(bpp%8 == 0);
const int shift = __max(0, UsedBitsPerChannel() - 8);
channels = bpp/8; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
UINT32 uPos = uOffset;
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
uAvg = u[uPos];
vAvg = v[uPos];
aAvg = a[uPos] + yuvOffset16;
// Yuv
g = y[yPos] + yuvOffset16 - ((uAvg + vAvg ) >> 2); // must be logical shift operator
buff[cnt + channelMap[1]] = Clamp8(g >> shift);
buff[cnt + channelMap[2]] = Clamp8((uAvg + g) >> shift);
buff[cnt + channelMap[0]] = Clamp8((vAvg + g) >> shift);
buff[cnt + channelMap[3]] = Clamp8(aAvg >> shift);
cnt += channels;
if (!m_downsample || (j & 1)) uPos++;
yPos++;
}
if (!m_downsample || (i & 1)) uOffset += uw;
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
#ifdef __PGF32SUPPORT__
case ImageModeGray32:
{
ASSERT(m_header.channels == 1);
ASSERT(m_header.bpp == 32);
const int yuvOffset31 = 1 << (UsedBitsPerChannel() - 1);
DataT* y = m_channel[0]; ASSERT(y);
if (bpp == 32) {
const int shift = 31 - UsedBitsPerChannel(); ASSERT(shift >= 0);
UINT32 *buff32 = reinterpret_cast<UINT32*>(buff);
int pitch32 = pitch/4;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff32[j] = Clamp31((y[yPos++] + yuvOffset31) << shift);
}
yOffset += yw;
buff32 += pitch32;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else if (bpp == 16) {
const int usedBits = UsedBitsPerChannel();
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
int pitch16 = pitch/2;
if (usedBits < 16) {
const int shift = 16 - usedBits;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff16[j] = Clamp16((y[yPos++] + yuvOffset31) << shift);
}
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else {
const int shift = __max(0, usedBits - 16);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff16[j] = Clamp16((y[yPos++] + yuvOffset31) >> shift);
}
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
} else {
ASSERT(bpp == 8);
const int shift = __max(0, UsedBitsPerChannel() - 8);
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
buff[j] = Clamp8((y[yPos++] + yuvOffset31) >> shift);
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
break;
}
#endif
case ImageModeRGB12:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == m_header.channels*4);
ASSERT(bpp == m_header.channels*4);
ASSERT(!m_downsample);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 yval;
UINT32 cnt;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
cnt = 0;
for (j=0; j < w; j++) {
// Yuv
uAvg = u[yPos];
vAvg = v[yPos];
yval = Clamp4(y[yPos] + YUVoffset4 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
if (j%2 == 0) {
buff[cnt] = UINT8(Clamp4(vAvg + yval) | (yval << 4));
cnt++;
buff[cnt] = Clamp4(uAvg + yval);
} else {
buff[cnt] |= Clamp4(vAvg + yval) << 4;
cnt++;
buff[cnt] = UINT8(yval | (Clamp4(uAvg + yval) << 4));
cnt++;
}
yPos++;
}
yOffset += yw;
buff += pitch;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
break;
}
case ImageModeRGB16:
{
ASSERT(m_header.channels == 3);
ASSERT(m_header.bpp == 16);
ASSERT(bpp == 16);
ASSERT(!m_downsample);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
UINT16 yval;
UINT16 *buff16 = reinterpret_cast<UINT16*>(buff);
int pitch16 = pitch/2;
for (i=0; i < h; i++) {
UINT32 yPos = yOffset;
for (j = 0; j < w; j++) {
// Yuv
uAvg = u[yPos];
vAvg = v[yPos];
yval = Clamp6(y[yPos++] + YUVoffset6 - ((uAvg + vAvg ) >> 2)); // must be logical shift operator
buff16[j] = (yval << 5) | ((Clamp6(uAvg + yval) >> 1) << 11) | (Clamp6(vAvg + yval) >> 1);
}
yOffset += yw;
buff16 += pitch16;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
break;
}
default:
ASSERT(false);
}
#ifdef _DEBUG
// display ROI (RGB) in debugger
roiimage.width = w;
roiimage.height = h;
if (pitch > 0) {
roiimage.pitch = pitch;
roiimage.data = buff;
} else {
roiimage.pitch = -pitch;
roiimage.data = buff + (h - 1)*pitch;
}
#endif
}
//////////////////////////////////////////////////////////////////////
/// Get YUV image data in interleaved format: (ordering is YUV[A])
/// The absolute value of pitch is the number of bytes of an image row of the given image buffer.
/// If pitch is negative, then the image buffer must point to the last row of a bottom-up image (first byte on last row).
/// if pitch is positive, then the image buffer must point to the first row of a top-down image (first byte).
/// The sequence of output channels in the output image buffer does not need to be the same as provided by PGF. In case of different sequences you have to
/// provide a channelMap of size of expected channels (depending on image mode). For example, PGF provides a channel sequence BGR in RGB color mode.
/// If your provided image buffer expects a channel sequence VUY, then the channelMap looks like { 2, 1, 0 }.
/// It might throw an IOException.
/// @param pitch The number of bytes of a row of the image buffer.
/// @param buff An image buffer.
/// @param bpp The number of bits per pixel used in image buffer.
/// @param channelMap A integer array containing the mapping of PGF channel ordering to expected channel ordering.
/// @param cb A pointer to a callback procedure. The procedure is called after each copied buffer row. If cb returns true, then it stops proceeding.
void CPGFImage::GetYUV(int pitch, DataT* buff, BYTE bpp, int channelMap[] /*= nullptr*/, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) const {
ASSERT(buff);
const UINT32 w = m_width[0];
const UINT32 h = m_height[0];
const bool wOdd = (1 == w%2);
const int dataBits = DataTSize*8; ASSERT(dataBits == 16 || dataBits == 32);
const int pitch2 = pitch/DataTSize;
const int yuvOffset = (dataBits == 16) ? YUVoffset8 : YUVoffset16;
const double dP = 1.0/h;
int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
if (channelMap == nullptr) channelMap = defMap;
int sampledPos = 0, yPos = 0;
DataT uAvg, vAvg;
double percent = 0;
UINT32 i, j;
if (m_header.channels == 3) {
ASSERT(bpp%dataBits == 0);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
int cnt, channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
if (i%2) sampledPos -= (w + 1)/2;
cnt = 0;
for (j=0; j < w; j++) {
if (m_downsample) {
// image was downsampled
uAvg = u[sampledPos];
vAvg = v[sampledPos];
} else {
uAvg = u[yPos];
vAvg = v[yPos];
}
buff[cnt + channelMap[0]] = y[yPos];
buff[cnt + channelMap[1]] = uAvg;
buff[cnt + channelMap[2]] = vAvg;
yPos++;
cnt += channels;
if (j%2) sampledPos++;
}
buff += pitch2;
if (wOdd) sampledPos++;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
} else if (m_header.channels == 4) {
ASSERT(m_header.bpp == m_header.channels*8);
ASSERT(bpp%dataBits == 0);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
UINT8 aAvg;
int cnt, channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
for (i=0; i < h; i++) {
if (i%2) sampledPos -= (w + 1)/2;
cnt = 0;
for (j=0; j < w; j++) {
if (m_downsample) {
// image was downsampled
uAvg = u[sampledPos];
vAvg = v[sampledPos];
aAvg = Clamp8(a[sampledPos] + yuvOffset);
} else {
uAvg = u[yPos];
vAvg = v[yPos];
aAvg = Clamp8(a[yPos] + yuvOffset);
}
// Yuv
buff[cnt + channelMap[0]] = y[yPos];
buff[cnt + channelMap[1]] = uAvg;
buff[cnt + channelMap[2]] = vAvg;
buff[cnt + channelMap[3]] = aAvg;
yPos++;
cnt += channels;
if (j%2) sampledPos++;
}
buff += pitch2;
if (wOdd) sampledPos++;
if (cb) {
percent += dP;
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
}
}
}
}
//////////////////////////////////////////////////////////////////////
/// Import a YUV image from a specified image buffer.
/// The absolute value of pitch is the number of bytes of an image row.
/// If pitch is negative, then buff points to the last row of a bottom-up image (first byte on last row).
/// If pitch is positive, then buff points to the first row of a top-down image (first byte).
/// The sequence of input channels in the input image buffer does not need to be the same as expected from PGF. In case of different sequences you have to
/// provide a channelMap of size of expected channels (depending on image mode). For example, PGF expects in RGB color mode a channel sequence BGR.
/// If your provided image buffer contains a channel sequence VUY, then the channelMap looks like { 2, 1, 0 }.
/// It might throw an IOException.
/// @param pitch The number of bytes of a row of the image buffer.
/// @param buff An image buffer.
/// @param bpp The number of bits per pixel used in image buffer.
/// @param channelMap A integer array containing the mapping of input channel ordering to expected channel ordering.
/// @param cb A pointer to a callback procedure. The procedure is called after each imported buffer row. If cb returns true, then it stops proceeding.
void CPGFImage::ImportYUV(int pitch, DataT *buff, BYTE bpp, int channelMap[] /*= nullptr*/, CallbackPtr cb /*= nullptr*/, void *data /*=nullptr*/) {
ASSERT(buff);
const double dP = 1.0/m_header.height;
const int dataBits = DataTSize*8; ASSERT(dataBits == 16 || dataBits == 32);
const int pitch2 = pitch/DataTSize;
const int yuvOffset = (dataBits == 16) ? YUVoffset8 : YUVoffset16;
int yPos = 0, cnt = 0;
double percent = 0;
int defMap[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; ASSERT(sizeof(defMap)/sizeof(defMap[0]) == MaxChannels);
if (channelMap == nullptr) channelMap = defMap;
if (m_header.channels == 3) {
ASSERT(bpp%dataBits == 0);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
const int channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
y[yPos] = buff[cnt + channelMap[0]];
u[yPos] = buff[cnt + channelMap[1]];
v[yPos] = buff[cnt + channelMap[2]];
yPos++;
cnt += channels;
}
buff += pitch2;
}
} else if (m_header.channels == 4) {
ASSERT(bpp%dataBits == 0);
DataT* y = m_channel[0]; ASSERT(y);
DataT* u = m_channel[1]; ASSERT(u);
DataT* v = m_channel[2]; ASSERT(v);
DataT* a = m_channel[3]; ASSERT(a);
const int channels = bpp/dataBits; ASSERT(channels >= m_header.channels);
for (UINT32 h=0; h < m_header.height; h++) {
if (cb) {
if ((*cb)(percent, true, data)) ReturnWithError(EscapePressed);
percent += dP;
}
cnt = 0;
for (UINT32 w=0; w < m_header.width; w++) {
y[yPos] = buff[cnt + channelMap[0]];
u[yPos] = buff[cnt + channelMap[1]];
v[yPos] = buff[cnt + channelMap[2]];
a[yPos] = buff[cnt + channelMap[3]] - yuvOffset;
yPos++;
cnt += channels;
}
buff += pitch2;
}
}
if (m_downsample) {
// Subsampling of the chrominance and alpha channels
for (int i=1; i < m_header.channels; i++) {
Downsample(i);
}
}
}
diff --git a/core/libs/pgfutils/libpgf/Subband.h b/core/libs/pgfutils/libpgf/Subband.h
index 84fe3e897c..a1e31a7776 100644
--- a/core/libs/pgfutils/libpgf/Subband.h
+++ b/core/libs/pgfutils/libpgf/Subband.h
@@ -1,180 +1,180 @@
/*
* The Progressive Graphics File; http://www.libpgf.org
*
* $Date: 2006-06-04 22:05:59 +0200 (So, 04 Jun 2006) $
* $Revision: 229 $
*
* This file Copyright (C) 2006 xeraina GmbH, Switzerland
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
//////////////////////////////////////////////////////////////////////
/// @file Subband.h
/// @brief PGF wavelet subband class
/// @author C. Stamm
#ifndef PGF_SUBBAND_H
#define PGF_SUBBAND_H
#include "PGFtypes.h"
class CEncoder;
class CDecoder;
class CRoiIndices;
//////////////////////////////////////////////////////////////////////
/// PGF wavelet channel subband class.
/// @author C. Stamm, R. Spuler
/// @brief Wavelet channel class
class CSubband {
friend class CWaveletTransform;
friend class CRoiIndices;
public:
//////////////////////////////////////////////////////////////////////
/// Standard constructor.
CSubband();
//////////////////////////////////////////////////////////////////////
/// Destructor.
~CSubband();
//////////////////////////////////////////////////////////////////////
/// Allocate a memory buffer to store all wavelet coefficients of this subband.
/// @return True if the allocation did work without any problems
bool AllocMemory();
//////////////////////////////////////////////////////////////////////
/// Delete the memory buffer of this subband.
void FreeMemory();
/////////////////////////////////////////////////////////////////////
/// Extracts a rectangular subregion of this subband.
/// Write wavelet coefficients into buffer.
/// It might throw an IOException.
/// @param encoder An encoder instance
/// @param tile True if just a rectangular region is extracted, false if the entire subband is extracted.
/// @param tileX Tile index in x-direction
/// @param tileY Tile index in y-direction
void ExtractTile(CEncoder& encoder, bool tile = false, UINT32 tileX = 0, UINT32 tileY = 0);
/////////////////////////////////////////////////////////////////////
/// Decoding and dequantization of this subband.
/// It might throw an IOException.
/// @param decoder A decoder instance
/// @param quantParam Dequantization value
/// @param tile True if just a rectangular region is placed, false if the entire subband is placed.
/// @param tileX Tile index in x-direction
/// @param tileY Tile index in y-direction
void PlaceTile(CDecoder& decoder, int quantParam, bool tile = false, UINT32 tileX = 0, UINT32 tileY = 0);
//////////////////////////////////////////////////////////////////////
/// Perform subband quantization with given quantization parameter.
/// A scalar quantization (with dead-zone) is used. A large quantization value
/// results in strong quantization and therefore in big quality loss.
/// @param quantParam A quantization parameter (larger or equal to 0)
void Quantize(int quantParam);
//////////////////////////////////////////////////////////////////////
/// Perform subband dequantization with given quantization parameter.
/// A scalar quantization (with dead-zone) is used. A large quantization value
/// results in strong quantization and therefore in big quality loss.
/// @param quantParam A quantization parameter (larger or equal to 0)
void Dequantize(int quantParam);
//////////////////////////////////////////////////////////////////////
/// Store wavelet coefficient in subband at given position.
/// @param pos A subband position (>= 0)
/// @param v A wavelet coefficient
void SetData(UINT32 pos, DataT v) { ASSERT(pos < m_size); m_data[pos] = v; }
//////////////////////////////////////////////////////////////////////
/// Get a pointer to an array of all wavelet coefficients of this subband.
/// @return Pointer to array of wavelet coefficients
DataT* GetBuffer() { return m_data; }
//////////////////////////////////////////////////////////////////////
/// Return wavelet coefficient at given position.
/// @param pos A subband position (>= 0)
/// @return Wavelet coefficient
DataT GetData(UINT32 pos) const { ASSERT(pos < m_size); return m_data[pos]; }
//////////////////////////////////////////////////////////////////////
/// Return level of this subband.
/// @return Level of this subband
int GetLevel() const { return m_level; }
//////////////////////////////////////////////////////////////////////
/// Return height of this subband.
/// @return Height of this subband (in pixels)
int GetHeight() const { return m_height; }
//////////////////////////////////////////////////////////////////////
/// Return width of this subband.
/// @return Width of this subband (in pixels)
int GetWidth() const { return m_width; }
//////////////////////////////////////////////////////////////////////
/// Return orientation of this subband.
/// LL LH
/// HL HH
/// @return Orientation of this subband (LL, HL, LH, HH)
Orientation GetOrientation() const { return m_orientation; }
#ifdef __PGFROISUPPORT__
/////////////////////////////////////////////////////////////////////
/// Set data buffer position to given position + one row.
/// @param pos Given position
void IncBuffRow(UINT32 pos) { m_dataPos = pos + BufferWidth(); }
#endif
private:
void Initialize(UINT32 width, UINT32 height, int level, Orientation orient);
void WriteBuffer(DataT val) { ASSERT(m_dataPos < m_size); m_data[m_dataPos++] = val; }
void SetBuffer(DataT* b) { ASSERT(b); m_data = b; }
DataT ReadBuffer() { ASSERT(m_dataPos < m_size); return m_data[m_dataPos++]; }
UINT32 GetBuffPos() const { return m_dataPos; }
#ifdef __PGFROISUPPORT__
UINT32 BufferWidth() const { return m_ROI.Width(); }
void TilePosition(UINT32 tileX, UINT32 tileY, UINT32& left, UINT32& top, UINT32& w, UINT32& h) const;
void TileIndex(bool topLeft, UINT32 xPos, UINT32 yPos, UINT32& tileX, UINT32& tileY, UINT32& x, UINT32& y) const;
const PGFRect& GetAlignedROI() const { return m_ROI; }
void SetNTiles(UINT32 nTiles) { m_nTiles = nTiles; }
void SetAlignedROI(const PGFRect& roi);
void InitBuffPos(UINT32 left = 0, UINT32 top = 0) { m_dataPos = top*BufferWidth() + left; ASSERT(m_dataPos < m_size); }
#else
void InitBuffPos() { m_dataPos = 0; }
#endif
private:
UINT32 m_width; ///< width in pixels
UINT32 m_height; ///< height in pixels
UINT32 m_size; ///< size of data buffer m_data
int m_level; ///< recursion level
- Orientation m_orientation; ///< 0=LL, 1=HL, 2=LH, 3=HH L=lowpass filtered, H=highpass filterd
+ Orientation m_orientation; ///< 0=LL, 1=HL, 2=LH, 3=HH L=lowpass filtered, H=highpass filtered
UINT32 m_dataPos; ///< current position in m_data
DataT* m_data; ///< buffer
#ifdef __PGFROISUPPORT__
PGFRect m_ROI; ///< region of interest (block aligned)
UINT32 m_nTiles; ///< number of tiles in one dimension in this subband
#endif
};
#endif //PGF_SUBBAND_H
diff --git a/core/libs/pgfutils/pgfutils.cpp b/core/libs/pgfutils/pgfutils.cpp
index 7039abc176..fba9a38dc6 100644
--- a/core/libs/pgfutils/pgfutils.cpp
+++ b/core/libs/pgfutils/pgfutils.cpp
@@ -1,483 +1,483 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-05-29
* Description : static helper methods for PGF image format.
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "pgfutils.h"
// C Ansi includes
extern "C"
{
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
// Qt includes
#include <QImage>
#include <QByteArray>
#include <QFile>
#include <qplatformdefs.h>
// Windows includes
#ifdef Q_OS_WIN32
# include <windows.h>
#endif
// LibPGF includes
#include <PGFimage.h>
// Local includes
#include "digikam_debug.h"
namespace Digikam
{
namespace PGFUtils
{
// Private method
bool writePGFImageDataToStream(const QImage& image,
CPGFStream& stream,
int quality,
UINT32& nWrittenBytes,
bool verbose);
bool readPGFImageData(const QByteArray& data,
QImage& img,
bool verbose)
{
try
{
if (data.isEmpty())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF image data to decode : size is null";
return false;
}
CPGFMemoryStream stream((UINT8*)data.data(), (size_t)data.size());
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: image data stream size is : " << stream.GetSize();
CPGFImage pgfImg;
- // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properlly with libppgf 6.11.24
+ // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24
pgfImg.ConfigureDecoder(false);
pgfImg.Open(&stream);
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF image is open";
if (pgfImg.Channels() != 4)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF channels not supported";
return false;
}
img = QImage(pgfImg.Width(), pgfImg.Height(), QImage::Format_ARGB32);
pgfImg.Read();
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF image is read";
if (QSysInfo::ByteOrder == QSysInfo::BigEndian)
{
int map[] = {3, 2, 1, 0};
pgfImg.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map);
}
else
{
int map[] = {0, 1, 2, 3};
pgfImg.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map);
}
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF image is decoded";
}
catch (IOException& e)
{
int err = e.error;
if (err >= AppError)
{
err -= AppError;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error running libpgf (" << err << ")!";
return false;
}
return true;
}
bool writePGFImageFile(const QImage& image,
const QString& filePath,
int quality,
bool verbose)
{
#ifdef Q_OS_WIN32
#ifdef UNICODE
HANDLE fd = CreateFile((LPCWSTR)(QFile::encodeName(filePath).constData()), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#else
HANDLE fd = CreateFile(QFile::encodeName(filePath).constData(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#endif
if (fd == INVALID_HANDLE_VALUE)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error: Could not open destination file.";
return false;
}
#elif defined(__POSIX__)
int fd = QT_OPEN(QFile::encodeName(filePath).constData(),
O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
if (fd == -1)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error: Could not open destination file.";
return false;
}
#endif
CPGFFileStream stream(fd);
UINT32 nWrittenBytes = 0;
bool ret = writePGFImageDataToStream(image, stream, quality, nWrittenBytes, verbose);
if (!nWrittenBytes)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Written PGF file : data size is null";
ret = false;
}
else
{
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: file size written : " << nWrittenBytes;
}
#ifdef Q_OS_WIN32
CloseHandle(fd);
#else
close(fd);
#endif
return ret;
}
bool writePGFImageData(const QImage& image, QByteArray& data, int quality, bool verbose)
{
try
{
// We will use uncompressed image bytes size to allocate PGF stream in memory. In all case, due to PGF compression ratio,
// PGF data will be so far lesser than image raw size.
int rawSize = image.byteCount();
CPGFMemoryStream stream(rawSize);
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF stream memory allocation in bytes: " << rawSize;
UINT32 nWrittenBytes = 0;
bool ret = writePGFImageDataToStream(image, stream, quality, nWrittenBytes, verbose);
int pgfsize =
#ifdef PGFCodecVersionID
# if PGFCodecVersionID == 0x061224
// Wrap around libpgf 6.12.24 about CPGFMemoryStream bytes size generated to make PGF file data.
// It miss 16 bytes at end. This solution fix the problem. Problem have been fixed in 6.12.27.
nWrittenBytes + 16;
# else
nWrittenBytes;
# endif
#else
nWrittenBytes;
#endif
data = QByteArray((const char*)stream.GetBuffer(), pgfsize);
if (!pgfsize)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Encoded PGF image : data size is null";
ret = false;
}
else
{
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: data size written : " << pgfsize;
}
return ret;
}
catch (IOException& e)
{
int err = e.error;
if (err >= AppError)
{
err -= AppError;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error running libpgf (" << err << ")!";
return false;
}
}
bool writePGFImageDataToStream(const QImage& image,
CPGFStream& stream,
int quality,
UINT32& nWrittenBytes,
bool verbose)
{
try
{
if (image.isNull())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Thumb image is null";
return false;
}
QImage img;
// Convert image with Alpha channel.
if (image.format() != QImage::Format_ARGB32)
{
img = image.convertToFormat(QImage::Format_ARGB32);
if (verbose)
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: RGB => ARGB";
}
else
{
img = image;
}
CPGFImage pgfImg;
PGFHeader header;
header.width = img.width();
header.height = img.height();
header.nLevels = 0; // Auto.
header.quality = quality;
header.bpp = img.depth();
header.channels = 4;
header.mode = ImageModeRGBA;
header.usedBitsPerChannel = 0; // Auto
#ifdef PGFCodecVersionID
# if PGFCodecVersionID < 0x061142
header.background.rgbtBlue = 0;
header.background.rgbtGreen = 0;
header.background.rgbtRed = 0;
# endif
#endif
pgfImg.SetHeader(header);
- // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properlly with libppgf 6.11.24
+ // NOTE: see bug #273765 : Loading PGF thumbs with OpenMP support through a separated thread do not work properly with libppgf 6.11.24
pgfImg.ConfigureEncoder(false);
if (verbose)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF image settings:";
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: width: " << header.width;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: height: " << header.height;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: nLevels: " << header.nLevels;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: quality: " << header.quality;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: bpp: " << header.bpp;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: channels: " << header.channels;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: mode: " << header.mode;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: usedBitsPerChannel: " << header.usedBitsPerChannel;
}
if (QSysInfo::ByteOrder == QSysInfo::BigEndian)
{
int map[] = {3, 2, 1, 0};
pgfImg.ImportBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map);
}
else
{
int map[] = {0, 1, 2, 3};
pgfImg.ImportBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map);
}
nWrittenBytes = 0;
#ifdef PGFCodecVersionID
# if PGFCodecVersionID >= 0x061124
pgfImg.Write(&stream, &nWrittenBytes);
# else
pgfImg.Write(&stream, 0, 0, &nWrittenBytes);
# endif
#else
pgfImg.Write(&stream, 0, 0, &nWrittenBytes);
#endif
}
catch (IOException& e)
{
int err = e.error;
if (err >= AppError)
{
err -= AppError;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error running libpgf (" << err << ")!";
return false;
}
return true;
}
bool loadPGFScaled(QImage& img, const QString& path, int maximumSize)
{
FILE* const file = fopen(QFile::encodeName(path).constData(), "rb");
if (!file)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error: Could not open source file.";
return false;
}
unsigned char header[3];
if (fread(&header, 3, 1, file) != 1)
{
fclose(file);
return false;
}
unsigned char pgfID[3] = { 0x50, 0x47, 0x46 };
if (memcmp(&header[0], &pgfID, 3) != 0)
{
// not a PGF file
fclose(file);
return false;
}
fclose(file);
// -------------------------------------------------------------------
// Initialize PGF API.
#ifdef Q_OS_WIN32
#ifdef UNICODE
HANDLE fd = CreateFile((LPCWSTR)(QFile::encodeName(path).constData()), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#else
HANDLE fd = CreateFile(QFile::encodeName(path).constData(), GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0);
#endif
if (fd == INVALID_HANDLE_VALUE)
{
return false;
}
#else
int fd = QT_OPEN(QFile::encodeName(path).constData(), O_RDONLY);
if (fd == -1)
{
return false;
}
#endif
try
{
CPGFFileStream stream(fd);
CPGFImage pgf;
pgf.Open(&stream);
// Try to find the right PGF level to get reduced image accordingly
// with preview size wanted.
int i = 0;
if (pgf.Levels() > 0)
{
for (i = pgf.Levels()-1 ; i >= 0 ; --i)
{
if (qMin((int)pgf.Width(i), (int)pgf.Height(i)) >= maximumSize)
{
break;
}
}
}
if (i < 0)
{
i = 0;
}
pgf.Read(i); // Read PGF image at reduced level i.
img = QImage(pgf.Width(i), pgf.Height(i), QImage::Format_RGB32);
/*
const PGFHeader* header = pgf.GetHeader();
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF width = " << header->width;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF height = " << header->height;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF bbp = " << header->bpp;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF channels = " << header->channels;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF quality = " << header->quality;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF mode = " << header->mode;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: PGF levels = " << header->nLevels;
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Level (w x h)= " << i << "(" << pgf.Width(i)
<< " x " << pgf.Height(i) << ")";
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: QImage depth = " << img.depth();
*/
if (QSysInfo::ByteOrder == QSysInfo::BigEndian)
{
int map[] = {3, 2, 1, 0};
pgf.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map);
}
else
{
int map[] = {0, 1, 2, 3};
pgf.GetBitmap(img.bytesPerLine(), (UINT8*)img.bits(), img.depth(), map);
}
}
catch (IOException& e)
{
int err = e.error;
if (err >= AppError)
{
err -= AppError;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "PGFUtils: Error running libpgf (" << err << ")!";
return false;
}
return true;
}
QString libPGFVersion()
{
return (QLatin1String(PGFCodecVersion));
}
} // namespace PGFUtils
} // namespace Digikam
diff --git a/core/libs/progressmanager/progressview.cpp b/core/libs/progressmanager/progressview.cpp
index 5e7cd43091..0d50b3d730 100644
--- a/core/libs/progressmanager/progressview.cpp
+++ b/core/libs/progressmanager/progressview.cpp
@@ -1,543 +1,543 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-01-13
* Description : progress manager
*
* Copyright (C) 2007-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2004 by Till Adam <adam at kde dot org>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "progressview.h"
// Qt includes
#include <QApplication>
#include <QCloseEvent>
#include <QEvent>
#include <QFrame>
#include <QLabel>
#include <QLayout>
#include <QObject>
#include <QProgressBar>
#include <QPushButton>
#include <QScrollBar>
#include <QTimer>
#include <QToolButton>
#include <QMap>
#include <QPixmap>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "progressmanager.h"
namespace Digikam
{
class TransactionItem;
TransactionItemView::TransactionItemView(QWidget* const parent, const QString& name)
: QScrollArea(parent)
{
setObjectName(name);
setFrameStyle(NoFrame);
m_bigBox = new DVBox(this);
setWidget(m_bigBox);
setWidgetResizable(true);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
}
TransactionItem* TransactionItemView::addTransactionItem(ProgressItem* const item, bool first)
{
TransactionItem* const ti = new TransactionItem(m_bigBox, item, first);
m_bigBox->layout()->addWidget(ti);
resize(m_bigBox->width(), m_bigBox->height());
return ti;
}
void TransactionItemView::resizeEvent(QResizeEvent* event)
{
// Tell the layout in the parent (progressview) that our size changed
updateGeometry();
QSize sz = parentWidget()->sizeHint();
int currentWidth = parentWidget()->width();
// Don't resize to sz.width() every time when it only reduces a little bit
if ( currentWidth < sz.width() || currentWidth > sz.width() + 100 )
{
currentWidth = sz.width();
}
parentWidget()->resize( currentWidth, sz.height() );
QScrollArea::resizeEvent( event );
}
QSize TransactionItemView::sizeHint() const
{
return minimumSizeHint();
}
QSize TransactionItemView::minimumSizeHint() const
{
int f = 2 * frameWidth();
// Make room for a vertical scrollbar in all cases, to avoid a horizontal one
int vsbExt = verticalScrollBar()->sizeHint().width();
int minw = topLevelWidget()->width() / 3;
int maxh = topLevelWidget()->height() / 2;
QSize sz( m_bigBox->minimumSizeHint() );
sz.setWidth( qMax( sz.width(), minw ) + f + vsbExt );
sz.setHeight( qMin( sz.height(), maxh ) + f );
return sz;
}
void TransactionItemView::slotLayoutFirstItem()
{
// This slot is called whenever a TransactionItem is deleted, so this is a
// good place to call updateGeometry(), so our parent takes the new size
// into account and resizes.
updateGeometry();
/*
The below relies on some details in Qt's behaviour regarding deleting
objects. This slot is called from the destroyed signal of an item just
- going away. That item is at that point still in the list of chilren, but
+ going away. That item is at that point still in the list of children, but
since the vtable is already gone, it will have type QObject. The first
one with both the right name and the right class therefor is what will
be the first item very shortly. That's the one we want to remove the
hline for.
*/
TransactionItem* const ti = m_bigBox->findChild<TransactionItem*>(QLatin1String("TransactionItem"));
if (ti)
{
ti->hideHLine();
}
else
{
emit signalTransactionViewIsEmpty();
}
}
// ----------------------------------------------------------------------------
class Q_DECL_HIDDEN TransactionItem::Private
{
public:
explicit Private()
: maxLabelWidth(650),
progress(0),
cancelButton(0),
itemLabel(0),
itemStatus(0),
itemThumb(0),
frame(0),
item(0)
{
}
const int maxLabelWidth;
QProgressBar* progress;
QPushButton* cancelButton;
QLabel* itemLabel;
QLabel* itemStatus;
QLabel* itemThumb;
QFrame* frame;
ProgressItem* item;
};
TransactionItem::TransactionItem(QWidget* const parent, ProgressItem* const item, bool first)
: DVBox(parent),
d(new Private)
{
d->item = item;
setSpacing(2);
setContentsMargins(2, 2, 2, 2);
setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
d->frame = new QFrame(this);
d->frame->setFrameShape(QFrame::HLine);
d->frame->setFrameShadow(QFrame::Raised);
d->frame->show();
setStretchFactor(d->frame, 3);
layout()->addWidget(d->frame);
DHBox* h = new DHBox(this);
h->setSpacing(5);
layout()->addWidget(h);
if (item->hasThumbnail())
{
d->itemThumb = new QLabel(h);
d->itemThumb->setFixedSize(QSize(22, 22));
h->layout()->addWidget(d->itemThumb);
h->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
}
d->itemLabel = new QLabel(fontMetrics().elidedText(item->label(), Qt::ElideRight, d->maxLabelWidth), h);
h->layout()->addWidget(d->itemLabel);
h->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
d->progress = new QProgressBar(h);
d->progress->setMaximum(100);
d->progress->setValue(item->progress());
h->layout()->addWidget(d->progress);
if (item->canBeCanceled())
{
d->cancelButton = new QPushButton(QIcon::fromTheme(QLatin1String("dialog-cancel")), QString(), h);
d->cancelButton->setToolTip( i18n("Cancel this operation."));
connect(d->cancelButton, SIGNAL(clicked()),
this, SLOT(slotItemCanceled()));
h->layout()->addWidget(d->cancelButton);
}
h = new DHBox(this);
h->setSpacing(5);
h->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed));
layout()->addWidget(h);
d->itemStatus = new QLabel(h);
d->itemStatus->setTextFormat(Qt::RichText);
d->itemStatus->setText(fontMetrics().elidedText(item->status(), Qt::ElideRight, d->maxLabelWidth));
h->layout()->addWidget(d->itemStatus);
if (first)
{
hideHLine();
}
}
TransactionItem::~TransactionItem()
{
delete d;
}
ProgressItem* TransactionItem::item() const
{
return d->item;
}
void TransactionItem::hideHLine()
{
d->frame->hide();
}
void TransactionItem::setProgress(int progress)
{
d->progress->setValue(progress);
}
void TransactionItem::setItemComplete()
{
d->item = 0;
}
void TransactionItem::setLabel(const QString& label)
{
d->itemLabel->setText(fontMetrics().elidedText(label, Qt::ElideRight, d->maxLabelWidth));
}
void TransactionItem::setThumbnail(const QPixmap& thumb)
{
d->itemThumb->setPixmap(thumb);
}
void TransactionItem::setStatus(const QString& status)
{
d->itemStatus->setText(fontMetrics().elidedText(status, Qt::ElideRight, d->maxLabelWidth));
}
void TransactionItem::setTotalSteps(int totalSteps)
{
d->progress->setMaximum(totalSteps);
}
void TransactionItem::slotItemCanceled()
{
if ( d->item )
{
d->item->cancel();
}
}
void TransactionItem::addSubTransaction(ProgressItem* const item)
{
Q_UNUSED(item);
}
// ---------------------------------------------------------------------------
class Q_DECL_HIDDEN ProgressView::Private
{
public:
explicit Private()
: wasLastShown(false),
scrollView(0),
previousItem(0)
{
}
bool wasLastShown;
TransactionItemView* scrollView;
TransactionItem* previousItem;
QMap<const ProgressItem*, TransactionItem*> transactionsToListviewItems;
};
ProgressView::ProgressView(QWidget* const alignWidget, QWidget* const parent, const QString& name)
: OverlayWidget(alignWidget, parent, name),
d(new Private)
{
setFrameStyle(QFrame::Panel | QFrame::Sunken);
setAutoFillBackground(true);
d->scrollView = new TransactionItemView(this, QLatin1String("ProgressScrollView"));
layout()->addWidget( d->scrollView );
// No more close button for now, since there is no more autoshow
/*
QVBox* const rightBox = new QVBox( this );
QToolButton* const pbClose = new QToolButton( rightBox );
pbClose->setAutoRaise(true);
pbClose->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
pbClose->setFixedSize( 16, 16 );
pbClose->setIcon( KIconLoader::global()->loadIconSet( "window-close", KIconLoader::Small, 14 ) );
pbClose->setToolTip( i18n( "Hide detailed progress window" ) );
connect(pbClose, SIGNAL(clicked()), this, SLOT(slotClose()));
QWidget* const spacer = new QWidget( rightBox ); // don't let the close button take up all the height
rightBox->setStretchFactor( spacer, 100 );
*/
/*
* Get the singleton ProgressManager item which will inform us of
* appearing and vanishing items.
*/
ProgressManager* const pm = ProgressManager::instance();
connect(pm, SIGNAL(progressItemAdded(ProgressItem*)),
this, SLOT(slotTransactionAdded(ProgressItem*)));
connect(pm, SIGNAL(progressItemCompleted(ProgressItem*)),
this, SLOT(slotTransactionCompleted(ProgressItem*)));
connect(pm, SIGNAL(progressItemProgress(ProgressItem*,uint)),
this, SLOT(slotTransactionProgress(ProgressItem*,uint)));
connect(pm, SIGNAL(progressItemStatus(ProgressItem*,QString)),
this, SLOT(slotTransactionStatus(ProgressItem*,QString)));
connect(pm, SIGNAL(progressItemLabel(ProgressItem*,QString)),
this, SLOT(slotTransactionLabel(ProgressItem*,QString)));
connect(pm, SIGNAL(progressItemUsesBusyIndicator(ProgressItem*,bool)),
this, SLOT(slotTransactionUsesBusyIndicator(ProgressItem*,bool)));
connect(pm, SIGNAL(progressItemThumbnail(ProgressItem*,QPixmap)),
this, SLOT(slotTransactionThumbnail(ProgressItem*,QPixmap)));
connect(pm, SIGNAL(showProgressView()),
this, SLOT(slotShow()));
connect(d->scrollView, SIGNAL(signalTransactionViewIsEmpty()),
pm, SLOT(slotTransactionViewIsEmpty()));
}
ProgressView::~ProgressView()
{
// NOTE: no need to delete child widgets.
delete d;
}
void ProgressView::closeEvent(QCloseEvent* e)
{
e->accept();
hide();
}
void ProgressView::slotTransactionAdded(ProgressItem* item)
{
TransactionItem* parent = 0;
if ( item->parent() )
{
if ( d->transactionsToListviewItems.contains( item->parent() ) )
{
parent = d->transactionsToListviewItems[ item->parent() ];
parent->addSubTransaction( item );
}
}
else
{
const bool first = d->transactionsToListviewItems.isEmpty();
TransactionItem* const ti = d->scrollView->addTransactionItem( item, first );
if ( ti )
{
d->transactionsToListviewItems.insert( item, ti );
}
if (item->showAtStart())
{
// Force to show progress view for 5 seconds to inform user about new process add in queue.
QTimer::singleShot( 1000, this, SLOT(slotShow()) );
QTimer::singleShot( 6000, this, SLOT(slotClose()) );
return;
}
if (first && d->wasLastShown)
{
QTimer::singleShot( 1000, this, SLOT(slotShow()) );
}
}
}
void ProgressView::slotTransactionCompleted(ProgressItem* item)
{
if ( d->transactionsToListviewItems.contains( item ) )
{
TransactionItem* const ti = d->transactionsToListviewItems[item];
d->transactionsToListviewItems.remove( item );
ti->setItemComplete();
QTimer::singleShot( 3000, ti, SLOT(deleteLater()) );
// see the slot for comments as to why that works
connect ( ti, SIGNAL(destroyed()),
d->scrollView, SLOT(slotLayoutFirstItem()) );
}
// This was the last item, hide.
if ( d->transactionsToListviewItems.isEmpty() )
{
QTimer::singleShot( 3000, this, SLOT(slotHide()) );
}
}
void ProgressView::slotTransactionCanceled(ProgressItem*)
{
}
void ProgressView::slotTransactionProgress(ProgressItem* item, unsigned int progress)
{
if (d->transactionsToListviewItems.contains(item))
{
TransactionItem* const ti = d->transactionsToListviewItems[item];
ti->setProgress(progress);
}
}
void ProgressView::slotTransactionStatus(ProgressItem* item, const QString& status)
{
if (d->transactionsToListviewItems.contains(item))
{
TransactionItem* const ti = d->transactionsToListviewItems[item];
ti->setStatus(status);
}
}
void ProgressView::slotTransactionLabel(ProgressItem* item, const QString& label )
{
if ( d->transactionsToListviewItems.contains(item))
{
TransactionItem* const ti = d->transactionsToListviewItems[item];
ti->setLabel(label);
}
}
void ProgressView::slotTransactionUsesBusyIndicator(ProgressItem* item, bool value)
{
if (d->transactionsToListviewItems.contains(item))
{
TransactionItem* const ti = d->transactionsToListviewItems[item];
if (value)
{
ti->setTotalSteps(0);
}
else
{
ti->setTotalSteps(100);
}
}
}
void ProgressView::slotTransactionThumbnail(ProgressItem* item, const QPixmap& thumb)
{
if (d->transactionsToListviewItems.contains(item))
{
TransactionItem* const ti = d->transactionsToListviewItems[item];
ti->setThumbnail(thumb);
}
}
void ProgressView::slotShow()
{
setVisible(true);
}
void ProgressView::slotHide()
{
// check if a new item showed up since we started the timer. If not, hide
if ( d->transactionsToListviewItems.isEmpty() )
{
setVisible(false);
}
}
void ProgressView::slotClose()
{
d->wasLastShown = false;
setVisible(false);
}
void ProgressView::setVisible(bool b)
{
OverlayWidget::setVisible(b);
emit visibilityChanged(b);
}
void ProgressView::slotToggleVisibility()
{
/* Since we are only hiding with a timeout, there is a short period of
* time where the last item is still visible, but clicking on it in
* the statusbarwidget should not display the dialog, because there
* are no items to be shown anymore. Guard against that.
*/
d->wasLastShown = isHidden();
if ( !isHidden() || !d->transactionsToListviewItems.isEmpty() )
{
setVisible( isHidden() );
}
}
} // namespace Digikam
diff --git a/core/libs/rawengine/drawdecoder.h b/core/libs/rawengine/drawdecoder.h
index 6764d3912a..ef3382cf76 100644
--- a/core/libs/rawengine/drawdecoder.h
+++ b/core/libs/rawengine/drawdecoder.h
@@ -1,244 +1,244 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-12-09
* Description : a tread-safe libraw C++ program interface
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2007-2008 by Guillaume Castagnino <casta at xwing dot info>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DRAW_DECODER_H
#define DIGIKAM_DRAW_DECODER_H
// C++ includes
#include <cmath>
// Qt includes
#include <QBuffer>
#include <QString>
#include <QObject>
#include <QImage>
// Local includes
#include "drawdecodersettings.h"
#include "rawinfo.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT DRawDecoder : public QObject
{
Q_OBJECT
public:
/** Standard constructor.
*/
explicit DRawDecoder();
/** Standard destructor.
*/
virtual ~DRawDecoder();
public:
/** Get the preview of RAW picture as a QImage.
It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview().
*/
static bool loadRawPreview(QImage& image, const QString& path);
/** Get the preview of RAW picture as a QByteArray holding JPEG data.
It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview().
*/
static bool loadRawPreview(QByteArray& imgData, const QString& path);
/** Get the preview of RAW picture passed in QBuffer as a QByteArray holding JPEG data.
It tries loadEmbeddedPreview() first and if it fails, calls loadHalfPreview().
*/
static bool loadRawPreview(QByteArray& imgData, const QBuffer& inBuffer);
/** Get the embedded JPEG preview image from RAW picture as a QByteArray which will include Exif Data.
This is fast and non cancelable. This method does not require a class instance to run.
*/
static bool loadEmbeddedPreview(QByteArray& imgData, const QString& path);
/** Get the embedded JPEG preview image from RAW picture as a QImage. This is fast and non cancelable
This method does not require a class instance to run.
*/
static bool loadEmbeddedPreview(QImage& image, const QString& path);
/** Get the embedded JPEG preview image from RAW image passed in QBuffer as a QByteArray which will include Exif Data.
This is fast and non cancelable. This method does not require a class instance to run.
*/
static bool loadEmbeddedPreview(QByteArray& imgData, const QBuffer& inBuffer);
/** Get the half decoded RAW picture. This is slower than loadEmbeddedPreview() method
and non cancelable. This method does not require a class instance to run.
*/
static bool loadHalfPreview(QImage& image, const QString& path);
/** Get the half decoded RAW picture as JPEG data in QByteArray. This is slower than loadEmbeddedPreview()
method and non cancelable. This method does not require a class instance to run.
*/
static bool loadHalfPreview(QByteArray& imgData, const QString& path);
/** Get the half decoded RAW picture passed in QBuffer as JPEG data in QByteArray. This is slower than loadEmbeddedPreview()
method and non cancelable. This method does not require a class instance to run.
*/
static bool loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer);
/** Get the full decoded RAW picture. This is a more slower than loadHalfPreview() method
and non cancelable. This method does not require a class instance to run.
*/
static bool loadFullImage(QImage& image, const QString& path, const DRawDecoderSettings& settings = DRawDecoderSettings());
- /** Get the camera settings witch have taken RAW file. Look into rawinfo.h
- for more details. This is a fast and non cancelable method witch do not require
+ /** Get the camera settings which have taken RAW file. Look into rawinfo.h
+ for more details. This is a fast and non cancelable method which do not require
a class instance to run.
*/
static bool rawFileIdentify(RawInfo& identify, const QString& path);
/** Return the string of all RAW file type mime supported.
*/
static const char* rawFiles();
/** Return the list of all RAW file type mime supported,
as a QStringList, without wildcard and suffix dot.
*/
static QStringList rawFilesList();
/** Returns a version number for the list of supported RAW file types.
This version is incremented if the list of supported formats has changed
between library releases.
*/
static int rawFilesVersion();
/** Provide a list of supported RAW Camera name.
*/
static QStringList supportedCamera();
/** Return LibRaw version string.
*/
static QString librawVersion();
/** Return true or false if LibRaw use parallel demosaicing or not (libgomp support).
* Return -1 if undefined.
*/
static int librawUseGomp();
static bool isRawFile(const QUrl& url);
public:
/** Extract Raw image data undemosaiced and without post processing from 'filePath' picture file.
This is a cancelable method which require a class instance to run because RAW pictures loading
can take a while.
This method return:
- A byte array container 'rawData' with raw data.
- All info about Raw image into 'identify' container.
- - 'false' is returned if loadding failed, else 'true'.
+ - 'false' is returned if loading failed, else 'true'.
*/
bool extractRAWData(const QString& filePath, QByteArray& rawData, RawInfo& identify, unsigned int shotSelect=0);
/** Extract a small size of decode RAW data from 'filePath' picture file using
'DRawDecoderSettings' settings. This is a cancelable method which require
a class instance to run because RAW pictures decoding can take a while.
This method return:
- A byte array container 'imageData' with picture data. Pixels order is RGB.
Color depth can be 8 or 16. In 8 bits you can access to color component
using (uchar*), in 16 bits using (ushort*).
- Size size of image in number of pixels ('width' and 'height').
- The max average of RGB components from decoded picture.
- 'false' is returned if decoding failed, else 'true'.
*/
bool decodeHalfRAWImage(const QString& filePath, const DRawDecoderSettings& DRawDecoderSettings,
QByteArray& imageData, int& width, int& height, int& rgbmax);
/** Extract a full size of RAW data from 'filePath' picture file using
'DRawDecoderSettings' settings. This is a cancelable method which require
a class instance to run because RAW pictures decoding can take a while.
This method return:
- A byte array container 'imageData' with picture data. Pixels order is RGB.
Color depth can be 8 or 16. In 8 bits you can access to color component
using (uchar*), in 16 bits using (ushort*).
- Size size of image in number of pixels ('width' and 'height').
- The max average of RGB components from decoded picture.
- 'false' is returned if decoding failed, else 'true'.
*/
bool decodeRAWImage(const QString& filePath, const DRawDecoderSettings& DRawDecoderSettings,
QByteArray& imageData, int& width, int& height, int& rgbmax);
/** To cancel 'decodeHalfRAWImage' and 'decodeRAWImage' methods running
in a separate thread.
*/
void cancel();
protected:
/** Used internally to cancel RAW decoding operation. Normally, you don't need to use it
directly, excepted if you derivated this class. Usual way is to use cancel() method
*/
bool m_cancel;
/** The settings container used to perform RAW pictures decoding. See 'rawdecodingsetting.h'
for details.
*/
DRawDecoderSettings m_decoderSettings;
protected:
- /** Re-implement this method to control the cancelisation of loop witch wait data
- from RAW decoding process with your propers envirronement.
+ /** Re-implement this method to control the cancelisation of loop which wait data
+ from RAW decoding process with your proper environment.
By default, this method check if m_cancel is true.
*/
virtual bool checkToCancelWaitingData();
/** Re-implement this method to control the pseudo progress value during RAW decoding (when dcraw run with an
internal loop without feedback) with your proper environment. By default, this method does nothing.
Progress value average for this stage is 0%-n%, with 'n' == 40% max (see setWaitingDataProgress() method).
*/
virtual void setWaitingDataProgress(double value);
public:
// Declared public to be called externally by callbackForLibRaw() static method.
class Private;
private:
Private* const d;
friend class Private;
};
} // namespace Digikam
#endif // DIGIKAM_DRAW_DECODER_H
diff --git a/core/libs/rawengine/drawdecodersettings.h b/core/libs/rawengine/drawdecodersettings.h
index 9469f3fe9b..8f8e83aa27 100644
--- a/core/libs/rawengine/drawdecodersettings.h
+++ b/core/libs/rawengine/drawdecodersettings.h
@@ -1,309 +1,309 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-12-09
* Description : Raw decoding settings
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2007-2008 by Guillaume Castagnino <casta at xwing dot info>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DRAW_DECODER_SETTINGS_H
#define DIGIKAM_DRAW_DECODER_SETTINGS_H
// Qt includes
#include <QRect>
#include <QString>
#include <QDebug>
// Local includes
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT DRawDecoderSettings
{
public:
/** RAW decoding Interpolation methods
*
* NOTE: from original dcraw demosaic
*
* Bilinear: use high-speed but low-quality bilinear
* interpolation (default - for slow computer). In this method,
* the red value of a non-red pixel is computed as the average of
* the adjacent red pixels, and similar for blue and green.
* VNG: use Variable Number of Gradients interpolation.
* This method computes gradients near the pixel of interest and uses
* the lower gradients (representing smoother and more similar parts
* of the image) to make an estimate.
* PPG: use Patterned Pixel Grouping interpolation.
* Pixel Grouping uses assumptions about natural scenery in making estimates.
* It has fewer color artifacts on natural images than the Variable Number of
* Gradients method.
* AHD: use Adaptive Homogeneity-Directed interpolation.
* This method selects the direction of interpolation so as to
* maximize a homogeneity metric, thus typically minimizing color artifacts.
* DCB: DCB interpolation (see http://www.linuxphoto.org/html/dcb.html for details)
* DHT: DHT interpolation.
- * AAHD: Enhanced Adaptative AHD interpolation.
+ * AAHD: Enhanced Adaptive AHD interpolation.
*/
enum DecodingQuality
{
BILINEAR = 0,
VNG = 1,
PPG = 2,
AHD = 3,
DCB = 4,
DHT = 11,
AAHD = 12
};
/** White balances alternatives
* NONE: no white balance used : reverts to standard daylight D65 WB.
* CAMERA: Use the camera embedded WB if available. Reverts to NONE if not.
* AUTO: Averages an auto WB on the entire image.
* CUSTOM: Let use set it's own temperature and green factor (later converted to RGBG factors).
- * AERA: Let use an aera from image to average white balance (see whiteBalanceArea for details).
+ * AERA: Let use an area from image to average white balance (see whiteBalanceArea for details).
*/
enum WhiteBalance
{
NONE = 0,
CAMERA = 1,
AUTO = 2,
CUSTOM = 3,
AERA = 4
};
/** Noise Reduction method to apply before demosaicing
* NONR: No noise reduction.
* WAVELETSNR: wavelets correction to erase noise while preserving real detail. It's applied after interpolation.
* FBDDNR: Fake Before Demosaicing Denoising noise reduction. It's applied before interpolation.
*/
enum NoiseReduction
{
NONR = 0,
WAVELETSNR,
FBDDNR
};
/** Input color profile used to decoded image
* NOINPUTCS: No input color profile.
* EMBEDDED: Use the camera profile embedded in RAW file if exist.
* CUSTOMINPUTCS: Use a custom input color space profile.
*/
enum InputColorSpace
{
NOINPUTCS = 0,
EMBEDDED,
CUSTOMINPUTCS
};
/** Output RGB color space used to decoded image
* RAWCOLOR: No output color profile (Linear RAW).
* SRGB: Use standard sRGB color space.
* ADOBERGB: Use standard Adobe RGB color space.
* WIDEGAMMUT: Use standard RGB Wide Gamut color space.
* PROPHOTO: Use standard RGB Pro Photo color space.
* CUSTOMOUTPUTCS: Use a custom workspace color profile.
*/
enum OutputColorSpace
{
RAWCOLOR = 0,
SRGB,
ADOBERGB,
WIDEGAMMUT,
PROPHOTO,
CUSTOMOUTPUTCS
};
/** Standard constructor with default settings
*/
DRawDecoderSettings();
/** Equivalent to the copy constructor
*/
DRawDecoderSettings& operator=(const DRawDecoderSettings& prm);
/** Compare for equality
*/
bool operator==(const DRawDecoderSettings& o) const;
/** Standard destructor
*/
virtual ~DRawDecoderSettings();
- /** Method to use a settings to optimize time loading, for exemple to compute image histogram
+ /** Method to use a settings to optimize time loading, for example to compute image histogram
*/
void optimizeTimeLoading();
public:
/** If true, images with overblown channels are processed much more accurate,
- * without 'pink clouds' (and blue highlights under tungsteen lamps).
+ * without 'pink clouds' (and blue highlights under tungsten lamps).
*/
bool fixColorsHighlights;
/** If false, use a fixed white level, ignoring the image histogram.
*/
bool autoBrightness;
/** Turn on RAW file decoding in 16 bits per color per pixel instead 8 bits.
*/
bool sixteenBitsImage;
/** Half-size color image decoding (twice as fast as "enableRAWQuality").
* Turn on this option to reduce time loading to render histogram for example,
* no to render an image to screen.
*/
bool halfSizeColorImage;
/** White balance type to use. See WhiteBalance values for detail
*/
WhiteBalance whiteBalance;
/** The temperature and the green multiplier of the custom white balance
*/
int customWhiteBalance;
double customWhiteBalanceGreen;
/** Turn on RAW file decoding using RGB interpolation as four colors.
*/
bool RGBInterpolate4Colors;
/** For cameras with non-square pixels, do not stretch the image to its
* correct aspect ratio. In any case, this option guarantees that each
* output pixel corresponds to one RAW pixel.
*/
bool DontStretchPixels;
/** Unclip Highlight color level:
* 0 = Clip all highlights to solid white.
* 1 = Leave highlights unclipped in various shades of pink.
* 2 = Blend clipped and unclipped values together for a gradual
* fade to white.
* 3-9 = Reconstruct highlights. Low numbers favor whites; high numbers
* favor colors.
*/
int unclipColors;
/** RAW quality decoding factor value. See DecodingQuality values
* for details.
*/
DecodingQuality RAWQuality;
/** After interpolation, clean up color artifacts by repeatedly applying
* a 3x3 median filter to the R-G and B-G channels.
*/
int medianFilterPasses;
/** Noise reduction method to apply before demosaicing.
*/
NoiseReduction NRType;
/** Noise reduction threshold value. Null value disable NR. Range is between 100 and 1000.
* For IMPULSENR : set the amount of Luminance impulse denoise.
*/
int NRThreshold;
/** Brightness of output image.
*/
double brightness;
/** Turn on the black point setting to decode RAW image.
*/
bool enableBlackPoint;
/** Black Point value of output image.
*/
int blackPoint;
/** Turn on the white point setting to decode RAW image.
*/
bool enableWhitePoint;
/** White Point value of output image.
*/
int whitePoint;
/** The input color profile used to decoded RAW data. See OutputColorProfile
* values for details.
*/
InputColorSpace inputColorSpace;
/** Path to custom input ICC profile to define the camera's raw colorspace.
*/
QString inputProfile;
/** The output color profile used to decoded RAW data. See OutputColorProfile
* values for details.
*/
OutputColorSpace outputColorSpace;
/** Path to custom output ICC profile to define the color workspace.
*/
QString outputProfile;
/** Path to text file including dead pixel list.
*/
QString deadPixelMap;
/** Rectangle used to calculate the white balance by averaging the region of image.
*/
QRect whiteBalanceArea;
//-- Extended demosaicing settings ----------------------------------------------------------
/// For DCB interpolation.
/** Number of DCB median filtering correction passes.
* -1 : disable (default)
* 1-10 : DCB correction passes
*/
int dcbIterations;
/** Turn on the DCB interpolation with enhance interpolated colors.
*/
bool dcbEnhanceFl;
/** Turn on the Exposure Correction before interpolation.
*/
bool expoCorrection;
/** Shift of Exposure Correction before interpolation in linear scale.
* Usable range is from 0.25 (darken image 1 stop : -2EV) to 8.0 (lighten ~1.5 photographic stops : +3EV).
*/
double expoCorrectionShift;
/** Amount of highlight preservation for exposure correction before interpolation in E.V.
* Usable range is from 0.0 (linear exposure shift, highlights may blow) to 1.0 (maximum highlights preservation)
* This settings can only take effect if expoCorrectionShift > 1.0.
*/
double expoCorrectionHighlight;
};
//! qDebug() stream operator. Writes settings @a s to the debug output in a nicely formatted way.
QDebug operator<<(QDebug dbg, const DRawDecoderSettings& s);
} // namespace Digikam
#endif // DIGIKAM_DRAW_DECODER_SETTINGS_H
diff --git a/core/libs/rawengine/drawdecoderwidget.cpp b/core/libs/rawengine/drawdecoderwidget.cpp
index 74d62ea92e..1aa50eada6 100644
--- a/core/libs/rawengine/drawdecoderwidget.cpp
+++ b/core/libs/rawengine/drawdecoderwidget.cpp
@@ -1,1197 +1,1197 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* date : 2006-09-13
* Description : Raw Decoder settings widgets
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2007-2008 by Guillaume Castagnino <casta at xwing dot info>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#define OPTIONFIXCOLORSHIGHLIGHTSENTRY "FixColorsHighlights"
#define OPTIONDECODESIXTEENBITENTRY "SixteenBitsImage"
#define OPTIONWHITEBALANCEENTRY "White Balance"
#define OPTIONCUSTOMWHITEBALANCEENTRY "Custom White Balance"
#define OPTIONCUSTOMWBGREENENTRY "Custom White Balance Green"
#define OPTIONFOURCOLORRGBENTRY "Four Color RGB"
#define OPTIONUNCLIPCOLORSENTRY "Unclip Color"
#define OPTIONDONTSTRETCHPIXELSSENTRY "Dont Stretch Pixels" // krazy:exclude=spelling
#define OPTIONMEDIANFILTERPASSESENTRY "Median Filter Passes" // krazy:exclude=spelling
#define OPTIONNOISEREDUCTIONTYPEENTRY "Noise Reduction Type"
#define OPTIONNOISEREDUCTIONTHRESHOLDENTRY "Noise Reduction Threshold"
#define OPTIONAUTOBRIGHTNESSENTRY "AutoBrightness"
#define OPTIONDECODINGQUALITYENTRY "Decoding Quality"
#define OPTIONINPUTCOLORSPACEENTRY "Input Color Space"
#define OPTIONOUTPUTCOLORSPACEENTRY "Output Color Space"
#define OPTIONINPUTCOLORPROFILEENTRY "Input Color Profile"
#define OPTIONOUTPUTCOLORPROFILEENTRY "Output Color Profile"
#define OPTIONBRIGHTNESSMULTIPLIERENTRY "Brightness Multiplier"
#define OPTIONUSEBLACKPOINTENTRY "Use Black Point"
#define OPTIONBLACKPOINTENTRY "Black Point"
#define OPTIONUSEWHITEPOINTENTRY "Use White Point"
#define OPTIONWHITEPOINTENTRY "White Point"
//-- Extended demosaicing settings ----------------------------------------------------------
#define OPTIONDCBITERATIONSENTRY "Dcb Iterations"
#define OPTIONDCBENHANCEFLENTRY "Dcb Enhance Filter"
#define OPTIONEXPOCORRECTIONENTRY "Expo Correction"
#define OPTIONEXPOCORRECTIONSHIFTENTRY "Expo Correction Shift"
#define OPTIONEXPOCORRECTIONHIGHLIGHTENTRY "Expo Correction Highlight"
#include "drawdecoderwidget.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QCheckBox>
#include <QLabel>
#include <QWhatsThis>
#include <QGridLayout>
#include <QApplication>
#include <QStyle>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "drawdecoder.h"
#include "dnuminput.h"
#include "dcombobox.h"
#include "digikam_debug.h"
namespace Digikam
{
class Q_DECL_HIDDEN DRawDecoderWidget::Private
{
public:
explicit Private()
{
autoBrightnessBox = 0;
sixteenBitsImage = 0;
fourColorCheckBox = 0;
brightnessLabel = 0;
brightnessSpinBox = 0;
blackPointCheckBox = 0;
blackPointSpinBox = 0;
whitePointCheckBox = 0;
whitePointSpinBox = 0;
whiteBalanceComboBox = 0;
whiteBalanceLabel = 0;
customWhiteBalanceSpinBox = 0;
customWhiteBalanceLabel = 0;
customWhiteBalanceGreenSpinBox = 0;
customWhiteBalanceGreenLabel = 0;
unclipColorLabel = 0;
dontStretchPixelsCheckBox = 0;
RAWQualityComboBox = 0;
RAWQualityLabel = 0;
noiseReductionComboBox = 0;
NRSpinBox1 = 0;
NRLabel1 = 0;
unclipColorComboBox = 0;
reconstructLabel = 0;
reconstructSpinBox = 0;
outputColorSpaceLabel = 0;
outputColorSpaceComboBox = 0;
demosaicingSettings = 0;
whiteBalanceSettings = 0;
correctionsSettings = 0;
colormanSettings = 0;
medianFilterPassesSpinBox = 0;
medianFilterPassesLabel = 0;
inIccUrlEdit = 0;
outIccUrlEdit = 0;
inputColorSpaceLabel = 0;
inputColorSpaceComboBox = 0;
fixColorsHighlightsBox = 0;
refineInterpolationBox = 0;
noiseReductionLabel = 0;
expoCorrectionBox = 0;
expoCorrectionShiftSpinBox = 0;
expoCorrectionHighlightSpinBox = 0;
expoCorrectionShiftLabel = 0;
expoCorrectionHighlightLabel = 0;
}
/** Convert Exposure correction shift E.V value from GUI to Linear value needs by libraw decoder.
*/
double shiftExpoFromEvToLinear(double ev) const
{
// From GUI : -2.0EV => 0.25
// +3.0EV => 8.00
return (1.55*ev + 3.35);
}
/** Convert Exposure correction shift Linear value from liraw decoder to E.V value needs by GUI.
*/
double shiftExpoFromLinearToEv(double lin) const
{
// From GUI : 0.25 => -2.0EV
// 8.00 => +3.0EV
return ((lin-3.35) / 1.55);
}
public:
QWidget* demosaicingSettings;
QWidget* whiteBalanceSettings;
QWidget* correctionsSettings;
QWidget* colormanSettings;
QLabel* whiteBalanceLabel;
QLabel* customWhiteBalanceLabel;
QLabel* customWhiteBalanceGreenLabel;
QLabel* brightnessLabel;
QLabel* RAWQualityLabel;
QLabel* NRLabel1;
QLabel* unclipColorLabel;
QLabel* reconstructLabel;
QLabel* inputColorSpaceLabel;
QLabel* outputColorSpaceLabel;
QLabel* medianFilterPassesLabel;
QLabel* noiseReductionLabel;
QLabel* expoCorrectionShiftLabel;
QLabel* expoCorrectionHighlightLabel;
QCheckBox* blackPointCheckBox;
QCheckBox* whitePointCheckBox;
QCheckBox* sixteenBitsImage;
QCheckBox* autoBrightnessBox;
QCheckBox* fourColorCheckBox;
QCheckBox* dontStretchPixelsCheckBox;
QCheckBox* fixColorsHighlightsBox;
QCheckBox* refineInterpolationBox;
QCheckBox* expoCorrectionBox;
DFileSelector* inIccUrlEdit;
DFileSelector* outIccUrlEdit;
DComboBox* noiseReductionComboBox;
DComboBox* whiteBalanceComboBox;
DComboBox* RAWQualityComboBox;
DComboBox* unclipColorComboBox;
DComboBox* inputColorSpaceComboBox;
DComboBox* outputColorSpaceComboBox;
DIntNumInput* customWhiteBalanceSpinBox;
DIntNumInput* reconstructSpinBox;
DIntNumInput* blackPointSpinBox;
DIntNumInput* whitePointSpinBox;
DIntNumInput* NRSpinBox1;
DIntNumInput* medianFilterPassesSpinBox;
DDoubleNumInput* customWhiteBalanceGreenSpinBox;
DDoubleNumInput* brightnessSpinBox;
DDoubleNumInput* expoCorrectionShiftSpinBox;
DDoubleNumInput* expoCorrectionHighlightSpinBox;
};
DRawDecoderWidget::DRawDecoderWidget(QWidget* const parent, int advSettings)
: DExpanderBox(parent),
d(new Private)
{
setup(advSettings);
}
void DRawDecoderWidget::setup(int advSettings)
{
setObjectName( QLatin1String("DCRawSettings Expander" ));
// ---------------------------------------------------------------
// DEMOSAICING Settings panel
d->demosaicingSettings = new QWidget(this);
QGridLayout* const demosaicingLayout = new QGridLayout(d->demosaicingSettings);
int line = 0;
d->sixteenBitsImage = new QCheckBox(i18nc("@option:check", "16 bits color depth"), d->demosaicingSettings);
d->sixteenBitsImage->setWhatsThis(xi18nc("@info:whatsthis", "<para>If enabled, all RAW files will "
"be decoded in 16-bit color depth using a linear gamma curve. To "
"prevent dark picture rendering in the editor, it is recommended to "
"use Color Management in this mode.</para>"
"<para>If disabled, all RAW files will be decoded in 8-bit color "
"depth with a BT.709 gamma curve and a 99th-percentile white point. "
"This mode is faster than 16-bit decoding.</para>"));
demosaicingLayout->addWidget(d->sixteenBitsImage, 0, 0, 1, 2);
if (advSettings & SIXTEENBITS)
{
d->sixteenBitsImage->show();
line = 1;
}
else
{
d->sixteenBitsImage->hide();
}
d->fourColorCheckBox = new QCheckBox(i18nc("@option:check", "Interpolate RGB as four colors"), d->demosaicingSettings);
d->fourColorCheckBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Interpolate RGB as four "
"colors</title>"
"<para>The default is to assume that all green pixels are the same. "
"If even-row green pixels are more sensitive to ultraviolet light "
"than odd-row this difference causes a mesh pattern in the output; "
"using this option solves this problem with minimal loss of detail.</para>"
"<para>To resume, this option blurs the image a little, but it "
"eliminates false 2x2 mesh patterns with VNG quality method or "
"mazes with AHD quality method.</para>"));
demosaicingLayout->addWidget(d->fourColorCheckBox, line, 0, 1, line == 0 ? 2 : 3);
line++;
QLabel* const dcrawVersion = new QLabel(d->demosaicingSettings);
dcrawVersion->setAlignment(Qt::AlignRight);
dcrawVersion->setToolTip(i18nc("@info:tooltip", "Visit LibRaw project website"));
dcrawVersion->setOpenExternalLinks(true);
dcrawVersion->setTextFormat(Qt::RichText);
dcrawVersion->setTextInteractionFlags(Qt::LinksAccessibleByMouse);
dcrawVersion->setText(QString::fromLatin1("<a href=\"%1\">%2</a>")
.arg(QLatin1String("http://www.libraw.org"))
.arg(QString::fromLatin1("libraw %1").arg(DRawDecoder::librawVersion())));
demosaicingLayout->addWidget(dcrawVersion, 0, 2, 1, 1);
d->dontStretchPixelsCheckBox = new QCheckBox(i18nc("@option:check", "Do not stretch or rotate pixels"), d->demosaicingSettings);
d->dontStretchPixelsCheckBox->setWhatsThis(xi18nc("@info:whatsthis",
"<title>Do not stretch or rotate pixels</title>"
"<para>For Fuji Super CCD cameras, show the image tilted 45 degrees. "
"For cameras with non-square pixels, do not stretch the image to "
"its correct aspect ratio. In any case, this option guarantees that "
"each output pixel corresponds to one RAW pixel.</para>"));
demosaicingLayout->addWidget(d->dontStretchPixelsCheckBox, line, 0, 1, 3);
line++;
d->RAWQualityLabel = new QLabel(i18nc("@label:listbox", "Quality:"), d->demosaicingSettings);
d->RAWQualityComboBox = new DComboBox(d->demosaicingSettings);
// Original Raw engine demosaicing methods
d->RAWQualityComboBox->insertItem(0, i18nc("@item:inlistbox Quality", "Bilinear"), QVariant(DRawDecoderSettings::BILINEAR));
d->RAWQualityComboBox->insertItem(1, i18nc("@item:inlistbox Quality", "VNG"), QVariant(DRawDecoderSettings::VNG));
d->RAWQualityComboBox->insertItem(2, i18nc("@item:inlistbox Quality", "PPG"), QVariant(DRawDecoderSettings::PPG));
d->RAWQualityComboBox->insertItem(3, i18nc("@item:inlistbox Quality", "AHD"), QVariant(DRawDecoderSettings::AHD));
d->RAWQualityComboBox->insertItem(4, i18nc("@item:inlistbox Quality", "DCB"), QVariant(DRawDecoderSettings::DCB));
d->RAWQualityComboBox->insertItem(5, i18nc("@item:inlistbox Quality", "DHT"), QVariant(DRawDecoderSettings::DHT));
d->RAWQualityComboBox->insertItem(6, i18nc("@item:inlistbox Quality", "AAHD"), QVariant(DRawDecoderSettings::AAHD));
d->RAWQualityComboBox->setDefaultIndex(0);
d->RAWQualityComboBox->setCurrentIndex(0);
d->RAWQualityComboBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Quality (interpolation)</title>"
"<para>Select here the demosaicing method to use when decoding RAW "
"images. A demosaicing algorithm is a digital image process used to "
"interpolate a complete image from the partial raw data received "
"from the color-filtered image sensor, internal to many digital "
"cameras, in form of a matrix of colored pixels. Also known as CFA "
"interpolation or color reconstruction, another common spelling is "
"demosaicing. The following methods are available for demosaicing "
"RAW images:</para>"
"<para><list><item><emphasis strong='true'>Bilinear</emphasis>: use "
"high-speed but low-quality bilinear interpolation (default - for "
"slow computers). In this method, the red value of a non-red pixel "
"is computed as the average of the adjacent red pixels, and similarly "
"for blue and green.</item>"
"<item><emphasis strong='true'>VNG</emphasis>: use Variable Number "
"of Gradients interpolation. This method computes gradients near "
"the pixel of interest and uses the lower gradients (representing "
"smoother and more similar parts of the image) to make an estimate.</item>"
"<item><emphasis strong='true'>PPG</emphasis>: use Patterned-Pixel-"
"Grouping interpolation. Pixel Grouping uses assumptions about "
"natural scenery in making estimates. It has fewer color artifacts "
"on natural images than the Variable Number of Gradients method.</item>"
"<item><emphasis strong='true'>AHD</emphasis>: use Adaptive "
"Homogeneity-Directed interpolation. This method selects the "
"direction of interpolation so as to maximize a homogeneity metric, "
"thus typically minimizing color artifacts.</item>"
"<item><emphasis strong='true'>DCB</emphasis>: DCB interpolation from "
"linuxphoto.org project.</item>"
"<item><emphasis strong='true'>DHT</emphasis>: DHT interpolation."
"See https://www.libraw.org/node/2306 for details.</item>"
"<item><emphasis strong='true'>AAHD</emphasis>: modified AHD "
"interpolation.</item>"
"</list></para>"));
demosaicingLayout->addWidget(d->RAWQualityLabel, line, 0, 1, 1);
demosaicingLayout->addWidget(d->RAWQualityComboBox, line, 1, 1, 2);
line++;
d->medianFilterPassesSpinBox = new DIntNumInput(d->demosaicingSettings);
d->medianFilterPassesSpinBox->setRange(0, 10, 1);
d->medianFilterPassesSpinBox->setDefaultValue(0);
d->medianFilterPassesLabel = new QLabel(i18nc("@label:slider", "Pass:"), d->whiteBalanceSettings);
d->medianFilterPassesSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Pass</title>"
"<para>Set here the passes used by the median filter applied after "
"interpolation to Red-Green and Blue-Green channels.</para>"
"<para>This setting is only available for specific Quality options: "
"<emphasis strong='true'>Bilinear</emphasis>, <emphasis strong='true'>"
"VNG</emphasis>, <emphasis strong='true'>PPG</emphasis>, "
"<emphasis strong='true'>AHD</emphasis>, <emphasis strong='true'>"
"DCB</emphasis>, and <emphasis strong='true'>VCD & AHD</emphasis>.</para>"));
demosaicingLayout->addWidget(d->medianFilterPassesLabel, line, 0, 1, 1);
demosaicingLayout->addWidget(d->medianFilterPassesSpinBox, line, 1, 1, 2);
line++;
d->refineInterpolationBox = new QCheckBox(i18nc("@option:check", "Refine interpolation"), d->demosaicingSettings);
d->refineInterpolationBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Refine interpolation</title>"
"<para>This setting is available only for few Quality options:</para>"
"<para><list><item><emphasis strong='true'>DCB</emphasis>: turn on "
"the enhance interpolated colors filter.</item>"
"<item><emphasis strong='true'>VCD & AHD</emphasis>: turn on the "
"enhanced effective color interpolation (EECI) refine to improve "
"sharpness.</item></list></para>"));
demosaicingLayout->addWidget(d->refineInterpolationBox, line, 0, 1, 2);
d->medianFilterPassesLabel->setEnabled(false);
d->medianFilterPassesSpinBox->setEnabled(false);
d->refineInterpolationBox->setEnabled(false);
addItem(d->demosaicingSettings, QIcon::fromTheme(QLatin1String("image-x-adobe-dng")).pixmap(16, 16), i18nc("@label", "Demosaicing"), QLatin1String("demosaicing"), true);
// ---------------------------------------------------------------
// WHITE BALANCE Settings Panel
d->whiteBalanceSettings = new QWidget(this);
QGridLayout* const whiteBalanceLayout = new QGridLayout(d->whiteBalanceSettings);
d->whiteBalanceLabel = new QLabel(i18nc("@label:listbox", "Method:"), d->whiteBalanceSettings);
d->whiteBalanceComboBox = new DComboBox(d->whiteBalanceSettings);
d->whiteBalanceComboBox->insertItem(DRawDecoderSettings::NONE, i18nc("@item:inlistbox", "Default D65"));
d->whiteBalanceComboBox->insertItem(DRawDecoderSettings::CAMERA, i18nc("@item:inlistbox", "Camera"));
d->whiteBalanceComboBox->insertItem(DRawDecoderSettings::AUTO, i18nc("@item:inlistbox set while balance automatically", "Automatic"));
d->whiteBalanceComboBox->insertItem(DRawDecoderSettings::CUSTOM, i18nc("@item:inlistbox set white balance manually", "Manual"));
d->whiteBalanceComboBox->setDefaultIndex(DRawDecoderSettings::CAMERA);
d->whiteBalanceComboBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>White Balance</title>"
"<para>Configure the raw white balance:</para>"
"<para><list><item><emphasis strong='true'>Default D65</emphasis>: "
"Use a standard daylight D65 white balance.</item>"
"<item><emphasis strong='true'>Camera</emphasis>: Use the white "
"balance specified by the camera. If not available, reverts to "
"default neutral white balance.</item>"
"<item><emphasis strong='true'>Automatic</emphasis>: Calculates an "
"automatic white balance averaging the entire image.</item>"
"<item><emphasis strong='true'>Manual</emphasis>: Set a custom "
"temperature and green level values.</item></list></para>"));
d->customWhiteBalanceSpinBox = new DIntNumInput(d->whiteBalanceSettings);
d->customWhiteBalanceSpinBox->setRange(2000, 12000, 10);
d->customWhiteBalanceSpinBox->setDefaultValue(6500);
d->customWhiteBalanceLabel = new QLabel(i18nc("@label:slider", "T(K):"), d->whiteBalanceSettings);
d->customWhiteBalanceSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Temperature</title>"
"<para>Set here the color temperature in Kelvin.</para>"));
d->customWhiteBalanceGreenSpinBox = new DDoubleNumInput(d->whiteBalanceSettings);
d->customWhiteBalanceGreenSpinBox->setDecimals(2);
d->customWhiteBalanceGreenSpinBox->setRange(0.2, 2.5, 0.01);
d->customWhiteBalanceGreenSpinBox->setDefaultValue(1.0);
d->customWhiteBalanceGreenLabel = new QLabel(i18nc("@label:slider Green component", "Green:"), d->whiteBalanceSettings);
d->customWhiteBalanceGreenSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<para>Set here the "
"green component to set magenta color cast removal level.</para>"));
d->unclipColorLabel = new QLabel(i18nc("@label:listbox", "Highlights:"), d->whiteBalanceSettings);
d->unclipColorComboBox = new DComboBox(d->whiteBalanceSettings);
d->unclipColorComboBox->insertItem(0, i18nc("@item:inlistbox", "Solid white"));
d->unclipColorComboBox->insertItem(1, i18nc("@item:inlistbox", "Unclip"));
d->unclipColorComboBox->insertItem(2, i18nc("@item:inlistbox", "Blend"));
d->unclipColorComboBox->insertItem(3, i18nc("@item:inlistbox", "Rebuild"));
d->unclipColorComboBox->setDefaultIndex(0);
d->unclipColorComboBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Highlights</title>"
"<para>Select here the highlight clipping method:</para>"
"<para><list><item><emphasis strong='true'>Solid white</emphasis>: "
"clip all highlights to solid white</item>"
"<item><emphasis strong='true'>Unclip</emphasis>: leave highlights "
"unclipped in various shades of pink</item>"
"<item><emphasis strong='true'>Blend</emphasis>:Blend clipped and "
"unclipped values together for a gradual fade to white</item>"
"<item><emphasis strong='true'>Rebuild</emphasis>: reconstruct "
"highlights using a level value</item></list></para>"));
d->reconstructLabel = new QLabel(i18nc("@label:slider Highlight reconstruct level", "Level:"), d->whiteBalanceSettings);
d->reconstructSpinBox = new DIntNumInput(d->whiteBalanceSettings);
d->reconstructSpinBox->setRange(0, 6, 1);
d->reconstructSpinBox->setDefaultValue(0);
d->reconstructSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Level</title>"
"<para>Specify the reconstruct highlight level. Low values favor "
"whites and high values favor colors.</para>"));
d->expoCorrectionBox = new QCheckBox(i18nc("@option:check", "Exposure Correction (E.V)"), d->whiteBalanceSettings);
d->expoCorrectionBox->setWhatsThis(xi18nc("@info:whatsthis", "<para>Turn on the exposure "
"correction before interpolation.</para>"));
d->expoCorrectionShiftLabel = new QLabel(i18nc("@label:slider", "Linear Shift:"), d->whiteBalanceSettings);
d->expoCorrectionShiftSpinBox = new DDoubleNumInput(d->whiteBalanceSettings);
d->expoCorrectionShiftSpinBox->setDecimals(2);
d->expoCorrectionShiftSpinBox->setRange(-2.0, 3.0, 0.01);
d->expoCorrectionShiftSpinBox->setDefaultValue(0.0);
d->expoCorrectionShiftSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Shift</title>"
"<para>Linear Shift of exposure correction before interpolation in E.V</para>"));
d->expoCorrectionHighlightLabel = new QLabel(i18nc("@label:slider", "Highlight:"), d->whiteBalanceSettings);
d->expoCorrectionHighlightSpinBox = new DDoubleNumInput(d->whiteBalanceSettings);
d->expoCorrectionHighlightSpinBox->setDecimals(2);
d->expoCorrectionHighlightSpinBox->setRange(0.0, 1.0, 0.01);
d->expoCorrectionHighlightSpinBox->setDefaultValue(0.0);
d->expoCorrectionHighlightSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Highlight</title>"
"<para>Amount of highlight preservation for exposure correction "
"before interpolation in E.V. Only take effect if Shift Correction is > 1.0 E.V</para>"));
d->fixColorsHighlightsBox = new QCheckBox(i18nc("@option:check", "Correct false colors in highlights"), d->whiteBalanceSettings);
d->fixColorsHighlightsBox->setWhatsThis(xi18nc("@info:whatsthis", "<para>If enabled, images with "
"overblown channels are processed much more accurately, without "
"'pink clouds' (and blue highlights under tungsten lamps).</para>"));
d->autoBrightnessBox = new QCheckBox(i18nc("@option:check", "Auto Brightness"), d->whiteBalanceSettings);
d->autoBrightnessBox->setWhatsThis(xi18nc("@info:whatsthis", "<para>If disable, use a fixed white level "
"and ignore the image histogram to adjust brightness.</para>"));
d->brightnessLabel = new QLabel(i18nc("@label:slider", "Brightness:"), d->whiteBalanceSettings);
d->brightnessSpinBox = new DDoubleNumInput(d->whiteBalanceSettings);
d->brightnessSpinBox->setDecimals(2);
d->brightnessSpinBox->setRange(0.0, 10.0, 0.01);
d->brightnessSpinBox->setDefaultValue(1.0);
d->brightnessSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Brightness</title>"
"<para>Specify the brightness level of output image. The default "
"value is 1.0 (works in 8-bit mode only).</para>"));
if (! (advSettings & POSTPROCESSING))
{
d->brightnessLabel->hide();
d->brightnessSpinBox->hide();
}
d->blackPointCheckBox = new QCheckBox(i18nc("@option:check Black point", "Black:"), d->whiteBalanceSettings);
d->blackPointCheckBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Black point</title>"
"<para>Use a specific black point value to decode RAW pictures. If "
"you set this option to off, the Black Point value will be "
"automatically computed.</para>"));
d->blackPointSpinBox = new DIntNumInput(d->whiteBalanceSettings);
d->blackPointSpinBox->setRange(0, 1000, 1);
d->blackPointSpinBox->setDefaultValue(0);
d->blackPointSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Black point value</title>"
"<para>Specify specific black point value of the output image.</para>"));
d->whitePointCheckBox = new QCheckBox(i18nc("@option:check White point", "White:"), d->whiteBalanceSettings);
d->whitePointCheckBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>White point</title>"
"<para>Use a specific white point value to decode RAW pictures. If "
"you set this option to off, the White Point value will be "
"automatically computed.</para>"));
d->whitePointSpinBox = new DIntNumInput(d->whiteBalanceSettings);
d->whitePointSpinBox->setRange(0, 20000, 1);
d->whitePointSpinBox->setDefaultValue(0);
d->whitePointSpinBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>White point value</title>"
"<para>Specify specific white point value of the output image.</para>"));
if (! (advSettings & BLACKWHITEPOINTS))
{
d->blackPointCheckBox->hide();
d->blackPointSpinBox->hide();
d->whitePointCheckBox->hide();
d->whitePointSpinBox->hide();
}
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
whiteBalanceLayout->addWidget(d->whiteBalanceLabel, 0, 0, 1, 1);
whiteBalanceLayout->addWidget(d->whiteBalanceComboBox, 0, 1, 1, 2);
whiteBalanceLayout->addWidget(d->customWhiteBalanceLabel, 1, 0, 1, 1);
whiteBalanceLayout->addWidget(d->customWhiteBalanceSpinBox, 1, 1, 1, 2);
whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenLabel, 2, 0, 1, 1);
whiteBalanceLayout->addWidget(d->customWhiteBalanceGreenSpinBox, 2, 1, 1, 2);
whiteBalanceLayout->addWidget(d->unclipColorLabel, 3, 0, 1, 1);
whiteBalanceLayout->addWidget(d->unclipColorComboBox, 3, 1, 1, 2);
whiteBalanceLayout->addWidget(d->reconstructLabel, 4, 0, 1, 1);
whiteBalanceLayout->addWidget(d->reconstructSpinBox, 4, 1, 1, 2);
whiteBalanceLayout->addWidget(d->expoCorrectionBox, 5, 0, 1, 2);
whiteBalanceLayout->addWidget(d->expoCorrectionShiftLabel, 6, 0, 1, 1);
whiteBalanceLayout->addWidget(d->expoCorrectionShiftSpinBox, 6, 1, 1, 2);
whiteBalanceLayout->addWidget(d->expoCorrectionHighlightLabel, 7, 0, 1, 1);
whiteBalanceLayout->addWidget(d->expoCorrectionHighlightSpinBox, 7, 1, 1, 2);
whiteBalanceLayout->addWidget(d->fixColorsHighlightsBox, 8, 0, 1, 3);
whiteBalanceLayout->addWidget(d->autoBrightnessBox, 9, 0, 1, 3);
whiteBalanceLayout->addWidget(d->brightnessLabel, 10, 0, 1, 1);
whiteBalanceLayout->addWidget(d->brightnessSpinBox, 10, 1, 1, 2);
whiteBalanceLayout->addWidget(d->blackPointCheckBox, 11, 0, 1, 1);
whiteBalanceLayout->addWidget(d->blackPointSpinBox, 11, 1, 1, 2);
whiteBalanceLayout->addWidget(d->whitePointCheckBox, 12, 0, 1, 1);
whiteBalanceLayout->addWidget(d->whitePointSpinBox, 12, 1, 1, 2);
whiteBalanceLayout->setContentsMargins(spacing, spacing, spacing, spacing);
whiteBalanceLayout->setSpacing(spacing);
addItem(d->whiteBalanceSettings, QIcon::fromTheme(QLatin1String("bordertool")).pixmap(16, 16), i18nc("@label", "White Balance"), QLatin1String("whitebalance"), true);
// ---------------------------------------------------------------
// CORRECTIONS Settings panel
d->correctionsSettings = new QWidget(this);
QGridLayout* const correctionsLayout = new QGridLayout(d->correctionsSettings);
d->noiseReductionLabel = new QLabel(i18nc("@label:listbox", "Noise reduction:"), d->correctionsSettings);
d->noiseReductionComboBox = new DComboBox(d->colormanSettings);
d->noiseReductionComboBox->insertItem(DRawDecoderSettings::NONR, i18nc("@item:inlistbox Noise Reduction", "None"));
d->noiseReductionComboBox->insertItem(DRawDecoderSettings::WAVELETSNR, i18nc("@item:inlistbox Noise Reduction", "Wavelets"));
d->noiseReductionComboBox->insertItem(DRawDecoderSettings::FBDDNR, i18nc("@item:inlistbox Noise Reduction", "FBDD"));
d->noiseReductionComboBox->setDefaultIndex(DRawDecoderSettings::NONR);
d->noiseReductionComboBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Noise Reduction</title>"
"<para>Select here the noise reduction method to apply during RAW "
"decoding.</para>"
"<para><list><item><emphasis strong='true'>None</emphasis>: no "
"noise reduction.</item>"
"<item><emphasis strong='true'>Wavelets</emphasis>: wavelets "
"correction to erase noise while preserving real detail. It is "
"applied after interpolation.</item>"
"<item><emphasis strong='true'>FBDD</emphasis>: Fake Before "
"Demosaicing Denoising noise reduction. It is applied before "
"interpolation.</item></list></para>"));
d->NRSpinBox1 = new DIntNumInput(d->correctionsSettings);
d->NRSpinBox1->setRange(100, 1000, 1);
d->NRSpinBox1->setDefaultValue(100);
d->NRLabel1 = new QLabel(d->correctionsSettings);
correctionsLayout->addWidget(d->noiseReductionLabel, 0, 0, 1, 1);
correctionsLayout->addWidget(d->noiseReductionComboBox, 0, 1, 1, 2);
correctionsLayout->addWidget(d->NRLabel1, 1, 0, 1, 1);
correctionsLayout->addWidget(d->NRSpinBox1, 1, 1, 1, 2);
correctionsLayout->setRowStretch(2, 10);
correctionsLayout->setContentsMargins(spacing, spacing, spacing, spacing);
correctionsLayout->setSpacing(spacing);
addItem(d->correctionsSettings, QIcon::fromTheme(QLatin1String("document-edit")).pixmap(16, 16), i18nc("@label", "Corrections"), QLatin1String("corrections"), false);
// ---------------------------------------------------------------
// COLOR MANAGEMENT Settings panel
d->colormanSettings = new QWidget(this);
QGridLayout* const colormanLayout = new QGridLayout(d->colormanSettings);
d->inputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Camera Profile:"), d->colormanSettings);
d->inputColorSpaceComboBox = new DComboBox(d->colormanSettings);
d->inputColorSpaceComboBox->insertItem(DRawDecoderSettings::NOINPUTCS, i18nc("@item:inlistbox Camera Profile", "None"));
d->inputColorSpaceComboBox->insertItem(DRawDecoderSettings::EMBEDDED, i18nc("@item:inlistbox Camera Profile", "Embedded"));
d->inputColorSpaceComboBox->insertItem(DRawDecoderSettings::CUSTOMINPUTCS, i18nc("@item:inlistbox Camera Profile", "Custom"));
d->inputColorSpaceComboBox->setDefaultIndex(DRawDecoderSettings::NOINPUTCS);
d->inputColorSpaceComboBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Camera Profile</title>"
"<para>Select here the input color space used to decode RAW data.</para>"
"<para><list><item><emphasis strong='true'>None</emphasis>: no "
"input color profile is used during RAW decoding.</item>"
"<item><emphasis strong='true'>Embedded</emphasis>: use embedded "
"color profile from RAW file, if it exists.</item>"
"<item><emphasis strong='true'>Custom</emphasis>: use a custom "
"input color space profile.</item></list></para>"));
d->inIccUrlEdit = new DFileSelector(d->colormanSettings);
d->inIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile);
d->inIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc *.icm)"));
d->outputColorSpaceLabel = new QLabel(i18nc("@label:listbox", "Workspace:"), d->colormanSettings);
d->outputColorSpaceComboBox = new DComboBox( d->colormanSettings );
d->outputColorSpaceComboBox->insertItem(DRawDecoderSettings::RAWCOLOR, i18nc("@item:inlistbox Workspace", "Raw (no profile)"));
d->outputColorSpaceComboBox->insertItem(DRawDecoderSettings::SRGB, i18nc("@item:inlistbox Workspace", "sRGB"));
d->outputColorSpaceComboBox->insertItem(DRawDecoderSettings::ADOBERGB, i18nc("@item:inlistbox Workspace", "Adobe RGB"));
d->outputColorSpaceComboBox->insertItem(DRawDecoderSettings::WIDEGAMMUT, i18nc("@item:inlistbox Workspace", "Wide Gamut"));
d->outputColorSpaceComboBox->insertItem(DRawDecoderSettings::PROPHOTO, i18nc("@item:inlistbox Workspace", "Pro-Photo"));
d->outputColorSpaceComboBox->insertItem(DRawDecoderSettings::CUSTOMOUTPUTCS, i18nc("@item:inlistbox Workspace", "Custom"));
d->outputColorSpaceComboBox->setDefaultIndex(DRawDecoderSettings::SRGB);
d->outputColorSpaceComboBox->setWhatsThis(xi18nc("@info:whatsthis", "<title>Workspace</title>"
"<para>Select here the output color space used to decode RAW data.</para>"
"<para><list><item><emphasis strong='true'>Raw (linear)</emphasis>: "
"in this mode, no output color space is used during RAW decoding.</item>"
"<item><emphasis strong='true'>sRGB</emphasis>: this is an RGB "
"color space, created cooperatively by Hewlett-Packard and "
"Microsoft. It is the best choice for images destined for the Web "
"and portrait photography.</item>"
"<item><emphasis strong='true'>Adobe RGB</emphasis>: this color "
"space is an extended RGB color space, developed by Adobe. It is "
"used for photography applications such as advertising and fine "
"art.</item>"
"<item><emphasis strong='true'>Wide Gamut</emphasis>: this color "
"space is an expanded version of the Adobe RGB color space.</item>"
"<item><emphasis strong='true'>Pro-Photo</emphasis>: this color "
"space is an RGB color space, developed by Kodak, that offers an "
"especially large gamut designed for use with photographic outputs "
"in mind.</item>"
"<item><emphasis strong='true'>Custom</emphasis>: use a custom "
"output color space profile.</item></list></para>"));
d->outIccUrlEdit = new DFileSelector(d->colormanSettings);
d->outIccUrlEdit->setFileDlgMode(QFileDialog::ExistingFile);
d->outIccUrlEdit->setFileDlgFilter(i18n("ICC Files (*.icc *.icm)"));
colormanLayout->addWidget(d->inputColorSpaceLabel, 0, 0, 1, 1);
colormanLayout->addWidget(d->inputColorSpaceComboBox, 0, 1, 1, 2);
colormanLayout->addWidget(d->inIccUrlEdit, 1, 0, 1, 3);
colormanLayout->addWidget(d->outputColorSpaceLabel, 2, 0, 1, 1);
colormanLayout->addWidget(d->outputColorSpaceComboBox, 2, 1, 1, 2);
colormanLayout->addWidget(d->outIccUrlEdit, 3, 0, 1, 3);
colormanLayout->setRowStretch(4, 10);
colormanLayout->setContentsMargins(spacing, spacing, spacing, spacing);
colormanLayout->setSpacing(spacing);
addItem(d->colormanSettings, QIcon::fromTheme(QLatin1String("preferences-desktop-display-color")).pixmap(16, 16), i18nc("@label", "Color Management"), QLatin1String("colormanagement"), false);
if (! (advSettings & COLORSPACE))
{
removeItem(COLORMANAGEMENT);
}
addStretch();
// ---------------------------------------------------------------
connect(d->unclipColorComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::slotUnclipColorActivated);
connect(d->whiteBalanceComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::slotWhiteBalanceToggled);
connect(d->noiseReductionComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::slotNoiseReductionChanged);
connect(d->blackPointCheckBox, SIGNAL(toggled(bool)),
d->blackPointSpinBox, SLOT(setEnabled(bool)));
connect(d->whitePointCheckBox, SIGNAL(toggled(bool)),
d->whitePointSpinBox, SLOT(setEnabled(bool)));
connect(d->sixteenBitsImage, &QCheckBox::toggled,
this, &DRawDecoderWidget::slotsixteenBitsImageToggled);
connect(d->inputColorSpaceComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::slotInputColorSpaceChanged);
connect(d->outputColorSpaceComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::slotOutputColorSpaceChanged);
connect(d->expoCorrectionBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::slotExposureCorrectionToggled);
connect(d->expoCorrectionShiftSpinBox, &DDoubleNumInput::valueChanged,
this, &DRawDecoderWidget::slotExpoCorrectionShiftChanged);
// Wrapper to emit signal when something is changed in settings.
connect(d->inIccUrlEdit->lineEdit(), &QLineEdit::textChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->outIccUrlEdit->lineEdit(), &QLineEdit::textChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->whiteBalanceComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->RAWQualityComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::slotRAWQualityChanged);
connect(d->unclipColorComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->inputColorSpaceComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->outputColorSpaceComboBox, static_cast<void (DComboBox::*)(int)>(&DComboBox::activated),
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->blackPointCheckBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->whitePointCheckBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->sixteenBitsImage, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->fixColorsHighlightsBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->autoBrightnessBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->fourColorCheckBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->dontStretchPixelsCheckBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->refineInterpolationBox, &QCheckBox::toggled,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->customWhiteBalanceSpinBox, &DIntNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->reconstructSpinBox, &DIntNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->blackPointSpinBox, &DIntNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->whitePointSpinBox, &DIntNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->NRSpinBox1, &DIntNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->medianFilterPassesSpinBox, &DIntNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->customWhiteBalanceGreenSpinBox, &DDoubleNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->brightnessSpinBox, &DDoubleNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
connect(d->expoCorrectionHighlightSpinBox, &DDoubleNumInput::valueChanged,
this, &DRawDecoderWidget::signalSettingsChanged);
}
DRawDecoderWidget::~DRawDecoderWidget()
{
delete d;
}
void DRawDecoderWidget::updateMinimumWidth()
{
int width = 0;
for (int i = 0; i < count(); i++)
{
if (widget(i)->width() > width)
{
width = widget(i)->width();
}
}
setMinimumWidth(width);
}
DFileSelector* DRawDecoderWidget::inputProfileUrlEdit() const
{
return d->inIccUrlEdit;
}
DFileSelector* DRawDecoderWidget::outputProfileUrlEdit() const
{
return d->outIccUrlEdit;
}
void DRawDecoderWidget::resetToDefault()
{
setSettings(DRawDecoderSettings());
}
void DRawDecoderWidget::slotsixteenBitsImageToggled(bool b)
{
setEnabledBrightnessSettings(!b);
emit signalSixteenBitsImageToggled(d->sixteenBitsImage->isChecked());
}
void DRawDecoderWidget::slotWhiteBalanceToggled(int v)
{
if (v == 3)
{
d->customWhiteBalanceSpinBox->setEnabled(true);
d->customWhiteBalanceGreenSpinBox->setEnabled(true);
d->customWhiteBalanceLabel->setEnabled(true);
d->customWhiteBalanceGreenLabel->setEnabled(true);
}
else
{
d->customWhiteBalanceSpinBox->setEnabled(false);
d->customWhiteBalanceGreenSpinBox->setEnabled(false);
d->customWhiteBalanceLabel->setEnabled(false);
d->customWhiteBalanceGreenLabel->setEnabled(false);
}
}
void DRawDecoderWidget::slotUnclipColorActivated(int v)
{
if (v == 3) // Reconstruct Highlight method
{
d->reconstructLabel->setEnabled(true);
d->reconstructSpinBox->setEnabled(true);
}
else
{
d->reconstructLabel->setEnabled(false);
d->reconstructSpinBox->setEnabled(false);
}
}
void DRawDecoderWidget::slotNoiseReductionChanged(int item)
{
d->NRSpinBox1->setEnabled(true);
d->NRLabel1->setEnabled(true);
d->NRLabel1->setText(i18nc("@label", "Threshold:"));
d->NRSpinBox1->setWhatsThis(xi18nc("@info:whatsthis", "<title>Threshold</title>"
"<para>Set here the noise reduction threshold value to use.</para>"));
switch(item)
{
case DRawDecoderSettings::WAVELETSNR:
case DRawDecoderSettings::FBDDNR:
// NOTE : no ops
break;
default:
d->NRSpinBox1->setEnabled(false);
d->NRLabel1->setEnabled(false);
break;
}
emit signalSettingsChanged();
}
void DRawDecoderWidget::slotExposureCorrectionToggled(bool b)
{
d->expoCorrectionShiftLabel->setEnabled(b);
d->expoCorrectionShiftSpinBox->setEnabled(b);
d->expoCorrectionHighlightLabel->setEnabled(b);
d->expoCorrectionHighlightSpinBox->setEnabled(b);
slotExpoCorrectionShiftChanged(d->expoCorrectionShiftSpinBox->value());
}
void DRawDecoderWidget::slotExpoCorrectionShiftChanged(double ev)
{
- // Only enable Highligh exposure correction if Shift correction is >= 1.0, else this settings do not take effect.
+ // Only enable Highlight exposure correction if Shift correction is >= 1.0, else this settings do not take effect.
bool b = (ev >= 1.0);
d->expoCorrectionHighlightLabel->setEnabled(b);
d->expoCorrectionHighlightSpinBox->setEnabled(b);
emit signalSettingsChanged();
}
void DRawDecoderWidget::slotInputColorSpaceChanged(int item)
{
d->inIccUrlEdit->setEnabled(item == DRawDecoderSettings::CUSTOMINPUTCS);
}
void DRawDecoderWidget::slotOutputColorSpaceChanged(int item)
{
d->outIccUrlEdit->setEnabled(item == DRawDecoderSettings::CUSTOMOUTPUTCS);
}
void DRawDecoderWidget::slotRAWQualityChanged(int quality)
{
switch (quality)
{
case DRawDecoderSettings::DCB:
d->medianFilterPassesLabel->setEnabled(true);
d->medianFilterPassesSpinBox->setEnabled(true);
d->refineInterpolationBox->setEnabled(true);
break;
default: // BILINEAR, VNG, PPG, AHD
d->medianFilterPassesLabel->setEnabled(true);
d->medianFilterPassesSpinBox->setEnabled(true);
d->refineInterpolationBox->setEnabled(false);
break;
}
emit signalSettingsChanged();
}
void DRawDecoderWidget::setEnabledBrightnessSettings(bool b)
{
d->brightnessLabel->setEnabled(b);
d->brightnessSpinBox->setEnabled(b);
}
bool DRawDecoderWidget::brightnessSettingsIsEnabled() const
{
return d->brightnessSpinBox->isEnabled();
}
void DRawDecoderWidget::setSettings(const DRawDecoderSettings& settings)
{
d->sixteenBitsImage->setChecked(settings.sixteenBitsImage);
switch(settings.whiteBalance)
{
case DRawDecoderSettings::CAMERA:
d->whiteBalanceComboBox->setCurrentIndex(1);
break;
case DRawDecoderSettings::AUTO:
d->whiteBalanceComboBox->setCurrentIndex(2);
break;
case DRawDecoderSettings::CUSTOM:
d->whiteBalanceComboBox->setCurrentIndex(3);
break;
default:
d->whiteBalanceComboBox->setCurrentIndex(0);
break;
}
slotWhiteBalanceToggled(d->whiteBalanceComboBox->currentIndex());
d->customWhiteBalanceSpinBox->setValue(settings.customWhiteBalance);
d->customWhiteBalanceGreenSpinBox->setValue(settings.customWhiteBalanceGreen);
d->fourColorCheckBox->setChecked(settings.RGBInterpolate4Colors);
d->autoBrightnessBox->setChecked(settings.autoBrightness);
d->fixColorsHighlightsBox->setChecked(settings.fixColorsHighlights);
switch(settings.unclipColors)
{
case 0:
d->unclipColorComboBox->setCurrentIndex(0);
break;
case 1:
d->unclipColorComboBox->setCurrentIndex(1);
break;
case 2:
d->unclipColorComboBox->setCurrentIndex(2);
break;
default: // Reconstruct Highlight method
d->unclipColorComboBox->setCurrentIndex(3);
d->reconstructSpinBox->setValue(settings.unclipColors-3);
break;
}
slotUnclipColorActivated(d->unclipColorComboBox->currentIndex());
d->dontStretchPixelsCheckBox->setChecked(settings.DontStretchPixels);
d->brightnessSpinBox->setValue(settings.brightness);
d->blackPointCheckBox->setChecked(settings.enableBlackPoint);
d->blackPointSpinBox->setEnabled(settings.enableBlackPoint);
d->blackPointSpinBox->setValue(settings.blackPoint);
d->whitePointCheckBox->setChecked(settings.enableWhitePoint);
d->whitePointSpinBox->setEnabled(settings.enableWhitePoint);
d->whitePointSpinBox->setValue(settings.whitePoint);
int q = d->RAWQualityComboBox->combo()->findData(settings.RAWQuality);
if (q == -1)
q = 0; // Fail back to bilinear
d->RAWQualityComboBox->setCurrentIndex(q);
switch (q)
{
case DRawDecoderSettings::DCB:
d->medianFilterPassesSpinBox->setValue(settings.dcbIterations);
d->refineInterpolationBox->setChecked(settings.dcbEnhanceFl);
break;
default:
d->medianFilterPassesSpinBox->setValue(settings.medianFilterPasses);
d->refineInterpolationBox->setChecked(false); // option not used.
break;
}
slotRAWQualityChanged(q);
d->inputColorSpaceComboBox->setCurrentIndex((int)settings.inputColorSpace);
slotInputColorSpaceChanged((int)settings.inputColorSpace);
d->outputColorSpaceComboBox->setCurrentIndex((int)settings.outputColorSpace);
slotOutputColorSpaceChanged((int)settings.outputColorSpace);
d->noiseReductionComboBox->setCurrentIndex(settings.NRType);
slotNoiseReductionChanged(settings.NRType);
d->NRSpinBox1->setValue(settings.NRThreshold);
d->expoCorrectionBox->setChecked(settings.expoCorrection);
slotExposureCorrectionToggled(settings.expoCorrection);
d->expoCorrectionShiftSpinBox->setValue(d->shiftExpoFromLinearToEv(settings.expoCorrectionShift));
d->expoCorrectionHighlightSpinBox->setValue(settings.expoCorrectionHighlight);
d->inIccUrlEdit->setFileDlgPath(settings.inputProfile);
d->outIccUrlEdit->setFileDlgPath(settings.outputProfile);
}
DRawDecoderSettings DRawDecoderWidget::settings() const
{
DRawDecoderSettings prm;
prm.sixteenBitsImage = d->sixteenBitsImage->isChecked();
switch(d->whiteBalanceComboBox->currentIndex())
{
case 1:
prm.whiteBalance = DRawDecoderSettings::CAMERA;
break;
case 2:
prm.whiteBalance = DRawDecoderSettings::AUTO;
break;
case 3:
prm.whiteBalance = DRawDecoderSettings::CUSTOM;
break;
default:
prm.whiteBalance = DRawDecoderSettings::NONE;
break;
}
prm.customWhiteBalance = d->customWhiteBalanceSpinBox->value();
prm.customWhiteBalanceGreen = d->customWhiteBalanceGreenSpinBox->value();
prm.RGBInterpolate4Colors = d->fourColorCheckBox->isChecked();
prm.autoBrightness = d->autoBrightnessBox->isChecked();
prm.fixColorsHighlights = d->fixColorsHighlightsBox->isChecked();
switch(d->unclipColorComboBox->currentIndex())
{
case 0:
prm.unclipColors = 0;
break;
case 1:
prm.unclipColors = 1;
break;
case 2:
prm.unclipColors = 2;
break;
default: // Reconstruct Highlight method
prm.unclipColors = d->reconstructSpinBox->value()+3;
break;
}
prm.DontStretchPixels = d->dontStretchPixelsCheckBox->isChecked();
prm.brightness = d->brightnessSpinBox->value();
prm.enableBlackPoint = d->blackPointCheckBox->isChecked();
prm.blackPoint = d->blackPointSpinBox->value();
prm.enableWhitePoint = d->whitePointCheckBox->isChecked();
prm.whitePoint = d->whitePointSpinBox->value();
prm.RAWQuality = (DRawDecoderSettings::DecodingQuality)d->RAWQualityComboBox->combo()->currentData().toInt();
switch(prm.RAWQuality)
{
case DRawDecoderSettings::DCB:
prm.dcbIterations = d->medianFilterPassesSpinBox->value();
prm.dcbEnhanceFl = d->refineInterpolationBox->isChecked();
break;
default:
prm.medianFilterPasses = d->medianFilterPassesSpinBox->value();
break;
}
prm.NRType = (DRawDecoderSettings::NoiseReduction)d->noiseReductionComboBox->currentIndex();
switch (prm.NRType)
{
case DRawDecoderSettings::NONR:
{
prm.NRThreshold = 0;
break;
}
default: // WAVELETSNR and FBDDNR
{
prm.NRThreshold = d->NRSpinBox1->value();
break;
}
}
prm.expoCorrection = d->expoCorrectionBox->isChecked();
prm.expoCorrectionShift = d->shiftExpoFromEvToLinear(d->expoCorrectionShiftSpinBox->value());
prm.expoCorrectionHighlight = d->expoCorrectionHighlightSpinBox->value();
prm.inputColorSpace = (DRawDecoderSettings::InputColorSpace)(d->inputColorSpaceComboBox->currentIndex());
prm.outputColorSpace = (DRawDecoderSettings::OutputColorSpace)(d->outputColorSpaceComboBox->currentIndex());
prm.inputProfile = d->inIccUrlEdit->fileDlgPath();
prm.outputProfile = d->outIccUrlEdit->fileDlgPath();
return prm;
}
void DRawDecoderWidget::readSettings(KConfigGroup& group)
{
DRawDecoderSettings prm;
readSettings(prm, group);
setSettings(prm);
DExpanderBox::readSettings(group);
}
void DRawDecoderWidget::writeSettings(KConfigGroup& group)
{
DRawDecoderSettings prm = settings();
writeSettings(prm, group);
DExpanderBox::writeSettings(group);
}
void DRawDecoderWidget::readSettings(DRawDecoderSettings& prm, KConfigGroup& group)
{
DRawDecoderSettings defaultPrm;
prm.fixColorsHighlights = group.readEntry(OPTIONFIXCOLORSHIGHLIGHTSENTRY, defaultPrm.fixColorsHighlights);
prm.sixteenBitsImage = group.readEntry(OPTIONDECODESIXTEENBITENTRY, defaultPrm.sixteenBitsImage);
prm.whiteBalance = (DRawDecoderSettings::WhiteBalance)group.readEntry(OPTIONWHITEBALANCEENTRY, (int)defaultPrm.whiteBalance);
prm.customWhiteBalance = group.readEntry(OPTIONCUSTOMWHITEBALANCEENTRY, defaultPrm.customWhiteBalance);
prm.customWhiteBalanceGreen = group.readEntry(OPTIONCUSTOMWBGREENENTRY, defaultPrm.customWhiteBalanceGreen);
prm.RGBInterpolate4Colors = group.readEntry(OPTIONFOURCOLORRGBENTRY, defaultPrm.RGBInterpolate4Colors);
prm.unclipColors = group.readEntry(OPTIONUNCLIPCOLORSENTRY, defaultPrm.unclipColors);
prm.DontStretchPixels = group.readEntry(OPTIONDONTSTRETCHPIXELSSENTRY, defaultPrm.DontStretchPixels);
prm.NRType = (DRawDecoderSettings::NoiseReduction)group.readEntry(OPTIONNOISEREDUCTIONTYPEENTRY, (int)defaultPrm.NRType);
prm.brightness = group.readEntry(OPTIONBRIGHTNESSMULTIPLIERENTRY, defaultPrm.brightness);
prm.enableBlackPoint = group.readEntry(OPTIONUSEBLACKPOINTENTRY, defaultPrm.enableBlackPoint);
prm.blackPoint = group.readEntry(OPTIONBLACKPOINTENTRY, defaultPrm.blackPoint);
prm.enableWhitePoint = group.readEntry(OPTIONUSEWHITEPOINTENTRY, defaultPrm.enableWhitePoint);
prm.whitePoint = group.readEntry(OPTIONWHITEPOINTENTRY, defaultPrm.whitePoint);
prm.medianFilterPasses = group.readEntry(OPTIONMEDIANFILTERPASSESENTRY, defaultPrm.medianFilterPasses);
prm.NRThreshold = group.readEntry(OPTIONNOISEREDUCTIONTHRESHOLDENTRY, defaultPrm.NRThreshold);
prm.RAWQuality = (DRawDecoderSettings::DecodingQuality)group.readEntry(OPTIONDECODINGQUALITYENTRY, (int)defaultPrm.RAWQuality);
prm.outputColorSpace = (DRawDecoderSettings::OutputColorSpace)group.readEntry(OPTIONOUTPUTCOLORSPACEENTRY, (int)defaultPrm.outputColorSpace);
prm.autoBrightness = group.readEntry(OPTIONAUTOBRIGHTNESSENTRY, defaultPrm.autoBrightness);
//-- Extended demosaicing settings ----------------------------------------------------------
prm.dcbIterations = group.readEntry(OPTIONDCBITERATIONSENTRY, defaultPrm.dcbIterations);
prm.dcbEnhanceFl = group.readEntry(OPTIONDCBENHANCEFLENTRY, defaultPrm.dcbEnhanceFl);
prm.expoCorrection = group.readEntry(OPTIONEXPOCORRECTIONENTRY, defaultPrm.expoCorrection);
prm.expoCorrectionShift = group.readEntry(OPTIONEXPOCORRECTIONSHIFTENTRY, defaultPrm.expoCorrectionShift);
prm.expoCorrectionHighlight = group.readEntry(OPTIONEXPOCORRECTIONHIGHLIGHTENTRY, defaultPrm.expoCorrectionHighlight);
}
void DRawDecoderWidget::writeSettings(const DRawDecoderSettings& prm, KConfigGroup& group)
{
group.writeEntry(OPTIONFIXCOLORSHIGHLIGHTSENTRY, prm.fixColorsHighlights);
group.writeEntry(OPTIONDECODESIXTEENBITENTRY, prm.sixteenBitsImage);
group.writeEntry(OPTIONWHITEBALANCEENTRY, (int)prm.whiteBalance);
group.writeEntry(OPTIONCUSTOMWHITEBALANCEENTRY, prm.customWhiteBalance);
group.writeEntry(OPTIONCUSTOMWBGREENENTRY, prm.customWhiteBalanceGreen);
group.writeEntry(OPTIONFOURCOLORRGBENTRY, prm.RGBInterpolate4Colors);
group.writeEntry(OPTIONUNCLIPCOLORSENTRY, prm.unclipColors);
group.writeEntry(OPTIONDONTSTRETCHPIXELSSENTRY, prm.DontStretchPixels);
group.writeEntry(OPTIONNOISEREDUCTIONTYPEENTRY, (int)prm.NRType);
group.writeEntry(OPTIONBRIGHTNESSMULTIPLIERENTRY, prm.brightness);
group.writeEntry(OPTIONUSEBLACKPOINTENTRY, prm.enableBlackPoint);
group.writeEntry(OPTIONBLACKPOINTENTRY, prm.blackPoint);
group.writeEntry(OPTIONUSEWHITEPOINTENTRY, prm.enableWhitePoint);
group.writeEntry(OPTIONWHITEPOINTENTRY, prm.whitePoint);
group.writeEntry(OPTIONMEDIANFILTERPASSESENTRY, prm.medianFilterPasses);
group.writeEntry(OPTIONNOISEREDUCTIONTHRESHOLDENTRY, prm.NRThreshold);
group.writeEntry(OPTIONDECODINGQUALITYENTRY, (int)prm.RAWQuality);
group.writeEntry(OPTIONOUTPUTCOLORSPACEENTRY, (int)prm.outputColorSpace);
group.writeEntry(OPTIONAUTOBRIGHTNESSENTRY, prm.autoBrightness);
//-- Extended demosaicing settings ----------------------------------------------------------
group.writeEntry(OPTIONDCBITERATIONSENTRY, prm.dcbIterations);
group.writeEntry(OPTIONDCBENHANCEFLENTRY, prm.dcbEnhanceFl);
group.writeEntry(OPTIONEXPOCORRECTIONENTRY, prm.expoCorrection);
group.writeEntry(OPTIONEXPOCORRECTIONSHIFTENTRY, prm.expoCorrectionShift);
group.writeEntry(OPTIONEXPOCORRECTIONHIGHLIGHTENTRY, prm.expoCorrectionHighlight);
}
} // NameSpace Digikam
diff --git a/core/libs/rawengine/libraw/internal/dcraw_common.cpp b/core/libs/rawengine/libraw/internal/dcraw_common.cpp
index 29cc72fae0..f1a23a416e 100644
--- a/core/libs/rawengine/libraw/internal/dcraw_common.cpp
+++ b/core/libs/rawengine/libraw/internal/dcraw_common.cpp
@@ -1,20265 +1,20265 @@
/*
Copyright 2008-2018 LibRaw LLC (info@libraw.org)
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
This file is generated from Dave Coffin's dcraw.c
dcraw.c -- Dave Coffin's raw photo decoder
Copyright 1997-2010 by Dave Coffin, dcoffin a cybercom o net
Look into dcraw homepage (probably http://cybercom.net/~dcoffin/dcraw/)
for more information
*/
#include <math.h>
#define CLASS LibRaw::
#include "libraw/libraw_types.h"
#define LIBRAW_LIBRARY_BUILD
#define LIBRAW_IO_REDEFINED
#include "libraw/libraw.h"
#include "internal/defines.h"
#include "internal/var_defines.h"
int CLASS fcol(int row, int col)
{
static const char filter[16][16] = {
{2, 1, 1, 3, 2, 3, 2, 0, 3, 2, 3, 0, 1, 2, 1, 0}, {0, 3, 0, 2, 0, 1, 3, 1, 0, 1, 1, 2, 0, 3, 3, 2},
{2, 3, 3, 2, 3, 1, 1, 3, 3, 1, 2, 1, 2, 0, 0, 3}, {0, 1, 0, 1, 0, 2, 0, 2, 2, 0, 3, 0, 1, 3, 2, 1},
{3, 1, 1, 2, 0, 1, 0, 2, 1, 3, 1, 3, 0, 1, 3, 0}, {2, 0, 0, 3, 3, 2, 3, 1, 2, 0, 2, 0, 3, 2, 2, 1},
{2, 3, 3, 1, 2, 1, 2, 1, 2, 1, 1, 2, 3, 0, 0, 1}, {1, 0, 0, 2, 3, 0, 0, 3, 0, 3, 0, 3, 2, 1, 2, 3},
{2, 3, 3, 1, 1, 2, 1, 0, 3, 2, 3, 0, 2, 3, 1, 3}, {1, 0, 2, 0, 3, 0, 3, 2, 0, 1, 1, 2, 0, 1, 0, 2},
{0, 1, 1, 3, 3, 2, 2, 1, 1, 3, 3, 0, 2, 1, 3, 2}, {2, 3, 2, 0, 0, 1, 3, 0, 2, 0, 1, 2, 3, 0, 1, 0},
{1, 3, 1, 2, 3, 2, 3, 2, 0, 2, 0, 1, 1, 0, 3, 0}, {0, 2, 0, 3, 1, 0, 0, 1, 1, 3, 3, 2, 3, 2, 2, 1},
{2, 1, 3, 2, 3, 1, 2, 1, 0, 3, 0, 2, 0, 2, 0, 2}, {0, 3, 1, 0, 0, 2, 0, 3, 2, 1, 3, 1, 1, 3, 1, 3}};
if (filters == 1)
return filter[(row + top_margin) & 15][(col + left_margin) & 15];
if (filters == 9)
return xtrans[(row + 6) % 6][(col + 6) % 6];
return FC(row, col);
}
#if !defined(__FreeBSD__)
static size_t local_strnlen(const char *s, size_t n)
{
const char *p = (const char *)memchr(s, 0, n);
return (p ? p - s : n);
}
/* add OS X version check here ?? */
#define strnlen(a, b) local_strnlen(a, b)
#endif
#ifdef LIBRAW_LIBRARY_BUILD
static int Fuji_wb_list1[] = {LIBRAW_WBI_FineWeather, LIBRAW_WBI_Shade, LIBRAW_WBI_FL_D,
LIBRAW_WBI_FL_L, LIBRAW_WBI_FL_W, LIBRAW_WBI_Tungsten};
static int nFuji_wb_list1 = sizeof(Fuji_wb_list1) / sizeof(int);
static int FujiCCT_K[31] = {2500, 2550, 2650, 2700, 2800, 2850, 2950, 3000, 3100, 3200, 3300,
3400, 3600, 3700, 3800, 4000, 4200, 4300, 4500, 4800, 5000, 5300,
5600, 5900, 6300, 6700, 7100, 7700, 8300, 9100, 10000};
static int Fuji_wb_list2[] = {LIBRAW_WBI_Auto, 0, LIBRAW_WBI_Custom, 6, LIBRAW_WBI_FineWeather, 1,
LIBRAW_WBI_Shade, 8, LIBRAW_WBI_FL_D, 10, LIBRAW_WBI_FL_L, 11,
LIBRAW_WBI_FL_W, 12, LIBRAW_WBI_Tungsten, 2, LIBRAW_WBI_Underwater, 35,
LIBRAW_WBI_Ill_A, 82, LIBRAW_WBI_D65, 83};
static int nFuji_wb_list2 = sizeof(Fuji_wb_list2) / sizeof(int);
static int Oly_wb_list1[] = {LIBRAW_WBI_Shade, LIBRAW_WBI_Cloudy, LIBRAW_WBI_FineWeather,
LIBRAW_WBI_Tungsten, LIBRAW_WBI_Sunset, LIBRAW_WBI_FL_D,
LIBRAW_WBI_FL_N, LIBRAW_WBI_FL_W, LIBRAW_WBI_FL_WW};
static int Oly_wb_list2[] = {LIBRAW_WBI_Auto,
0,
LIBRAW_WBI_Tungsten,
3000,
0x100,
3300,
0x100,
3600,
0x100,
3900,
LIBRAW_WBI_FL_W,
4000,
0x100,
4300,
LIBRAW_WBI_FL_D,
4500,
0x100,
4800,
LIBRAW_WBI_FineWeather,
5300,
LIBRAW_WBI_Cloudy,
6000,
LIBRAW_WBI_FL_N,
6600,
LIBRAW_WBI_Shade,
7500,
LIBRAW_WBI_Custom1,
0,
LIBRAW_WBI_Custom2,
0,
LIBRAW_WBI_Custom3,
0,
LIBRAW_WBI_Custom4,
0};
static int Pentax_wb_list1[] = {LIBRAW_WBI_Daylight, LIBRAW_WBI_Shade, LIBRAW_WBI_Cloudy, LIBRAW_WBI_Tungsten,
LIBRAW_WBI_FL_D, LIBRAW_WBI_FL_N, LIBRAW_WBI_FL_W, LIBRAW_WBI_Flash};
static int Pentax_wb_list2[] = {LIBRAW_WBI_Daylight, LIBRAW_WBI_Shade, LIBRAW_WBI_Cloudy,
LIBRAW_WBI_Tungsten, LIBRAW_WBI_FL_D, LIBRAW_WBI_FL_N,
LIBRAW_WBI_FL_W, LIBRAW_WBI_Flash, LIBRAW_WBI_FL_L};
static int nPentax_wb_list2 = sizeof(Pentax_wb_list2) / sizeof(int);
static int stread(char *buf, size_t len, LibRaw_abstract_datastream *fp)
{
if(len>0)
{
int r = fp->read(buf, len, 1);
buf[len - 1] = 0;
return r;
}
else
return 0;
}
#define stmread(buf, maxlen, fp) stread(buf, MIN(maxlen, sizeof(buf)), fp)
#endif
#if !defined(__GLIBC__) && !defined(__FreeBSD__)
char *my_memmem(char *haystack, size_t haystacklen, char *needle, size_t needlelen)
{
char *c;
for (c = haystack; c <= haystack + haystacklen - needlelen; c++)
if (!memcmp(c, needle, needlelen))
return c;
return 0;
}
#define memmem my_memmem
char *my_strcasestr(char *haystack, const char *needle)
{
char *c;
for (c = haystack; *c; c++)
if (!strncasecmp(c, needle, strlen(needle)))
return c;
return 0;
}
#define strcasestr my_strcasestr
#endif
#define strbuflen(buf) strnlen(buf, sizeof(buf) - 1)
ushort CLASS sget2(uchar *s)
{
if (order == 0x4949) /* "II" means little-endian */
return s[0] | s[1] << 8;
else /* "MM" means big-endian */
return s[0] << 8 | s[1];
}
// DNG was written by:
#define nonDNG 0
#define CameraDNG 1
#define AdobeDNG 2
#ifdef LIBRAW_LIBRARY_BUILD
static int getwords(char *line, char *words[], int maxwords, int maxlen)
{
line[maxlen - 1] = 0;
char *p = line;
int nwords = 0;
while (1)
{
while (isspace(*p))
p++;
if (*p == '\0')
return nwords;
words[nwords++] = p;
while (!isspace(*p) && *p != '\0')
p++;
if (*p == '\0')
return nwords;
*p++ = '\0';
if (nwords >= maxwords)
return nwords;
}
}
static ushort saneSonyCameraInfo(uchar a, uchar b, uchar c, uchar d, uchar e, uchar f)
{
if ((a >> 4) > 9)
return 0;
else if ((a & 0x0f) > 9)
return 0;
else if ((b >> 4) > 9)
return 0;
else if ((b & 0x0f) > 9)
return 0;
else if ((c >> 4) > 9)
return 0;
else if ((c & 0x0f) > 9)
return 0;
else if ((d >> 4) > 9)
return 0;
else if ((d & 0x0f) > 9)
return 0;
else if ((e >> 4) > 9)
return 0;
else if ((e & 0x0f) > 9)
return 0;
else if ((f >> 4) > 9)
return 0;
else if ((f & 0x0f) > 9)
return 0;
return 1;
}
static ushort bcd2dec(uchar data)
{
if ((data >> 4) > 9)
return 0;
else if ((data & 0x0f) > 9)
return 0;
else
return (data >> 4) * 10 + (data & 0x0f);
}
static uchar SonySubstitution[257] =
"\x00\x01\x32\xb1\x0a\x0e\x87\x28\x02\xcc\xca\xad\x1b\xdc\x08\xed\x64\x86\xf0\x4f\x8c\x6c\xb8\xcb\x69\xc4\x2c\x03"
"\x97\xb6\x93\x7c\x14\xf3\xe2\x3e\x30\x8e\xd7\x60\x1c\xa1\xab\x37\xec\x75\xbe\x23\x15\x6a\x59\x3f\xd0\xb9\x96\xb5"
"\x50\x27\x88\xe3\x81\x94\xe0\xc0\x04\x5c\xc6\xe8\x5f\x4b\x70\x38\x9f\x82\x80\x51\x2b\xc5\x45\x49\x9b\x21\x52\x53"
"\x54\x85\x0b\x5d\x61\xda\x7b\x55\x26\x24\x07\x6e\x36\x5b\x47\xb7\xd9\x4a\xa2\xdf\xbf\x12\x25\xbc\x1e\x7f\x56\xea"
"\x10\xe6\xcf\x67\x4d\x3c\x91\x83\xe1\x31\xb3\x6f\xf4\x05\x8a\x46\xc8\x18\x76\x68\xbd\xac\x92\x2a\x13\xe9\x0f\xa3"
"\x7a\xdb\x3d\xd4\xe7\x3a\x1a\x57\xaf\x20\x42\xb2\x9e\xc3\x8b\xf2\xd5\xd3\xa4\x7e\x1f\x98\x9c\xee\x74\xa5\xa6\xa7"
"\xd8\x5e\xb0\xb4\x34\xce\xa8\x79\x77\x5a\xc1\x89\xae\x9a\x11\x33\x9d\xf5\x39\x19\x65\x78\x16\x71\xd2\xa9\x44\x63"
"\x40\x29\xba\xa0\x8f\xe4\xd6\x3b\x84\x0d\xc2\x4e\x58\xdd\x99\x22\x6b\xc9\xbb\x17\x06\xe5\x7d\x66\x43\x62\xf6\xcd"
"\x35\x90\x2e\x41\x8d\x6d\xaa\x09\x73\x95\x0c\xf1\x1d\xde\x4c\x2f\x2d\xf7\xd1\x72\xeb\xef\x48\xc7\xf8\xf9\xfa\xfb"
"\xfc\xfd\xfe\xff";
ushort CLASS sget2Rev(uchar *s) // specific to some Canon Makernotes fields, where they have endian in reverse
{
if (order == 0x4d4d) /* "II" means little-endian, and we reverse to "MM" - big endian */
return s[0] | s[1] << 8;
else /* "MM" means big-endian... */
return s[0] << 8 | s[1];
}
#endif
ushort CLASS get2()
{
uchar str[2] = {0xff, 0xff};
fread(str, 1, 2, ifp);
return sget2(str);
}
unsigned CLASS sget4(uchar *s)
{
if (order == 0x4949)
return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
else
return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
}
#define sget4(s) sget4((uchar *)s)
unsigned CLASS get4()
{
uchar str[4] = {0xff, 0xff, 0xff, 0xff};
fread(str, 1, 4, ifp);
return sget4(str);
}
unsigned CLASS getint(int type) { return type == 3 ? get2() : get4(); }
float CLASS int_to_float(int i)
{
union {
int i;
float f;
} u;
u.i = i;
return u.f;
}
double CLASS getreal(int type)
{
union {
char c[8];
double d;
} u, v;
int i, rev;
switch (type)
{
case 3:
return (unsigned short)get2();
case 4:
return (unsigned int)get4();
case 5:
u.d = (unsigned int)get4();
v.d = (unsigned int)get4();
return u.d / (v.d ? v.d : 1);
case 8:
return (signed short)get2();
case 9:
return (signed int)get4();
case 10:
u.d = (signed int)get4();
v.d = (signed int)get4();
return u.d / (v.d ? v.d : 1);
case 11:
return int_to_float(get4());
case 12:
rev = 7 * ((order == 0x4949) == (ntohs(0x1234) == 0x1234));
for (i = 0; i < 8; i++)
u.c[i ^ rev] = fgetc(ifp);
return u.d;
default:
return fgetc(ifp);
}
}
void CLASS read_shorts(ushort *pixel, unsigned count)
{
if (fread(pixel, 2, count, ifp) < count)
derror();
if ((order == 0x4949) == (ntohs(0x1234) == 0x1234))
swab((char *)pixel, (char *)pixel, count * 2);
}
void CLASS cubic_spline(const int *x_, const int *y_, const int len)
{
float **A, *b, *c, *d, *x, *y;
int i, j;
A = (float **)calloc(((2 * len + 4) * sizeof **A + sizeof *A), 2 * len);
if (!A)
return;
A[0] = (float *)(A + 2 * len);
for (i = 1; i < 2 * len; i++)
A[i] = A[0] + 2 * len * i;
y = len + (x = i + (d = i + (c = i + (b = A[0] + i * i))));
for (i = 0; i < len; i++)
{
x[i] = x_[i] / 65535.0;
y[i] = y_[i] / 65535.0;
}
for (i = len - 1; i > 0; i--)
{
b[i] = (y[i] - y[i - 1]) / (x[i] - x[i - 1]);
d[i - 1] = x[i] - x[i - 1];
}
for (i = 1; i < len - 1; i++)
{
A[i][i] = 2 * (d[i - 1] + d[i]);
if (i > 1)
{
A[i][i - 1] = d[i - 1];
A[i - 1][i] = d[i - 1];
}
A[i][len - 1] = 6 * (b[i + 1] - b[i]);
}
for (i = 1; i < len - 2; i++)
{
float v = A[i + 1][i] / A[i][i];
for (j = 1; j <= len - 1; j++)
A[i + 1][j] -= v * A[i][j];
}
for (i = len - 2; i > 0; i--)
{
float acc = 0;
for (j = i; j <= len - 2; j++)
acc += A[i][j] * c[j];
c[i] = (A[i][len - 1] - acc) / A[i][i];
}
for (i = 0; i < 0x10000; i++)
{
float x_out = (float)(i / 65535.0);
float y_out = 0;
for (j = 0; j < len - 1; j++)
{
if (x[j] <= x_out && x_out <= x[j + 1])
{
float v = x_out - x[j];
y_out = y[j] + ((y[j + 1] - y[j]) / d[j] - (2 * d[j] * c[j] + c[j + 1] * d[j]) / 6) * v + (c[j] * 0.5) * v * v +
((c[j + 1] - c[j]) / (6 * d[j])) * v * v * v;
}
}
curve[i] = y_out < 0.0 ? 0 : (y_out >= 1.0 ? 65535 : (ushort)(y_out * 65535.0 + 0.5));
}
free(A);
}
void CLASS canon_600_fixed_wb(int temp)
{
static const short mul[4][5] = {
{667, 358, 397, 565, 452}, {731, 390, 367, 499, 517}, {1119, 396, 348, 448, 537}, {1399, 485, 431, 508, 688}};
int lo, hi, i;
float frac = 0;
for (lo = 4; --lo;)
if (*mul[lo] <= temp)
break;
for (hi = 0; hi < 3; hi++)
if (*mul[hi] >= temp)
break;
if (lo != hi)
frac = (float)(temp - *mul[lo]) / (*mul[hi] - *mul[lo]);
for (i = 1; i < 5; i++)
pre_mul[i - 1] = 1 / (frac * mul[hi][i] + (1 - frac) * mul[lo][i]);
}
/* Return values: 0 = white 1 = near white 2 = not white */
int CLASS canon_600_color(int ratio[2], int mar)
{
int clipped = 0, target, miss;
if (flash_used)
{
if (ratio[1] < -104)
{
ratio[1] = -104;
clipped = 1;
}
if (ratio[1] > 12)
{
ratio[1] = 12;
clipped = 1;
}
}
else
{
if (ratio[1] < -264 || ratio[1] > 461)
return 2;
if (ratio[1] < -50)
{
ratio[1] = -50;
clipped = 1;
}
if (ratio[1] > 307)
{
ratio[1] = 307;
clipped = 1;
}
}
target = flash_used || ratio[1] < 197 ? -38 - (398 * ratio[1] >> 10) : -123 + (48 * ratio[1] >> 10);
if (target - mar <= ratio[0] && target + 20 >= ratio[0] && !clipped)
return 0;
miss = target - ratio[0];
if (abs(miss) >= mar * 4)
return 2;
if (miss < -20)
miss = -20;
if (miss > mar)
miss = mar;
ratio[0] = target - miss;
return 1;
}
void CLASS canon_600_auto_wb()
{
int mar, row, col, i, j, st, count[] = {0, 0};
int test[8], total[2][8], ratio[2][2], stat[2];
memset(&total, 0, sizeof total);
i = canon_ev + 0.5;
if (i < 10)
mar = 150;
else if (i > 12)
mar = 20;
else
mar = 280 - 20 * i;
if (flash_used)
mar = 80;
for (row = 14; row < height - 14; row += 4)
for (col = 10; col < width; col += 2)
{
for (i = 0; i < 8; i++)
test[(i & 4) + FC(row + (i >> 1), col + (i & 1))] = BAYER(row + (i >> 1), col + (i & 1));
for (i = 0; i < 8; i++)
if (test[i] < 150 || test[i] > 1500)
goto next;
for (i = 0; i < 4; i++)
if (abs(test[i] - test[i + 4]) > 50)
goto next;
for (i = 0; i < 2; i++)
{
for (j = 0; j < 4; j += 2)
ratio[i][j >> 1] = ((test[i * 4 + j + 1] - test[i * 4 + j]) << 10) / test[i * 4 + j];
stat[i] = canon_600_color(ratio[i], mar);
}
if ((st = stat[0] | stat[1]) > 1)
goto next;
for (i = 0; i < 2; i++)
if (stat[i])
for (j = 0; j < 2; j++)
test[i * 4 + j * 2 + 1] = test[i * 4 + j * 2] * (0x400 + ratio[i][j]) >> 10;
for (i = 0; i < 8; i++)
total[st][i] += test[i];
count[st]++;
next:;
}
if (count[0] | count[1])
{
st = count[0] * 200 < count[1];
for (i = 0; i < 4; i++)
pre_mul[i] = 1.0 / (total[st][i] + total[st][i + 4]);
}
}
void CLASS canon_600_coeff()
{
static const short table[6][12] = {{-190, 702, -1878, 2390, 1861, -1349, 905, -393, -432, 944, 2617, -2105},
{-1203, 1715, -1136, 1648, 1388, -876, 267, 245, -1641, 2153, 3921, -3409},
{-615, 1127, -1563, 2075, 1437, -925, 509, 3, -756, 1268, 2519, -2007},
{-190, 702, -1886, 2398, 2153, -1641, 763, -251, -452, 964, 3040, -2528},
{-190, 702, -1878, 2390, 1861, -1349, 905, -393, -432, 944, 2617, -2105},
{-807, 1319, -1785, 2297, 1388, -876, 769, -257, -230, 742, 2067, -1555}};
int t = 0, i, c;
float mc, yc;
mc = pre_mul[1] / pre_mul[2];
yc = pre_mul[3] / pre_mul[2];
if (mc > 1 && mc <= 1.28 && yc < 0.8789)
t = 1;
if (mc > 1.28 && mc <= 2)
{
if (yc < 0.8789)
t = 3;
else if (yc <= 2)
t = 4;
}
if (flash_used)
t = 5;
for (raw_color = i = 0; i < 3; i++)
FORCC rgb_cam[i][c] = table[t][i * 4 + c] / 1024.0;
}
void CLASS canon_600_load_raw()
{
uchar data[1120], *dp;
ushort *pix;
int irow, row;
for (irow = row = 0; irow < height; irow++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (fread(data, 1, 1120, ifp) < 1120)
derror();
pix = raw_image + row * raw_width;
for (dp = data; dp < data + 1120; dp += 10, pix += 8)
{
pix[0] = (dp[0] << 2) + (dp[1] >> 6);
pix[1] = (dp[2] << 2) + (dp[1] >> 4 & 3);
pix[2] = (dp[3] << 2) + (dp[1] >> 2 & 3);
pix[3] = (dp[4] << 2) + (dp[1] & 3);
pix[4] = (dp[5] << 2) + (dp[9] & 3);
pix[5] = (dp[6] << 2) + (dp[9] >> 2 & 3);
pix[6] = (dp[7] << 2) + (dp[9] >> 4 & 3);
pix[7] = (dp[8] << 2) + (dp[9] >> 6);
}
if ((row += 2) > height)
row = 1;
}
}
void CLASS canon_600_correct()
{
int row, col, val;
static const short mul[4][2] = {{1141, 1145}, {1128, 1109}, {1178, 1149}, {1128, 1109}};
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < width; col++)
{
if ((val = BAYER(row, col) - black) < 0)
val = 0;
val = val * mul[row & 3][col & 1] >> 9;
BAYER(row, col) = val;
}
}
canon_600_fixed_wb(1311);
canon_600_auto_wb();
canon_600_coeff();
maximum = (0x3ff - black) * 1109 >> 9;
black = 0;
}
int CLASS canon_s2is()
{
unsigned row;
for (row = 0; row < 100; row++)
{
fseek(ifp, row * 3340 + 3284, SEEK_SET);
if (getc(ifp) > 15)
return 1;
}
return 0;
}
unsigned CLASS getbithuff(int nbits, ushort *huff)
{
#ifdef LIBRAW_NOTHREADS
static unsigned bitbuf = 0;
static int vbits = 0, reset = 0;
#else
#define bitbuf tls->getbits.bitbuf
#define vbits tls->getbits.vbits
#define reset tls->getbits.reset
#endif
unsigned c;
if (nbits > 25)
return 0;
if (nbits < 0)
return bitbuf = vbits = reset = 0;
if (nbits == 0 || vbits < 0)
return 0;
while (!reset && vbits < nbits && (c = fgetc(ifp)) != EOF && !(reset = zero_after_ff && c == 0xff && fgetc(ifp)))
{
bitbuf = (bitbuf << 8) + (uchar)c;
vbits += 8;
}
c = bitbuf << (32 - vbits) >> (32 - nbits);
if (huff)
{
vbits -= huff[c] >> 8;
c = (uchar)huff[c];
}
else
vbits -= nbits;
if (vbits < 0)
derror();
return c;
#ifndef LIBRAW_NOTHREADS
#undef bitbuf
#undef vbits
#undef reset
#endif
}
#define getbits(n) getbithuff(n, 0)
#define gethuff(h) getbithuff(*h, h + 1)
/*
Construct a decode tree according the specification in *source.
The first 16 bytes specify how many codes should be 1-bit, 2-bit
3-bit, etc. Bytes after that are the leaf values.
For example, if the source is
{ 0,1,4,2,3,1,2,0,0,0,0,0,0,0,0,0,
0x04,0x03,0x05,0x06,0x02,0x07,0x01,0x08,0x09,0x00,0x0a,0x0b,0xff },
then the code is
00 0x04
010 0x03
011 0x05
100 0x06
101 0x02
1100 0x07
1101 0x01
11100 0x08
11101 0x09
11110 0x00
111110 0x0a
1111110 0x0b
1111111 0xff
*/
ushort *CLASS make_decoder_ref(const uchar **source)
{
int max, len, h, i, j;
const uchar *count;
ushort *huff;
count = (*source += 16) - 17;
for (max = 16; max && !count[max]; max--)
;
huff = (ushort *)calloc(1 + (1 << max), sizeof *huff);
merror(huff, "make_decoder()");
huff[0] = max;
for (h = len = 1; len <= max; len++)
for (i = 0; i < count[len]; i++, ++*source)
for (j = 0; j < 1 << (max - len); j++)
if (h <= 1 << max)
huff[h++] = len << 8 | **source;
return huff;
}
ushort *CLASS make_decoder(const uchar *source) { return make_decoder_ref(&source); }
void CLASS crw_init_tables(unsigned table, ushort *huff[2])
{
static const uchar first_tree[3][29] = {
{0, 1, 4, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0x04, 0x03, 0x05, 0x06, 0x02, 0x07, 0x01, 0x08, 0x09, 0x00, 0x0a, 0x0b, 0xff},
{0, 2, 2, 3, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0,
0, 0x03, 0x02, 0x04, 0x01, 0x05, 0x00, 0x06, 0x07, 0x09, 0x08, 0x0a, 0x0b, 0xff},
{0, 0, 6, 3, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0x06, 0x05, 0x07, 0x04, 0x08, 0x03, 0x09, 0x02, 0x00, 0x0a, 0x01, 0x0b, 0xff},
};
static const uchar second_tree[3][180] = {
{0, 2, 2, 2, 1, 4, 2, 1, 2, 5, 1, 1, 0, 0, 0, 139, 0x03, 0x04,
0x02, 0x05, 0x01, 0x06, 0x07, 0x08, 0x12, 0x13, 0x11, 0x14, 0x09, 0x15, 0x22, 0x00, 0x21, 0x16, 0x0a, 0xf0,
0x23, 0x17, 0x24, 0x31, 0x32, 0x18, 0x19, 0x33, 0x25, 0x41, 0x34, 0x42, 0x35, 0x51, 0x36, 0x37, 0x38, 0x29,
0x79, 0x26, 0x1a, 0x39, 0x56, 0x57, 0x28, 0x27, 0x52, 0x55, 0x58, 0x43, 0x76, 0x59, 0x77, 0x54, 0x61, 0xf9,
0x71, 0x78, 0x75, 0x96, 0x97, 0x49, 0xb7, 0x53, 0xd7, 0x74, 0xb6, 0x98, 0x47, 0x48, 0x95, 0x69, 0x99, 0x91,
0xfa, 0xb8, 0x68, 0xb5, 0xb9, 0xd6, 0xf7, 0xd8, 0x67, 0x46, 0x45, 0x94, 0x89, 0xf8, 0x81, 0xd5, 0xf6, 0xb4,
0x88, 0xb1, 0x2a, 0x44, 0x72, 0xd9, 0x87, 0x66, 0xd4, 0xf5, 0x3a, 0xa7, 0x73, 0xa9, 0xa8, 0x86, 0x62, 0xc7,
0x65, 0xc8, 0xc9, 0xa1, 0xf4, 0xd1, 0xe9, 0x5a, 0x92, 0x85, 0xa6, 0xe7, 0x93, 0xe8, 0xc1, 0xc6, 0x7a, 0x64,
0xe1, 0x4a, 0x6a, 0xe6, 0xb3, 0xf1, 0xd3, 0xa5, 0x8a, 0xb2, 0x9a, 0xba, 0x84, 0xa4, 0x63, 0xe5, 0xc5, 0xf3,
0xd2, 0xc4, 0x82, 0xaa, 0xda, 0xe4, 0xf2, 0xca, 0x83, 0xa3, 0xa2, 0xc3, 0xea, 0xc2, 0xe2, 0xe3, 0xff, 0xff},
{0, 2, 2, 1, 4, 1, 4, 1, 3, 3, 1, 0, 0, 0, 0, 140, 0x02, 0x03,
0x01, 0x04, 0x05, 0x12, 0x11, 0x06, 0x13, 0x07, 0x08, 0x14, 0x22, 0x09, 0x21, 0x00, 0x23, 0x15, 0x31, 0x32,
0x0a, 0x16, 0xf0, 0x24, 0x33, 0x41, 0x42, 0x19, 0x17, 0x25, 0x18, 0x51, 0x34, 0x43, 0x52, 0x29, 0x35, 0x61,
0x39, 0x71, 0x62, 0x36, 0x53, 0x26, 0x38, 0x1a, 0x37, 0x81, 0x27, 0x91, 0x79, 0x55, 0x45, 0x28, 0x72, 0x59,
0xa1, 0xb1, 0x44, 0x69, 0x54, 0x58, 0xd1, 0xfa, 0x57, 0xe1, 0xf1, 0xb9, 0x49, 0x47, 0x63, 0x6a, 0xf9, 0x56,
0x46, 0xa8, 0x2a, 0x4a, 0x78, 0x99, 0x3a, 0x75, 0x74, 0x86, 0x65, 0xc1, 0x76, 0xb6, 0x96, 0xd6, 0x89, 0x85,
0xc9, 0xf5, 0x95, 0xb4, 0xc7, 0xf7, 0x8a, 0x97, 0xb8, 0x73, 0xb7, 0xd8, 0xd9, 0x87, 0xa7, 0x7a, 0x48, 0x82,
0x84, 0xea, 0xf4, 0xa6, 0xc5, 0x5a, 0x94, 0xa4, 0xc6, 0x92, 0xc3, 0x68, 0xb5, 0xc8, 0xe4, 0xe5, 0xe6, 0xe9,
0xa2, 0xa3, 0xe3, 0xc2, 0x66, 0x67, 0x93, 0xaa, 0xd4, 0xd5, 0xe7, 0xf8, 0x88, 0x9a, 0xd7, 0x77, 0xc4, 0x64,
0xe2, 0x98, 0xa5, 0xca, 0xda, 0xe8, 0xf3, 0xf6, 0xa9, 0xb2, 0xb3, 0xf2, 0xd2, 0x83, 0xba, 0xd3, 0xff, 0xff},
{0, 0, 6, 2, 1, 3, 3, 2, 5, 1, 2, 2, 8, 10, 0, 117, 0x04, 0x05,
0x03, 0x06, 0x02, 0x07, 0x01, 0x08, 0x09, 0x12, 0x13, 0x14, 0x11, 0x15, 0x0a, 0x16, 0x17, 0xf0, 0x00, 0x22,
0x21, 0x18, 0x23, 0x19, 0x24, 0x32, 0x31, 0x25, 0x33, 0x38, 0x37, 0x34, 0x35, 0x36, 0x39, 0x79, 0x57, 0x58,
0x59, 0x28, 0x56, 0x78, 0x27, 0x41, 0x29, 0x77, 0x26, 0x42, 0x76, 0x99, 0x1a, 0x55, 0x98, 0x97, 0xf9, 0x48,
0x54, 0x96, 0x89, 0x47, 0xb7, 0x49, 0xfa, 0x75, 0x68, 0xb6, 0x67, 0x69, 0xb9, 0xb8, 0xd8, 0x52, 0xd7, 0x88,
0xb5, 0x74, 0x51, 0x46, 0xd9, 0xf8, 0x3a, 0xd6, 0x87, 0x45, 0x7a, 0x95, 0xd5, 0xf6, 0x86, 0xb4, 0xa9, 0x94,
0x53, 0x2a, 0xa8, 0x43, 0xf5, 0xf7, 0xd4, 0x66, 0xa7, 0x5a, 0x44, 0x8a, 0xc9, 0xe8, 0xc8, 0xe7, 0x9a, 0x6a,
0x73, 0x4a, 0x61, 0xc7, 0xf4, 0xc6, 0x65, 0xe9, 0x72, 0xe6, 0x71, 0x91, 0x93, 0xa6, 0xda, 0x92, 0x85, 0x62,
0xf3, 0xc5, 0xb2, 0xa4, 0x84, 0xba, 0x64, 0xa5, 0xb3, 0xd2, 0x81, 0xe5, 0xd3, 0xaa, 0xc4, 0xca, 0xf2, 0xb1,
0xe4, 0xd1, 0x83, 0x63, 0xea, 0xc3, 0xe2, 0x82, 0xf1, 0xa3, 0xc2, 0xa1, 0xc1, 0xe3, 0xa2, 0xe1, 0xff, 0xff}};
if (table > 2)
table = 2;
huff[0] = make_decoder(first_tree[table]);
huff[1] = make_decoder(second_tree[table]);
}
/*
Return 0 if the image starts with compressed data,
1 if it starts with uncompressed low-order bits.
In Canon compressed data, 0xff is always followed by 0x00.
*/
int CLASS canon_has_lowbits()
{
uchar test[0x4000];
int ret = 1, i;
fseek(ifp, 0, SEEK_SET);
fread(test, 1, sizeof test, ifp);
for (i = 540; i < sizeof test - 1; i++)
if (test[i] == 0xff)
{
if (test[i + 1])
return 1;
ret = 0;
}
return ret;
}
void CLASS canon_load_raw()
{
ushort *pixel, *prow, *huff[2];
int nblocks, lowbits, i, c, row, r, save, val;
int block, diffbuf[64], leaf, len, diff, carry = 0, pnum = 0, base[2];
crw_init_tables(tiff_compress, huff);
lowbits = canon_has_lowbits();
if (!lowbits)
maximum = 0x3ff;
fseek(ifp, 540 + lowbits * raw_height * raw_width / 4, SEEK_SET);
zero_after_ff = 1;
getbits(-1);
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < raw_height; row += 8)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
pixel = raw_image + row * raw_width;
nblocks = MIN(8, raw_height - row) * raw_width >> 6;
for (block = 0; block < nblocks; block++)
{
memset(diffbuf, 0, sizeof diffbuf);
for (i = 0; i < 64; i++)
{
leaf = gethuff(huff[i > 0]);
if (leaf == 0 && i)
break;
if (leaf == 0xff)
continue;
i += leaf >> 4;
len = leaf & 15;
if (len == 0)
continue;
diff = getbits(len);
if ((diff & (1 << (len - 1))) == 0)
diff -= (1 << len) - 1;
if (i < 64)
diffbuf[i] = diff;
}
diffbuf[0] += carry;
carry = diffbuf[0];
for (i = 0; i < 64; i++)
{
if (pnum++ % raw_width == 0)
base[0] = base[1] = 512;
if ((pixel[(block << 6) + i] = base[i & 1] += diffbuf[i]) >> 10)
derror();
}
}
if (lowbits)
{
save = ftell(ifp);
fseek(ifp, 26 + row * raw_width / 4, SEEK_SET);
for (prow = pixel, i = 0; i < raw_width * 2; i++)
{
c = fgetc(ifp);
for (r = 0; r < 8; r += 2, prow++)
{
val = (*prow << 2) + ((c >> r) & 3);
if (raw_width == 2672 && val < 512)
val += 2;
*prow = val;
}
}
fseek(ifp, save, SEEK_SET);
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
FORC(2) free(huff[c]);
throw;
}
#endif
FORC(2) free(huff[c]);
}
int CLASS ljpeg_start(struct jhead *jh, int info_only)
{
ushort c, tag, len;
int cnt = 0;
uchar data[0x10000];
const uchar *dp;
memset(jh, 0, sizeof *jh);
jh->restart = INT_MAX;
if ((fgetc(ifp), fgetc(ifp)) != 0xd8)
return 0;
do
{
if (feof(ifp))
return 0;
if (cnt++ > 1024)
return 0; // 1024 tags limit
if (!fread(data, 2, 2, ifp))
return 0;
tag = data[0] << 8 | data[1];
len = (data[2] << 8 | data[3]) - 2;
if (tag <= 0xff00)
return 0;
fread(data, 1, len, ifp);
switch (tag)
{
case 0xffc3: // start of frame; lossless, Huffman
jh->sraw = ((data[7] >> 4) * (data[7] & 15) - 1) & 3;
case 0xffc1:
case 0xffc0:
jh->algo = tag & 0xff;
jh->bits = data[0];
jh->high = data[1] << 8 | data[2];
jh->wide = data[3] << 8 | data[4];
jh->clrs = data[5] + jh->sraw;
if (len == 9 && !dng_version)
getc(ifp);
break;
case 0xffc4: // define Huffman tables
if (info_only)
break;
for (dp = data; dp < data + len && !((c = *dp++) & -20);)
jh->free[c] = jh->huff[c] = make_decoder_ref(&dp);
break;
case 0xffda: // start of scan
jh->psv = data[1 + data[0] * 2];
jh->bits -= data[3 + data[0] * 2] & 15;
break;
case 0xffdb:
FORC(64) jh->quant[c] = data[c * 2 + 1] << 8 | data[c * 2 + 2];
break;
case 0xffdd:
jh->restart = data[0] << 8 | data[1];
}
} while (tag != 0xffda);
if (jh->bits > 16 || jh->clrs > 6 || !jh->bits || !jh->high || !jh->wide || !jh->clrs)
return 0;
if (info_only)
return 1;
if (!jh->huff[0])
return 0;
FORC(19) if (!jh->huff[c + 1]) jh->huff[c + 1] = jh->huff[c];
if (jh->sraw)
{
FORC(4) jh->huff[2 + c] = jh->huff[1];
FORC(jh->sraw) jh->huff[1 + c] = jh->huff[0];
}
jh->row = (ushort *)calloc(jh->wide * jh->clrs, 4);
merror(jh->row, "ljpeg_start()");
return zero_after_ff = 1;
}
void CLASS ljpeg_end(struct jhead *jh)
{
int c;
FORC4 if (jh->free[c]) free(jh->free[c]);
free(jh->row);
}
int CLASS ljpeg_diff(ushort *huff)
{
int len, diff;
if (!huff)
#ifdef LIBRAW_LIBRARY_BUILD
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#else
longjmp(failure, 2);
#endif
len = gethuff(huff);
if (len == 16 && (!dng_version || dng_version >= 0x1010000))
return -32768;
diff = getbits(len);
if ((diff & (1 << (len - 1))) == 0)
diff -= (1 << len) - 1;
return diff;
}
ushort *CLASS ljpeg_row(int jrow, struct jhead *jh)
{
int col, c, diff, pred, spred = 0;
ushort mark = 0, *row[3];
if (jrow * jh->wide % jh->restart == 0)
{
FORC(6) jh->vpred[c] = 1 << (jh->bits - 1);
if (jrow)
{
fseek(ifp, -2, SEEK_CUR);
do
mark = (mark << 8) + (c = fgetc(ifp));
while (c != EOF && mark >> 4 != 0xffd);
}
getbits(-1);
}
FORC3 row[c] = jh->row + jh->wide * jh->clrs * ((jrow + c) & 1);
for (col = 0; col < jh->wide; col++)
FORC(jh->clrs)
{
diff = ljpeg_diff(jh->huff[c]);
if (jh->sraw && c <= jh->sraw && (col | c))
pred = spred;
else if (col)
pred = row[0][-jh->clrs];
else
pred = (jh->vpred[c] += diff) - diff;
if (jrow && col)
switch (jh->psv)
{
case 1:
break;
case 2:
pred = row[1][0];
break;
case 3:
pred = row[1][-jh->clrs];
break;
case 4:
pred = pred + row[1][0] - row[1][-jh->clrs];
break;
case 5:
pred = pred + ((row[1][0] - row[1][-jh->clrs]) >> 1);
break;
case 6:
pred = row[1][0] + ((pred - row[1][-jh->clrs]) >> 1);
break;
case 7:
pred = (pred + row[1][0]) >> 1;
break;
default:
pred = 0;
}
if ((**row = pred + diff) >> jh->bits)
derror();
if (c <= jh->sraw)
spred = **row;
row[0]++;
row[1]++;
}
return row[2];
}
void CLASS lossless_jpeg_load_raw()
{
int jwide, jhigh, jrow, jcol, val, jidx, i, j, row = 0, col = 0;
struct jhead jh;
ushort *rp;
if (!ljpeg_start(&jh, 0))
return;
if (jh.wide < 1 || jh.high < 1 || jh.clrs < 1 || jh.bits < 1)
#ifdef LIBRAW_LIBRARY_BUILD
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#else
longjmp(failure, 2);
#endif
jwide = jh.wide * jh.clrs;
jhigh = jh.high;
if (jh.clrs == 4 && jwide >= raw_width * 2)
jhigh *= 2;
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (jrow = 0; jrow < jh.high; jrow++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
rp = ljpeg_row(jrow, &jh);
if (load_flags & 1)
row = jrow & 1 ? height - 1 - jrow / 2 : jrow / 2;
for (jcol = 0; jcol < jwide; jcol++)
{
val = curve[*rp++];
if (cr2_slice[0])
{
jidx = jrow * jwide + jcol;
i = jidx / (cr2_slice[1] * raw_height);
if ((j = i >= cr2_slice[0]))
i = cr2_slice[0];
jidx -= i * (cr2_slice[1] * raw_height);
row = jidx / cr2_slice[1 + j];
col = jidx % cr2_slice[1 + j] + i * cr2_slice[1];
}
if (raw_width == 3984 && (col -= 2) < 0)
col += (row--, raw_width);
if (row > raw_height)
#ifdef LIBRAW_LIBRARY_BUILD
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#else
longjmp(failure, 3);
#endif
if ((unsigned)row < raw_height)
RAW(row, col) = val;
if (++col >= raw_width)
col = (row++, 0);
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
ljpeg_end(&jh);
throw;
}
#endif
ljpeg_end(&jh);
}
void CLASS canon_sraw_load_raw()
{
struct jhead jh;
short *rp = 0, (*ip)[4];
int jwide, slice, scol, ecol, row, col, jrow = 0, jcol = 0, pix[3], c;
int v[3] = {0, 0, 0}, ver, hue;
#ifdef LIBRAW_LIBRARY_BUILD
int saved_w = width, saved_h = height;
#endif
char *cp;
if (!ljpeg_start(&jh, 0) || jh.clrs < 4)
return;
jwide = (jh.wide >>= 1) * jh.clrs;
#ifdef LIBRAW_LIBRARY_BUILD
if (load_flags & 256)
{
width = raw_width;
height = raw_height;
}
try
{
#endif
for (ecol = slice = 0; slice <= cr2_slice[0]; slice++)
{
scol = ecol;
ecol += cr2_slice[1] * 2 / jh.clrs;
if (!cr2_slice[0] || ecol > raw_width - 1)
ecol = raw_width & -2;
for (row = 0; row < height; row += (jh.clrs >> 1) - 1)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
ip = (short(*)[4])image + row * width;
for (col = scol; col < ecol; col += 2, jcol += jh.clrs)
{
if ((jcol %= jwide) == 0)
rp = (short *)ljpeg_row(jrow++, &jh);
if (col >= width)
continue;
#ifdef LIBRAW_LIBRARY_BUILD
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SRAW_NO_INTERPOLATE)
{
FORC(jh.clrs - 2)
{
ip[col + (c >> 1) * width + (c & 1)][0] = rp[jcol + c];
ip[col + (c >> 1) * width + (c & 1)][1] = ip[col + (c >> 1) * width + (c & 1)][2] = 8192;
}
ip[col][1] = rp[jcol + jh.clrs - 2] - 8192;
ip[col][2] = rp[jcol + jh.clrs - 1] - 8192;
}
else if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SRAW_NO_RGB)
{
FORC(jh.clrs - 2)
ip[col + (c >> 1) * width + (c & 1)][0] = rp[jcol + c];
ip[col][1] = rp[jcol + jh.clrs - 2] - 8192;
ip[col][2] = rp[jcol + jh.clrs - 1] - 8192;
}
else
#endif
{
FORC(jh.clrs - 2)
ip[col + (c >> 1) * width + (c & 1)][0] = rp[jcol + c];
ip[col][1] = rp[jcol + jh.clrs - 2] - 16384;
ip[col][2] = rp[jcol + jh.clrs - 1] - 16384;
}
}
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
ljpeg_end(&jh);
throw;
}
#endif
#ifdef LIBRAW_LIBRARY_BUILD
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SRAW_NO_INTERPOLATE)
{
ljpeg_end(&jh);
maximum = 0x3fff;
height = saved_h;
width = saved_w;
return;
}
#endif
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (cp = model2; *cp && !isdigit(*cp); cp++)
;
sscanf(cp, "%d.%d.%d", v, v + 1, v + 2);
ver = (v[0] * 1000 + v[1]) * 1000 + v[2];
hue = (jh.sraw + 1) << 2;
if (unique_id >= 0x80000281 || (unique_id == 0x80000218 && ver > 1000006))
hue = jh.sraw << 1;
ip = (short(*)[4])image;
rp = ip[0];
for (row = 0; row < height; row++, ip += width)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (row & (jh.sraw >> 1))
{
for (col = 0; col < width; col += 2)
for (c = 1; c < 3; c++)
if (row == height - 1)
{
ip[col][c] = ip[col - width][c];
}
else
{
ip[col][c] = (ip[col - width][c] + ip[col + width][c] + 1) >> 1;
}
}
for (col = 1; col < width; col += 2)
for (c = 1; c < 3; c++)
if (col == width - 1)
ip[col][c] = ip[col - 1][c];
else
ip[col][c] = (ip[col - 1][c] + ip[col + 1][c] + 1) >> 1;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (!(imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SRAW_NO_RGB))
#endif
for (; rp < ip[0]; rp += 4)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (unique_id == 0x80000218 || unique_id == 0x80000250 || unique_id == 0x80000261 || unique_id == 0x80000281 ||
unique_id == 0x80000287)
{
rp[1] = (rp[1] << 2) + hue;
rp[2] = (rp[2] << 2) + hue;
pix[0] = rp[0] + ((50 * rp[1] + 22929 * rp[2]) >> 14);
pix[1] = rp[0] + ((-5640 * rp[1] - 11751 * rp[2]) >> 14);
pix[2] = rp[0] + ((29040 * rp[1] - 101 * rp[2]) >> 14);
}
else
{
if (unique_id < 0x80000218)
rp[0] -= 512;
pix[0] = rp[0] + rp[2];
pix[2] = rp[0] + rp[1];
pix[1] = rp[0] + ((-778 * rp[1] - (rp[2] << 11)) >> 12);
}
FORC3 rp[c] = CLIP15(pix[c] * sraw_mul[c] >> 10);
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
ljpeg_end(&jh);
throw;
}
height = saved_h;
width = saved_w;
#endif
ljpeg_end(&jh);
maximum = 0x3fff;
}
void CLASS adobe_copy_pixel(unsigned row, unsigned col, ushort **rp)
{
int c;
if (tiff_samples == 2 && shot_select)
(*rp)++;
if (raw_image)
{
if (row < raw_height && col < raw_width)
RAW(row, col) = curve[**rp];
*rp += tiff_samples;
}
else
{
#ifdef LIBRAW_LIBRARY_BUILD
if (row < raw_height && col < raw_width)
FORC(tiff_samples)
image[row * raw_width + col][c] = curve[(*rp)[c]];
*rp += tiff_samples;
#else
if (row < height && col < width)
FORC(tiff_samples)
image[row * width + col][c] = curve[(*rp)[c]];
*rp += tiff_samples;
#endif
}
if (tiff_samples == 2 && shot_select)
(*rp)--;
}
void CLASS ljpeg_idct(struct jhead *jh)
{
int c, i, j, len, skip, coef;
float work[3][8][8];
static float cs[106] = {0};
static const uchar zigzag[80] = {0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33,
40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54,
47, 55, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63};
if (!cs[0])
FORC(106) cs[c] = cos((c & 31) * M_PI / 16) / 2;
memset(work, 0, sizeof work);
work[0][0][0] = jh->vpred[0] += ljpeg_diff(jh->huff[0]) * jh->quant[0];
for (i = 1; i < 64; i++)
{
len = gethuff(jh->huff[16]);
i += skip = len >> 4;
if (!(len &= 15) && skip < 15)
break;
coef = getbits(len);
if ((coef & (1 << (len - 1))) == 0)
coef -= (1 << len) - 1;
((float *)work)[zigzag[i]] = coef * jh->quant[i];
}
FORC(8) work[0][0][c] *= M_SQRT1_2;
FORC(8) work[0][c][0] *= M_SQRT1_2;
for (i = 0; i < 8; i++)
for (j = 0; j < 8; j++)
FORC(8) work[1][i][j] += work[0][i][c] * cs[(j * 2 + 1) * c];
for (i = 0; i < 8; i++)
for (j = 0; j < 8; j++)
FORC(8) work[2][i][j] += work[1][c][j] * cs[(i * 2 + 1) * c];
FORC(64) jh->idct[c] = CLIP(((float *)work[2])[c] + 0.5);
}
void CLASS lossless_dng_load_raw()
{
unsigned save, trow = 0, tcol = 0, jwide, jrow, jcol, row, col, i, j;
struct jhead jh;
ushort *rp;
while (trow < raw_height)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
save = ftell(ifp);
if (tile_length < INT_MAX)
fseek(ifp, get4(), SEEK_SET);
if (!ljpeg_start(&jh, 0))
break;
jwide = jh.wide;
if (filters)
jwide *= jh.clrs;
jwide /= MIN(is_raw, tiff_samples);
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
switch (jh.algo)
{
case 0xc1:
jh.vpred[0] = 16384;
getbits(-1);
for (jrow = 0; jrow + 7 < jh.high; jrow += 8)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (jcol = 0; jcol + 7 < jh.wide; jcol += 8)
{
ljpeg_idct(&jh);
rp = jh.idct;
row = trow + jcol / tile_width + jrow * 2;
col = tcol + jcol % tile_width;
for (i = 0; i < 16; i += 2)
for (j = 0; j < 8; j++)
adobe_copy_pixel(row + i, col + j, &rp);
}
}
break;
case 0xc3:
for (row = col = jrow = 0; jrow < jh.high; jrow++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
rp = ljpeg_row(jrow, &jh);
if(tiff_samples == 1 && jh.clrs > 1 && jh.clrs*jwide == raw_width)
for (jcol = 0; jcol < jwide*jh.clrs; jcol++)
{
adobe_copy_pixel(trow + row, tcol + col, &rp);
if (++col >= tile_width || col >= raw_width)
row += 1 + (col = 0);
}
else
for (jcol = 0; jcol < jwide; jcol++)
{
adobe_copy_pixel(trow + row, tcol + col, &rp);
if (++col >= tile_width || col >= raw_width)
row += 1 + (col = 0);
}
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
ljpeg_end(&jh);
throw;
}
#endif
fseek(ifp, save + 4, SEEK_SET);
if ((tcol += tile_width) >= raw_width)
trow += tile_length + (tcol = 0);
ljpeg_end(&jh);
}
}
void CLASS packed_dng_load_raw()
{
ushort *pixel, *rp;
int row, col;
pixel = (ushort *)calloc(raw_width, tiff_samples * sizeof *pixel);
merror(pixel, "packed_dng_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (tiff_bps == 16)
read_shorts(pixel, raw_width * tiff_samples);
else
{
getbits(-1);
for (col = 0; col < raw_width * tiff_samples; col++)
pixel[col] = getbits(tiff_bps);
}
for (rp = pixel, col = 0; col < raw_width; col++)
adobe_copy_pixel(row, col, &rp);
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
}
void CLASS pentax_load_raw()
{
ushort bit[2][15], huff[4097];
int dep, row, col, diff, c, i;
ushort vpred[2][2] = {{0, 0}, {0, 0}}, hpred[2];
fseek(ifp, meta_offset, SEEK_SET);
dep = (get2() + 12) & 15;
fseek(ifp, 12, SEEK_CUR);
FORC(dep) bit[0][c] = get2();
FORC(dep) bit[1][c] = fgetc(ifp);
FORC(dep)
for (i = bit[0][c]; i <= ((bit[0][c] + (4096 >> bit[1][c]) - 1) & 4095);)
huff[++i] = bit[1][c] << 8 | c;
huff[0] = 12;
fseek(ifp, data_offset, SEEK_SET);
getbits(-1);
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
{
diff = ljpeg_diff(huff);
if (col < 2)
hpred[col] = vpred[row & 1][col] += diff;
else
hpred[col & 1] += diff;
RAW(row, col) = hpred[col & 1];
if (hpred[col & 1] >> tiff_bps)
derror();
}
}
}
#ifdef LIBRAW_LIBRARY_BUILD
void CLASS nikon_coolscan_load_raw()
{
if(!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
int bypp = tiff_bps <= 8 ? 1 : 2;
int bufsize = width * 3 * bypp;
if (tiff_bps <= 8)
gamma_curve(1.0 / imgdata.params.coolscan_nef_gamma, 0., 1, 255);
else
gamma_curve(1.0 / imgdata.params.coolscan_nef_gamma, 0., 1, 65535);
fseek(ifp, data_offset, SEEK_SET);
unsigned char *buf = (unsigned char *)malloc(bufsize);
unsigned short *ubuf = (unsigned short *)buf;
for (int row = 0; row < raw_height; row++)
{
int red = fread(buf, 1, bufsize, ifp);
unsigned short(*ip)[4] = (unsigned short(*)[4])image + row * width;
if (tiff_bps <= 8)
for (int col = 0; col < width; col++)
{
ip[col][0] = curve[buf[col * 3]];
ip[col][1] = curve[buf[col * 3 + 1]];
ip[col][2] = curve[buf[col * 3 + 2]];
ip[col][3] = 0;
}
else
for (int col = 0; col < width; col++)
{
ip[col][0] = curve[ubuf[col * 3]];
ip[col][1] = curve[ubuf[col * 3 + 1]];
ip[col][2] = curve[ubuf[col * 3 + 2]];
ip[col][3] = 0;
}
}
free(buf);
}
#endif
void CLASS nikon_load_raw()
{
static const uchar nikon_tree[][32] = {
{0, 1, 5, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, /* 12-bit lossy */
5, 4, 3, 6, 2, 7, 1, 0, 8, 9, 11, 10, 12},
{0, 1, 5, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, /* 12-bit lossy after split */
0x39, 0x5a, 0x38, 0x27, 0x16, 5, 4, 3, 2, 1, 0, 11, 12, 12},
{0, 1, 4, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 12-bit lossless */
5, 4, 6, 3, 7, 2, 8, 1, 9, 0, 10, 11, 12},
{0, 1, 4, 3, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, /* 14-bit lossy */
5, 6, 4, 7, 8, 3, 9, 2, 1, 0, 10, 11, 12, 13, 14},
{0, 1, 5, 1, 1, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, /* 14-bit lossy after split */
8, 0x5c, 0x4b, 0x3a, 0x29, 7, 6, 5, 4, 3, 2, 1, 0, 13, 14},
{0, 1, 4, 2, 2, 3, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, /* 14-bit lossless */
7, 6, 8, 5, 9, 4, 10, 3, 11, 12, 2, 0, 1, 13, 14}};
ushort *huff, ver0, ver1, vpred[2][2], hpred[2], csize;
int i, min, max, step = 0, tree = 0, split = 0, row, col, len, shl, diff;
fseek(ifp, meta_offset, SEEK_SET);
ver0 = fgetc(ifp);
ver1 = fgetc(ifp);
if (ver0 == 0x49 || ver1 == 0x58)
fseek(ifp, 2110, SEEK_CUR);
if (ver0 == 0x46)
tree = 2;
if (tiff_bps == 14)
tree += 3;
read_shorts(vpred[0], 4);
max = 1 << tiff_bps & 0x7fff;
if ((csize = get2()) > 1)
step = max / (csize - 1);
if (ver0 == 0x44 && ver1 == 0x20 && step > 0)
{
for (i = 0; i < csize; i++)
curve[i * step] = get2();
for (i = 0; i < max; i++)
curve[i] = (curve[i - i % step] * (step - i % step) + curve[i - i % step + step] * (i % step)) / step;
fseek(ifp, meta_offset + 562, SEEK_SET);
split = get2();
}
else if (ver0 != 0x46 && csize <= 0x4001)
read_shorts(curve, max = csize);
while (curve[max - 2] == curve[max - 1])
max--;
huff = make_decoder(nikon_tree[tree]);
fseek(ifp, data_offset, SEEK_SET);
getbits(-1);
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (min = row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (split && row == split)
{
free(huff);
huff = make_decoder(nikon_tree[tree + 1]);
max += (min = 16) << 1;
}
for (col = 0; col < raw_width; col++)
{
i = gethuff(huff);
len = i & 15;
shl = i >> 4;
diff = ((getbits(len - shl) << 1) + 1) << shl >> 1;
if ((diff & (1 << (len - 1))) == 0)
diff -= (1 << len) - !shl;
if (col < 2)
hpred[col] = vpred[row & 1][col] += diff;
else
hpred[col & 1] += diff;
if ((ushort)(hpred[col & 1] + min) >= max)
derror();
RAW(row, col) = curve[LIM((short)hpred[col & 1], 0, 0x3fff)];
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(huff);
throw;
}
#endif
free(huff);
}
void CLASS nikon_yuv_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
int row, col, yuv[4], rgb[3], b, c;
UINT64 bitbuf = 0;
float cmul[4];
FORC4 { cmul[c] = cam_mul[c] > 0.001f ? cam_mul[c] : 1.f; }
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
{
if (!(b = col & 1))
{
bitbuf = 0;
FORC(6) bitbuf |= (UINT64)fgetc(ifp) << c * 8;
FORC(4) yuv[c] = (bitbuf >> c * 12 & 0xfff) - (c >> 1 << 11);
}
rgb[0] = yuv[b] + 1.370705 * yuv[3];
rgb[1] = yuv[b] - 0.337633 * yuv[2] - 0.698001 * yuv[3];
rgb[2] = yuv[b] + 1.732446 * yuv[2];
FORC3 image[row * width + col][c] = curve[LIM(rgb[c], 0, 0xfff)] / cmul[c];
}
}
}
/*
Returns 1 for a Coolpix 995, 0 for anything else.
*/
int CLASS nikon_e995()
{
int i, histo[256];
const uchar often[] = {0x00, 0x55, 0xaa, 0xff};
memset(histo, 0, sizeof histo);
fseek(ifp, -2000, SEEK_END);
for (i = 0; i < 2000; i++)
histo[fgetc(ifp)]++;
for (i = 0; i < 4; i++)
if (histo[often[i]] < 200)
return 0;
return 1;
}
/*
Returns 1 for a Coolpix 2100, 0 for anything else.
*/
int CLASS nikon_e2100()
{
uchar t[12];
int i;
fseek(ifp, 0, SEEK_SET);
for (i = 0; i < 1024; i++)
{
fread(t, 1, 12, ifp);
if (((t[2] & t[4] & t[7] & t[9]) >> 4 & t[1] & t[6] & t[8] & t[11] & 3) != 3)
return 0;
}
return 1;
}
void CLASS nikon_3700()
{
int bits, i;
uchar dp[24];
static const struct
{
int bits;
char t_make[12], t_model[15];
} table[] = {
{0x00, "Pentax", "Optio 33WR"}, {0x03, "Nikon", "E3200"}, {0x32, "Nikon", "E3700"}, {0x33, "Olympus", "C740UZ"}};
fseek(ifp, 3072, SEEK_SET);
fread(dp, 1, 24, ifp);
bits = (dp[8] & 3) << 4 | (dp[20] & 3);
for (i = 0; i < sizeof table / sizeof *table; i++)
if (bits == table[i].bits)
{
strcpy(make, table[i].t_make);
strcpy(model, table[i].t_model);
}
}
/*
Separates a Minolta DiMAGE Z2 from a Nikon E4300.
*/
int CLASS minolta_z2()
{
int i, nz;
char tail[424];
fseek(ifp, -sizeof tail, SEEK_END);
fread(tail, 1, sizeof tail, ifp);
for (nz = i = 0; i < sizeof tail; i++)
if (tail[i])
nz++;
return nz > 20;
}
void CLASS ppm_thumb()
{
char *thumb;
thumb_length = thumb_width * thumb_height * 3;
thumb = (char *)malloc(thumb_length);
merror(thumb, "ppm_thumb()");
fprintf(ofp, "P6\n%d %d\n255\n", thumb_width, thumb_height);
fread(thumb, 1, thumb_length, ifp);
fwrite(thumb, 1, thumb_length, ofp);
free(thumb);
}
void CLASS ppm16_thumb()
{
int i;
char *thumb;
thumb_length = thumb_width * thumb_height * 3;
thumb = (char *)calloc(thumb_length, 2);
merror(thumb, "ppm16_thumb()");
read_shorts((ushort *)thumb, thumb_length);
for (i = 0; i < thumb_length; i++)
thumb[i] = ((ushort *)thumb)[i] >> 8;
fprintf(ofp, "P6\n%d %d\n255\n", thumb_width, thumb_height);
fwrite(thumb, 1, thumb_length, ofp);
free(thumb);
}
void CLASS layer_thumb()
{
int i, c;
char *thumb, map[][4] = {"012", "102"};
colors = thumb_misc >> 5 & 7;
thumb_length = thumb_width * thumb_height;
thumb = (char *)calloc(colors, thumb_length);
merror(thumb, "layer_thumb()");
fprintf(ofp, "P%d\n%d %d\n255\n", 5 + (colors >> 1), thumb_width, thumb_height);
fread(thumb, thumb_length, colors, ifp);
for (i = 0; i < thumb_length; i++)
FORCC putc(thumb[i + thumb_length * (map[thumb_misc >> 8][c] - '0')], ofp);
free(thumb);
}
void CLASS rollei_thumb()
{
unsigned i;
ushort *thumb;
thumb_length = thumb_width * thumb_height;
thumb = (ushort *)calloc(thumb_length, 2);
merror(thumb, "rollei_thumb()");
fprintf(ofp, "P6\n%d %d\n255\n", thumb_width, thumb_height);
read_shorts(thumb, thumb_length);
for (i = 0; i < thumb_length; i++)
{
putc(thumb[i] << 3, ofp);
putc(thumb[i] >> 5 << 2, ofp);
putc(thumb[i] >> 11 << 3, ofp);
}
free(thumb);
}
void CLASS rollei_load_raw()
{
uchar pixel[10];
unsigned iten = 0, isix, i, buffer = 0, todo[16];
#ifdef LIBRAW_LIBRARY_BUILD
if(raw_width > 32767 || raw_height > 32767)
throw LIBRAW_EXCEPTION_IO_BADFILE;
#endif
unsigned maxpixel = raw_width*(raw_height+7);
isix = raw_width * raw_height * 5 / 8;
while (fread(pixel, 1, 10, ifp) == 10)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (i = 0; i < 10; i += 2)
{
todo[i] = iten++;
todo[i + 1] = pixel[i] << 8 | pixel[i + 1];
buffer = pixel[i] >> 2 | buffer << 6;
}
for (; i < 16; i += 2)
{
todo[i] = isix++;
todo[i + 1] = buffer >> (14 - i) * 5;
}
for (i = 0; i < 16; i += 2)
if(todo[i] < maxpixel)
raw_image[todo[i]] = (todo[i + 1] & 0x3ff);
else
derror();
}
maximum = 0x3ff;
}
int CLASS raw(unsigned row, unsigned col) { return (row < raw_height && col < raw_width) ? RAW(row, col) : 0; }
void CLASS phase_one_flat_field(int is_float, int nc)
{
ushort head[8];
unsigned wide, high, y, x, c, rend, cend, row, col;
float *mrow, num, mult[4];
read_shorts(head, 8);
if (head[2] * head[3] * head[4] * head[5] == 0)
return;
wide = head[2] / head[4] + (head[2] % head[4] != 0);
high = head[3] / head[5] + (head[3] % head[5] != 0);
mrow = (float *)calloc(nc * wide, sizeof *mrow);
merror(mrow, "phase_one_flat_field()");
for (y = 0; y < high; y++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (x = 0; x < wide; x++)
for (c = 0; c < nc; c += 2)
{
num = is_float ? getreal(11) : get2() / 32768.0;
if (y == 0)
mrow[c * wide + x] = num;
else
mrow[(c + 1) * wide + x] = (num - mrow[c * wide + x]) / head[5];
}
if (y == 0)
continue;
rend = head[1] + y * head[5];
for (row = rend - head[5]; row < raw_height && row < rend && row < head[1] + head[3] - head[5]; row++)
{
for (x = 1; x < wide; x++)
{
for (c = 0; c < nc; c += 2)
{
mult[c] = mrow[c * wide + x - 1];
mult[c + 1] = (mrow[c * wide + x] - mult[c]) / head[4];
}
cend = head[0] + x * head[4];
for (col = cend - head[4]; col < raw_width && col < cend && col < head[0] + head[2] - head[4]; col++)
{
c = nc > 2 ? FC(row - top_margin, col - left_margin) : 0;
if (!(c & 1))
{
c = RAW(row, col) * mult[c];
RAW(row, col) = LIM(c, 0, 65535);
}
for (c = 0; c < nc; c += 2)
mult[c] += mult[c + 1];
}
}
for (x = 0; x < wide; x++)
for (c = 0; c < nc; c += 2)
mrow[c * wide + x] += mrow[(c + 1) * wide + x];
}
}
free(mrow);
}
int CLASS phase_one_correct()
{
unsigned entries, tag, data, save, col, row, type;
int len, i, j, k, cip, val[4], dev[4], sum, max;
int head[9], diff, mindiff = INT_MAX, off_412 = 0;
/* static */ const signed char dir[12][2] = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}, {-2, 0}, {0, -2},
{0, 2}, {2, 0}, {-2, -2}, {-2, 2}, {2, -2}, {2, 2}};
float poly[8], num, cfrac, frac, mult[2], *yval[2] = {NULL, NULL};
ushort *xval[2];
int qmult_applied = 0, qlin_applied = 0;
#ifdef LIBRAW_LIBRARY_BUILD
if (!meta_length)
#else
if (half_size || !meta_length)
#endif
return 0;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Phase One correction...\n"));
#endif
fseek(ifp, meta_offset, SEEK_SET);
order = get2();
fseek(ifp, 6, SEEK_CUR);
fseek(ifp, meta_offset + get4(), SEEK_SET);
entries = get4();
get4();
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
while (entries--)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
tag = get4();
len = get4();
data = get4();
save = ftell(ifp);
fseek(ifp, meta_offset + data, SEEK_SET);
if (tag == 0x419)
{ /* Polynomial curve */
for (get4(), i = 0; i < 8; i++)
poly[i] = getreal(11);
poly[3] += (ph1.tag_210 - poly[7]) * poly[6] + 1;
for (i = 0; i < 0x10000; i++)
{
num = (poly[5] * i + poly[3]) * i + poly[1];
curve[i] = LIM(num, 0, 65535);
}
goto apply; /* apply to right half */
}
else if (tag == 0x41a)
{ /* Polynomial curve */
for (i = 0; i < 4; i++)
poly[i] = getreal(11);
for (i = 0; i < 0x10000; i++)
{
for (num = 0, j = 4; j--;)
num = num * i + poly[j];
curve[i] = LIM(num + i, 0, 65535);
}
apply: /* apply to whole image */
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = (tag & 1) * ph1.split_col; col < raw_width; col++)
RAW(row, col) = curve[RAW(row, col)];
}
}
else if (tag == 0x400)
{ /* Sensor defects */
while ((len -= 8) >= 0)
{
col = get2();
row = get2();
type = get2();
get2();
if (col >= raw_width)
continue;
if (type == 131 || type == 137) /* Bad column */
for (row = 0; row < raw_height; row++)
if (FC(row - top_margin, col - left_margin) == 1)
{
for (sum = i = 0; i < 4; i++)
sum += val[i] = raw(row + dir[i][0], col + dir[i][1]);
for (max = i = 0; i < 4; i++)
{
dev[i] = abs((val[i] << 2) - sum);
if (dev[max] < dev[i])
max = i;
}
RAW(row, col) = (sum - val[max]) / 3.0 + 0.5;
}
else
{
for (sum = 0, i = 8; i < 12; i++)
sum += raw(row + dir[i][0], col + dir[i][1]);
RAW(row, col) = 0.5 + sum * 0.0732233 + (raw(row, col - 2) + raw(row, col + 2)) * 0.3535534;
}
else if (type == 129)
{ /* Bad pixel */
if (row >= raw_height)
continue;
j = (FC(row - top_margin, col - left_margin) != 1) * 4;
for (sum = 0, i = j; i < j + 8; i++)
sum += raw(row + dir[i][0], col + dir[i][1]);
RAW(row, col) = (sum + 4) >> 3;
}
}
}
else if (tag == 0x401)
{ /* All-color flat fields */
phase_one_flat_field(1, 2);
}
else if (tag == 0x416 || tag == 0x410)
{
phase_one_flat_field(0, 2);
}
else if (tag == 0x40b)
{ /* Red+blue flat field */
phase_one_flat_field(0, 4);
}
else if (tag == 0x412)
{
fseek(ifp, 36, SEEK_CUR);
diff = abs(get2() - ph1.tag_21a);
if (mindiff > diff)
{
mindiff = diff;
off_412 = ftell(ifp) - 38;
}
}
else if (tag == 0x41f && !qlin_applied)
{ /* Quadrant linearization */
ushort lc[2][2][16], ref[16];
int qr, qc;
for (qr = 0; qr < 2; qr++)
for (qc = 0; qc < 2; qc++)
for (i = 0; i < 16; i++)
lc[qr][qc][i] = get4();
for (i = 0; i < 16; i++)
{
int v = 0;
for (qr = 0; qr < 2; qr++)
for (qc = 0; qc < 2; qc++)
v += lc[qr][qc][i];
ref[i] = (v + 2) >> 2;
}
for (qr = 0; qr < 2; qr++)
{
for (qc = 0; qc < 2; qc++)
{
int cx[19], cf[19];
for (i = 0; i < 16; i++)
{
cx[1 + i] = lc[qr][qc][i];
cf[1 + i] = ref[i];
}
cx[0] = cf[0] = 0;
cx[17] = cf[17] = ((unsigned int)ref[15] * 65535) / lc[qr][qc][15];
cf[18] = cx[18] = 65535;
cubic_spline(cx, cf, 19);
for (row = (qr ? ph1.split_row : 0); row < (qr ? raw_height : ph1.split_row); row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = (qc ? ph1.split_col : 0); col < (qc ? raw_width : ph1.split_col); col++)
RAW(row, col) = curve[RAW(row, col)];
}
}
}
qlin_applied = 1;
}
else if (tag == 0x41e && !qmult_applied)
{ /* Quadrant multipliers */
float qmult[2][2] = {{1, 1}, {1, 1}};
get4();
get4();
get4();
get4();
qmult[0][0] = 1.0 + getreal(11);
get4();
get4();
get4();
get4();
get4();
qmult[0][1] = 1.0 + getreal(11);
get4();
get4();
get4();
qmult[1][0] = 1.0 + getreal(11);
get4();
get4();
get4();
qmult[1][1] = 1.0 + getreal(11);
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
{
i = qmult[row >= ph1.split_row][col >= ph1.split_col] * RAW(row, col);
RAW(row, col) = LIM(i, 0, 65535);
}
}
qmult_applied = 1;
}
else if (tag == 0x431 && !qmult_applied)
{ /* Quadrant combined */
ushort lc[2][2][7], ref[7];
int qr, qc;
for (i = 0; i < 7; i++)
ref[i] = get4();
for (qr = 0; qr < 2; qr++)
for (qc = 0; qc < 2; qc++)
for (i = 0; i < 7; i++)
lc[qr][qc][i] = get4();
for (qr = 0; qr < 2; qr++)
{
for (qc = 0; qc < 2; qc++)
{
int cx[9], cf[9];
for (i = 0; i < 7; i++)
{
cx[1 + i] = ref[i];
cf[1 + i] = ((unsigned)ref[i] * lc[qr][qc][i]) / 10000;
}
cx[0] = cf[0] = 0;
cx[8] = cf[8] = 65535;
cubic_spline(cx, cf, 9);
for (row = (qr ? ph1.split_row : 0); row < (qr ? raw_height : ph1.split_row); row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = (qc ? ph1.split_col : 0); col < (qc ? raw_width : ph1.split_col); col++)
RAW(row, col) = curve[RAW(row, col)];
}
}
}
qmult_applied = 1;
qlin_applied = 1;
}
fseek(ifp, save, SEEK_SET);
}
if (off_412)
{
fseek(ifp, off_412, SEEK_SET);
for (i = 0; i < 9; i++)
head[i] = get4() & 0x7fff;
yval[0] = (float *)calloc(head[1] * head[3] + head[2] * head[4], 6);
merror(yval[0], "phase_one_correct()");
yval[1] = (float *)(yval[0] + head[1] * head[3]);
xval[0] = (ushort *)(yval[1] + head[2] * head[4]);
xval[1] = (ushort *)(xval[0] + head[1] * head[3]);
get2();
for (i = 0; i < 2; i++)
for (j = 0; j < head[i + 1] * head[i + 3]; j++)
yval[i][j] = getreal(11);
for (i = 0; i < 2; i++)
for (j = 0; j < head[i + 1] * head[i + 3]; j++)
xval[i][j] = get2();
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
{
cfrac = (float)col * head[3] / raw_width;
cfrac -= cip = cfrac;
num = RAW(row, col) * 0.5;
for (i = cip; i < cip + 2; i++)
{
for (k = j = 0; j < head[1]; j++)
if (num < xval[0][k = head[1] * i + j])
break;
frac = (j == 0 || j == head[1]) ? 0 : (xval[0][k] - num) / (xval[0][k] - xval[0][k - 1]);
mult[i - cip] = yval[0][k - 1] * frac + yval[0][k] * (1 - frac);
}
i = ((mult[0] * (1 - cfrac) + mult[1] * cfrac) * row + num) * 2;
RAW(row, col) = LIM(i, 0, 65535);
}
}
free(yval[0]);
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
if (yval[0])
free(yval[0]);
return LIBRAW_CANCELLED_BY_CALLBACK;
}
#endif
return 0;
}
void CLASS phase_one_load_raw()
{
int a, b, i;
ushort akey, bkey, t_mask;
fseek(ifp, ph1.key_off, SEEK_SET);
akey = get2();
bkey = get2();
t_mask = ph1.format == 1 ? 0x5555 : 0x1354;
#ifdef LIBRAW_LIBRARY_BUILD
if (ph1.black_col || ph1.black_row)
{
imgdata.rawdata.ph1_cblack = (short(*)[2])calloc(raw_height * 2, sizeof(ushort));
merror(imgdata.rawdata.ph1_cblack, "phase_one_load_raw()");
imgdata.rawdata.ph1_rblack = (short(*)[2])calloc(raw_width * 2, sizeof(ushort));
merror(imgdata.rawdata.ph1_rblack, "phase_one_load_raw()");
if (ph1.black_col)
{
fseek(ifp, ph1.black_col, SEEK_SET);
read_shorts((ushort *)imgdata.rawdata.ph1_cblack[0], raw_height * 2);
}
if (ph1.black_row)
{
fseek(ifp, ph1.black_row, SEEK_SET);
read_shorts((ushort *)imgdata.rawdata.ph1_rblack[0], raw_width * 2);
}
}
#endif
fseek(ifp, data_offset, SEEK_SET);
read_shorts(raw_image, raw_width * raw_height);
if (ph1.format)
for (i = 0; i < raw_width * raw_height; i += 2)
{
a = raw_image[i + 0] ^ akey;
b = raw_image[i + 1] ^ bkey;
raw_image[i + 0] = (a & t_mask) | (b & ~t_mask);
raw_image[i + 1] = (b & t_mask) | (a & ~t_mask);
}
}
unsigned CLASS ph1_bithuff(int nbits, ushort *huff)
{
#ifndef LIBRAW_NOTHREADS
#define bitbuf tls->ph1_bits.bitbuf
#define vbits tls->ph1_bits.vbits
#else
static UINT64 bitbuf = 0;
static int vbits = 0;
#endif
unsigned c;
if (nbits == -1)
return bitbuf = vbits = 0;
if (nbits == 0)
return 0;
if (vbits < nbits)
{
bitbuf = bitbuf << 32 | get4();
vbits += 32;
}
c = bitbuf << (64 - vbits) >> (64 - nbits);
if (huff)
{
vbits -= huff[c] >> 8;
return (uchar)huff[c];
}
vbits -= nbits;
return c;
#ifndef LIBRAW_NOTHREADS
#undef bitbuf
#undef vbits
#endif
}
#define ph1_bits(n) ph1_bithuff(n, 0)
#define ph1_huff(h) ph1_bithuff(*h, h + 1)
void CLASS phase_one_load_raw_c()
{
static const int length[] = {8, 7, 6, 9, 11, 10, 5, 12, 14, 13};
int *offset, len[2], pred[2], row, col, i, j;
ushort *pixel;
short(*c_black)[2], (*r_black)[2];
#ifdef LIBRAW_LIBRARY_BUILD
if (ph1.format == 6)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
pixel = (ushort *)calloc(raw_width * 3 + raw_height * 4, 2);
merror(pixel, "phase_one_load_raw_c()");
offset = (int *)(pixel + raw_width);
fseek(ifp, strip_offset, SEEK_SET);
for (row = 0; row < raw_height; row++)
offset[row] = get4();
c_black = (short(*)[2])(offset + raw_height);
fseek(ifp, ph1.black_col, SEEK_SET);
if (ph1.black_col)
read_shorts((ushort *)c_black[0], raw_height * 2);
r_black = c_black + raw_height;
fseek(ifp, ph1.black_row, SEEK_SET);
if (ph1.black_row)
read_shorts((ushort *)r_black[0], raw_width * 2);
#ifdef LIBRAW_LIBRARY_BUILD
// Copy data to internal copy (ever if not read)
if (ph1.black_col || ph1.black_row)
{
imgdata.rawdata.ph1_cblack = (short(*)[2])calloc(raw_height * 2, sizeof(ushort));
merror(imgdata.rawdata.ph1_cblack, "phase_one_load_raw_c()");
memmove(imgdata.rawdata.ph1_cblack, (ushort *)c_black[0], raw_height * 2 * sizeof(ushort));
imgdata.rawdata.ph1_rblack = (short(*)[2])calloc(raw_width * 2, sizeof(ushort));
merror(imgdata.rawdata.ph1_rblack, "phase_one_load_raw_c()");
memmove(imgdata.rawdata.ph1_rblack, (ushort *)r_black[0], raw_width * 2 * sizeof(ushort));
}
#endif
for (i = 0; i < 256; i++)
curve[i] = i * i / 3.969 + 0.5;
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
fseek(ifp, data_offset + offset[row], SEEK_SET);
ph1_bits(-1);
pred[0] = pred[1] = 0;
for (col = 0; col < raw_width; col++)
{
if (col >= (raw_width & -8))
len[0] = len[1] = 14;
else if ((col & 7) == 0)
for (i = 0; i < 2; i++)
{
for (j = 0; j < 5 && !ph1_bits(1); j++)
;
if (j--)
len[i] = length[j * 2 + ph1_bits(1)];
}
if ((i = len[col & 1]) == 14)
pixel[col] = pred[col & 1] = ph1_bits(16);
else
pixel[col] = pred[col & 1] += ph1_bits(i) + 1 - (1 << (i - 1));
if (pred[col & 1] >> 16)
derror();
if (ph1.format == 5 && pixel[col] < 256)
pixel[col] = curve[pixel[col]];
}
#ifndef LIBRAW_LIBRARY_BUILD
for (col = 0; col < raw_width; col++)
{
int shift = ph1.format == 8 ? 0 : 2;
i = (pixel[col] << shift) - ph1.t_black + c_black[row][col >= ph1.split_col] +
r_black[col][row >= ph1.split_row];
if (i > 0)
RAW(row, col) = i;
}
#else
if (ph1.format == 8)
memmove(&RAW(row, 0), &pixel[0], raw_width * 2);
else
for (col = 0; col < raw_width; col++)
RAW(row, col) = pixel[col] << 2;
#endif
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
maximum = 0xfffc - ph1.t_black;
}
void CLASS hasselblad_load_raw()
{
struct jhead jh;
int shot, row, col, *back[5], len[2], diff[12], pred, sh, f, s, c;
unsigned upix, urow, ucol;
ushort *ip;
if (!ljpeg_start(&jh, 0))
return;
order = 0x4949;
ph1_bits(-1);
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
back[4] = (int *)calloc(raw_width, 3 * sizeof **back);
merror(back[4], "hasselblad_load_raw()");
FORC3 back[c] = back[4] + c * raw_width;
cblack[6] >>= sh = tiff_samples > 1;
shot = LIM(shot_select, 1, tiff_samples) - 1;
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
FORC4 back[(c + 3) & 3] = back[c];
for (col = 0; col < raw_width; col += 2)
{
for (s = 0; s < tiff_samples * 2; s += 2)
{
FORC(2) len[c] = ph1_huff(jh.huff[0]);
FORC(2)
{
diff[s + c] = ph1_bits(len[c]);
if ((diff[s + c] & (1 << (len[c] - 1))) == 0)
diff[s + c] -= (1 << len[c]) - 1;
if (diff[s + c] == 65535)
diff[s + c] = -32768;
}
}
for (s = col; s < col + 2; s++)
{
pred = 0x8000 + load_flags;
if (col)
pred = back[2][s - 2];
if (col && row > 1)
switch (jh.psv)
{
case 11:
pred += back[0][s] / 2 - back[0][s - 2] / 2;
break;
}
f = (row & 1) * 3 ^ ((col + s) & 1);
FORC(tiff_samples)
{
pred += diff[(s & 1) * tiff_samples + c];
upix = pred >> sh & 0xffff;
if (raw_image && c == shot)
RAW(row, s) = upix;
if (image)
{
urow = row - top_margin + (c & 1);
ucol = col - left_margin - ((c >> 1) & 1);
ip = &image[urow * width + ucol][f];
if (urow < height && ucol < width)
*ip = c < 4 ? upix : (*ip + upix) >> 1;
}
}
back[2][s] = pred;
}
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(back[4]);
ljpeg_end(&jh);
throw;
}
#endif
free(back[4]);
ljpeg_end(&jh);
if (image)
mix_green = 1;
}
void CLASS leaf_hdr_load_raw()
{
ushort *pixel = 0;
unsigned tile = 0, r, c, row, col;
if (!filters || !raw_image)
{
#ifdef LIBRAW_LIBRARY_BUILD
if(!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
pixel = (ushort *)calloc(raw_width, sizeof *pixel);
merror(pixel, "leaf_hdr_load_raw()");
}
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
FORC(tiff_samples)
for (r = 0; r < raw_height; r++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (r % tile_length == 0)
{
fseek(ifp, data_offset + 4 * tile++, SEEK_SET);
fseek(ifp, get4(), SEEK_SET);
}
if (filters && c != shot_select)
continue;
if (filters && raw_image)
pixel = raw_image + r * raw_width;
read_shorts(pixel, raw_width);
if (!filters && image && (row = r - top_margin) < height)
for (col = 0; col < width; col++)
image[row * width + col][c] = pixel[col + left_margin];
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
if (!filters)
free(pixel);
throw;
}
#endif
if (!filters)
{
maximum = 0xffff;
raw_color = 1;
free(pixel);
}
}
void CLASS unpacked_load_raw()
{
int row, col, bits = 0;
while (1 << ++bits < maximum)
;
read_shorts(raw_image, raw_width * raw_height);
fseek(ifp,-2,SEEK_CUR); // avoid EOF error
if (maximum < 0xffff || load_flags)
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
if ((RAW(row, col) >>= load_flags) >> bits && (unsigned)(row - top_margin) < height &&
(unsigned)(col - left_margin) < width)
derror();
}
}
void CLASS unpacked_load_raw_reversed()
{
int row, col, bits = 0;
while (1 << ++bits < maximum)
;
for (row = raw_height - 1; row >= 0; row--)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
read_shorts(&raw_image[row * raw_width], raw_width);
for (col = 0; col < raw_width; col++)
if ((RAW(row, col) >>= load_flags) >> bits && (unsigned)(row - top_margin) < height &&
(unsigned)(col - left_margin) < width)
derror();
}
}
void CLASS sinar_4shot_load_raw()
{
ushort *pixel;
unsigned shot, row, col, r, c;
if (raw_image)
{
shot = LIM(shot_select, 1, 4) - 1;
fseek(ifp, data_offset + shot * 4, SEEK_SET);
fseek(ifp, get4(), SEEK_SET);
unpacked_load_raw();
return;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
pixel = (ushort *)calloc(raw_width, sizeof *pixel);
merror(pixel, "sinar_4shot_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (shot = 0; shot < 4; shot++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
fseek(ifp, data_offset + shot * 4, SEEK_SET);
fseek(ifp, get4(), SEEK_SET);
for (row = 0; row < raw_height; row++)
{
read_shorts(pixel, raw_width);
if ((r = row - top_margin - (shot >> 1 & 1)) >= height)
continue;
for (col = 0; col < raw_width; col++)
{
if ((c = col - left_margin - (shot & 1)) >= width)
continue;
image[r * width + c][(row & 1) * 3 ^ (~col & 1)] = pixel[col];
}
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
mix_green = 1;
}
void CLASS imacon_full_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
int row, col;
#ifdef LIBRAW_LIBRARY_BUILD
unsigned short *buf = (unsigned short *)malloc(width * 3 * sizeof(unsigned short));
merror(buf, "imacon_full_load_raw");
#endif
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
read_shorts(buf, width * 3);
unsigned short(*rowp)[4] = &image[row * width];
for (col = 0; col < width; col++)
{
rowp[col][0] = buf[col * 3];
rowp[col][1] = buf[col * 3 + 1];
rowp[col][2] = buf[col * 3 + 2];
rowp[col][3] = 0;
}
#else
for (col = 0; col < width; col++)
read_shorts(image[row * width + col], 3);
#endif
}
#ifdef LIBRAW_LIBRARY_BUILD
free(buf);
#endif
}
void CLASS packed_load_raw()
{
int vbits = 0, bwide, rbits, bite, half, irow, row, col, val, i;
UINT64 bitbuf = 0;
bwide = raw_width * tiff_bps / 8;
bwide += bwide & load_flags >> 7;
rbits = bwide * 8 - raw_width * tiff_bps;
if (load_flags & 1)
bwide = bwide * 16 / 15;
bite = 8 + (load_flags & 24);
half = (raw_height + 1) >> 1;
for (irow = 0; irow < raw_height; irow++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
row = irow;
if (load_flags & 2 && (row = irow % half * 2 + irow / half) == 1 && load_flags & 4)
{
if (vbits = 0, tiff_compress)
fseek(ifp, data_offset - (-half * bwide & -2048), SEEK_SET);
else
{
fseek(ifp, 0, SEEK_END);
fseek(ifp, ftell(ifp) >> 3 << 2, SEEK_SET);
}
}
if(feof(ifp)) throw LIBRAW_EXCEPTION_IO_EOF;
for (col = 0; col < raw_width; col++)
{
for (vbits -= tiff_bps; vbits < 0; vbits += bite)
{
bitbuf <<= bite;
for (i = 0; i < bite; i += 8)
bitbuf |= (unsigned)(fgetc(ifp) << i);
}
val = bitbuf << (64 - tiff_bps - vbits) >> (64 - tiff_bps);
RAW(row, col ^ (load_flags >> 6 & 1)) = val;
if (load_flags & 1 && (col % 10) == 9 && fgetc(ifp) && row < height + top_margin && col < width + left_margin)
derror();
}
vbits -= rbits;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
ushort raw_stride;
void CLASS parse_broadcom()
{
/* This structure is at offset 0xb0 from the 'BRCM' ident. */
struct
{
uint8_t umode[32];
uint16_t uwidth;
uint16_t uheight;
uint16_t padding_right;
uint16_t padding_down;
uint32_t unknown_block[6];
uint16_t transform;
uint16_t format;
uint8_t bayer_order;
uint8_t bayer_format;
} header;
header.bayer_order = 0;
fseek(ifp, 0xb0 - 0x20, SEEK_CUR);
fread(&header, 1, sizeof(header), ifp);
raw_stride = ((((((header.uwidth + header.padding_right) * 5) + 3) >> 2) + 0x1f) & (~0x1f));
raw_width = width = header.uwidth;
raw_height = height = header.uheight;
filters = 0x16161616; /* default Bayer order is 2, BGGR */
switch (header.bayer_order)
{
case 0: /* RGGB */
filters = 0x94949494;
break;
case 1: /* GBRG */
filters = 0x49494949;
break;
case 3: /* GRBG */
filters = 0x61616161;
break;
}
}
void CLASS broadcom_load_raw()
{
uchar *data, *dp;
int rev, row, col, c;
rev = 3 * (order == 0x4949);
data = (uchar *)malloc(raw_stride * 2);
merror(data, "broadcom_load_raw()");
for (row = 0; row < raw_height; row++)
{
if (fread(data + raw_stride, 1, raw_stride, ifp) < raw_stride)
derror();
FORC(raw_stride) data[c] = data[raw_stride + (c ^ rev)];
for (dp = data, col = 0; col < raw_width; dp += 5, col += 4)
FORC4 RAW(row, col + c) = (dp[c] << 2) | (dp[4] >> (c << 1) & 3);
}
free(data);
}
#endif
void CLASS nokia_load_raw()
{
uchar *data, *dp;
int rev, dwide, row, col, c;
double sum[] = {0, 0};
rev = 3 * (order == 0x4949);
dwide = (raw_width * 5 + 1) / 4;
data = (uchar *)malloc(dwide * 2);
merror(data, "nokia_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (fread(data + dwide, 1, dwide, ifp) < dwide)
derror();
FORC(dwide) data[c] = data[dwide + (c ^ rev)];
for (dp = data, col = 0; col < raw_width; dp += 5, col += 4)
FORC4 RAW(row, col + c) = (dp[c] << 2) | (dp[4] >> (c << 1) & 3);
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(data);
throw;
}
#endif
free(data);
maximum = 0x3ff;
if (strncmp(make, "OmniVision", 10))
return;
row = raw_height / 2;
FORC(width - 1)
{
sum[c & 1] += SQR(RAW(row, c) - RAW(row + 1, c + 1));
sum[~c & 1] += SQR(RAW(row + 1, c) - RAW(row, c + 1));
}
if (sum[1] > sum[0])
filters = 0x4b4b4b4b;
}
void CLASS android_tight_load_raw()
{
uchar *data, *dp;
int bwide, row, col, c;
bwide = -(-5 * raw_width >> 5) << 3;
data = (uchar *)malloc(bwide);
merror(data, "android_tight_load_raw()");
for (row = 0; row < raw_height; row++)
{
if (fread(data, 1, bwide, ifp) < bwide)
derror();
for (dp = data, col = 0; col < raw_width; dp += 5, col += 4)
FORC4 RAW(row, col + c) = (dp[c] << 2) | (dp[4] >> (c << 1) & 3);
}
free(data);
}
void CLASS android_loose_load_raw()
{
uchar *data, *dp;
int bwide, row, col, c;
UINT64 bitbuf = 0;
bwide = (raw_width + 5) / 6 << 3;
data = (uchar *)malloc(bwide);
merror(data, "android_loose_load_raw()");
for (row = 0; row < raw_height; row++)
{
if (fread(data, 1, bwide, ifp) < bwide)
derror();
for (dp = data, col = 0; col < raw_width; dp += 8, col += 6)
{
FORC(8) bitbuf = (bitbuf << 8) | dp[c ^ 7];
FORC(6) RAW(row, col + c) = (bitbuf >> c * 10) & 0x3ff;
}
}
free(data);
}
void CLASS canon_rmf_load_raw()
{
int row, col, bits, orow, ocol, c;
#ifdef LIBRAW_LIBRARY_BUILD
int *words = (int *)malloc(sizeof(int) * (raw_width / 3 + 1));
merror(words, "canon_rmf_load_raw");
#endif
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
fread(words, sizeof(int), raw_width / 3, ifp);
for (col = 0; col < raw_width - 2; col += 3)
{
bits = words[col / 3];
FORC3
{
orow = row;
if ((ocol = col + c - 4) < 0)
{
ocol += raw_width;
if ((orow -= 2) < 0)
orow += raw_height;
}
RAW(orow, ocol) = curve[bits >> (10 * c + 2) & 0x3ff];
}
}
#else
for (col = 0; col < raw_width - 2; col += 3)
{
bits = get4();
FORC3
{
orow = row;
if ((ocol = col + c - 4) < 0)
{
ocol += raw_width;
if ((orow -= 2) < 0)
orow += raw_height;
}
RAW(orow, ocol) = curve[bits >> (10 * c + 2) & 0x3ff];
}
}
#endif
}
#ifdef LIBRAW_LIBRARY_BUILD
free(words);
#endif
maximum = curve[0x3ff];
}
unsigned CLASS pana_data(int nb, unsigned *bytes)
{
#ifndef LIBRAW_NOTHREADS
#define vpos tls->pana_data.vpos
#define buf tls->pana_data.buf
#else
static uchar buf[0x4002];
static int vpos;
#endif
int byte;
if (!nb && !bytes)
return vpos = 0;
if (!vpos)
{
fread(buf + load_flags, 1, 0x4000 - load_flags, ifp);
fread(buf, 1, load_flags, ifp);
}
if (pana_encoding == 5)
{
for (byte = 0; byte < 16; byte++)
{
bytes[byte] = buf[vpos++];
vpos &= 0x3FFF;
}
}
else
{
vpos = (vpos - nb) & 0x1ffff;
byte = vpos >> 3 ^ 0x3ff0;
return (buf[byte] | buf[byte + 1] << 8) >> (vpos & 7) & ~((~0u) << nb);
}
return 0;
#ifndef LIBRAW_NOTHREADS
#undef vpos
#undef buf
#endif
}
void CLASS panasonic_load_raw()
{
int row, col, i, j, sh = 0, pred[2], nonz[2];
unsigned bytes[16];
ushort *raw_block_data;
int enc_blck_size = pana_bpp == 12 ? 10 : 9;
pana_data(0, 0);
if (pana_encoding == 5)
{
for (row = 0; row < raw_height; row++)
{
raw_block_data = raw_image + row * raw_width;
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col += enc_blck_size)
{
pana_data(0, bytes);
if (pana_bpp == 12)
{
raw_block_data[col] = ((bytes[1] & 0xF) << 8) + bytes[0];
raw_block_data[col + 1] = 16 * bytes[2] + (bytes[1] >> 4);
raw_block_data[col + 2] = ((bytes[4] & 0xF) << 8) + bytes[3];
raw_block_data[col + 3] = 16 * bytes[5] + (bytes[4] >> 4);
raw_block_data[col + 4] = ((bytes[7] & 0xF) << 8) + bytes[6];
raw_block_data[col + 5] = 16 * bytes[8] + (bytes[7] >> 4);
raw_block_data[col + 6] = ((bytes[10] & 0xF) << 8) + bytes[9];
raw_block_data[col + 7] = 16 * bytes[11] + (bytes[10] >> 4);
raw_block_data[col + 8] = ((bytes[13] & 0xF) << 8) + bytes[12];
raw_block_data[col + 9] = 16 * bytes[14] + (bytes[13] >> 4);
}
else if (pana_bpp == 14)
{
raw_block_data[col] = bytes[0] + ((bytes[1] & 0x3F) << 8);
raw_block_data[col + 1] = (bytes[1] >> 6) + 4 * (bytes[2]) +
((bytes[3] & 0xF) << 10);
raw_block_data[col + 2] = (bytes[3] >> 4) + 16 * (bytes[4]) +
((bytes[5] & 3) << 12);
raw_block_data[col + 3] = ((bytes[5] & 0xFC) >> 2) + (bytes[6] << 6);
raw_block_data[col + 4] = bytes[7] + ((bytes[8] & 0x3F) << 8);
raw_block_data[col + 5] = (bytes[8] >> 6) + 4 * bytes[9] + ((bytes[10] & 0xF) << 10);
raw_block_data[col + 6] = (bytes[10] >> 4) + 16 * bytes[11] + ((bytes[12] & 3) << 12);
raw_block_data[col + 7] = ((bytes[12] & 0xFC) >> 2) + (bytes[13] << 6);
raw_block_data[col + 8] = bytes[14] + ((bytes[15] & 0x3F) << 8);
}
}
}
}
else
{
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
{
if ((i = col % 14) == 0)
pred[0] = pred[1] = nonz[0] = nonz[1] = 0;
if (i % 3 == 2)
sh = 4 >> (3 - pana_data(2, 0));
if (nonz[i & 1])
{
if ((j = pana_data(8, 0)))
{
if ((pred[i & 1] -= 0x80 << sh) < 0 || sh == 4)
pred[i & 1] &= ~((~0u) << sh);
pred[i & 1] += j << sh;
}
}
else if ((nonz[i & 1] = pana_data(8, 0)) || i > 11)
pred[i & 1] = nonz[i & 1] << 4 | pana_data(4, 0);
if ((RAW(row, col) = pred[col & 1]) > 4098 && col < width && row < height)
derror();
}
}
}
}
void CLASS olympus_load_raw()
{
ushort huff[4096];
int row, col, nbits, sign, low, high, i, c, w, n, nw;
int acarry[2][3], *carry, pred, diff;
huff[n = 0] = 0xc0c;
for (i = 12; i--;)
FORC(2048 >> i) huff[++n] = (i + 1) << 8 | i;
fseek(ifp, 7, SEEK_CUR);
getbits(-1);
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
memset(acarry, 0, sizeof acarry);
for (col = 0; col < raw_width; col++)
{
carry = acarry[col & 1];
i = 2 * (carry[2] < 3);
for (nbits = 2 + i; (ushort)carry[0] >> (nbits + i); nbits++)
;
low = (sign = getbits(3)) & 3;
sign = sign << 29 >> 31;
if ((high = getbithuff(12, huff)) == 12)
high = getbits(16 - nbits) >> 1;
carry[0] = (high << nbits) | getbits(nbits);
diff = (carry[0] ^ sign) + carry[1];
carry[1] = (diff * 3 + carry[1]) >> 5;
carry[2] = carry[0] > 16 ? 0 : carry[2] + 1;
if (col >= width)
continue;
if (row < 2 && col < 2)
pred = 0;
else if (row < 2)
pred = RAW(row, col - 2);
else if (col < 2)
pred = RAW(row - 2, col);
else
{
w = RAW(row, col - 2);
n = RAW(row - 2, col);
nw = RAW(row - 2, col - 2);
if ((w < nw && nw < n) || (n < nw && nw < w))
{
if (ABS(w - nw) > 32 || ABS(n - nw) > 32)
pred = w + n - nw;
else
pred = (w + n) >> 1;
}
else
pred = ABS(w - nw) > ABS(n - nw) ? w : n;
}
if ((RAW(row, col) = pred + ((diff << 2) | low)) >> 12)
derror();
}
}
}
void CLASS minolta_rd175_load_raw()
{
uchar pixel[768];
unsigned irow, box, row, col;
for (irow = 0; irow < 1481; irow++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (fread(pixel, 1, 768, ifp) < 768)
derror();
box = irow / 82;
row = irow % 82 * 12 + ((box < 12) ? box | 1 : (box - 12) * 2);
switch (irow)
{
case 1477:
case 1479:
continue;
case 1476:
row = 984;
break;
case 1480:
row = 985;
break;
case 1478:
row = 985;
box = 1;
}
if ((box < 12) && (box & 1))
{
for (col = 0; col < 1533; col++, row ^= 1)
if (col != 1)
RAW(row, col) = (col + 1) & 2 ? pixel[col / 2 - 1] + pixel[col / 2 + 1] : pixel[col / 2] << 1;
RAW(row, 1) = pixel[1] << 1;
RAW(row, 1533) = pixel[765] << 1;
}
else
for (col = row & 1; col < 1534; col += 2)
RAW(row, col) = pixel[col / 2] << 1;
}
maximum = 0xff << 1;
}
void CLASS quicktake_100_load_raw()
{
uchar pixel[484][644];
static const short gstep[16] = {-89, -60, -44, -32, -22, -15, -8, -2, 2, 8, 15, 22, 32, 44, 60, 89};
static const short rstep[6][4] = {{-3, -1, 1, 3}, {-5, -1, 1, 5}, {-8, -2, 2, 8},
{-13, -3, 3, 13}, {-19, -4, 4, 19}, {-28, -6, 6, 28}};
static const short t_curve[256] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45,
46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68,
69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 86, 88, 90, 92, 94, 97, 99,
101, 103, 105, 107, 110, 112, 114, 116, 118, 120, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 147,
149, 151, 153, 155, 158, 160, 162, 164, 166, 168, 171, 173, 175, 177, 179, 181, 184, 186, 188, 190, 192, 195,
197, 199, 201, 203, 205, 208, 210, 212, 214, 216, 218, 221, 223, 226, 230, 235, 239, 244, 248, 252, 257, 261,
265, 270, 274, 278, 283, 287, 291, 296, 300, 305, 309, 313, 318, 322, 326, 331, 335, 339, 344, 348, 352, 357,
361, 365, 370, 374, 379, 383, 387, 392, 396, 400, 405, 409, 413, 418, 422, 426, 431, 435, 440, 444, 448, 453,
457, 461, 466, 470, 474, 479, 483, 487, 492, 496, 500, 508, 519, 531, 542, 553, 564, 575, 587, 598, 609, 620,
631, 643, 654, 665, 676, 687, 698, 710, 721, 732, 743, 754, 766, 777, 788, 799, 810, 822, 833, 844, 855, 866,
878, 889, 900, 911, 922, 933, 945, 956, 967, 978, 989, 1001, 1012, 1023};
int rb, row, col, sharp, val = 0;
#ifdef LIBRAW_LIBRARY_BUILD
if(width>640 || height > 480)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
getbits(-1);
memset(pixel, 0x80, sizeof pixel);
for (row = 2; row < height + 2; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 2 + (row & 1); col < width + 2; col += 2)
{
val = ((pixel[row - 1][col - 1] + 2 * pixel[row - 1][col + 1] + pixel[row][col - 2]) >> 2) + gstep[getbits(4)];
pixel[row][col] = val = LIM(val, 0, 255);
if (col < 4)
pixel[row][col - 2] = pixel[row + 1][~row & 1] = val;
if (row == 2)
pixel[row - 1][col + 1] = pixel[row - 1][col + 3] = val;
}
pixel[row][col] = val;
}
for (rb = 0; rb < 2; rb++)
for (row = 2 + rb; row < height + 2; row += 2)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 3 - (row & 1); col < width + 2; col += 2)
{
if (row < 4 || col < 4)
sharp = 2;
else
{
val = ABS(pixel[row - 2][col] - pixel[row][col - 2]) + ABS(pixel[row - 2][col] - pixel[row - 2][col - 2]) +
ABS(pixel[row][col - 2] - pixel[row - 2][col - 2]);
sharp = val < 4 ? 0 : val < 8 ? 1 : val < 16 ? 2 : val < 32 ? 3 : val < 48 ? 4 : 5;
}
val = ((pixel[row - 2][col] + pixel[row][col - 2]) >> 1) + rstep[sharp][getbits(2)];
pixel[row][col] = val = LIM(val, 0, 255);
if (row < 4)
pixel[row - 2][col + 2] = val;
if (col < 4)
pixel[row + 2][col - 2] = val;
}
}
for (row = 2; row < height + 2; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 3 - (row & 1); col < width + 2; col += 2)
{
val = ((pixel[row][col - 1] + (pixel[row][col] << 2) + pixel[row][col + 1]) >> 1) - 0x100;
pixel[row][col] = LIM(val, 0, 255);
}
}
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < width; col++)
RAW(row, col) = t_curve[pixel[row + 2][col + 2]];
}
maximum = 0x3ff;
}
#define radc_token(tree) ((signed char)getbithuff(8, huff[tree]))
#define FORYX \
for (y = 1; y < 3; y++) \
for (x = col + 1; x >= col; x--)
#define PREDICTOR \
(c ? (buf[c][y - 1][x] + buf[c][y][x + 1]) / 2 : (buf[c][y - 1][x + 1] + 2 * buf[c][y - 1][x] + buf[c][y][x + 1]) / 4)
#ifdef __GNUC__
#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
#pragma GCC optimize("no-aggressive-loop-optimizations")
#endif
#endif
void CLASS kodak_radc_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
// All kodak radc images are 768x512
if (width > 768 || raw_width > 768 || height > 512 || raw_height > 512)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
static const signed char src[] = {
1, 1, 2, 3, 3, 4, 4, 2, 5, 7, 6, 5, 7, 6, 7, 8, 1, 0, 2, 1, 3, 3, 4, 4, 5, 2, 6, 7, 7, 6,
8, 5, 8, 8, 2, 1, 2, 3, 3, 0, 3, 2, 3, 4, 4, 6, 5, 5, 6, 7, 6, 8, 2, 0, 2, 1, 2, 3, 3, 2,
4, 4, 5, 6, 6, 7, 7, 5, 7, 8, 2, 1, 2, 4, 3, 0, 3, 2, 3, 3, 4, 7, 5, 5, 6, 6, 6, 8, 2, 3,
3, 1, 3, 2, 3, 4, 3, 5, 3, 6, 4, 7, 5, 0, 5, 8, 2, 3, 2, 6, 3, 0, 3, 1, 4, 4, 4, 5, 4, 7,
5, 2, 5, 8, 2, 4, 2, 7, 3, 3, 3, 6, 4, 1, 4, 2, 4, 5, 5, 0, 5, 8, 2, 6, 3, 1, 3, 3, 3, 5,
3, 7, 3, 8, 4, 0, 5, 2, 5, 4, 2, 0, 2, 1, 3, 2, 3, 3, 4, 4, 4, 5, 5, 6, 5, 7, 4, 8, 1, 0,
2, 2, 2, -2, 1, -3, 1, 3, 2, -17, 2, -5, 2, 5, 2, 17, 2, -7, 2, 2, 2, 9, 2, 18, 2, -18, 2, -9, 2, -2,
2, 7, 2, -28, 2, 28, 3, -49, 3, -9, 3, 9, 4, 49, 5, -79, 5, 79, 2, -1, 2, 13, 2, 26, 3, 39, 4, -16, 5, 55,
6, -37, 6, 76, 2, -26, 2, -13, 2, 1, 3, -39, 4, 16, 5, -55, 6, -76, 6, 37};
ushort huff[19][256];
int row, col, tree, nreps, rep, step, i, c, s, r, x, y, val;
short last[3] = {16, 16, 16}, mul[3], buf[3][3][386];
static const ushort pt[] = {0, 0, 1280, 1344, 2320, 3616, 3328, 8000, 4095, 16383, 65535, 16383};
for (i = 2; i < 12; i += 2)
for (c = pt[i - 2]; c <= pt[i]; c++)
curve[c] = (float)(c - pt[i - 2]) / (pt[i] - pt[i - 2]) * (pt[i + 1] - pt[i - 1]) + pt[i - 1] + 0.5;
for (s = i = 0; i < sizeof src; i += 2)
FORC(256 >> src[i])
((ushort *)huff)[s++] = src[i] << 8 | (uchar)src[i + 1];
s = kodak_cbpp == 243 ? 2 : 3;
FORC(256) huff[18][c] = (8 - s) << 8 | c >> s << s | 1 << (s - 1);
getbits(-1);
for (i = 0; i < sizeof(buf) / sizeof(short); i++)
((short *)buf)[i] = 2048;
for (row = 0; row < height; row += 4)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
FORC3 mul[c] = getbits(6);
#ifdef LIBRAW_LIBRARY_BUILD
if (!mul[0] || !mul[1] || !mul[2])
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
FORC3
{
val = ((0x1000000 / last[c] + 0x7ff) >> 12) * mul[c];
s = val > 65564 ? 10 : 12;
x = ~((~0u) << (s - 1));
val <<= 12 - s;
for (i = 0; i < sizeof(buf[0]) / sizeof(short); i++)
((short *)buf[c])[i] = (((short *)buf[c])[i] * val + x) >> s;
last[c] = mul[c];
for (r = 0; r <= !c; r++)
{
buf[c][1][width / 2] = buf[c][2][width / 2] = mul[c] << 7;
for (tree = 1, col = width / 2; col > 0;)
{
if ((tree = radc_token(tree)))
{
col -= 2;
if(col>=0)
{
if (tree == 8)
FORYX buf[c][y][x] = (uchar)radc_token(18) * mul[c];
else
FORYX buf[c][y][x] = radc_token(tree + 10) * 16 + PREDICTOR;
}
}
else
do
{
nreps = (col > 2) ? radc_token(9) + 1 : 1;
for (rep = 0; rep < 8 && rep < nreps && col > 0; rep++)
{
col -= 2;
if(col>=0)
FORYX buf[c][y][x] = PREDICTOR;
if (rep & 1)
{
step = radc_token(10) << 4;
FORYX buf[c][y][x] += step;
}
}
} while (nreps == 9);
}
for (y = 0; y < 2; y++)
for (x = 0; x < width / 2; x++)
{
val = (buf[c][y + 1][x] << 4) / mul[c];
if (val < 0)
val = 0;
if (c)
RAW(row + y * 2 + c - 1, x * 2 + 2 - c) = val;
else
RAW(row + r * 2 + y, x * 2 + y) = val;
}
memcpy(buf[c][0] + !c, buf[c][2], sizeof buf[c][0] - 2 * !c);
}
}
for (y = row; y < row + 4; y++)
for (x = 0; x < width; x++)
if ((x + y) & 1)
{
r = x ? x - 1 : x + 1;
s = x + 1 < width ? x + 1 : x - 1;
val = (RAW(y, x) - 2048) * 2 + (RAW(y, r) + RAW(y, s)) / 2;
if (val < 0)
val = 0;
RAW(y, x) = val;
}
}
for (i = 0; i < height * width; i++)
raw_image[i] = curve[raw_image[i]];
maximum = 0x3fff;
}
#undef FORYX
#undef PREDICTOR
#ifdef NO_JPEG
void CLASS kodak_jpeg_load_raw() {}
void CLASS lossy_dng_load_raw() {}
#else
#ifndef LIBRAW_LIBRARY_BUILD
METHODDEF(boolean)
fill_input_buffer(j_decompress_ptr cinfo)
{
static uchar jpeg_buffer[4096];
size_t nbytes;
nbytes = fread(jpeg_buffer, 1, 4096, ifp);
swab(jpeg_buffer, jpeg_buffer, nbytes);
cinfo->src->next_input_byte = jpeg_buffer;
cinfo->src->bytes_in_buffer = nbytes;
return TRUE;
}
void CLASS kodak_jpeg_load_raw()
{
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPARRAY buf;
JSAMPLE(*pixel)[3];
int row, col;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
jpeg_stdio_src(&cinfo, ifp);
cinfo.src->fill_input_buffer = fill_input_buffer;
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
if ((cinfo.output_width != width) || (cinfo.output_height * 2 != height) || (cinfo.output_components != 3))
{
fprintf(stderr, _("%s: incorrect JPEG dimensions\n"), ifname);
jpeg_destroy_decompress(&cinfo);
longjmp(failure, 3);
}
buf = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, width * 3, 1);
while (cinfo.output_scanline < cinfo.output_height)
{
row = cinfo.output_scanline * 2;
jpeg_read_scanlines(&cinfo, buf, 1);
pixel = (JSAMPLE(*)[3])buf[0];
for (col = 0; col < width; col += 2)
{
RAW(row + 0, col + 0) = pixel[col + 0][1] << 1;
RAW(row + 1, col + 1) = pixel[col + 1][1] << 1;
RAW(row + 0, col + 1) = pixel[col][0] + pixel[col + 1][0];
RAW(row + 1, col + 0) = pixel[col][2] + pixel[col + 1][2];
}
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
maximum = 0xff << 1;
}
#else
struct jpegErrorManager
{
struct jpeg_error_mgr pub;
};
static void jpegErrorExit(j_common_ptr cinfo)
{
jpegErrorManager *myerr = (jpegErrorManager *)cinfo->err;
throw LIBRAW_EXCEPTION_DECODE_JPEG;
}
// LibRaw's Kodak_jpeg_load_raw
void CLASS kodak_jpeg_load_raw()
{
if (data_size < 1)
throw LIBRAW_EXCEPTION_DECODE_JPEG;
int row, col;
jpegErrorManager jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
unsigned char *jpg_buf = (unsigned char *)malloc(data_size);
merror(jpg_buf, "kodak_jpeg_load_raw");
unsigned char *pixel_buf = (unsigned char *)malloc(width * 3);
jpeg_create_decompress(&cinfo);
merror(pixel_buf, "kodak_jpeg_load_raw");
fread(jpg_buf, data_size, 1, ifp);
swab((char *)jpg_buf, (char *)jpg_buf, data_size);
try
{
jpeg_mem_src(&cinfo, jpg_buf, data_size);
int rc = jpeg_read_header(&cinfo, TRUE);
if (rc != 1)
throw LIBRAW_EXCEPTION_DECODE_JPEG;
jpeg_start_decompress(&cinfo);
if ((cinfo.output_width != width) || (cinfo.output_height * 2 != height) || (cinfo.output_components != 3))
{
throw LIBRAW_EXCEPTION_DECODE_JPEG;
}
unsigned char *buf[1];
buf[0] = pixel_buf;
while (cinfo.output_scanline < cinfo.output_height)
{
checkCancel();
row = cinfo.output_scanline * 2;
jpeg_read_scanlines(&cinfo, buf, 1);
unsigned char(*pixel)[3] = (unsigned char(*)[3])buf[0];
for (col = 0; col < width; col += 2)
{
RAW(row + 0, col + 0) = pixel[col + 0][1] << 1;
RAW(row + 1, col + 1) = pixel[col + 1][1] << 1;
RAW(row + 0, col + 1) = pixel[col][0] + pixel[col + 1][0];
RAW(row + 1, col + 0) = pixel[col][2] + pixel[col + 1][2];
}
}
}
catch (...)
{
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
free(jpg_buf);
free(pixel_buf);
throw;
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
free(jpg_buf);
free(pixel_buf);
maximum = 0xff << 1;
}
#endif
#ifndef LIBRAW_LIBRARY_BUILD
void CLASS gamma_curve(double pwr, double ts, int mode, int imax);
#endif
void CLASS lossy_dng_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
struct jpeg_decompress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPARRAY buf;
JSAMPLE(*pixel)[3];
unsigned sorder = order, ntags, opcode, deg, i, j, c;
unsigned save = data_offset - 4, trow = 0, tcol = 0, row, col;
ushort cur[3][256];
double coeff[9], tot;
if (meta_offset)
{
fseek(ifp, meta_offset, SEEK_SET);
order = 0x4d4d;
ntags = get4();
while (ntags--)
{
opcode = get4();
get4();
get4();
if (opcode != 8)
{
fseek(ifp, get4(), SEEK_CUR);
continue;
}
fseek(ifp, 20, SEEK_CUR);
if ((c = get4()) > 2)
break;
fseek(ifp, 12, SEEK_CUR);
if ((deg = get4()) > 8)
break;
for (i = 0; i <= deg && i < 9; i++)
coeff[i] = getreal(12);
for (i = 0; i < 256; i++)
{
for (tot = j = 0; j <= deg; j++)
tot += coeff[j] * pow(i / 255.0, (int)j);
cur[c][i] = tot * 0xffff;
}
}
order = sorder;
}
else
{
gamma_curve(1 / 2.4, 12.92, 1, 255);
FORC3 memcpy(cur[c], curve, sizeof cur[0]);
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
while (trow < raw_height)
{
fseek(ifp, save += 4, SEEK_SET);
if (tile_length < INT_MAX)
fseek(ifp, get4(), SEEK_SET);
#ifdef LIBRAW_LIBRARY_BUILD
if (libraw_internal_data.internal_data.input->jpeg_src(&cinfo) == -1)
{
jpeg_destroy_decompress(&cinfo);
throw LIBRAW_EXCEPTION_DECODE_JPEG;
}
#else
jpeg_stdio_src(&cinfo, ifp);
#endif
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
buf = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, cinfo.output_width * 3, 1);
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
while (cinfo.output_scanline < cinfo.output_height && (row = trow + cinfo.output_scanline) < height)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
jpeg_read_scanlines(&cinfo, buf, 1);
pixel = (JSAMPLE(*)[3])buf[0];
for (col = 0; col < cinfo.output_width && tcol + col < width; col++)
{
FORC3 image[row * width + tcol + col][c] = cur[c][pixel[col][c]];
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
jpeg_destroy_decompress(&cinfo);
throw;
}
#endif
jpeg_abort_decompress(&cinfo);
if ((tcol += tile_width) >= raw_width)
trow += tile_length + (tcol = 0);
}
jpeg_destroy_decompress(&cinfo);
maximum = 0xffff;
}
#endif
void CLASS kodak_dc120_load_raw()
{
static const int mul[4] = {162, 192, 187, 92};
static const int add[4] = {0, 636, 424, 212};
uchar pixel[848];
int row, shift, col;
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (fread(pixel, 1, 848, ifp) < 848)
derror();
shift = row * mul[row & 3] + add[row & 3];
for (col = 0; col < width; col++)
RAW(row, col) = (ushort)pixel[(col + shift) % 848];
}
maximum = 0xff;
}
void CLASS eight_bit_load_raw()
{
uchar *pixel;
unsigned row, col;
pixel = (uchar *)calloc(raw_width, sizeof *pixel);
merror(pixel, "eight_bit_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (fread(pixel, 1, raw_width, ifp) < raw_width)
derror();
for (col = 0; col < raw_width; col++)
RAW(row, col) = curve[pixel[col]];
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
maximum = curve[0xff];
}
void CLASS kodak_c330_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
uchar *pixel;
int row, col, y, cb, cr, rgb[3], c;
pixel = (uchar *)calloc(raw_width, 2 * sizeof *pixel);
merror(pixel, "kodak_c330_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (fread(pixel, raw_width, 2, ifp) < 2)
derror();
if (load_flags && (row & 31) == 31)
fseek(ifp, raw_width * 32, SEEK_CUR);
for (col = 0; col < width; col++)
{
y = pixel[col * 2];
cb = pixel[(col * 2 & -4) | 1] - 128;
cr = pixel[(col * 2 & -4) | 3] - 128;
rgb[1] = y - ((cb + cr + 2) >> 2);
rgb[2] = rgb[1] + cb;
rgb[0] = rgb[1] + cr;
FORC3 image[row * width + col][c] = curve[LIM(rgb[c], 0, 255)];
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
maximum = curve[0xff];
}
void CLASS kodak_c603_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
uchar *pixel;
int row, col, y, cb, cr, rgb[3], c;
pixel = (uchar *)calloc(raw_width, 3 * sizeof *pixel);
merror(pixel, "kodak_c603_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if (~row & 1)
if (fread(pixel, raw_width, 3, ifp) < 3)
derror();
for (col = 0; col < width; col++)
{
y = pixel[width * 2 * (row & 1) + col];
cb = pixel[width + (col & -2)] - 128;
cr = pixel[width + (col & -2) + 1] - 128;
rgb[1] = y - ((cb + cr + 2) >> 2);
rgb[2] = rgb[1] + cb;
rgb[0] = rgb[1] + cr;
FORC3 image[row * width + col][c] = curve[LIM(rgb[c], 0, 255)];
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
maximum = curve[0xff];
}
void CLASS kodak_262_load_raw()
{
static const uchar kodak_tree[2][26] = {
{0, 1, 5, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
{0, 3, 1, 1, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9}};
ushort *huff[2];
uchar *pixel;
int *strip, ns, c, row, col, chess, pi = 0, pi1, pi2, pred, val;
FORC(2) huff[c] = make_decoder(kodak_tree[c]);
ns = (raw_height + 63) >> 5;
pixel = (uchar *)malloc(raw_width * 32 + ns * 4);
merror(pixel, "kodak_262_load_raw()");
strip = (int *)(pixel + raw_width * 32);
order = 0x4d4d;
FORC(ns) strip[c] = get4();
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
if ((row & 31) == 0)
{
fseek(ifp, strip[row >> 5], SEEK_SET);
getbits(-1);
pi = 0;
}
for (col = 0; col < raw_width; col++)
{
chess = (row + col) & 1;
pi1 = chess ? pi - 2 : pi - raw_width - 1;
pi2 = chess ? pi - 2 * raw_width : pi - raw_width + 1;
if (col <= chess)
pi1 = -1;
if (pi1 < 0)
pi1 = pi2;
if (pi2 < 0)
pi2 = pi1;
if (pi1 < 0 && col > 1)
pi1 = pi2 = pi - 2;
pred = (pi1 < 0) ? 0 : (pixel[pi1] + pixel[pi2]) >> 1;
pixel[pi] = val = pred + ljpeg_diff(huff[chess]);
if (val >> 8)
derror();
val = curve[pixel[pi++]];
RAW(row, col) = val;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(pixel);
throw;
}
#endif
free(pixel);
FORC(2) free(huff[c]);
}
int CLASS kodak_65000_decode(short *out, int bsize)
{
uchar c, blen[768];
ushort raw[6];
INT64 bitbuf = 0;
int save, bits = 0, i, j, len, diff;
save = ftell(ifp);
bsize = (bsize + 3) & -4;
for (i = 0; i < bsize; i += 2)
{
c = fgetc(ifp);
if ((blen[i] = c & 15) > 12 || (blen[i + 1] = c >> 4) > 12)
{
fseek(ifp, save, SEEK_SET);
for (i = 0; i < bsize; i += 8)
{
read_shorts(raw, 6);
out[i] = raw[0] >> 12 << 8 | raw[2] >> 12 << 4 | raw[4] >> 12;
out[i + 1] = raw[1] >> 12 << 8 | raw[3] >> 12 << 4 | raw[5] >> 12;
for (j = 0; j < 6; j++)
out[i + 2 + j] = raw[j] & 0xfff;
}
return 1;
}
}
if ((bsize & 7) == 4)
{
bitbuf = fgetc(ifp) << 8;
bitbuf += fgetc(ifp);
bits = 16;
}
for (i = 0; i < bsize; i++)
{
len = blen[i];
if (bits < len)
{
for (j = 0; j < 32; j += 8)
bitbuf += (INT64)fgetc(ifp) << (bits + (j ^ 8));
bits += 32;
}
diff = bitbuf & (0xffff >> (16 - len));
bitbuf >>= len;
bits -= len;
if ((diff & (1 << (len - 1))) == 0)
diff -= (1 << len) - 1;
out[i] = diff;
}
return 0;
}
void CLASS kodak_65000_load_raw()
{
short buf[272]; /* 264 looks enough */
int row, col, len, pred[2], ret, i;
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < width; col += 256)
{
pred[0] = pred[1] = 0;
len = MIN(256, width - col);
ret = kodak_65000_decode(buf, len);
for (i = 0; i < len; i++)
{
int idx = ret ? buf[i] : (pred[i & 1] += buf[i]);
if (idx >= 0 && idx < 0xffff)
{
if ((RAW(row, col + i) = curve[idx]) >> 12)
derror();
}
else
derror();
}
}
}
}
void CLASS kodak_ycbcr_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
short buf[384], *bp;
int row, col, len, c, i, j, k, y[2][2], cb, cr, rgb[3];
ushort *ip;
unsigned int bits = (load_flags && load_flags > 9 && load_flags < 17) ? load_flags : 10;
for (row = 0; row < height; row += 2)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < width; col += 128)
{
len = MIN(128, width - col);
kodak_65000_decode(buf, len * 3);
y[0][1] = y[1][1] = cb = cr = 0;
for (bp = buf, i = 0; i < len; i += 2, bp += 2)
{
cb += bp[4];
cr += bp[5];
rgb[1] = -((cb + cr + 2) >> 2);
rgb[2] = rgb[1] + cb;
rgb[0] = rgb[1] + cr;
for (j = 0; j < 2; j++)
for (k = 0; k < 2; k++)
{
if ((y[j][k] = y[j][k ^ 1] + *bp++) >> bits)
derror();
ip = image[(row + j) * width + col + i + k];
FORC3 ip[c] = curve[LIM(y[j][k] + rgb[c], 0, 0xfff)];
}
}
}
}
}
void CLASS kodak_rgb_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
short buf[768], *bp;
int row, col, len, c, i, rgb[3], ret;
ushort *ip = image[0];
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < width; col += 256)
{
len = MIN(256, width - col);
ret = kodak_65000_decode(buf, len * 3);
memset(rgb, 0, sizeof rgb);
for (bp = buf, i = 0; i < len; i++, ip += 4)
#ifdef LIBRAW_LIBRARY_BUILD
if (load_flags == 12)
{
FORC3 ip[c] = ret ? (*bp++) : (rgb[c] += *bp++);
}
else
#endif
FORC3 if ((ip[c] = ret ? (*bp++) : (rgb[c] += *bp++)) >> 12) derror();
}
}
}
void CLASS kodak_thumb_load_raw()
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!image)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
int row, col;
colors = thumb_misc >> 5;
for (row = 0; row < height; row++)
for (col = 0; col < width; col++)
read_shorts(image[row * width + col], colors);
maximum = (1 << (thumb_misc & 31)) - 1;
}
void CLASS sony_decrypt(unsigned *data, int len, int start, int key)
{
#ifndef LIBRAW_NOTHREADS
#define pad tls->sony_decrypt.pad
#define p tls->sony_decrypt.p
#else
static unsigned pad[128], p;
#endif
if (start)
{
for (p = 0; p < 4; p++)
pad[p] = key = key * 48828125 + 1;
pad[3] = pad[3] << 1 | (pad[0] ^ pad[2]) >> 31;
for (p = 4; p < 127; p++)
pad[p] = (pad[p - 4] ^ pad[p - 2]) << 1 | (pad[p - 3] ^ pad[p - 1]) >> 31;
for (p = 0; p < 127; p++)
pad[p] = htonl(pad[p]);
}
while (len--)
{
*data++ ^= pad[p & 127] = pad[(p + 1) & 127] ^ pad[(p + 65) & 127];
p++;
}
#ifndef LIBRAW_NOTHREADS
#undef pad
#undef p
#endif
}
void CLASS sony_load_raw()
{
uchar head[40];
ushort *pixel;
unsigned i, key, row, col;
fseek(ifp, 200896, SEEK_SET);
fseek(ifp, (unsigned)fgetc(ifp) * 4 - 1, SEEK_CUR);
order = 0x4d4d;
key = get4();
fseek(ifp, 164600, SEEK_SET);
fread(head, 1, 40, ifp);
sony_decrypt((unsigned *)head, 10, 1, key);
for (i = 26; i-- > 22;)
key = key << 8 | head[i];
fseek(ifp, data_offset, SEEK_SET);
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
pixel = raw_image + row * raw_width;
if (fread(pixel, 2, raw_width, ifp) < raw_width)
derror();
sony_decrypt((unsigned *)pixel, raw_width / 2, !row, key);
for (col = 0; col < raw_width; col++)
if ((pixel[col] = ntohs(pixel[col])) >> 14)
derror();
}
maximum = 0x3ff0;
}
void CLASS sony_arw_load_raw()
{
ushort huff[32770];
static const ushort tab[18] = {0xf11, 0xf10, 0xe0f, 0xd0e, 0xc0d, 0xb0c, 0xa0b, 0x90a, 0x809,
0x708, 0x607, 0x506, 0x405, 0x304, 0x303, 0x300, 0x202, 0x201};
int i, c, n, col, row, sum = 0;
huff[0] = 15;
for (n = i = 0; i < 18; i++)
FORC(32768 >> (tab[i] >> 8)) huff[++n] = tab[i];
getbits(-1);
for (col = raw_width; col--;)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (row = 0; row < raw_height + 1; row += 2)
{
if (row == raw_height)
row = 1;
if ((sum += ljpeg_diff(huff)) >> 12)
derror();
if (row < height)
RAW(row, col) = sum;
}
}
}
void CLASS sony_arw2_load_raw()
{
uchar *data, *dp;
ushort pix[16];
int row, col, val, max, min, imax, imin, sh, bit, i;
data = (uchar *)malloc(raw_width + 1);
merror(data, "sony_arw2_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
try
{
#endif
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
fread(data, 1, raw_width, ifp);
for (dp = data, col = 0; col < raw_width - 30; dp += 16)
{
max = 0x7ff & (val = sget4(dp));
min = 0x7ff & val >> 11;
imax = 0x0f & val >> 22;
imin = 0x0f & val >> 26;
for (sh = 0; sh < 4 && 0x80 << sh <= max - min; sh++)
;
#ifdef LIBRAW_LIBRARY_BUILD
/* flag checks if outside of loop */
if (!(imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_ALLFLAGS) // no flag set
|| (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_DELTATOVALUE))
{
for (bit = 30, i = 0; i < 16; i++)
if (i == imax)
pix[i] = max;
else if (i == imin)
pix[i] = min;
else
{
pix[i] = ((sget2(dp + (bit >> 3)) >> (bit & 7) & 0x7f) << sh) + min;
if (pix[i] > 0x7ff)
pix[i] = 0x7ff;
bit += 7;
}
}
else if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_BASEONLY)
{
for (bit = 30, i = 0; i < 16; i++)
if (i == imax)
pix[i] = max;
else if (i == imin)
pix[i] = min;
else
pix[i] = 0;
}
else if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_DELTAONLY)
{
for (bit = 30, i = 0; i < 16; i++)
if (i == imax)
pix[i] = 0;
else if (i == imin)
pix[i] = 0;
else
{
pix[i] = ((sget2(dp + (bit >> 3)) >> (bit & 7) & 0x7f) << sh) + min;
if (pix[i] > 0x7ff)
pix[i] = 0x7ff;
bit += 7;
}
}
else if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_DELTAZEROBASE)
{
for (bit = 30, i = 0; i < 16; i++)
if (i == imax)
pix[i] = 0;
else if (i == imin)
pix[i] = 0;
else
{
pix[i] = ((sget2(dp + (bit >> 3)) >> (bit & 7) & 0x7f) << sh);
if (pix[i] > 0x7ff)
pix[i] = 0x7ff;
bit += 7;
}
}
#else
/* unaltered dcraw processing */
for (bit = 30, i = 0; i < 16; i++)
if (i == imax)
pix[i] = max;
else if (i == imin)
pix[i] = min;
else
{
pix[i] = ((sget2(dp + (bit >> 3)) >> (bit & 7) & 0x7f) << sh) + min;
if (pix[i] > 0x7ff)
pix[i] = 0x7ff;
bit += 7;
}
#endif
#ifdef LIBRAW_LIBRARY_BUILD
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_DELTATOVALUE)
{
for (i = 0; i < 16; i++, col += 2)
{
unsigned slope = pix[i] < 1001 ? 2 : curve[pix[i] << 1] - curve[(pix[i] << 1) - 2];
unsigned step = 1 << sh;
RAW(row, col) = curve[pix[i] << 1] > black + imgdata.params.sony_arw2_posterization_thr
? LIM(((slope * step * 1000) / (curve[pix[i] << 1] - black)), 0, 10000)
: 0;
}
}
else
{
for (i = 0; i < 16; i++, col += 2)
RAW(row, col) = curve[pix[i] << 1];
}
#else
for (i = 0; i < 16; i++, col += 2)
RAW(row, col) = curve[pix[i] << 1] >> 2;
#endif
col -= col & 1 ? 1 : 31;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
free(data);
throw;
}
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SONYARW2_DELTATOVALUE)
maximum = 10000;
#endif
free(data);
}
void CLASS samsung_load_raw()
{
int row, col, c, i, dir, op[4], len[4];
#ifdef LIBRAW_LIBRARY_BUILD
if(raw_width> 32768 || raw_height > 32768) // definitely too much for old samsung
throw LIBRAW_EXCEPTION_IO_BADFILE;
#endif
unsigned maxpixels = raw_width*(raw_height+7);
order = 0x4949;
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
fseek(ifp, strip_offset + row * 4, SEEK_SET);
fseek(ifp, data_offset + get4(), SEEK_SET);
ph1_bits(-1);
FORC4 len[c] = row < 2 ? 7 : 4;
for (col = 0; col < raw_width; col += 16)
{
dir = ph1_bits(1);
FORC4 op[c] = ph1_bits(2);
FORC4 switch (op[c])
{
case 3:
len[c] = ph1_bits(4);
break;
case 2:
len[c]--;
break;
case 1:
len[c]++;
}
for (c = 0; c < 16; c += 2)
{
i = len[((c & 1) << 1) | (c >> 3)];
unsigned idest = RAWINDEX(row, col + c);
unsigned isrc = (dir ? RAWINDEX(row + (~c | -2), col + c) : col ? RAWINDEX(row, col + (c | -2)) : 0);
if(idest < maxpixels && isrc < maxpixels) // less than zero is handled by unsigned conversion
RAW(row, col + c) = ((signed)ph1_bits(i) << (32 - i) >> (32 - i)) + (dir ? RAW(row + (~c | -2), col + c) : col ? RAW(row, col + (c | -2)) : 128);
else
derror();
if (c == 14)
c = -1;
}
}
}
for (row = 0; row < raw_height - 1; row += 2)
for (col = 0; col < raw_width - 1; col += 2)
SWAP(RAW(row, col + 1), RAW(row + 1, col));
}
void CLASS samsung2_load_raw()
{
static const ushort tab[14] = {0x304, 0x307, 0x206, 0x205, 0x403, 0x600, 0x709,
0x80a, 0x90b, 0xa0c, 0xa0d, 0x501, 0x408, 0x402};
ushort huff[1026], vpred[2][2] = {{0, 0}, {0, 0}}, hpred[2];
int i, c, n, row, col, diff;
huff[0] = 10;
for (n = i = 0; i < 14; i++)
FORC(1024 >> (tab[i] >> 8)) huff[++n] = tab[i];
getbits(-1);
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < raw_width; col++)
{
diff = ljpeg_diff(huff);
if (col < 2)
hpred[col] = vpred[row & 1][col] += diff;
else
hpred[col & 1] += diff;
RAW(row, col) = hpred[col & 1];
if (hpred[col & 1] >> tiff_bps)
derror();
}
}
}
void CLASS samsung3_load_raw()
{
int opt, init, mag, pmode, row, tab, col, pred, diff, i, c;
ushort lent[3][2], len[4], *prow[2];
order = 0x4949;
fseek(ifp, 9, SEEK_CUR);
opt = fgetc(ifp);
init = (get2(), get2());
for (row = 0; row < raw_height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
fseek(ifp, (data_offset - ftell(ifp)) & 15, SEEK_CUR);
ph1_bits(-1);
mag = 0;
pmode = 7;
FORC(6)((ushort *)lent)[c] = row < 2 ? 7 : 4;
prow[row & 1] = &RAW(row - 1, 1 - ((row & 1) << 1)); // green
prow[~row & 1] = &RAW(row - 2, 0); // red and blue
for (tab = 0; tab + 15 < raw_width; tab += 16)
{
if (~opt & 4 && !(tab & 63))
{
i = ph1_bits(2);
mag = i < 3 ? mag - '2' + "204"[i] : ph1_bits(12);
}
if (opt & 2)
pmode = 7 - 4 * ph1_bits(1);
else if (!ph1_bits(1))
pmode = ph1_bits(3);
if (opt & 1 || !ph1_bits(1))
{
FORC4 len[c] = ph1_bits(2);
FORC4
{
i = ((row & 1) << 1 | (c & 1)) % 3;
len[c] = len[c] < 3 ? lent[i][0] - '1' + "120"[len[c]] : ph1_bits(4);
lent[i][0] = lent[i][1];
lent[i][1] = len[c];
}
}
FORC(16)
{
col = tab + (((c & 7) << 1) ^ (c >> 3) ^ (row & 1));
pred =
(pmode == 7 || row < 2)
? (tab ? RAW(row, tab - 2 + (col & 1)) : init)
: (prow[col & 1][col - '4' + "0224468"[pmode]] + prow[col & 1][col - '4' + "0244668"[pmode]] + 1) >> 1;
diff = ph1_bits(i = len[c >> 2]);
if (diff >> (i - 1))
diff -= 1 << i;
diff = diff * (mag * 2 + 1) + mag;
RAW(row, col) = pred + diff;
}
}
}
}
#define HOLE(row) ((holes >> (((row)-raw_height) & 7)) & 1)
/* Kudos to Rich Taylor for figuring out SMaL's compression algorithm. */
void CLASS smal_decode_segment(unsigned seg[2][2], int holes)
{
uchar hist[3][13] = {{7, 7, 0, 0, 63, 55, 47, 39, 31, 23, 15, 7, 0},
{7, 7, 0, 0, 63, 55, 47, 39, 31, 23, 15, 7, 0},
{3, 3, 0, 0, 63, 47, 31, 15, 0}};
int low, high = 0xff, carry = 0, nbits = 8;
int pix, s, count, bin, next, i, sym[3];
uchar diff, pred[] = {0, 0};
ushort data = 0, range = 0;
fseek(ifp, seg[0][1] + 1, SEEK_SET);
getbits(-1);
if (seg[1][0] > raw_width * raw_height)
seg[1][0] = raw_width * raw_height;
for (pix = seg[0][0]; pix < seg[1][0]; pix++)
{
for (s = 0; s < 3; s++)
{
data = data << nbits | getbits(nbits);
if (carry < 0)
carry = (nbits += carry + 1) < 1 ? nbits - 1 : 0;
while (--nbits >= 0)
if ((data >> nbits & 0xff) == 0xff)
break;
if (nbits > 0)
data = ((data & ((1 << (nbits - 1)) - 1)) << 1) |
((data + (((data & (1 << (nbits - 1)))) << 1)) & ((~0u) << nbits));
if (nbits >= 0)
{
data += getbits(1);
carry = nbits - 8;
}
count = ((((data - range + 1) & 0xffff) << 2) - 1) / (high >> 4);
for (bin = 0; hist[s][bin + 5] > count; bin++)
;
low = hist[s][bin + 5] * (high >> 4) >> 2;
if (bin)
high = hist[s][bin + 4] * (high >> 4) >> 2;
high -= low;
for (nbits = 0; high << nbits < 128; nbits++)
;
range = (range + low) << nbits;
high <<= nbits;
next = hist[s][1];
if (++hist[s][2] > hist[s][3])
{
next = (next + 1) & hist[s][0];
hist[s][3] = (hist[s][next + 4] - hist[s][next + 5]) >> 2;
hist[s][2] = 1;
}
if (hist[s][hist[s][1] + 4] - hist[s][hist[s][1] + 5] > 1)
{
if (bin < hist[s][1])
for (i = bin; i < hist[s][1]; i++)
hist[s][i + 5]--;
else if (next <= bin)
for (i = hist[s][1]; i < bin; i++)
hist[s][i + 5]++;
}
hist[s][1] = next;
sym[s] = bin;
}
diff = sym[2] << 5 | sym[1] << 2 | (sym[0] & 3);
if (sym[0] & 4)
diff = diff ? -diff : 0x80;
if (ftell(ifp) + 12 >= seg[1][1])
diff = 0;
#ifdef LIBRAW_LIBRARY_BUILD
if (pix >= raw_width * raw_height)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
raw_image[pix] = pred[pix & 1] += diff;
if (!(pix & 1) && HOLE(pix / raw_width))
pix += 2;
}
maximum = 0xff;
}
void CLASS smal_v6_load_raw()
{
unsigned seg[2][2];
fseek(ifp, 16, SEEK_SET);
seg[0][0] = 0;
seg[0][1] = get2();
seg[1][0] = raw_width * raw_height;
seg[1][1] = INT_MAX;
smal_decode_segment(seg, 0);
}
int CLASS median4(int *p)
{
int min, max, sum, i;
min = max = sum = p[0];
for (i = 1; i < 4; i++)
{
sum += p[i];
if (min > p[i])
min = p[i];
if (max < p[i])
max = p[i];
}
return (sum - min - max) >> 1;
}
void CLASS fill_holes(int holes)
{
int row, col, val[4];
for (row = 2; row < height - 2; row++)
{
if (!HOLE(row))
continue;
for (col = 1; col < width - 1; col += 4)
{
val[0] = RAW(row - 1, col - 1);
val[1] = RAW(row - 1, col + 1);
val[2] = RAW(row + 1, col - 1);
val[3] = RAW(row + 1, col + 1);
RAW(row, col) = median4(val);
}
for (col = 2; col < width - 2; col += 4)
if (HOLE(row - 2) || HOLE(row + 2))
RAW(row, col) = (RAW(row, col - 2) + RAW(row, col + 2)) >> 1;
else
{
val[0] = RAW(row, col - 2);
val[1] = RAW(row, col + 2);
val[2] = RAW(row - 2, col);
val[3] = RAW(row + 2, col);
RAW(row, col) = median4(val);
}
}
}
void CLASS smal_v9_load_raw()
{
unsigned seg[256][2], offset, nseg, holes, i;
fseek(ifp, 67, SEEK_SET);
offset = get4();
nseg = (uchar)fgetc(ifp);
fseek(ifp, offset, SEEK_SET);
for (i = 0; i < nseg * 2; i++)
((unsigned *)seg)[i] = get4() + data_offset * (i & 1);
fseek(ifp, 78, SEEK_SET);
holes = fgetc(ifp);
fseek(ifp, 88, SEEK_SET);
seg[nseg][0] = raw_height * raw_width;
seg[nseg][1] = get4() + data_offset;
for (i = 0; i < nseg; i++)
smal_decode_segment(seg + i, holes);
if (holes)
fill_holes(holes);
}
void CLASS redcine_load_raw()
{
#ifndef NO_JASPER
int c, row, col;
jas_stream_t *in;
jas_image_t *jimg;
jas_matrix_t *jmat;
jas_seqent_t *data;
ushort *img, *pix;
jas_init();
#ifndef LIBRAW_LIBRARY_BUILD
in = jas_stream_fopen(ifname, "rb");
#else
in = (jas_stream_t *)ifp->make_jas_stream();
if (!in)
throw LIBRAW_EXCEPTION_DECODE_JPEG2000;
#endif
jas_stream_seek(in, data_offset + 20, SEEK_SET);
jimg = jas_image_decode(in, -1, 0);
#ifndef LIBRAW_LIBRARY_BUILD
if (!jimg)
longjmp(failure, 3);
#else
if (!jimg)
{
jas_stream_close(in);
throw LIBRAW_EXCEPTION_DECODE_JPEG2000;
}
#endif
jmat = jas_matrix_create(height / 2, width / 2);
merror(jmat, "redcine_load_raw()");
img = (ushort *)calloc((height + 2), (width + 2) * 2);
merror(img, "redcine_load_raw()");
#ifdef LIBRAW_LIBRARY_BUILD
bool fastexitflag = false;
try
{
#endif
FORC4
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
jas_image_readcmpt(jimg, c, 0, 0, width / 2, height / 2, jmat);
data = jas_matrix_getref(jmat, 0, 0);
for (row = c >> 1; row < height; row += 2)
for (col = c & 1; col < width; col += 2)
img[(row + 1) * (width + 2) + col + 1] = data[(row / 2) * (width / 2) + col / 2];
}
for (col = 1; col <= width; col++)
{
img[col] = img[2 * (width + 2) + col];
img[(height + 1) * (width + 2) + col] = img[(height - 1) * (width + 2) + col];
}
for (row = 0; row < height + 2; row++)
{
img[row * (width + 2)] = img[row * (width + 2) + 2];
img[(row + 1) * (width + 2) - 1] = img[(row + 1) * (width + 2) - 3];
}
for (row = 1; row <= height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
pix = img + row * (width + 2) + (col = 1 + (FC(row, 1) & 1));
for (; col <= width; col += 2, pix += 2)
{
c = (((pix[0] - 0x800) << 3) + pix[-(width + 2)] + pix[width + 2] + pix[-1] + pix[1]) >> 2;
pix[0] = LIM(c, 0, 4095);
}
}
for (row = 0; row < height; row++)
{
#ifdef LIBRAW_LIBRARY_BUILD
checkCancel();
#endif
for (col = 0; col < width; col++)
RAW(row, col) = curve[img[(row + 1) * (width + 2) + col + 1]];
}
#ifdef LIBRAW_LIBRARY_BUILD
}
catch (...)
{
fastexitflag = true;
}
#endif
free(img);
jas_matrix_destroy(jmat);
jas_image_destroy(jimg);
jas_stream_close(in);
#ifdef LIBRAW_LIBRARY_BUILD
if (fastexitflag)
throw LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK;
#endif
#endif
}
void CLASS crop_masked_pixels()
{
int row, col;
unsigned
#ifndef LIBRAW_LIBRARY_BUILD
r,
raw_pitch = raw_width * 2, c, m, mblack[8], zero, val;
#else
c,
m, zero, val;
#define mblack imgdata.color.black_stat
#endif
#ifndef LIBRAW_LIBRARY_BUILD
if (load_raw == &CLASS phase_one_load_raw || load_raw == &CLASS phase_one_load_raw_c)
phase_one_correct();
if (fuji_width)
{
for (row = 0; row < raw_height - top_margin * 2; row++)
{
for (col = 0; col < fuji_width << !fuji_layout; col++)
{
if (fuji_layout)
{
r = fuji_width - 1 - col + (row >> 1);
c = col + ((row + 1) >> 1);
}
else
{
r = fuji_width - 1 + row - (col >> 1);
c = row + ((col + 1) >> 1);
}
if (r < height && c < width)
BAYER(r, c) = RAW(row + top_margin, col + left_margin);
}
}
}
else
{
for (row = 0; row < height; row++)
for (col = 0; col < width; col++)
BAYER2(row, col) = RAW(row + top_margin, col + left_margin);
}
#endif
if (mask[0][3] > 0)
goto mask_set;
if (load_raw == &CLASS canon_load_raw || load_raw == &CLASS lossless_jpeg_load_raw)
{
mask[0][1] = mask[1][1] += 2;
mask[0][3] -= 2;
goto sides;
}
if (load_raw == &CLASS canon_600_load_raw || load_raw == &CLASS sony_load_raw ||
(load_raw == &CLASS eight_bit_load_raw && strncmp(model, "DC2", 3)) || load_raw == &CLASS kodak_262_load_raw ||
(load_raw == &CLASS packed_load_raw && (load_flags & 32)))
{
sides:
mask[0][0] = mask[1][0] = top_margin;
mask[0][2] = mask[1][2] = top_margin + height;
mask[0][3] += left_margin;
mask[1][1] += left_margin + width;
mask[1][3] += raw_width;
}
if (load_raw == &CLASS nokia_load_raw)
{
mask[0][2] = top_margin;
mask[0][3] = width;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (load_raw == &CLASS broadcom_load_raw)
{
mask[0][2] = top_margin;
mask[0][3] = width;
}
#endif
mask_set:
memset(mblack, 0, sizeof mblack);
for (zero = m = 0; m < 8; m++)
for (row = MAX(mask[m][0], 0); row < MIN(mask[m][2], raw_height); row++)
for (col = MAX(mask[m][1], 0); col < MIN(mask[m][3], raw_width); col++)
{
c = FC(row - top_margin, col - left_margin);
mblack[c] += val = raw_image[(row)*raw_pitch / 2 + (col)];
mblack[4 + c]++;
zero += !val;
}
if (load_raw == &CLASS canon_600_load_raw && width < raw_width)
{
black = (mblack[0] + mblack[1] + mblack[2] + mblack[3]) / (mblack[4] + mblack[5] + mblack[6] + mblack[7]) - 4;
#ifndef LIBRAW_LIBRARY_BUILD
canon_600_correct();
#endif
}
else if (zero < mblack[4] && mblack[5] && mblack[6] && mblack[7])
{
FORC4 cblack[c] = mblack[c] / mblack[4 + c];
black = cblack[4] = cblack[5] = cblack[6] = 0;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
#undef mblack
#endif
void CLASS remove_zeroes()
{
unsigned row, col, tot, n, r, c;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_REMOVE_ZEROES, 0, 2);
#endif
for (row = 0; row < height; row++)
for (col = 0; col < width; col++)
if (BAYER(row, col) == 0)
{
tot = n = 0;
for (r = row - 2; r <= row + 2; r++)
for (c = col - 2; c <= col + 2; c++)
if (r < height && c < width && FC(r, c) == FC(row, col) && BAYER(r, c))
tot += (n++, BAYER(r, c));
if (n)
BAYER(row, col) = tot / n;
}
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_REMOVE_ZEROES, 1, 2);
#endif
}
static const uchar xlat[2][256] = {
{0xc1, 0xbf, 0x6d, 0x0d, 0x59, 0xc5, 0x13, 0x9d, 0x83, 0x61, 0x6b, 0x4f, 0xc7, 0x7f, 0x3d, 0x3d, 0x53, 0x59, 0xe3,
0xc7, 0xe9, 0x2f, 0x95, 0xa7, 0x95, 0x1f, 0xdf, 0x7f, 0x2b, 0x29, 0xc7, 0x0d, 0xdf, 0x07, 0xef, 0x71, 0x89, 0x3d,
0x13, 0x3d, 0x3b, 0x13, 0xfb, 0x0d, 0x89, 0xc1, 0x65, 0x1f, 0xb3, 0x0d, 0x6b, 0x29, 0xe3, 0xfb, 0xef, 0xa3, 0x6b,
0x47, 0x7f, 0x95, 0x35, 0xa7, 0x47, 0x4f, 0xc7, 0xf1, 0x59, 0x95, 0x35, 0x11, 0x29, 0x61, 0xf1, 0x3d, 0xb3, 0x2b,
0x0d, 0x43, 0x89, 0xc1, 0x9d, 0x9d, 0x89, 0x65, 0xf1, 0xe9, 0xdf, 0xbf, 0x3d, 0x7f, 0x53, 0x97, 0xe5, 0xe9, 0x95,
0x17, 0x1d, 0x3d, 0x8b, 0xfb, 0xc7, 0xe3, 0x67, 0xa7, 0x07, 0xf1, 0x71, 0xa7, 0x53, 0xb5, 0x29, 0x89, 0xe5, 0x2b,
0xa7, 0x17, 0x29, 0xe9, 0x4f, 0xc5, 0x65, 0x6d, 0x6b, 0xef, 0x0d, 0x89, 0x49, 0x2f, 0xb3, 0x43, 0x53, 0x65, 0x1d,
0x49, 0xa3, 0x13, 0x89, 0x59, 0xef, 0x6b, 0xef, 0x65, 0x1d, 0x0b, 0x59, 0x13, 0xe3, 0x4f, 0x9d, 0xb3, 0x29, 0x43,
0x2b, 0x07, 0x1d, 0x95, 0x59, 0x59, 0x47, 0xfb, 0xe5, 0xe9, 0x61, 0x47, 0x2f, 0x35, 0x7f, 0x17, 0x7f, 0xef, 0x7f,
0x95, 0x95, 0x71, 0xd3, 0xa3, 0x0b, 0x71, 0xa3, 0xad, 0x0b, 0x3b, 0xb5, 0xfb, 0xa3, 0xbf, 0x4f, 0x83, 0x1d, 0xad,
0xe9, 0x2f, 0x71, 0x65, 0xa3, 0xe5, 0x07, 0x35, 0x3d, 0x0d, 0xb5, 0xe9, 0xe5, 0x47, 0x3b, 0x9d, 0xef, 0x35, 0xa3,
0xbf, 0xb3, 0xdf, 0x53, 0xd3, 0x97, 0x53, 0x49, 0x71, 0x07, 0x35, 0x61, 0x71, 0x2f, 0x43, 0x2f, 0x11, 0xdf, 0x17,
0x97, 0xfb, 0x95, 0x3b, 0x7f, 0x6b, 0xd3, 0x25, 0xbf, 0xad, 0xc7, 0xc5, 0xc5, 0xb5, 0x8b, 0xef, 0x2f, 0xd3, 0x07,
0x6b, 0x25, 0x49, 0x95, 0x25, 0x49, 0x6d, 0x71, 0xc7},
{0xa7, 0xbc, 0xc9, 0xad, 0x91, 0xdf, 0x85, 0xe5, 0xd4, 0x78, 0xd5, 0x17, 0x46, 0x7c, 0x29, 0x4c, 0x4d, 0x03, 0xe9,
0x25, 0x68, 0x11, 0x86, 0xb3, 0xbd, 0xf7, 0x6f, 0x61, 0x22, 0xa2, 0x26, 0x34, 0x2a, 0xbe, 0x1e, 0x46, 0x14, 0x68,
0x9d, 0x44, 0x18, 0xc2, 0x40, 0xf4, 0x7e, 0x5f, 0x1b, 0xad, 0x0b, 0x94, 0xb6, 0x67, 0xb4, 0x0b, 0xe1, 0xea, 0x95,
0x9c, 0x66, 0xdc, 0xe7, 0x5d, 0x6c, 0x05, 0xda, 0xd5, 0xdf, 0x7a, 0xef, 0xf6, 0xdb, 0x1f, 0x82, 0x4c, 0xc0, 0x68,
0x47, 0xa1, 0xbd, 0xee, 0x39, 0x50, 0x56, 0x4a, 0xdd, 0xdf, 0xa5, 0xf8, 0xc6, 0xda, 0xca, 0x90, 0xca, 0x01, 0x42,
0x9d, 0x8b, 0x0c, 0x73, 0x43, 0x75, 0x05, 0x94, 0xde, 0x24, 0xb3, 0x80, 0x34, 0xe5, 0x2c, 0xdc, 0x9b, 0x3f, 0xca,
0x33, 0x45, 0xd0, 0xdb, 0x5f, 0xf5, 0x52, 0xc3, 0x21, 0xda, 0xe2, 0x22, 0x72, 0x6b, 0x3e, 0xd0, 0x5b, 0xa8, 0x87,
0x8c, 0x06, 0x5d, 0x0f, 0xdd, 0x09, 0x19, 0x93, 0xd0, 0xb9, 0xfc, 0x8b, 0x0f, 0x84, 0x60, 0x33, 0x1c, 0x9b, 0x45,
0xf1, 0xf0, 0xa3, 0x94, 0x3a, 0x12, 0x77, 0x33, 0x4d, 0x44, 0x78, 0x28, 0x3c, 0x9e, 0xfd, 0x65, 0x57, 0x16, 0x94,
0x6b, 0xfb, 0x59, 0xd0, 0xc8, 0x22, 0x36, 0xdb, 0xd2, 0x63, 0x98, 0x43, 0xa1, 0x04, 0x87, 0x86, 0xf7, 0xa6, 0x26,
0xbb, 0xd6, 0x59, 0x4d, 0xbf, 0x6a, 0x2e, 0xaa, 0x2b, 0xef, 0xe6, 0x78, 0xb6, 0x4e, 0xe0, 0x2f, 0xdc, 0x7c, 0xbe,
0x57, 0x19, 0x32, 0x7e, 0x2a, 0xd0, 0xb8, 0xba, 0x29, 0x00, 0x3c, 0x52, 0x7d, 0xa8, 0x49, 0x3b, 0x2d, 0xeb, 0x25,
0x49, 0xfa, 0xa3, 0xaa, 0x39, 0xa7, 0xc5, 0xa7, 0x50, 0x11, 0x36, 0xfb, 0xc6, 0x67, 0x4a, 0xf5, 0xa5, 0x12, 0x65,
0x7e, 0xb0, 0xdf, 0xaf, 0x4e, 0xb3, 0x61, 0x7f, 0x2f}};
void CLASS gamma_curve(double pwr, double ts, int mode, int imax)
{
int i;
double g[6], bnd[2] = {0, 0}, r;
g[0] = pwr;
g[1] = ts;
g[2] = g[3] = g[4] = 0;
bnd[g[1] >= 1] = 1;
if (g[1] && (g[1] - 1) * (g[0] - 1) <= 0)
{
for (i = 0; i < 48; i++)
{
g[2] = (bnd[0] + bnd[1]) / 2;
if (g[0])
bnd[(pow(g[2] / g[1], -g[0]) - 1) / g[0] - 1 / g[2] > -1] = g[2];
else
bnd[g[2] / exp(1 - 1 / g[2]) < g[1]] = g[2];
}
g[3] = g[2] / g[1];
if (g[0])
g[4] = g[2] * (1 / g[0] - 1);
}
if (g[0])
g[5] = 1 / (g[1] * SQR(g[3]) / 2 - g[4] * (1 - g[3]) + (1 - pow(g[3], 1 + g[0])) * (1 + g[4]) / (1 + g[0])) - 1;
else
g[5] = 1 / (g[1] * SQR(g[3]) / 2 + 1 - g[2] - g[3] - g[2] * g[3] * (log(g[3]) - 1)) - 1;
if (!mode--)
{
memcpy(gamm, g, sizeof gamm);
return;
}
for (i = 0; i < 0x10000; i++)
{
curve[i] = 0xffff;
if ((r = (double)i / imax) < 1)
curve[i] = 0x10000 *
(mode ? (r < g[3] ? r * g[1] : (g[0] ? pow(r, g[0]) * (1 + g[4]) - g[4] : log(r) * g[2] + 1))
: (r < g[2] ? r / g[1] : (g[0] ? pow((r + g[4]) / (1 + g[4]), 1 / g[0]) : exp((r - 1) / g[2]))));
}
}
void CLASS pseudoinverse(double (*in)[3], double (*out)[3], int size)
{
double work[3][6], num;
int i, j, k;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 6; j++)
work[i][j] = j == i + 3;
for (j = 0; j < 3; j++)
for (k = 0; k < size; k++)
work[i][j] += in[k][i] * in[k][j];
}
for (i = 0; i < 3; i++)
{
num = work[i][i];
for (j = 0; j < 6; j++)
if(fabs(num)>0.00001f)
work[i][j] /= num;
for (k = 0; k < 3; k++)
{
if (k == i)
continue;
num = work[k][i];
for (j = 0; j < 6; j++)
work[k][j] -= work[i][j] * num;
}
}
for (i = 0; i < size; i++)
for (j = 0; j < 3; j++)
for (out[i][j] = k = 0; k < 3; k++)
out[i][j] += work[j][k + 3] * in[i][k];
}
void CLASS cam_xyz_coeff(float _rgb_cam[3][4], double cam_xyz[4][3])
{
double cam_rgb[4][3], inverse[4][3], num;
int i, j, k;
for (i = 0; i < colors; i++) /* Multiply out XYZ colorspace */
for (j = 0; j < 3; j++)
for (cam_rgb[i][j] = k = 0; k < 3; k++)
cam_rgb[i][j] += cam_xyz[i][k] * xyz_rgb[k][j];
for (i = 0; i < colors; i++)
{ /* Normalize cam_rgb so that */
for (num = j = 0; j < 3; j++) /* cam_rgb * (1,1,1) is (1,1,1,1) */
num += cam_rgb[i][j];
if (num > 0.00001)
{
for (j = 0; j < 3; j++)
cam_rgb[i][j] /= num;
pre_mul[i] = 1 / num;
}
else
{
for (j = 0; j < 3; j++)
cam_rgb[i][j] = 0.0;
pre_mul[i] = 1.0;
}
}
pseudoinverse(cam_rgb, inverse, colors);
for (i = 0; i < 3; i++)
for (j = 0; j < colors; j++)
_rgb_cam[i][j] = inverse[j][i];
}
#ifdef COLORCHECK
void CLASS colorcheck()
{
#define NSQ 24
// Coordinates of the GretagMacbeth ColorChecker squares
// width, height, 1st_column, 1st_row
int cut[NSQ][4]; // you must set these
// ColorChecker Chart under 6500-kelvin illumination
static const double gmb_xyY[NSQ][3] = {{0.400, 0.350, 10.1}, // Dark Skin
{0.377, 0.345, 35.8}, // Light Skin
{0.247, 0.251, 19.3}, // Blue Sky
{0.337, 0.422, 13.3}, // Foliage
{0.265, 0.240, 24.3}, // Blue Flower
{0.261, 0.343, 43.1}, // Bluish Green
{0.506, 0.407, 30.1}, // Orange
{0.211, 0.175, 12.0}, // Purplish Blue
{0.453, 0.306, 19.8}, // Moderate Red
{0.285, 0.202, 6.6}, // Purple
{0.380, 0.489, 44.3}, // Yellow Green
{0.473, 0.438, 43.1}, // Orange Yellow
{0.187, 0.129, 6.1}, // Blue
{0.305, 0.478, 23.4}, // Green
{0.539, 0.313, 12.0}, // Red
{0.448, 0.470, 59.1}, // Yellow
{0.364, 0.233, 19.8}, // Magenta
{0.196, 0.252, 19.8}, // Cyan
{0.310, 0.316, 90.0}, // White
{0.310, 0.316, 59.1}, // Neutral 8
{0.310, 0.316, 36.2}, // Neutral 6.5
{0.310, 0.316, 19.8}, // Neutral 5
{0.310, 0.316, 9.0}, // Neutral 3.5
{0.310, 0.316, 3.1}}; // Black
double gmb_cam[NSQ][4], gmb_xyz[NSQ][3];
double inverse[NSQ][3], cam_xyz[4][3], balance[4], num;
int c, i, j, k, sq, row, col, pass, count[4];
memset(gmb_cam, 0, sizeof gmb_cam);
for (sq = 0; sq < NSQ; sq++)
{
FORCC count[c] = 0;
for (row = cut[sq][3]; row < cut[sq][3] + cut[sq][1]; row++)
for (col = cut[sq][2]; col < cut[sq][2] + cut[sq][0]; col++)
{
c = FC(row, col);
if (c >= colors)
c -= 2;
gmb_cam[sq][c] += BAYER2(row, col);
BAYER2(row, col) = black + (BAYER2(row, col) - black) / 2;
count[c]++;
}
FORCC gmb_cam[sq][c] = gmb_cam[sq][c] / count[c] - black;
gmb_xyz[sq][0] = gmb_xyY[sq][2] * gmb_xyY[sq][0] / gmb_xyY[sq][1];
gmb_xyz[sq][1] = gmb_xyY[sq][2];
gmb_xyz[sq][2] = gmb_xyY[sq][2] * (1 - gmb_xyY[sq][0] - gmb_xyY[sq][1]) / gmb_xyY[sq][1];
}
pseudoinverse(gmb_xyz, inverse, NSQ);
for (pass = 0; pass < 2; pass++)
{
for (raw_color = i = 0; i < colors; i++)
for (j = 0; j < 3; j++)
for (cam_xyz[i][j] = k = 0; k < NSQ; k++)
cam_xyz[i][j] += gmb_cam[k][i] * inverse[k][j];
cam_xyz_coeff(rgb_cam, cam_xyz);
FORCC balance[c] = pre_mul[c] * gmb_cam[20][c];
for (sq = 0; sq < NSQ; sq++)
FORCC gmb_cam[sq][c] *= balance[c];
}
if (verbose)
{
printf(" { \"%s %s\", %d,\n\t{", make, model, black);
num = 10000 / (cam_xyz[1][0] + cam_xyz[1][1] + cam_xyz[1][2]);
FORCC for (j = 0; j < 3; j++) printf("%c%d", (c | j) ? ',' : ' ', (int)(cam_xyz[c][j] * num + 0.5));
puts(" } },");
}
#undef NSQ
}
#endif
void CLASS hat_transform(float *temp, float *base, int st, int size, int sc)
{
int i;
for (i = 0; i < sc; i++)
temp[i] = 2 * base[st * i] + base[st * (sc - i)] + base[st * (i + sc)];
for (; i + sc < size; i++)
temp[i] = 2 * base[st * i] + base[st * (i - sc)] + base[st * (i + sc)];
for (; i < size; i++)
temp[i] = 2 * base[st * i] + base[st * (i - sc)] + base[st * (2 * size - 2 - (i + sc))];
}
#if !defined(LIBRAW_USE_OPENMP)
void CLASS wavelet_denoise()
{
float *fimg = 0, *temp, thold, mul[2], avg, diff;
int scale = 1, size, lev, hpass, lpass, row, col, nc, c, i, wlast, blk[2];
ushort *window[4];
static const float noise[] = {0.8002, 0.2735, 0.1202, 0.0585, 0.0291, 0.0152, 0.0080, 0.0044};
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Wavelet denoising...\n"));
#endif
while (maximum << scale < 0x10000)
scale++;
maximum <<= --scale;
black <<= scale;
FORC4 cblack[c] <<= scale;
if ((size = iheight * iwidth) < 0x15550000)
fimg = (float *)malloc((size * 3 + iheight + iwidth) * sizeof *fimg);
merror(fimg, "wavelet_denoise()");
temp = fimg + size * 3;
if ((nc = colors) == 3 && filters)
nc++;
FORC(nc)
{ /* denoise R,G1,B,G3 individually */
for (i = 0; i < size; i++)
fimg[i] = 256 * sqrt((double)(image[i][c] << scale));
for (hpass = lev = 0; lev < 5; lev++)
{
lpass = size * ((lev & 1) + 1);
for (row = 0; row < iheight; row++)
{
hat_transform(temp, fimg + hpass + row * iwidth, 1, iwidth, 1 << lev);
for (col = 0; col < iwidth; col++)
fimg[lpass + row * iwidth + col] = temp[col] * 0.25;
}
for (col = 0; col < iwidth; col++)
{
hat_transform(temp, fimg + lpass + col, iwidth, iheight, 1 << lev);
for (row = 0; row < iheight; row++)
fimg[lpass + row * iwidth + col] = temp[row] * 0.25;
}
thold = threshold * noise[lev];
for (i = 0; i < size; i++)
{
fimg[hpass + i] -= fimg[lpass + i];
if (fimg[hpass + i] < -thold)
fimg[hpass + i] += thold;
else if (fimg[hpass + i] > thold)
fimg[hpass + i] -= thold;
else
fimg[hpass + i] = 0;
if (hpass)
fimg[i] += fimg[hpass + i];
}
hpass = lpass;
}
for (i = 0; i < size; i++)
image[i][c] = CLIP(SQR(fimg[i] + fimg[lpass + i]) / 0x10000);
}
if (filters && colors == 3)
{ /* pull G1 and G3 closer together */
for (row = 0; row < 2; row++)
{
mul[row] = 0.125 * pre_mul[FC(row + 1, 0) | 1] / pre_mul[FC(row, 0) | 1];
blk[row] = cblack[FC(row, 0) | 1];
}
for (i = 0; i < 4; i++)
window[i] = (ushort *)fimg + width * i;
for (wlast = -1, row = 1; row < height - 1; row++)
{
while (wlast < row + 1)
{
for (wlast++, i = 0; i < 4; i++)
window[(i + 3) & 3] = window[i];
for (col = FC(wlast, 1) & 1; col < width; col += 2)
window[2][col] = BAYER(wlast, col);
}
thold = threshold / 512;
for (col = (FC(row, 0) & 1) + 1; col < width - 1; col += 2)
{
avg = (window[0][col - 1] + window[0][col + 1] + window[2][col - 1] + window[2][col + 1] - blk[~row & 1] * 4) *
mul[row & 1] +
(window[1][col] + blk[row & 1]) * 0.5;
avg = avg < 0 ? 0 : sqrt(avg);
diff = sqrt((double)BAYER(row, col)) - avg;
if (diff < -thold)
diff += thold;
else if (diff > thold)
diff -= thold;
else
diff = 0;
BAYER(row, col) = CLIP(SQR(avg + diff) + 0.5);
}
}
}
free(fimg);
}
#else /* LIBRAW_USE_OPENMP */
void CLASS wavelet_denoise()
{
float *fimg = 0, *temp, thold, mul[2], avg, diff;
int scale = 1, size, lev, hpass, lpass, row, col, nc, c, i, wlast, blk[2];
ushort *window[4];
static const float noise[] = {0.8002, 0.2735, 0.1202, 0.0585, 0.0291, 0.0152, 0.0080, 0.0044};
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Wavelet denoising...\n"));
#endif
while (maximum << scale < 0x10000)
scale++;
maximum <<= --scale;
black <<= scale;
FORC4 cblack[c] <<= scale;
if ((size = iheight * iwidth) < 0x15550000)
fimg = (float *)malloc((size * 3 + iheight + iwidth) * sizeof *fimg);
merror(fimg, "wavelet_denoise()");
temp = fimg + size * 3;
if ((nc = colors) == 3 && filters)
nc++;
#ifdef LIBRAW_LIBRARY_BUILD
#pragma omp parallel default(shared) private(i, col, row, thold, lev, lpass, hpass, temp, c) firstprivate(scale, size)
#endif
{
temp = (float *)malloc((iheight + iwidth) * sizeof *fimg);
FORC(nc)
{ /* denoise R,G1,B,G3 individually */
#ifdef LIBRAW_LIBRARY_BUILD
#pragma omp for
#endif
for (i = 0; i < size; i++)
fimg[i] = 256 * sqrt((double)(image[i][c] << scale));
for (hpass = lev = 0; lev < 5; lev++)
{
lpass = size * ((lev & 1) + 1);
#ifdef LIBRAW_LIBRARY_BUILD
#pragma omp for
#endif
for (row = 0; row < iheight; row++)
{
hat_transform(temp, fimg + hpass + row * iwidth, 1, iwidth, 1 << lev);
for (col = 0; col < iwidth; col++)
fimg[lpass + row * iwidth + col] = temp[col] * 0.25;
}
#ifdef LIBRAW_LIBRARY_BUILD
#pragma omp for
#endif
for (col = 0; col < iwidth; col++)
{
hat_transform(temp, fimg + lpass + col, iwidth, iheight, 1 << lev);
for (row = 0; row < iheight; row++)
fimg[lpass + row * iwidth + col] = temp[row] * 0.25;
}
thold = threshold * noise[lev];
#ifdef LIBRAW_LIBRARY_BUILD
#pragma omp for
#endif
for (i = 0; i < size; i++)
{
fimg[hpass + i] -= fimg[lpass + i];
if (fimg[hpass + i] < -thold)
fimg[hpass + i] += thold;
else if (fimg[hpass + i] > thold)
fimg[hpass + i] -= thold;
else
fimg[hpass + i] = 0;
if (hpass)
fimg[i] += fimg[hpass + i];
}
hpass = lpass;
}
#ifdef LIBRAW_LIBRARY_BUILD
#pragma omp for
#endif
for (i = 0; i < size; i++)
image[i][c] = CLIP(SQR(fimg[i] + fimg[lpass + i]) / 0x10000);
}
free(temp);
} /* end omp parallel */
- /* the following loops are hard to parallize, no idea yes,
+ /* the following loops are hard to parallelize, no idea yes,
* problem is wlast which is carrying dependency
- * second part should be easyer, but did not yet get it right.
+ * second part should be easier, but did not yet get it right.
*/
if (filters && colors == 3)
{ /* pull G1 and G3 closer together */
for (row = 0; row < 2; row++)
{
mul[row] = 0.125 * pre_mul[FC(row + 1, 0) | 1] / pre_mul[FC(row, 0) | 1];
blk[row] = cblack[FC(row, 0) | 1];
}
for (i = 0; i < 4; i++)
window[i] = (ushort *)fimg + width * i;
for (wlast = -1, row = 1; row < height - 1; row++)
{
while (wlast < row + 1)
{
for (wlast++, i = 0; i < 4; i++)
window[(i + 3) & 3] = window[i];
for (col = FC(wlast, 1) & 1; col < width; col += 2)
window[2][col] = BAYER(wlast, col);
}
thold = threshold / 512;
for (col = (FC(row, 0) & 1) + 1; col < width - 1; col += 2)
{
avg = (window[0][col - 1] + window[0][col + 1] + window[2][col - 1] + window[2][col + 1] - blk[~row & 1] * 4) *
mul[row & 1] +
(window[1][col] + blk[row & 1]) * 0.5;
avg = avg < 0 ? 0 : sqrt(avg);
diff = sqrt((double)BAYER(row, col)) - avg;
if (diff < -thold)
diff += thold;
else if (diff > thold)
diff -= thold;
else
diff = 0;
BAYER(row, col) = CLIP(SQR(avg + diff) + 0.5);
}
}
}
free(fimg);
}
#endif
// green equilibration
void CLASS green_matching()
{
int i, j;
double m1, m2, c1, c2;
int o1_1, o1_2, o1_3, o1_4;
int o2_1, o2_2, o2_3, o2_4;
ushort(*img)[4];
const int margin = 3;
int oj = 2, oi = 2;
float f;
const float thr = 0.01f;
if (half_size || shrink)
return;
if (FC(oj, oi) != 3)
oj++;
if (FC(oj, oi) != 3)
oi++;
if (FC(oj, oi) != 3)
oj--;
img = (ushort(*)[4])calloc(height * width, sizeof *image);
merror(img, "green_matching()");
memcpy(img, image, height * width * sizeof *image);
for (j = oj; j < height - margin; j += 2)
for (i = oi; i < width - margin; i += 2)
{
o1_1 = img[(j - 1) * width + i - 1][1];
o1_2 = img[(j - 1) * width + i + 1][1];
o1_3 = img[(j + 1) * width + i - 1][1];
o1_4 = img[(j + 1) * width + i + 1][1];
o2_1 = img[(j - 2) * width + i][3];
o2_2 = img[(j + 2) * width + i][3];
o2_3 = img[j * width + i - 2][3];
o2_4 = img[j * width + i + 2][3];
m1 = (o1_1 + o1_2 + o1_3 + o1_4) / 4.0;
m2 = (o2_1 + o2_2 + o2_3 + o2_4) / 4.0;
c1 = (abs(o1_1 - o1_2) + abs(o1_1 - o1_3) + abs(o1_1 - o1_4) + abs(o1_2 - o1_3) + abs(o1_3 - o1_4) +
abs(o1_2 - o1_4)) /
6.0;
c2 = (abs(o2_1 - o2_2) + abs(o2_1 - o2_3) + abs(o2_1 - o2_4) + abs(o2_2 - o2_3) + abs(o2_3 - o2_4) +
abs(o2_2 - o2_4)) /
6.0;
if ((img[j * width + i][3] < maximum * 0.95) && (c1 < maximum * thr) && (c2 < maximum * thr))
{
f = image[j * width + i][3] * m1 / m2;
image[j * width + i][3] = f > 0xffff ? 0xffff : f;
}
}
free(img);
}
void CLASS scale_colors()
{
unsigned bottom, right, size, row, col, ur, uc, i, x, y, c, sum[8];
int val, dark, sat;
double dsum[8], dmin, dmax;
float scale_mul[4], fr, fc;
ushort *img = 0, *pix;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_SCALE_COLORS, 0, 2);
#endif
if (user_mul[0])
memcpy(pre_mul, user_mul, sizeof pre_mul);
if (use_auto_wb || (use_camera_wb && cam_mul[0] == -1))
{
memset(dsum, 0, sizeof dsum);
bottom = MIN(greybox[1] + greybox[3], height);
right = MIN(greybox[0] + greybox[2], width);
for (row = greybox[1]; row < bottom; row += 8)
for (col = greybox[0]; col < right; col += 8)
{
memset(sum, 0, sizeof sum);
for (y = row; y < row + 8 && y < bottom; y++)
for (x = col; x < col + 8 && x < right; x++)
FORC4
{
if (filters)
{
c = fcol(y, x);
val = BAYER2(y, x);
}
else
val = image[y * width + x][c];
if (val > maximum - 25)
goto skip_block;
if ((val -= cblack[c]) < 0)
val = 0;
sum[c] += val;
sum[c + 4]++;
if (filters)
break;
}
FORC(8) dsum[c] += sum[c];
skip_block:;
}
FORC4 if (dsum[c]) pre_mul[c] = dsum[c + 4] / dsum[c];
}
if (use_camera_wb && cam_mul[0] != -1)
{
memset(sum, 0, sizeof sum);
for (row = 0; row < 8; row++)
for (col = 0; col < 8; col++)
{
c = FC(row, col);
if ((val = white[row][col] - cblack[c]) > 0)
sum[c] += val;
sum[c + 4]++;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (load_raw == &LibRaw::nikon_load_sraw)
{
// Nikon sRAW: camera WB already applied:
pre_mul[0] = pre_mul[1] = pre_mul[2] = pre_mul[3] = 1.0;
}
else
#endif
if (sum[0] && sum[1] && sum[2] && sum[3])
FORC4 pre_mul[c] = (float)sum[c + 4] / sum[c];
else if (cam_mul[0] && cam_mul[2])
memcpy(pre_mul, cam_mul, sizeof pre_mul);
else
{
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.process_warnings |= LIBRAW_WARN_BAD_CAMERA_WB;
#endif
#ifdef DCRAW_VERBOSE
fprintf(stderr, _("%s: Cannot use camera white balance.\n"), ifname);
#endif
}
}
#ifdef LIBRAW_LIBRARY_BUILD
// Nikon sRAW, daylight
if (load_raw == &LibRaw::nikon_load_sraw && !use_camera_wb && !use_auto_wb && cam_mul[0] > 0.001f &&
cam_mul[1] > 0.001f && cam_mul[2] > 0.001f)
{
for (c = 0; c < 3; c++)
pre_mul[c] /= cam_mul[c];
}
#endif
if (pre_mul[1] == 0)
pre_mul[1] = 1;
if (pre_mul[3] == 0)
pre_mul[3] = colors < 4 ? pre_mul[1] : 1;
dark = black;
sat = maximum;
if (threshold)
wavelet_denoise();
maximum -= black;
for (dmin = DBL_MAX, dmax = c = 0; c < 4; c++)
{
if (dmin > pre_mul[c])
dmin = pre_mul[c];
if (dmax < pre_mul[c])
dmax = pre_mul[c];
}
if (!highlight)
dmax = dmin;
FORC4 scale_mul[c] = (pre_mul[c] /= dmax) * 65535.0 / maximum;
#ifdef DCRAW_VERBOSE
if (verbose)
{
fprintf(stderr, _("Scaling with darkness %d, saturation %d, and\nmultipliers"), dark, sat);
FORC4 fprintf(stderr, " %f", pre_mul[c]);
fputc('\n', stderr);
}
#endif
if (filters > 1000 && (cblack[4] + 1) / 2 == 1 && (cblack[5] + 1) / 2 == 1)
{
FORC4 cblack[FC(c / 2, c % 2)] += cblack[6 + c / 2 % cblack[4] * cblack[5] + c % 2 % cblack[5]];
cblack[4] = cblack[5] = 0;
}
size = iheight * iwidth;
#ifdef LIBRAW_LIBRARY_BUILD
scale_colors_loop(scale_mul);
#else
for (i = 0; i < size * 4; i++)
{
if (!(val = ((ushort *)image)[i]))
continue;
if (cblack[4] && cblack[5])
val -= cblack[6 + i / 4 / iwidth % cblack[4] * cblack[5] + i / 4 % iwidth % cblack[5]];
val -= cblack[i & 3];
val *= scale_mul[i & 3];
((ushort *)image)[i] = CLIP(val);
}
#endif
if ((aber[0] != 1 || aber[2] != 1) && colors == 3)
{
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Correcting chromatic aberration...\n"));
#endif
for (c = 0; c < 4; c += 2)
{
if (aber[c] == 1)
continue;
img = (ushort *)malloc(size * sizeof *img);
merror(img, "scale_colors()");
for (i = 0; i < size; i++)
img[i] = image[i][c];
for (row = 0; row < iheight; row++)
{
ur = fr = (row - iheight * 0.5) * aber[c] + iheight * 0.5;
if (ur > iheight - 2)
continue;
fr -= ur;
for (col = 0; col < iwidth; col++)
{
uc = fc = (col - iwidth * 0.5) * aber[c] + iwidth * 0.5;
if (uc > iwidth - 2)
continue;
fc -= uc;
pix = img + ur * iwidth + uc;
image[row * iwidth + col][c] =
(pix[0] * (1 - fc) + pix[1] * fc) * (1 - fr) + (pix[iwidth] * (1 - fc) + pix[iwidth + 1] * fc) * fr;
}
}
free(img);
}
}
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_SCALE_COLORS, 1, 2);
#endif
}
void CLASS pre_interpolate()
{
ushort(*img)[4];
int row, col, c;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_PRE_INTERPOLATE, 0, 2);
#endif
if (shrink)
{
if (half_size)
{
height = iheight;
width = iwidth;
if (filters == 9)
{
for (row = 0; row < 3; row++)
for (col = 1; col < 4; col++)
if (!(image[row * width + col][0] | image[row * width + col][2]))
goto break2;
break2:
for (; row < height; row += 3)
for (col = (col - 1) % 3 + 1; col < width - 1; col += 3)
{
img = image + row * width + col;
for (c = 0; c < 3; c += 2)
img[0][c] = (img[-1][c] + img[1][c]) >> 1;
}
}
}
else
{
img = (ushort(*)[4])calloc(height, width * sizeof *img);
merror(img, "pre_interpolate()");
for (row = 0; row < height; row++)
for (col = 0; col < width; col++)
{
c = fcol(row, col);
img[row * width + col][c] = image[(row >> 1) * iwidth + (col >> 1)][c];
}
free(image);
image = img;
shrink = 0;
}
}
if (filters > 1000 && colors == 3)
{
mix_green = four_color_rgb ^ half_size;
if (four_color_rgb | half_size)
colors++;
else
{
for (row = FC(1, 0) >> 1; row < height; row += 2)
for (col = FC(row, 1) & 1; col < width; col += 2)
image[row * width + col][1] = image[row * width + col][3];
filters &= ~((filters & 0x55555555U) << 1);
}
}
if (half_size)
filters = 0;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_PRE_INTERPOLATE, 1, 2);
#endif
}
void CLASS border_interpolate(int border)
{
unsigned row, col, y, x, f, c, sum[8];
for (row = 0; row < height; row++)
for (col = 0; col < width; col++)
{
if (col == border && row >= border && row < height - border)
col = width - border;
memset(sum, 0, sizeof sum);
for (y = row - 1; y != row + 2; y++)
for (x = col - 1; x != col + 2; x++)
if (y < height && x < width)
{
f = fcol(y, x);
sum[f] += image[y * width + x][f];
sum[f + 4]++;
}
f = fcol(row, col);
FORCC if (c != f && sum[c + 4]) image[row * width + col][c] = sum[c] / sum[c + 4];
}
}
void CLASS lin_interpolate_loop(int code[16][16][32], int size)
{
int row;
for (row = 1; row < height - 1; row++)
{
int col, *ip;
ushort *pix;
for (col = 1; col < width - 1; col++)
{
int i;
int sum[4];
pix = image[row * width + col];
ip = code[row % size][col % size];
memset(sum, 0, sizeof sum);
for (i = *ip++; i--; ip += 3)
sum[ip[2]] += pix[ip[0]] << ip[1];
for (i = colors; --i; ip += 2)
pix[ip[0]] = sum[ip[0]] * ip[1] >> 8;
}
}
}
void CLASS lin_interpolate()
{
int code[16][16][32], size = 16, *ip, sum[4];
int f, c, x, y, row, col, shift, color;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Bilinear interpolation...\n"));
#endif
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, 0, 3);
#endif
if (filters == 9)
size = 6;
border_interpolate(1);
for (row = 0; row < size; row++)
for (col = 0; col < size; col++)
{
ip = code[row][col] + 1;
f = fcol(row, col);
memset(sum, 0, sizeof sum);
for (y = -1; y <= 1; y++)
for (x = -1; x <= 1; x++)
{
shift = (y == 0) + (x == 0);
color = fcol(row + y, col + x);
if (color == f)
continue;
*ip++ = (width * y + x) * 4 + color;
*ip++ = shift;
*ip++ = color;
sum[color] += 1 << shift;
}
code[row][col][0] = (ip - code[row][col]) / 3;
FORCC
if (c != f)
{
*ip++ = c;
*ip++ = sum[c] > 0 ? 256 / sum[c] : 0;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, 1, 3);
#endif
lin_interpolate_loop(code, size);
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, 2, 3);
#endif
}
/*
This algorithm is officially called:
"Interpolation using a Threshold-based variable number of gradients"
described in http://scien.stanford.edu/pages/labsite/1999/psych221/projects/99/tingchen/algodep/vargra.html
I've extended the basic idea to work with non-Bayer filter arrays.
Gradients are numbered clockwise from NW=0 to W=7.
*/
void CLASS vng_interpolate()
{
static const signed char *cp,
terms[] = {-2, -2, +0, -1, 0, 0x01, -2, -2, +0, +0, 1, 0x01, -2, -1, -1, +0, 0, 0x01, -2, -1, +0, -1, 0, 0x02,
-2, -1, +0, +0, 0, 0x03, -2, -1, +0, +1, 1, 0x01, -2, +0, +0, -1, 0, 0x06, -2, +0, +0, +0, 1, 0x02,
-2, +0, +0, +1, 0, 0x03, -2, +1, -1, +0, 0, 0x04, -2, +1, +0, -1, 1, 0x04, -2, +1, +0, +0, 0, 0x06,
-2, +1, +0, +1, 0, 0x02, -2, +2, +0, +0, 1, 0x04, -2, +2, +0, +1, 0, 0x04, -1, -2, -1, +0, 0, -128,
-1, -2, +0, -1, 0, 0x01, -1, -2, +1, -1, 0, 0x01, -1, -2, +1, +0, 1, 0x01, -1, -1, -1, +1, 0, -120,
-1, -1, +1, -2, 0, 0x40, -1, -1, +1, -1, 0, 0x22, -1, -1, +1, +0, 0, 0x33, -1, -1, +1, +1, 1, 0x11,
-1, +0, -1, +2, 0, 0x08, -1, +0, +0, -1, 0, 0x44, -1, +0, +0, +1, 0, 0x11, -1, +0, +1, -2, 1, 0x40,
-1, +0, +1, -1, 0, 0x66, -1, +0, +1, +0, 1, 0x22, -1, +0, +1, +1, 0, 0x33, -1, +0, +1, +2, 1, 0x10,
-1, +1, +1, -1, 1, 0x44, -1, +1, +1, +0, 0, 0x66, -1, +1, +1, +1, 0, 0x22, -1, +1, +1, +2, 0, 0x10,
-1, +2, +0, +1, 0, 0x04, -1, +2, +1, +0, 1, 0x04, -1, +2, +1, +1, 0, 0x04, +0, -2, +0, +0, 1, -128,
+0, -1, +0, +1, 1, -120, +0, -1, +1, -2, 0, 0x40, +0, -1, +1, +0, 0, 0x11, +0, -1, +2, -2, 0, 0x40,
+0, -1, +2, -1, 0, 0x20, +0, -1, +2, +0, 0, 0x30, +0, -1, +2, +1, 1, 0x10, +0, +0, +0, +2, 1, 0x08,
+0, +0, +2, -2, 1, 0x40, +0, +0, +2, -1, 0, 0x60, +0, +0, +2, +0, 1, 0x20, +0, +0, +2, +1, 0, 0x30,
+0, +0, +2, +2, 1, 0x10, +0, +1, +1, +0, 0, 0x44, +0, +1, +1, +2, 0, 0x10, +0, +1, +2, -1, 1, 0x40,
+0, +1, +2, +0, 0, 0x60, +0, +1, +2, +1, 0, 0x20, +0, +1, +2, +2, 0, 0x10, +1, -2, +1, +0, 0, -128,
+1, -1, +1, +1, 0, -120, +1, +0, +1, +2, 0, 0x08, +1, +0, +2, -1, 0, 0x40, +1, +0, +2, +1, 0, 0x10},
chood[] = {-1, -1, -1, 0, -1, +1, 0, +1, +1, +1, +1, 0, +1, -1, 0, -1};
ushort(*brow[5])[4], *pix;
int prow = 8, pcol = 2, *ip, *code[16][16], gval[8], gmin, gmax, sum[4];
int row, col, x, y, x1, x2, y1, y2, t, weight, grads, color, diag;
int g, diff, thold, num, c;
lin_interpolate();
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("VNG interpolation...\n"));
#endif
if (filters == 1)
prow = pcol = 16;
if (filters == 9)
prow = pcol = 6;
ip = (int *)calloc(prow * pcol, 1280);
merror(ip, "vng_interpolate()");
for (row = 0; row < prow; row++) /* Precalculate for VNG */
for (col = 0; col < pcol; col++)
{
code[row][col] = ip;
for (cp = terms, t = 0; t < 64; t++)
{
y1 = *cp++;
x1 = *cp++;
y2 = *cp++;
x2 = *cp++;
weight = *cp++;
grads = *cp++;
color = fcol(row + y1, col + x1);
if (fcol(row + y2, col + x2) != color)
continue;
diag = (fcol(row, col + 1) == color && fcol(row + 1, col) == color) ? 2 : 1;
if (abs(y1 - y2) == diag && abs(x1 - x2) == diag)
continue;
*ip++ = (y1 * width + x1) * 4 + color;
*ip++ = (y2 * width + x2) * 4 + color;
*ip++ = weight;
for (g = 0; g < 8; g++)
if (grads & 1 << g)
*ip++ = g;
*ip++ = -1;
}
*ip++ = INT_MAX;
for (cp = chood, g = 0; g < 8; g++)
{
y = *cp++;
x = *cp++;
*ip++ = (y * width + x) * 4;
color = fcol(row, col);
if (fcol(row + y, col + x) != color && fcol(row + y * 2, col + x * 2) == color)
*ip++ = (y * width + x) * 8 + color;
else
*ip++ = 0;
}
}
brow[4] = (ushort(*)[4])calloc(width * 3, sizeof **brow);
merror(brow[4], "vng_interpolate()");
for (row = 0; row < 3; row++)
brow[row] = brow[4] + row * width;
for (row = 2; row < height - 2; row++)
{ /* Do VNG interpolation */
#ifdef LIBRAW_LIBRARY_BUILD
if (!((row - 2) % 256))
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, (row - 2) / 256 + 1, ((height - 3) / 256) + 1);
#endif
for (col = 2; col < width - 2; col++)
{
pix = image[row * width + col];
ip = code[row % prow][col % pcol];
memset(gval, 0, sizeof gval);
while ((g = ip[0]) != INT_MAX)
{ /* Calculate gradients */
diff = ABS(pix[g] - pix[ip[1]]) << ip[2];
gval[ip[3]] += diff;
ip += 5;
if ((g = ip[-1]) == -1)
continue;
gval[g] += diff;
while ((g = *ip++) != -1)
gval[g] += diff;
}
ip++;
gmin = gmax = gval[0]; /* Choose a threshold */
for (g = 1; g < 8; g++)
{
if (gmin > gval[g])
gmin = gval[g];
if (gmax < gval[g])
gmax = gval[g];
}
if (gmax == 0)
{
memcpy(brow[2][col], pix, sizeof *image);
continue;
}
thold = gmin + (gmax >> 1);
memset(sum, 0, sizeof sum);
color = fcol(row, col);
for (num = g = 0; g < 8; g++, ip += 2)
{ /* Average the neighbors */
if (gval[g] <= thold)
{
FORCC
if (c == color && ip[1])
sum[c] += (pix[c] + pix[ip[1]]) >> 1;
else
sum[c] += pix[ip[0] + c];
num++;
}
}
FORCC
{ /* Save to buffer */
t = pix[color];
if (c != color)
t += (sum[c] - sum[color]) / num;
brow[2][col][c] = CLIP(t);
}
}
if (row > 3) /* Write buffer to image */
memcpy(image[(row - 2) * width + 2], brow[0] + 2, (width - 4) * sizeof *image);
for (g = 0; g < 4; g++)
brow[(g - 1) & 3] = brow[g];
}
memcpy(image[(row - 2) * width + 2], brow[0] + 2, (width - 4) * sizeof *image);
memcpy(image[(row - 1) * width + 2], brow[1] + 2, (width - 4) * sizeof *image);
free(brow[4]);
free(code[0][0]);
}
/*
Patterned Pixel Grouping Interpolation by Alain Desbiolles
*/
void CLASS ppg_interpolate()
{
int dir[5] = {1, width, -1, -width, 1};
int row, col, diff[2], guess[2], c, d, i;
ushort(*pix)[4];
border_interpolate(3);
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("PPG interpolation...\n"));
#endif
/* Fill in the green layer with gradients and pattern recognition: */
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, 0, 3);
#ifdef LIBRAW_USE_OPENMP
#pragma omp parallel for default(shared) private(guess, diff, row, col, d, c, i, pix) schedule(static)
#endif
#endif
for (row = 3; row < height - 3; row++)
for (col = 3 + (FC(row, 3) & 1), c = FC(row, col); col < width - 3; col += 2)
{
pix = image + row * width + col;
for (i = 0; (d = dir[i]) > 0; i++)
{
guess[i] = (pix[-d][1] + pix[0][c] + pix[d][1]) * 2 - pix[-2 * d][c] - pix[2 * d][c];
diff[i] = (ABS(pix[-2 * d][c] - pix[0][c]) + ABS(pix[2 * d][c] - pix[0][c]) + ABS(pix[-d][1] - pix[d][1])) * 3 +
(ABS(pix[3 * d][1] - pix[d][1]) + ABS(pix[-3 * d][1] - pix[-d][1])) * 2;
}
d = dir[i = diff[0] > diff[1]];
pix[0][1] = ULIM(guess[i] >> 2, pix[d][1], pix[-d][1]);
}
/* Calculate red and blue for each green pixel: */
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, 1, 3);
#ifdef LIBRAW_USE_OPENMP
#pragma omp parallel for default(shared) private(guess, diff, row, col, d, c, i, pix) schedule(static)
#endif
#endif
for (row = 1; row < height - 1; row++)
for (col = 1 + (FC(row, 2) & 1), c = FC(row, col + 1); col < width - 1; col += 2)
{
pix = image + row * width + col;
for (i = 0; (d = dir[i]) > 0; c = 2 - c, i++)
pix[0][c] = CLIP((pix[-d][c] + pix[d][c] + 2 * pix[0][1] - pix[-d][1] - pix[d][1]) >> 1);
}
/* Calculate blue for red pixels and vice versa: */
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_INTERPOLATE, 2, 3);
#ifdef LIBRAW_USE_OPENMP
#pragma omp parallel for default(shared) private(guess, diff, row, col, d, c, i, pix) schedule(static)
#endif
#endif
for (row = 1; row < height - 1; row++)
for (col = 1 + (FC(row, 1) & 1), c = 2 - FC(row, col); col < width - 1; col += 2)
{
pix = image + row * width + col;
for (i = 0; (d = dir[i] + dir[i + 1]) > 0; i++)
{
diff[i] = ABS(pix[-d][c] - pix[d][c]) + ABS(pix[-d][1] - pix[0][1]) + ABS(pix[d][1] - pix[0][1]);
guess[i] = pix[-d][c] + pix[d][c] + 2 * pix[0][1] - pix[-d][1] - pix[d][1];
}
if (diff[0] != diff[1])
pix[0][c] = CLIP(guess[diff[0] > diff[1]] >> 1);
else
pix[0][c] = CLIP((guess[0] + guess[1]) >> 2);
}
}
void CLASS cielab(ushort rgb[3], short lab[3])
{
int c, i, j, k;
float r, xyz[3];
#ifdef LIBRAW_NOTHREADS
static float cbrt[0x10000], xyz_cam[3][4];
#else
#define cbrt tls->ahd_data.cbrt
#define xyz_cam tls->ahd_data.xyz_cam
#endif
if (!rgb)
{
#ifndef LIBRAW_NOTHREADS
if (cbrt[0] < -1.0f)
#endif
for (i = 0; i < 0x10000; i++)
{
r = i / 65535.0;
cbrt[i] = r > 0.008856 ? pow(r, 1.f / 3.0f) : 7.787f * r + 16.f / 116.0f;
}
for (i = 0; i < 3; i++)
for (j = 0; j < colors; j++)
for (xyz_cam[i][j] = k = 0; k < 3; k++)
xyz_cam[i][j] += xyz_rgb[i][k] * rgb_cam[k][j] / d65_white[i];
return;
}
xyz[0] = xyz[1] = xyz[2] = 0.5;
FORCC
{
xyz[0] += xyz_cam[0][c] * rgb[c];
xyz[1] += xyz_cam[1][c] * rgb[c];
xyz[2] += xyz_cam[2][c] * rgb[c];
}
xyz[0] = cbrt[CLIP((int)xyz[0])];
xyz[1] = cbrt[CLIP((int)xyz[1])];
xyz[2] = cbrt[CLIP((int)xyz[2])];
lab[0] = 64 * (116 * xyz[1] - 16);
lab[1] = 64 * 500 * (xyz[0] - xyz[1]);
lab[2] = 64 * 200 * (xyz[1] - xyz[2]);
#ifndef LIBRAW_NOTHREADS
#undef cbrt
#undef xyz_cam
#endif
}
#define TS 512 /* Tile Size */
#define fcol(row, col) xtrans[(row + 6) % 6][(col + 6) % 6]
/*
Frank Markesteijn's algorithm for Fuji X-Trans sensors
*/
void CLASS xtrans_interpolate(int passes)
{
int c, d, f, g, h, i, v, ng, row, col, top, left, mrow, mcol;
#ifdef LIBRAW_LIBRARY_BUILD
int cstat[4] = {0, 0, 0, 0};
#endif
int val, ndir, pass, hm[8], avg[4], color[3][8];
static const short orth[12] = {1, 0, 0, 1, -1, 0, 0, -1, 1, 0, 0, 1},
patt[2][16] = {{0, 1, 0, -1, 2, 0, -1, 0, 1, 1, 1, -1, 0, 0, 0, 0},
{0, 1, 0, -2, 1, 0, -2, 0, 1, 1, -2, -2, 1, -1, -1, 1}},
dir[4] = {1, TS, TS + 1, TS - 1};
short allhex[3][3][2][8], *hex;
ushort min, max, sgrow, sgcol;
ushort(*rgb)[TS][TS][3], (*rix)[3], (*pix)[4];
short(*lab)[TS][3], (*lix)[3];
float(*drv)[TS][TS], diff[6], tr;
char(*homo)[TS][TS], *buffer;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("%d-pass X-Trans interpolation...\n"), passes);
#endif
#ifdef LIBRAW_LIBRARY_BUILD
if (width < TS || height < TS)
throw LIBRAW_EXCEPTION_IO_CORRUPT; // too small image
/* Check against right pattern */
for (row = 0; row < 6; row++)
for (col = 0; col < 6; col++)
cstat[fcol(row, col)]++;
if (cstat[0] < 6 || cstat[0] > 10 || cstat[1] < 16 || cstat[1] > 24 || cstat[2] < 6 || cstat[2] > 10 || cstat[3])
throw LIBRAW_EXCEPTION_IO_CORRUPT;
// Init allhex table to unreasonable values
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
for (int k = 0; k < 2; k++)
for (int l = 0; l < 8; l++)
allhex[i][j][k][l] = 32700;
#endif
cielab(0, 0);
ndir = 4 << (passes > 1);
buffer = (char *)malloc(TS * TS * (ndir * 11 + 6));
merror(buffer, "xtrans_interpolate()");
rgb = (ushort(*)[TS][TS][3])buffer;
lab = (short(*)[TS][3])(buffer + TS * TS * (ndir * 6));
drv = (float(*)[TS][TS])(buffer + TS * TS * (ndir * 6 + 6));
homo = (char(*)[TS][TS])(buffer + TS * TS * (ndir * 10 + 6));
int minv = 0, maxv = 0, minh = 0, maxh = 0;
/* Map a green hexagon around each non-green pixel and vice versa: */
for (row = 0; row < 3; row++)
for (col = 0; col < 3; col++)
for (ng = d = 0; d < 10; d += 2)
{
g = fcol(row, col) == 1;
if (fcol(row + orth[d], col + orth[d + 2]) == 1)
ng = 0;
else
ng++;
if (ng == 4)
{
sgrow = row;
sgcol = col;
}
if (ng == g + 1)
FORC(8)
{
v = orth[d] * patt[g][c * 2] + orth[d + 1] * patt[g][c * 2 + 1];
h = orth[d + 2] * patt[g][c * 2] + orth[d + 3] * patt[g][c * 2 + 1];
minv = MIN(v, minv);
maxv = MAX(v, maxv);
minh = MIN(v, minh);
maxh = MAX(v, maxh);
allhex[row][col][0][c ^ (g * 2 & d)] = h + v * width;
allhex[row][col][1][c ^ (g * 2 & d)] = h + v * TS;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
// Check allhex table initialization
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
for (int k = 0; k < 2; k++)
for (int l = 0; l < 8; l++)
if (allhex[i][j][k][l] > maxh + maxv * width + 1 || allhex[i][j][k][l] < minh + minv * width - 1)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
int retrycount = 0;
#endif
/* Set green1 and green3 to the minimum and maximum allowed values: */
for (row = 2; row < height - 2; row++)
for (min = ~(max = 0), col = 2; col < width - 2; col++)
{
if (fcol(row, col) == 1 && (min = ~(max = 0)))
continue;
pix = image + row * width + col;
hex = allhex[row % 3][col % 3][0];
if (!max)
FORC(6)
{
val = pix[hex[c]][1];
if (min > val)
min = val;
if (max < val)
max = val;
}
pix[0][1] = min;
pix[0][3] = max;
switch ((row - sgrow) % 3)
{
case 1:
if (row < height - 3)
{
row++;
col--;
}
break;
case 2:
if ((min = ~(max = 0)) && (col += 2) < width - 3 && row > 2)
{
row--;
#ifdef LIBRAW_LIBRARY_BUILD
if (retrycount++ > width * height)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
}
}
}
for (top = 3; top < height - 19; top += TS - 16)
for (left = 3; left < width - 19; left += TS - 16)
{
mrow = MIN(top + TS, height - 3);
mcol = MIN(left + TS, width - 3);
for (row = top; row < mrow; row++)
for (col = left; col < mcol; col++)
memcpy(rgb[0][row - top][col - left], image[row * width + col], 6);
FORC3 memcpy(rgb[c + 1], rgb[0], sizeof *rgb);
/* Interpolate green horizontally, vertically, and along both diagonals: */
for (row = top; row < mrow; row++)
for (col = left; col < mcol; col++)
{
if ((f = fcol(row, col)) == 1)
continue;
pix = image + row * width + col;
hex = allhex[row % 3][col % 3][0];
color[1][0] = 174 * (pix[hex[1]][1] + pix[hex[0]][1]) - 46 * (pix[2 * hex[1]][1] + pix[2 * hex[0]][1]);
color[1][1] = 223 * pix[hex[3]][1] + pix[hex[2]][1] * 33 + 92 * (pix[0][f] - pix[-hex[2]][f]);
FORC(2)
color[1][2 + c] = 164 * pix[hex[4 + c]][1] + 92 * pix[-2 * hex[4 + c]][1] +
33 * (2 * pix[0][f] - pix[3 * hex[4 + c]][f] - pix[-3 * hex[4 + c]][f]);
FORC4 rgb[c ^ !((row - sgrow) % 3)][row - top][col - left][1] = LIM(color[1][c] >> 8, pix[0][1], pix[0][3]);
}
for (pass = 0; pass < passes; pass++)
{
if (pass == 1)
memcpy(rgb += 4, buffer, 4 * sizeof *rgb);
/* Recalculate green from interpolated values of closer pixels: */
if (pass)
{
for (row = top + 2; row < mrow - 2; row++)
for (col = left + 2; col < mcol - 2; col++)
{
if ((f = fcol(row, col)) == 1)
continue;
pix = image + row * width + col;
hex = allhex[row % 3][col % 3][1];
for (d = 3; d < 6; d++)
{
rix = &rgb[(d - 2) ^ !((row - sgrow) % 3)][row - top][col - left];
val =
rix[-2 * hex[d]][1] + 2 * rix[hex[d]][1] - rix[-2 * hex[d]][f] - 2 * rix[hex[d]][f] + 3 * rix[0][f];
rix[0][1] = LIM(val / 3, pix[0][1], pix[0][3]);
}
}
}
/* Interpolate red and blue values for solitary green pixels: */
for (row = (top - sgrow + 4) / 3 * 3 + sgrow; row < mrow - 2; row += 3)
for (col = (left - sgcol + 4) / 3 * 3 + sgcol; col < mcol - 2; col += 3)
{
rix = &rgb[0][row - top][col - left];
h = fcol(row, col + 1);
memset(diff, 0, sizeof diff);
for (i = 1, d = 0; d < 6; d++, i ^= TS ^ 1, h ^= 2)
{
for (c = 0; c < 2; c++, h ^= 2)
{
g = 2 * rix[0][1] - rix[i << c][1] - rix[-i << c][1];
color[h][d] = g + rix[i << c][h] + rix[-i << c][h];
if (d > 1)
diff[d] += SQR(rix[i << c][1] - rix[-i << c][1] - rix[i << c][h] + rix[-i << c][h]) + SQR(g);
}
if (d > 1 && (d & 1))
if (diff[d - 1] < diff[d])
FORC(2) color[c * 2][d] = color[c * 2][d - 1];
if (d < 2 || (d & 1))
{
FORC(2) rix[0][c * 2] = CLIP(color[c * 2][d] / 2);
rix += TS * TS;
}
}
}
/* Interpolate red for blue pixels and vice versa: */
for (row = top + 3; row < mrow - 3; row++)
for (col = left + 3; col < mcol - 3; col++)
{
if ((f = 2 - fcol(row, col)) == 1)
continue;
rix = &rgb[0][row - top][col - left];
c = (row - sgrow) % 3 ? TS : 1;
h = 3 * (c ^ TS ^ 1);
for (d = 0; d < 4; d++, rix += TS * TS)
{
i = d > 1 || ((d ^ c) & 1) ||
((ABS(rix[0][1] - rix[c][1]) + ABS(rix[0][1] - rix[-c][1])) <
2 * (ABS(rix[0][1] - rix[h][1]) + ABS(rix[0][1] - rix[-h][1])))
? c
: h;
rix[0][f] = CLIP((rix[i][f] + rix[-i][f] + 2 * rix[0][1] - rix[i][1] - rix[-i][1]) / 2);
}
}
/* Fill in red and blue for 2x2 blocks of green: */
for (row = top + 2; row < mrow - 2; row++)
if ((row - sgrow) % 3)
for (col = left + 2; col < mcol - 2; col++)
if ((col - sgcol) % 3)
{
rix = &rgb[0][row - top][col - left];
hex = allhex[row % 3][col % 3][1];
for (d = 0; d < ndir; d += 2, rix += TS * TS)
if (hex[d] + hex[d + 1])
{
g = 3 * rix[0][1] - 2 * rix[hex[d]][1] - rix[hex[d + 1]][1];
for (c = 0; c < 4; c += 2)
rix[0][c] = CLIP((g + 2 * rix[hex[d]][c] + rix[hex[d + 1]][c]) / 3);
}
else
{
g = 2 * rix[0][1] - rix[hex[d]][1] - rix[hex[d + 1]][1];
for (c = 0; c < 4; c += 2)
rix[0][c] = CLIP((g + rix[hex[d]][c] + rix[hex[d + 1]][c]) / 2);
}
}
}
rgb = (ushort(*)[TS][TS][3])buffer;
mrow -= top;
mcol -= left;
/* Convert to CIELab and differentiate in all directions: */
for (d = 0; d < ndir; d++)
{
for (row = 2; row < mrow - 2; row++)
for (col = 2; col < mcol - 2; col++)
cielab(rgb[d][row][col], lab[row][col]);
for (f = dir[d & 3], row = 3; row < mrow - 3; row++)
for (col = 3; col < mcol - 3; col++)
{
lix = &lab[row][col];
g = 2 * lix[0][0] - lix[f][0] - lix[-f][0];
drv[d][row][col] = SQR(g) + SQR((2 * lix[0][1] - lix[f][1] - lix[-f][1] + g * 500 / 232)) +
SQR((2 * lix[0][2] - lix[f][2] - lix[-f][2] - g * 500 / 580));
}
}
/* Build homogeneity maps from the derivatives: */
memset(homo, 0, ndir * TS * TS);
for (row = 4; row < mrow - 4; row++)
for (col = 4; col < mcol - 4; col++)
{
for (tr = FLT_MAX, d = 0; d < ndir; d++)
if (tr > drv[d][row][col])
tr = drv[d][row][col];
tr *= 8;
for (d = 0; d < ndir; d++)
for (v = -1; v <= 1; v++)
for (h = -1; h <= 1; h++)
if (drv[d][row + v][col + h] <= tr)
homo[d][row][col]++;
}
- /* Average the most homogenous pixels for the final result: */
+ /* Average the most homogeneous pixels for the final result: */
if (height - top < TS + 4)
mrow = height - top + 2;
if (width - left < TS + 4)
mcol = width - left + 2;
for (row = MIN(top, 8); row < mrow - 8; row++)
for (col = MIN(left, 8); col < mcol - 8; col++)
{
for (d = 0; d < ndir; d++)
for (hm[d] = 0, v = -2; v <= 2; v++)
for (h = -2; h <= 2; h++)
hm[d] += homo[d][row + v][col + h];
for (d = 0; d < ndir - 4; d++)
if (hm[d] < hm[d + 4])
hm[d] = 0;
else if (hm[d] > hm[d + 4])
hm[d + 4] = 0;
for (max = hm[0], d = 1; d < ndir; d++)
if (max < hm[d])
max = hm[d];
max -= max >> 3;
memset(avg, 0, sizeof avg);
for (d = 0; d < ndir; d++)
if (hm[d] >= max)
{
FORC3 avg[c] += rgb[d][row][col][c];
avg[3]++;
}
FORC3 image[(row + top) * width + col + left][c] = avg[c] / avg[3];
}
}
free(buffer);
border_interpolate(8);
}
#undef fcol
/*
Adaptive Homogeneity-Directed interpolation is based on
the work of Keigo Hirakawa, Thomas Parks, and Paul Lee.
*/
#ifdef LIBRAW_LIBRARY_BUILD
void CLASS ahd_interpolate_green_h_and_v(int top, int left, ushort (*out_rgb)[TS][TS][3])
{
int row, col;
int c, val;
ushort(*pix)[4];
const int rowlimit = MIN(top + TS, height - 2);
const int collimit = MIN(left + TS, width - 2);
for (row = top; row < rowlimit; row++)
{
col = left + (FC(row, left) & 1);
for (c = FC(row, col); col < collimit; col += 2)
{
pix = image + row * width + col;
val = ((pix[-1][1] + pix[0][c] + pix[1][1]) * 2 - pix[-2][c] - pix[2][c]) >> 2;
out_rgb[0][row - top][col - left][1] = ULIM(val, pix[-1][1], pix[1][1]);
val = ((pix[-width][1] + pix[0][c] + pix[width][1]) * 2 - pix[-2 * width][c] - pix[2 * width][c]) >> 2;
out_rgb[1][row - top][col - left][1] = ULIM(val, pix[-width][1], pix[width][1]);
}
}
}
void CLASS ahd_interpolate_r_and_b_in_rgb_and_convert_to_cielab(int top, int left, ushort (*inout_rgb)[TS][3],
short (*out_lab)[TS][3])
{
unsigned row, col;
int c, val;
ushort(*pix)[4];
ushort(*rix)[3];
short(*lix)[3];
float xyz[3];
const unsigned num_pix_per_row = 4 * width;
const unsigned rowlimit = MIN(top + TS - 1, height - 3);
const unsigned collimit = MIN(left + TS - 1, width - 3);
ushort *pix_above;
ushort *pix_below;
int t1, t2;
for (row = top + 1; row < rowlimit; row++)
{
pix = image + row * width + left;
rix = &inout_rgb[row - top][0];
lix = &out_lab[row - top][0];
for (col = left + 1; col < collimit; col++)
{
pix++;
pix_above = &pix[0][0] - num_pix_per_row;
pix_below = &pix[0][0] + num_pix_per_row;
rix++;
lix++;
c = 2 - FC(row, col);
if (c == 1)
{
c = FC(row + 1, col);
t1 = 2 - c;
val = pix[0][1] + ((pix[-1][t1] + pix[1][t1] - rix[-1][1] - rix[1][1]) >> 1);
rix[0][t1] = CLIP(val);
val = pix[0][1] + ((pix_above[c] + pix_below[c] - rix[-TS][1] - rix[TS][1]) >> 1);
}
else
{
t1 = -4 + c; /* -4+c: pixel of color c to the left */
t2 = 4 + c; /* 4+c: pixel of color c to the right */
val = rix[0][1] + ((pix_above[t1] + pix_above[t2] + pix_below[t1] + pix_below[t2] - rix[-TS - 1][1] -
rix[-TS + 1][1] - rix[+TS - 1][1] - rix[+TS + 1][1] + 1) >>
2);
}
rix[0][c] = CLIP(val);
c = FC(row, col);
rix[0][c] = pix[0][c];
cielab(rix[0], lix[0]);
}
}
}
void CLASS ahd_interpolate_r_and_b_and_convert_to_cielab(int top, int left, ushort (*inout_rgb)[TS][TS][3],
short (*out_lab)[TS][TS][3])
{
int direction;
for (direction = 0; direction < 2; direction++)
{
ahd_interpolate_r_and_b_in_rgb_and_convert_to_cielab(top, left, inout_rgb[direction], out_lab[direction]);
}
}
void CLASS ahd_interpolate_build_homogeneity_map(int top, int left, short (*lab)[TS][TS][3],
char (*out_homogeneity_map)[TS][2])
{
int row, col;
int tr, tc;
int direction;
int i;
short(*lix)[3];
short(*lixs[2])[3];
short *adjacent_lix;
unsigned ldiff[2][4], abdiff[2][4], leps, abeps;
static const int dir[4] = {-1, 1, -TS, TS};
const int rowlimit = MIN(top + TS - 2, height - 4);
const int collimit = MIN(left + TS - 2, width - 4);
int homogeneity;
char(*homogeneity_map_p)[2];
memset(out_homogeneity_map, 0, 2 * TS * TS);
for (row = top + 2; row < rowlimit; row++)
{
tr = row - top;
homogeneity_map_p = &out_homogeneity_map[tr][1];
for (direction = 0; direction < 2; direction++)
{
lixs[direction] = &lab[direction][tr][1];
}
for (col = left + 2; col < collimit; col++)
{
tc = col - left;
homogeneity_map_p++;
for (direction = 0; direction < 2; direction++)
{
lix = ++lixs[direction];
for (i = 0; i < 4; i++)
{
adjacent_lix = lix[dir[i]];
ldiff[direction][i] = ABS(lix[0][0] - adjacent_lix[0]);
abdiff[direction][i] = SQR(lix[0][1] - adjacent_lix[1]) + SQR(lix[0][2] - adjacent_lix[2]);
}
}
leps = MIN(MAX(ldiff[0][0], ldiff[0][1]), MAX(ldiff[1][2], ldiff[1][3]));
abeps = MIN(MAX(abdiff[0][0], abdiff[0][1]), MAX(abdiff[1][2], abdiff[1][3]));
for (direction = 0; direction < 2; direction++)
{
homogeneity = 0;
for (i = 0; i < 4; i++)
{
if (ldiff[direction][i] <= leps && abdiff[direction][i] <= abeps)
{
homogeneity++;
}
}
homogeneity_map_p[0][direction] = homogeneity;
}
}
}
}
void CLASS ahd_interpolate_combine_homogeneous_pixels(int top, int left, ushort (*rgb)[TS][TS][3],
char (*homogeneity_map)[TS][2])
{
int row, col;
int tr, tc;
int i, j;
int direction;
int hm[2];
int c;
const int rowlimit = MIN(top + TS - 3, height - 5);
const int collimit = MIN(left + TS - 3, width - 5);
ushort(*pix)[4];
ushort(*rix[2])[3];
for (row = top + 3; row < rowlimit; row++)
{
tr = row - top;
pix = &image[row * width + left + 2];
for (direction = 0; direction < 2; direction++)
{
rix[direction] = &rgb[direction][tr][2];
}
for (col = left + 3; col < collimit; col++)
{
tc = col - left;
pix++;
for (direction = 0; direction < 2; direction++)
{
rix[direction]++;
}
for (direction = 0; direction < 2; direction++)
{
hm[direction] = 0;
for (i = tr - 1; i <= tr + 1; i++)
{
for (j = tc - 1; j <= tc + 1; j++)
{
hm[direction] += homogeneity_map[i][j][direction];
}
}
}
if (hm[0] != hm[1])
{
memcpy(pix[0], rix[hm[1] > hm[0]][0], 3 * sizeof(ushort));
}
else
{
FORC3 { pix[0][c] = (rix[0][0][c] + rix[1][0][c]) >> 1; }
}
}
}
}
void CLASS ahd_interpolate()
{
int i, j, k, top, left;
float xyz_cam[3][4], r;
char *buffer;
ushort(*rgb)[TS][TS][3];
short(*lab)[TS][TS][3];
char(*homo)[TS][2];
int terminate_flag = 0;
cielab(0, 0);
border_interpolate(5);
#ifdef LIBRAW_LIBRARY_BUILD
#ifdef LIBRAW_USE_OPENMP
#pragma omp parallel private(buffer, rgb, lab, homo, top, left, i, j, k) shared(xyz_cam, terminate_flag)
#endif
#endif
{
buffer = (char *)malloc(26 * TS * TS); /* 1664 kB */
merror(buffer, "ahd_interpolate()");
rgb = (ushort(*)[TS][TS][3])buffer;
lab = (short(*)[TS][TS][3])(buffer + 12 * TS * TS);
homo = (char(*)[TS][2])(buffer + 24 * TS * TS);
#ifdef LIBRAW_LIBRARY_BUILD
#ifdef LIBRAW_USE_OPENMP
#pragma omp for schedule(dynamic)
#endif
#endif
for (top = 2; top < height - 5; top += TS - 6)
{
#ifdef LIBRAW_LIBRARY_BUILD
#ifdef LIBRAW_USE_OPENMP
if (0 == omp_get_thread_num())
#endif
if (callbacks.progress_cb)
{
int rr =
(*callbacks.progress_cb)(callbacks.progresscb_data, LIBRAW_PROGRESS_INTERPOLATE, top - 2, height - 7);
if (rr)
terminate_flag = 1;
}
#endif
for (left = 2; !terminate_flag && (left < width - 5); left += TS - 6)
{
ahd_interpolate_green_h_and_v(top, left, rgb);
ahd_interpolate_r_and_b_and_convert_to_cielab(top, left, rgb, lab);
ahd_interpolate_build_homogeneity_map(top, left, lab, homo);
ahd_interpolate_combine_homogeneous_pixels(top, left, rgb, homo);
}
}
free(buffer);
}
#ifdef LIBRAW_LIBRARY_BUILD
if (terminate_flag)
throw LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK;
#endif
}
#else
void CLASS ahd_interpolate()
{
int i, j, top, left, row, col, tr, tc, c, d, val, hm[2];
static const int dir[4] = {-1, 1, -TS, TS};
unsigned ldiff[2][4], abdiff[2][4], leps, abeps;
ushort(*rgb)[TS][TS][3], (*rix)[3], (*pix)[4];
short(*lab)[TS][TS][3], (*lix)[3];
char(*homo)[TS][TS], *buffer;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("AHD interpolation...\n"));
#endif
cielab(0, 0);
border_interpolate(5);
buffer = (char *)malloc(26 * TS * TS);
merror(buffer, "ahd_interpolate()");
rgb = (ushort(*)[TS][TS][3])buffer;
lab = (short(*)[TS][TS][3])(buffer + 12 * TS * TS);
homo = (char(*)[TS][TS])(buffer + 24 * TS * TS);
for (top = 2; top < height - 5; top += TS - 6)
for (left = 2; left < width - 5; left += TS - 6)
{
/* Interpolate green horizontally and vertically: */
for (row = top; row < top + TS && row < height - 2; row++)
{
col = left + (FC(row, left) & 1);
for (c = FC(row, col); col < left + TS && col < width - 2; col += 2)
{
pix = image + row * width + col;
val = ((pix[-1][1] + pix[0][c] + pix[1][1]) * 2 - pix[-2][c] - pix[2][c]) >> 2;
rgb[0][row - top][col - left][1] = ULIM(val, pix[-1][1], pix[1][1]);
val = ((pix[-width][1] + pix[0][c] + pix[width][1]) * 2 - pix[-2 * width][c] - pix[2 * width][c]) >> 2;
rgb[1][row - top][col - left][1] = ULIM(val, pix[-width][1], pix[width][1]);
}
}
/* Interpolate red and blue, and convert to CIELab: */
for (d = 0; d < 2; d++)
for (row = top + 1; row < top + TS - 1 && row < height - 3; row++)
for (col = left + 1; col < left + TS - 1 && col < width - 3; col++)
{
pix = image + row * width + col;
rix = &rgb[d][row - top][col - left];
lix = &lab[d][row - top][col - left];
if ((c = 2 - FC(row, col)) == 1)
{
c = FC(row + 1, col);
val = pix[0][1] + ((pix[-1][2 - c] + pix[1][2 - c] - rix[-1][1] - rix[1][1]) >> 1);
rix[0][2 - c] = CLIP(val);
val = pix[0][1] + ((pix[-width][c] + pix[width][c] - rix[-TS][1] - rix[TS][1]) >> 1);
}
else
val = rix[0][1] + ((pix[-width - 1][c] + pix[-width + 1][c] + pix[+width - 1][c] + pix[+width + 1][c] -
rix[-TS - 1][1] - rix[-TS + 1][1] - rix[+TS - 1][1] - rix[+TS + 1][1] + 1) >>
2);
rix[0][c] = CLIP(val);
c = FC(row, col);
rix[0][c] = pix[0][c];
cielab(rix[0], lix[0]);
}
/* Build homogeneity maps from the CIELab images: */
memset(homo, 0, 2 * TS * TS);
for (row = top + 2; row < top + TS - 2 && row < height - 4; row++)
{
tr = row - top;
for (col = left + 2; col < left + TS - 2 && col < width - 4; col++)
{
tc = col - left;
for (d = 0; d < 2; d++)
{
lix = &lab[d][tr][tc];
for (i = 0; i < 4; i++)
{
ldiff[d][i] = ABS(lix[0][0] - lix[dir[i]][0]);
abdiff[d][i] = SQR(lix[0][1] - lix[dir[i]][1]) + SQR(lix[0][2] - lix[dir[i]][2]);
}
}
leps = MIN(MAX(ldiff[0][0], ldiff[0][1]), MAX(ldiff[1][2], ldiff[1][3]));
abeps = MIN(MAX(abdiff[0][0], abdiff[0][1]), MAX(abdiff[1][2], abdiff[1][3]));
for (d = 0; d < 2; d++)
for (i = 0; i < 4; i++)
if (ldiff[d][i] <= leps && abdiff[d][i] <= abeps)
homo[d][tr][tc]++;
}
}
- /* Combine the most homogenous pixels for the final result: */
+ /* Combine the most homogeneous pixels for the final result: */
for (row = top + 3; row < top + TS - 3 && row < height - 5; row++)
{
tr = row - top;
for (col = left + 3; col < left + TS - 3 && col < width - 5; col++)
{
tc = col - left;
for (d = 0; d < 2; d++)
for (hm[d] = 0, i = tr - 1; i <= tr + 1; i++)
for (j = tc - 1; j <= tc + 1; j++)
hm[d] += homo[d][i][j];
if (hm[0] != hm[1])
FORC3 image[row * width + col][c] = rgb[hm[1] > hm[0]][tr][tc][c];
else
FORC3 image[row * width + col][c] = (rgb[0][tr][tc][c] + rgb[1][tr][tc][c]) >> 1;
}
}
}
free(buffer);
}
#endif
#undef TS
void CLASS median_filter()
{
ushort(*pix)[4];
int pass, c, i, j, k, med[9];
static const uchar opt[] = /* Optimal 9-element median search */
{1, 2, 4, 5, 7, 8, 0, 1, 3, 4, 6, 7, 1, 2, 4, 5, 7, 8, 0,
3, 5, 8, 4, 7, 3, 6, 1, 4, 2, 5, 4, 7, 4, 2, 6, 4, 4, 2};
for (pass = 1; pass <= med_passes; pass++)
{
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_MEDIAN_FILTER, pass - 1, med_passes);
#endif
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Median filter pass %d...\n"), pass);
#endif
for (c = 0; c < 3; c += 2)
{
for (pix = image; pix < image + width * height; pix++)
pix[0][3] = pix[0][c];
for (pix = image + width; pix < image + width * (height - 1); pix++)
{
if ((pix - image + 1) % width < 2)
continue;
for (k = 0, i = -width; i <= width; i += width)
for (j = i - 1; j <= i + 1; j++)
med[k++] = pix[j][3] - pix[j][1];
for (i = 0; i < sizeof opt; i += 2)
if (med[opt[i]] > med[opt[i + 1]])
SWAP(med[opt[i]], med[opt[i + 1]]);
pix[0][c] = CLIP(med[4] + pix[0][1]);
}
}
}
}
void CLASS blend_highlights()
{
int clip = INT_MAX, row, col, c, i, j;
static const float trans[2][4][4] = {{{1, 1, 1}, {1.7320508, -1.7320508, 0}, {-1, -1, 2}},
{{1, 1, 1, 1}, {1, -1, 1, -1}, {1, 1, -1, -1}, {1, -1, -1, 1}}};
static const float itrans[2][4][4] = {{{1, 0.8660254, -0.5}, {1, -0.8660254, -0.5}, {1, 0, 1}},
{{1, 1, 1, 1}, {1, -1, 1, -1}, {1, 1, -1, -1}, {1, -1, -1, 1}}};
float cam[2][4], lab[2][4], sum[2], chratio;
if ((unsigned)(colors - 3) > 1)
return;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Blending highlights...\n"));
#endif
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_HIGHLIGHTS, 0, 2);
#endif
FORCC if (clip > (i = 65535 * pre_mul[c])) clip = i;
for (row = 0; row < height; row++)
for (col = 0; col < width; col++)
{
FORCC if (image[row * width + col][c] > clip) break;
if (c == colors)
continue;
FORCC
{
cam[0][c] = image[row * width + col][c];
cam[1][c] = MIN(cam[0][c], clip);
}
for (i = 0; i < 2; i++)
{
FORCC for (lab[i][c] = j = 0; j < colors; j++) lab[i][c] += trans[colors - 3][c][j] * cam[i][j];
for (sum[i] = 0, c = 1; c < colors; c++)
sum[i] += SQR(lab[i][c]);
}
chratio = sqrt(sum[1] / sum[0]);
for (c = 1; c < colors; c++)
lab[0][c] *= chratio;
FORCC for (cam[0][c] = j = 0; j < colors; j++) cam[0][c] += itrans[colors - 3][c][j] * lab[0][j];
FORCC image[row * width + col][c] = cam[0][c] / colors;
}
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_HIGHLIGHTS, 1, 2);
#endif
}
#define SCALE (4 >> shrink)
void CLASS recover_highlights()
{
float *map, sum, wgt, grow;
int hsat[4], count, spread, change, val, i;
unsigned high, wide, mrow, mcol, row, col, kc, c, d, y, x;
ushort *pixel;
static const signed char dir[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}};
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Rebuilding highlights...\n"));
#endif
grow = pow(2.0, 4 - highlight);
FORCC hsat[c] = 32000 * pre_mul[c];
for (kc = 0, c = 1; c < colors; c++)
if (pre_mul[kc] < pre_mul[c])
kc = c;
high = height / SCALE;
wide = width / SCALE;
map = (float *)calloc(high, wide * sizeof *map);
merror(map, "recover_highlights()");
FORCC if (c != kc)
{
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_HIGHLIGHTS, c - 1, colors - 1);
#endif
memset(map, 0, high * wide * sizeof *map);
for (mrow = 0; mrow < high; mrow++)
for (mcol = 0; mcol < wide; mcol++)
{
sum = wgt = count = 0;
for (row = mrow * SCALE; row < (mrow + 1) * SCALE; row++)
for (col = mcol * SCALE; col < (mcol + 1) * SCALE; col++)
{
pixel = image[row * width + col];
if (pixel[c] / hsat[c] == 1 && pixel[kc] > 24000)
{
sum += pixel[c];
wgt += pixel[kc];
count++;
}
}
if (count == SCALE * SCALE)
map[mrow * wide + mcol] = sum / wgt;
}
for (spread = 32 / grow; spread--;)
{
for (mrow = 0; mrow < high; mrow++)
for (mcol = 0; mcol < wide; mcol++)
{
if (map[mrow * wide + mcol])
continue;
sum = count = 0;
for (d = 0; d < 8; d++)
{
y = mrow + dir[d][0];
x = mcol + dir[d][1];
if (y < high && x < wide && map[y * wide + x] > 0)
{
sum += (1 + (d & 1)) * map[y * wide + x];
count += 1 + (d & 1);
}
}
if (count > 3)
map[mrow * wide + mcol] = -(sum + grow) / (count + grow);
}
for (change = i = 0; i < high * wide; i++)
if (map[i] < 0)
{
map[i] = -map[i];
change = 1;
}
if (!change)
break;
}
for (i = 0; i < high * wide; i++)
if (map[i] == 0)
map[i] = 1;
for (mrow = 0; mrow < high; mrow++)
for (mcol = 0; mcol < wide; mcol++)
{
for (row = mrow * SCALE; row < (mrow + 1) * SCALE; row++)
for (col = mcol * SCALE; col < (mcol + 1) * SCALE; col++)
{
pixel = image[row * width + col];
if (pixel[c] / hsat[c] > 1)
{
val = pixel[kc] * map[mrow * wide + mcol];
if (pixel[c] < val)
pixel[c] = CLIP(val);
}
}
}
}
free(map);
}
#undef SCALE
void CLASS tiff_get(unsigned base, unsigned *tag, unsigned *type, unsigned *len, unsigned *save)
{
#ifdef LIBRAW_IOSPACE_CHECK
INT64 pos = ftell(ifp);
INT64 fsize = ifp->size();
if(fsize < 12 || (fsize-pos) < 12)
throw LIBRAW_EXCEPTION_IO_EOF;
#endif
*tag = get2();
*type = get2();
*len = get4();
*save = ftell(ifp) + 4;
if (*len * ("11124811248484"[*type < 14 ? *type : 0] - '0') > 4)
fseek(ifp, get4() + base, SEEK_SET);
}
void CLASS parse_thumb_note(int base, unsigned toff, unsigned tlen)
{
unsigned entries, tag, type, len, save;
entries = get2();
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
if (tag == toff)
thumb_offset = get4() + base;
if (tag == tlen)
thumb_length = get4();
fseek(ifp, save, SEEK_SET);
}
}
static float powf_lim(float a, float b, float limup) { return (b > limup || b < -limup) ? 0.f : powf(a, b); }
static float libraw_powf64l(float a, float b) { return powf_lim(a, b, 64.f); }
#ifdef LIBRAW_LIBRARY_BUILD
static float my_roundf(float x)
{
float t;
if (x >= 0.0)
{
t = ceilf(x);
if (t - x > 0.5)
t -= 1.0;
return t;
}
else
{
t = ceilf(-x);
if (t + x > 0.5)
t -= 1.0;
return -t;
}
}
static float _CanonConvertAperture(ushort in)
{
if ((in == (ushort)0xffe0) || (in == (ushort)0x7fff))
return 0.0f;
return libraw_powf64l(2.0, in / 64.0);
}
static float _CanonConvertEV(short in)
{
short EV, Sign, Frac;
float Frac_f;
EV = in;
if (EV < 0)
{
EV = -EV;
Sign = -1;
}
else
{
Sign = 1;
}
Frac = EV & 0x1f;
EV -= Frac; // remove fraction
if (Frac == 0x0c)
{ // convert 1/3 and 2/3 codes
Frac_f = 32.0f / 3.0f;
}
else if (Frac == 0x14)
{
Frac_f = 64.0f / 3.0f;
}
else
Frac_f = (float)Frac;
return ((float)Sign * ((float)EV + Frac_f)) / 32.0f;
}
unsigned CLASS setCanonBodyFeatures(unsigned id)
{
if (id == 0x03740000) // EOS M3
id = 0x80000374;
else if (id == 0x03840000) // EOS M10
id = 0x80000384;
else if (id == 0x03940000) // EOS M5
id = 0x80000394;
else if (id == 0x04070000) // EOS M6
id = 0x80000407;
else if (id == 0x03980000) // EOS M100
id = 0x80000398;
imgdata.lens.makernotes.CamID = id;
if ((id == 0x80000001) || // 1D
(id == 0x80000174) || // 1D2
(id == 0x80000232) || // 1D2N
(id == 0x80000169) || // 1D3
(id == 0x80000281) // 1D4
)
{
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_APSH;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Canon_EF;
}
else if ((id == 0x80000167) || // 1Ds
(id == 0x80000188) || // 1Ds2
(id == 0x80000215) || // 1Ds3
(id == 0x80000269) || // 1DX
(id == 0x80000328) || // 1DX2
(id == 0x80000324) || // 1DC
(id == 0x80000213) || // 5D
(id == 0x80000218) || // 5D2
(id == 0x80000285) || // 5D3
(id == 0x80000349) || // 5D4
(id == 0x80000382) || // 5DS
(id == 0x80000401) || // 5DS R
(id == 0x80000302) // 6D
)
{
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_FF;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Canon_EF;
}
else if ((id == 0x80000331) || // M
(id == 0x80000355) || // M2
(id == 0x80000374) || // M3
(id == 0x80000384) || // M10
(id == 0x80000394) || // M5
(id == 0x80000407) || // M6
(id == 0x80000398) // M100
)
{
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_APSC;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Canon_EF_M;
}
else if ((id == 0x01140000) || // D30
(id == 0x01668000) || // D60
(id > 0x80000000))
{
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_APSC;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Canon_EF;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Unknown;
}
else
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
}
return id;
}
void CLASS processCanonCameraInfo(unsigned id, uchar *CameraInfo, unsigned maxlen, unsigned type)
{
ushort iCanonLensID = 0, iCanonMaxFocal = 0, iCanonMinFocal = 0, iCanonLens = 0, iCanonCurFocal = 0,
iCanonFocalType = 0;
if (maxlen < 16)
return; // too short
CameraInfo[0] = 0;
CameraInfo[1] = 0;
if (type == 4)
{
if ((maxlen == 94) || (maxlen == 138) || (maxlen == 148) || (maxlen == 156) || (maxlen == 162) || (maxlen == 167) ||
(maxlen == 171) || (maxlen == 264) || (maxlen > 400))
imgdata.other.CameraTemperature = sget4(CameraInfo + ((maxlen - 3) << 2));
else if (maxlen == 72)
imgdata.other.CameraTemperature = sget4(CameraInfo + ((maxlen - 1) << 2));
else if ((maxlen == 85) || (maxlen == 93))
imgdata.other.CameraTemperature = sget4(CameraInfo + ((maxlen - 2) << 2));
else if ((maxlen == 96) || (maxlen == 104))
imgdata.other.CameraTemperature = sget4(CameraInfo + ((maxlen - 4) << 2));
}
switch (id)
{
case 0x80000001: // 1D
case 0x80000167: // 1DS
iCanonCurFocal = 10;
iCanonLensID = 13;
iCanonMinFocal = 14;
iCanonMaxFocal = 16;
if (!imgdata.lens.makernotes.CurFocal)
imgdata.lens.makernotes.CurFocal = sget2(CameraInfo + iCanonCurFocal);
if (!imgdata.lens.makernotes.MinFocal)
imgdata.lens.makernotes.MinFocal = sget2(CameraInfo + iCanonMinFocal);
if (!imgdata.lens.makernotes.MaxFocal)
imgdata.lens.makernotes.MaxFocal = sget2(CameraInfo + iCanonMaxFocal);
imgdata.other.CameraTemperature = 0.0f;
break;
case 0x80000174: // 1DMkII
case 0x80000188: // 1DsMkII
iCanonCurFocal = 9;
iCanonLensID = 12;
iCanonMinFocal = 17;
iCanonMaxFocal = 19;
iCanonFocalType = 45;
break;
case 0x80000232: // 1DMkII N
iCanonCurFocal = 9;
iCanonLensID = 12;
iCanonMinFocal = 17;
iCanonMaxFocal = 19;
break;
case 0x80000169: // 1DMkIII
case 0x80000215: // 1DsMkIII
iCanonCurFocal = 29;
iCanonLensID = 273;
iCanonMinFocal = 275;
iCanonMaxFocal = 277;
break;
case 0x80000281: // 1DMkIV
iCanonCurFocal = 30;
iCanonLensID = 335;
iCanonMinFocal = 337;
iCanonMaxFocal = 339;
break;
case 0x80000269: // 1D X
iCanonCurFocal = 35;
iCanonLensID = 423;
iCanonMinFocal = 425;
iCanonMaxFocal = 427;
break;
case 0x80000213: // 5D
iCanonCurFocal = 40;
if (!sget2Rev(CameraInfo + 12))
iCanonLensID = 151;
else
iCanonLensID = 12;
iCanonMinFocal = 147;
iCanonMaxFocal = 149;
break;
case 0x80000218: // 5DMkII
iCanonCurFocal = 30;
iCanonLensID = 230;
iCanonMinFocal = 232;
iCanonMaxFocal = 234;
break;
case 0x80000285: // 5DMkIII
iCanonCurFocal = 35;
iCanonLensID = 339;
iCanonMinFocal = 341;
iCanonMaxFocal = 343;
break;
case 0x80000302: // 6D
iCanonCurFocal = 35;
iCanonLensID = 353;
iCanonMinFocal = 355;
iCanonMaxFocal = 357;
break;
case 0x80000250: // 7D
iCanonCurFocal = 30;
iCanonLensID = 274;
iCanonMinFocal = 276;
iCanonMaxFocal = 278;
break;
case 0x80000190: // 40D
iCanonCurFocal = 29;
iCanonLensID = 214;
iCanonMinFocal = 216;
iCanonMaxFocal = 218;
iCanonLens = 2347;
break;
case 0x80000261: // 50D
iCanonCurFocal = 30;
iCanonLensID = 234;
iCanonMinFocal = 236;
iCanonMaxFocal = 238;
break;
case 0x80000287: // 60D
iCanonCurFocal = 30;
iCanonLensID = 232;
iCanonMinFocal = 234;
iCanonMaxFocal = 236;
break;
case 0x80000325: // 70D
iCanonCurFocal = 35;
iCanonLensID = 358;
iCanonMinFocal = 360;
iCanonMaxFocal = 362;
break;
case 0x80000176: // 450D
iCanonCurFocal = 29;
iCanonLensID = 222;
iCanonLens = 2355;
break;
case 0x80000252: // 500D
iCanonCurFocal = 30;
iCanonLensID = 246;
iCanonMinFocal = 248;
iCanonMaxFocal = 250;
break;
case 0x80000270: // 550D
iCanonCurFocal = 30;
iCanonLensID = 255;
iCanonMinFocal = 257;
iCanonMaxFocal = 259;
break;
case 0x80000286: // 600D
case 0x80000288: // 1100D
iCanonCurFocal = 30;
iCanonLensID = 234;
iCanonMinFocal = 236;
iCanonMaxFocal = 238;
break;
case 0x80000301: // 650D
case 0x80000326: // 700D
iCanonCurFocal = 35;
iCanonLensID = 295;
iCanonMinFocal = 297;
iCanonMaxFocal = 299;
break;
case 0x80000254: // 1000D
iCanonCurFocal = 29;
iCanonLensID = 226;
iCanonMinFocal = 228;
iCanonMaxFocal = 230;
iCanonLens = 2359;
break;
}
if (iCanonFocalType)
{
if (iCanonFocalType >= maxlen)
return; // broken;
imgdata.lens.makernotes.FocalType = CameraInfo[iCanonFocalType];
if (!imgdata.lens.makernotes.FocalType) // zero means 'fixed' here, replacing with standard '1'
imgdata.lens.makernotes.FocalType = 1;
}
if (!imgdata.lens.makernotes.CurFocal)
{
if (iCanonCurFocal >= maxlen)
return; // broken;
imgdata.lens.makernotes.CurFocal = sget2Rev(CameraInfo + iCanonCurFocal);
}
if (!imgdata.lens.makernotes.LensID)
{
if (iCanonLensID >= maxlen)
return; // broken;
imgdata.lens.makernotes.LensID = sget2Rev(CameraInfo + iCanonLensID);
}
if (!imgdata.lens.makernotes.MinFocal)
{
if (iCanonMinFocal >= maxlen)
return; // broken;
imgdata.lens.makernotes.MinFocal = sget2Rev(CameraInfo + iCanonMinFocal);
}
if (!imgdata.lens.makernotes.MaxFocal)
{
if (iCanonMaxFocal >= maxlen)
return; // broken;
imgdata.lens.makernotes.MaxFocal = sget2Rev(CameraInfo + iCanonMaxFocal);
}
if (!imgdata.lens.makernotes.Lens[0] && iCanonLens)
{
if (iCanonLens + 64 >= maxlen)
return; // broken;
if (CameraInfo[iCanonLens] < 65) // non-Canon lens
{
memcpy(imgdata.lens.makernotes.Lens, CameraInfo + iCanonLens, 64);
}
else if (!strncmp((char *)CameraInfo + iCanonLens, "EF-S", 4))
{
memcpy(imgdata.lens.makernotes.Lens, "EF-S ", 5);
memcpy(imgdata.lens.makernotes.LensFeatures_pre, "EF-E", 4);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF_S;
memcpy(imgdata.lens.makernotes.Lens + 5, CameraInfo + iCanonLens + 4, 60);
}
else if (!strncmp((char *)CameraInfo + iCanonLens, "TS-E", 4))
{
memcpy(imgdata.lens.makernotes.Lens, "TS-E ", 5);
memcpy(imgdata.lens.makernotes.LensFeatures_pre, "TS-E", 4);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
memcpy(imgdata.lens.makernotes.Lens + 5, CameraInfo + iCanonLens + 4, 60);
}
else if (!strncmp((char *)CameraInfo + iCanonLens, "MP-E", 4))
{
memcpy(imgdata.lens.makernotes.Lens, "MP-E ", 5);
memcpy(imgdata.lens.makernotes.LensFeatures_pre, "MP-E", 4);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
memcpy(imgdata.lens.makernotes.Lens + 5, CameraInfo + iCanonLens + 4, 60);
}
else if (!strncmp((char *)CameraInfo + iCanonLens, "EF-M", 4))
{
memcpy(imgdata.lens.makernotes.Lens, "EF-M ", 5);
memcpy(imgdata.lens.makernotes.LensFeatures_pre, "EF-M", 4);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF_M;
memcpy(imgdata.lens.makernotes.Lens + 5, CameraInfo + iCanonLens + 4, 60);
}
else
{
memcpy(imgdata.lens.makernotes.Lens, CameraInfo + iCanonLens, 2);
memcpy(imgdata.lens.makernotes.LensFeatures_pre, "EF", 2);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
imgdata.lens.makernotes.Lens[2] = 32;
memcpy(imgdata.lens.makernotes.Lens + 3, CameraInfo + iCanonLens + 2, 62);
}
}
return;
}
void CLASS Canon_CameraSettings()
{
fseek(ifp, 10, SEEK_CUR);
imgdata.shootinginfo.DriveMode = get2();
get2();
imgdata.shootinginfo.FocusMode = get2();
fseek(ifp, 18, SEEK_CUR);
imgdata.shootinginfo.MeteringMode = get2();
get2();
imgdata.shootinginfo.AFPoint = get2();
imgdata.shootinginfo.ExposureMode = get2();
get2();
imgdata.lens.makernotes.LensID = get2();
imgdata.lens.makernotes.MaxFocal = get2();
imgdata.lens.makernotes.MinFocal = get2();
imgdata.lens.makernotes.CanonFocalUnits = get2();
if (imgdata.lens.makernotes.CanonFocalUnits > 1)
{
imgdata.lens.makernotes.MaxFocal /= (float)imgdata.lens.makernotes.CanonFocalUnits;
imgdata.lens.makernotes.MinFocal /= (float)imgdata.lens.makernotes.CanonFocalUnits;
}
imgdata.lens.makernotes.MaxAp = _CanonConvertAperture(get2());
imgdata.lens.makernotes.MinAp = _CanonConvertAperture(get2());
fseek(ifp, 12, SEEK_CUR);
imgdata.shootinginfo.ImageStabilization = get2();
}
void CLASS Canon_WBpresets(int skip1, int skip2)
{
int c;
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][c ^ (c >> 1)] = get2();
if (skip1)
fseek(ifp, skip1, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][c ^ (c >> 1)] = get2();
if (skip1)
fseek(ifp, skip1, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][c ^ (c >> 1)] = get2();
if (skip1)
fseek(ifp, skip1, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][c ^ (c >> 1)] = get2();
if (skip1)
fseek(ifp, skip1, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][c ^ (c >> 1)] = get2();
if (skip2)
fseek(ifp, skip2, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][c ^ (c >> 1)] = get2();
return;
}
void CLASS Canon_WBCTpresets(short WBCTversion)
{
if (WBCTversion == 0)
for (int i = 0; i < 15; i++) // tint, as shot R, as shot B, CСT
{
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = 1.0f;
fseek(ifp, 2, SEEK_CUR);
imgdata.color.WBCT_Coeffs[i][1] = 1024.0f / fMAX(get2(), 1.f);
imgdata.color.WBCT_Coeffs[i][3] = 1024.0f / fMAX(get2(), 1.f);
imgdata.color.WBCT_Coeffs[i][0] = get2();
}
else if (WBCTversion == 1)
for (int i = 0; i < 15; i++) // as shot R, as shot B, tint, CСT
{
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = 1.0f;
imgdata.color.WBCT_Coeffs[i][1] = 1024.0f / fMAX(get2(), 1.f);
imgdata.color.WBCT_Coeffs[i][3] = 1024.0f / fMAX(get2(), 1.f);
fseek(ifp, 2, SEEK_CUR);
imgdata.color.WBCT_Coeffs[i][0] = get2();
}
else if ((WBCTversion == 2) && ((unique_id == 0x80000374) || // M3
(unique_id == 0x80000384) || // M10
(unique_id == 0x80000394) || // M5
(unique_id == 0x80000407) || // M6
(unique_id == 0x80000398) || // M100
(unique_id == 0x03970000) || // G7 X Mark II
(unique_id == 0x04100000) || // G9 X Mark II
(unique_id == 0x04180000))) // G1 X Mark III
for (int i = 0; i < 15; i++) // tint, offset, as shot R, as shot B, CСT
{
fseek(ifp, 2, SEEK_CUR);
fseek(ifp, 2, SEEK_CUR);
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = 1.0f;
imgdata.color.WBCT_Coeffs[i][1] = 1024.0f / fMAX(1.f, get2());
imgdata.color.WBCT_Coeffs[i][3] = 1024.0f / fMAX(1.f, get2());
imgdata.color.WBCT_Coeffs[i][0] = get2();
}
else if ((WBCTversion == 2) && ((unique_id == 0x03950000) || (unique_id == 0x03930000))) // G5 X, G9 X
for (int i = 0; i < 15; i++) // tint, offset, as shot R, as shot B, CСT
{
fseek(ifp, 2, SEEK_CUR);
fseek(ifp, 2, SEEK_CUR);
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = 1.0f;
imgdata.color.WBCT_Coeffs[i][1] = (float)get2() / 512.0f;
imgdata.color.WBCT_Coeffs[i][3] = (float)get2() / 512.0f;
imgdata.color.WBCT_Coeffs[i][0] = get2();
}
return;
}
void CLASS processNikonLensData(uchar *LensData, unsigned len)
{
ushort i;
if (!(imgdata.lens.nikon.NikonLensType & 0x01))
{
imgdata.lens.makernotes.LensFeatures_pre[0] = 'A';
imgdata.lens.makernotes.LensFeatures_pre[1] = 'F';
}
else
{
imgdata.lens.makernotes.LensFeatures_pre[0] = 'M';
imgdata.lens.makernotes.LensFeatures_pre[1] = 'F';
}
if (imgdata.lens.nikon.NikonLensType & 0x02)
{
if (imgdata.lens.nikon.NikonLensType & 0x04)
imgdata.lens.makernotes.LensFeatures_suf[0] = 'G';
else
imgdata.lens.makernotes.LensFeatures_suf[0] = 'D';
imgdata.lens.makernotes.LensFeatures_suf[1] = ' ';
}
if (imgdata.lens.nikon.NikonLensType & 0x08)
{
imgdata.lens.makernotes.LensFeatures_suf[2] = 'V';
imgdata.lens.makernotes.LensFeatures_suf[3] = 'R';
}
if (imgdata.lens.nikon.NikonLensType & 0x10)
{
imgdata.lens.makernotes.LensMount = imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Nikon_CX;
imgdata.lens.makernotes.CameraFormat = imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_1INCH;
}
else
imgdata.lens.makernotes.LensMount = imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Nikon_F;
if (imgdata.lens.nikon.NikonLensType & 0x20)
{
strcpy(imgdata.lens.makernotes.Adapter, "FT-1");
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Nikon_F;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Nikon_CX;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_1INCH;
}
imgdata.lens.nikon.NikonLensType = imgdata.lens.nikon.NikonLensType & 0xdf;
if (len < 20)
{
switch (len)
{
case 9:
i = 2;
break;
case 15:
i = 7;
break;
case 16:
i = 8;
break;
}
imgdata.lens.nikon.NikonLensIDNumber = LensData[i];
imgdata.lens.nikon.NikonLensFStops = LensData[i + 1];
imgdata.lens.makernotes.LensFStops = (float)imgdata.lens.nikon.NikonLensFStops / 12.0f;
if (fabsf(imgdata.lens.makernotes.MinFocal) < 1.1f)
{
if ((imgdata.lens.nikon.NikonLensType ^ (uchar)0x01) || LensData[i + 2])
imgdata.lens.makernotes.MinFocal = 5.0f * libraw_powf64l(2.0f, (float)LensData[i + 2] / 24.0f);
if ((imgdata.lens.nikon.NikonLensType ^ (uchar)0x01) || LensData[i + 3])
imgdata.lens.makernotes.MaxFocal = 5.0f * libraw_powf64l(2.0f, (float)LensData[i + 3] / 24.0f);
if ((imgdata.lens.nikon.NikonLensType ^ (uchar)0x01) || LensData[i + 4])
imgdata.lens.makernotes.MaxAp4MinFocal = libraw_powf64l(2.0f, (float)LensData[i + 4] / 24.0f);
if ((imgdata.lens.nikon.NikonLensType ^ (uchar)0x01) || LensData[i + 5])
imgdata.lens.makernotes.MaxAp4MaxFocal = libraw_powf64l(2.0f, (float)LensData[i + 5] / 24.0f);
}
imgdata.lens.nikon.NikonMCUVersion = LensData[i + 6];
if (i != 2)
{
if ((LensData[i - 1]) && (fabsf(imgdata.lens.makernotes.CurFocal) < 1.1f))
imgdata.lens.makernotes.CurFocal = 5.0f * libraw_powf64l(2.0f, (float)LensData[i - 1] / 24.0f);
if (LensData[i + 7])
imgdata.lens.nikon.NikonEffectiveMaxAp = libraw_powf64l(2.0f, (float)LensData[i + 7] / 24.0f);
}
imgdata.lens.makernotes.LensID =
(unsigned long long)LensData[i] << 56 | (unsigned long long)LensData[i + 1] << 48 |
(unsigned long long)LensData[i + 2] << 40 | (unsigned long long)LensData[i + 3] << 32 |
(unsigned long long)LensData[i + 4] << 24 | (unsigned long long)LensData[i + 5] << 16 |
(unsigned long long)LensData[i + 6] << 8 | (unsigned long long)imgdata.lens.nikon.NikonLensType;
}
else if ((len == 459) || (len == 590))
{
memcpy(imgdata.lens.makernotes.Lens, LensData + 390, 64);
}
else if (len == 509)
{
memcpy(imgdata.lens.makernotes.Lens, LensData + 391, 64);
}
else if (len == 879)
{
memcpy(imgdata.lens.makernotes.Lens, LensData + 680, 64);
}
return;
}
void CLASS setOlympusBodyFeatures(unsigned long long id)
{
imgdata.lens.makernotes.CamID = id;
if (id == 0x5330303638ULL)
{
strcpy(model, "E-M10MarkIII");
}
if ((id == 0x4434303430ULL) || // E-1
(id == 0x4434303431ULL) || // E-300
((id & 0x00ffff0000ULL) == 0x0030300000ULL))
{
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_FT;
if ((id == 0x4434303430ULL) || // E-1
(id == 0x4434303431ULL) || // E-330
((id >= 0x5330303033ULL) && (id <= 0x5330303138ULL)) || // E-330 to E-520
(id == 0x5330303233ULL) || // E-620
(id == 0x5330303239ULL) || // E-450
(id == 0x5330303330ULL) || // E-600
(id == 0x5330303333ULL)) // E-5
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FT;
}
else
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_mFT;
}
}
else
{
imgdata.lens.makernotes.LensMount = imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
return;
}
void CLASS parseCanonMakernotes(unsigned tag, unsigned type, unsigned len)
{
if (tag == 0x0001)
Canon_CameraSettings();
else if (tag == 0x0002) // focal length
{
imgdata.lens.makernotes.FocalType = get2();
imgdata.lens.makernotes.CurFocal = get2();
if (imgdata.lens.makernotes.CanonFocalUnits > 1)
{
imgdata.lens.makernotes.CurFocal /= (float)imgdata.lens.makernotes.CanonFocalUnits;
}
}
else if (tag == 0x0004) // shot info
{
short tempAp;
fseek(ifp, 24, SEEK_CUR);
tempAp = get2();
if (tempAp != 0)
imgdata.other.CameraTemperature = (float)(tempAp - 128);
tempAp = get2();
if (tempAp != -1)
imgdata.other.FlashGN = ((float)tempAp) / 32;
get2();
// fseek(ifp, 30, SEEK_CUR);
imgdata.other.FlashEC = _CanonConvertEV((signed short)get2());
fseek(ifp, 8 - 32, SEEK_CUR);
if ((tempAp = get2()) != 0x7fff)
imgdata.lens.makernotes.CurAp = _CanonConvertAperture(tempAp);
if (imgdata.lens.makernotes.CurAp < 0.7f)
{
fseek(ifp, 32, SEEK_CUR);
imgdata.lens.makernotes.CurAp = _CanonConvertAperture(get2());
}
if (!aperture)
aperture = imgdata.lens.makernotes.CurAp;
}
else if (tag == 0x000c)
{
unsigned tS = get4();
sprintf (imgdata.shootinginfo.BodySerial, "%d", tS);
}
else if (tag == 0x0095 && // lens model tag
!imgdata.lens.makernotes.Lens[0])
{
fread(imgdata.lens.makernotes.Lens, 2, 1, ifp);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
if (imgdata.lens.makernotes.Lens[0] < 65) // non-Canon lens
fread(imgdata.lens.makernotes.Lens + 2, 62, 1, ifp);
else
{
char efs[2];
imgdata.lens.makernotes.LensFeatures_pre[0] = imgdata.lens.makernotes.Lens[0];
imgdata.lens.makernotes.LensFeatures_pre[1] = imgdata.lens.makernotes.Lens[1];
fread(efs, 2, 1, ifp);
if (efs[0] == 45 && (efs[1] == 83 || efs[1] == 69 || efs[1] == 77))
{ // "EF-S, TS-E, MP-E, EF-M" lenses
imgdata.lens.makernotes.Lens[2] = imgdata.lens.makernotes.LensFeatures_pre[2] = efs[0];
imgdata.lens.makernotes.Lens[3] = imgdata.lens.makernotes.LensFeatures_pre[3] = efs[1];
imgdata.lens.makernotes.Lens[4] = 32;
if (efs[1] == 83)
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF_S;
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_APSC;
}
else if (efs[1] == 77)
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF_M;
}
}
else
{ // "EF" lenses
imgdata.lens.makernotes.Lens[2] = 32;
imgdata.lens.makernotes.Lens[3] = efs[0];
imgdata.lens.makernotes.Lens[4] = efs[1];
}
fread(imgdata.lens.makernotes.Lens + 5, 58, 1, ifp);
}
}
else if (tag == 0x009a)
{
get4();
imgdata.sizes.raw_crop.cwidth = get4();
imgdata.sizes.raw_crop.cheight = get4();
imgdata.sizes.raw_crop.cleft = get4();
imgdata.sizes.raw_crop.ctop = get4();
}
else if (tag == 0x00a9)
{
long int save1 = ftell(ifp);
int c;
fseek(ifp, (0x1 << 1), SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
Canon_WBpresets(0, 0);
fseek(ifp, save1, SEEK_SET);
}
else if (tag == 0x00e0) // sensor info
{
imgdata.makernotes.canon.SensorWidth = (get2(), get2());
imgdata.makernotes.canon.SensorHeight = get2();
imgdata.makernotes.canon.SensorLeftBorder = (get2(), get2(), get2());
imgdata.makernotes.canon.SensorTopBorder = get2();
imgdata.makernotes.canon.SensorRightBorder = get2();
imgdata.makernotes.canon.SensorBottomBorder = get2();
imgdata.makernotes.canon.BlackMaskLeftBorder = get2();
imgdata.makernotes.canon.BlackMaskTopBorder = get2();
imgdata.makernotes.canon.BlackMaskRightBorder = get2();
imgdata.makernotes.canon.BlackMaskBottomBorder = get2();
}
else if (tag == 0x4013)
{
get4();
imgdata.makernotes.canon.AFMicroAdjMode = get4();
imgdata.makernotes.canon.AFMicroAdjValue = ((float)get4()) / ((float)get4());
}
else if (tag == 0x4001 && len > 500)
{
int c;
long int save1 = ftell(ifp);
switch (len)
{
case 582:
imgdata.makernotes.canon.CanonColorDataVer = 1; // 20D / 350D
{
fseek(ifp, save1 + (0x1e << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x41 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x46 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom2][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x23 << 1), SEEK_SET);
Canon_WBpresets(2, 2);
fseek(ifp, save1 + (0x4b << 1), SEEK_SET);
Canon_WBCTpresets(1); // ABCT
}
break;
case 653:
imgdata.makernotes.canon.CanonColorDataVer = 2; // 1Dmk2 / 1DsMK2
{
fseek(ifp, save1 + (0x18 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x90 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x95 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom2][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x9a << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom3][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x27 << 1), SEEK_SET);
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0xa4 << 1), SEEK_SET);
Canon_WBCTpresets(1); // ABCT
}
break;
case 796:
imgdata.makernotes.canon.CanonColorDataVer = 3; // 1DmkIIN / 5D / 30D / 400D
imgdata.makernotes.canon.CanonColorDataSubVer = get2();
{
fseek(ifp, save1 + (0x44 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x49 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x71 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x76 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom2][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x7b << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom3][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x80 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x4e << 1), SEEK_SET);
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0x85 << 1), SEEK_SET);
Canon_WBCTpresets(0); // BCAT
fseek(ifp, save1 + (0x0c4 << 1), SEEK_SET); // offset 196 short
int bls = 0;
FORC4
bls += (imgdata.makernotes.canon.ChannelBlackLevel[c] = get2());
imgdata.makernotes.canon.AverageBlackLevel = bls / 4;
}
break;
// 1DmkIII / 1DSmkIII / 1DmkIV / 5DmkII
// 7D / 40D / 50D / 60D / 450D / 500D
// 550D / 1000D / 1100D
case 674:
case 692:
case 702:
case 1227:
case 1250:
case 1251:
case 1337:
case 1338:
case 1346:
imgdata.makernotes.canon.CanonColorDataVer = 4;
imgdata.makernotes.canon.CanonColorDataSubVer = get2();
{
fseek(ifp, save1 + (0x44 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x49 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x53 << 1), SEEK_SET);
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0xa8 << 1), SEEK_SET);
Canon_WBCTpresets(0); // BCAT
fseek(ifp, save1 + (0x0e7 << 1), SEEK_SET); // offset 231 short
int bls = 0;
FORC4
bls += (imgdata.makernotes.canon.ChannelBlackLevel[c] = get2());
imgdata.makernotes.canon.AverageBlackLevel = bls / 4;
}
if ((imgdata.makernotes.canon.CanonColorDataSubVer == 4) || (imgdata.makernotes.canon.CanonColorDataSubVer == 5))
{
fseek(ifp, save1 + (0x2b8 << 1), SEEK_SET); // offset 696 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
else if ((imgdata.makernotes.canon.CanonColorDataSubVer == 6) ||
(imgdata.makernotes.canon.CanonColorDataSubVer == 7))
{
fseek(ifp, save1 + (0x2cf << 1), SEEK_SET); // offset 719 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
else if (imgdata.makernotes.canon.CanonColorDataSubVer == 9)
{
fseek(ifp, save1 + (0x2d3 << 1), SEEK_SET); // offset 723 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
break;
case 5120:
imgdata.makernotes.canon.CanonColorDataVer = 5; // PowerSot G10, G12, G5 X, G7 X, G9 X, EOS M3, EOS M5, EOS M6
{
if ((unique_id == 0x03970000) || // G7 X Mark II
(unique_id == 0x04100000) || // G9 X Mark II
(unique_id == 0x04180000) || // G1 X Mark III
(unique_id == 0x80000394) || // EOS M5
(unique_id == 0x80000398) || // EOS M100
(unique_id == 0x80000407)) // EOS M6
{
fseek(ifp, save1 + (0x4f << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, 8, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
fseek(ifp, 8, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Other][c ^ (c >> 1)] = get2();
fseek(ifp, 8, SEEK_CUR);
Canon_WBpresets(8, 24);
fseek(ifp, 168, SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_WW][c ^ (c >> 1)] = get2();
fseek(ifp, 24, SEEK_CUR);
Canon_WBCTpresets(2); // BCADT
fseek(ifp, 6, SEEK_CUR);
}
else
{
fseek(ifp, save1 + (0x4c << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
get2();
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
get2();
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Other][c ^ (c >> 1)] = get2();
get2();
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0xba << 1), SEEK_SET);
Canon_WBCTpresets(2); // BCADT
fseek(ifp, save1 + (0x108 << 1), SEEK_SET); // offset 264 short
}
int bls = 0;
FORC4 bls += (imgdata.makernotes.canon.ChannelBlackLevel[c] = get2());
imgdata.makernotes.canon.AverageBlackLevel = bls / 4;
}
break;
case 1273:
case 1275:
imgdata.makernotes.canon.CanonColorDataVer = 6; // 600D / 1200D
imgdata.makernotes.canon.CanonColorDataSubVer = get2();
{
fseek(ifp, save1 + (0x44 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x49 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x67 << 1), SEEK_SET);
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0xbc << 1), SEEK_SET);
Canon_WBCTpresets(0); // BCAT
fseek(ifp, save1 + (0x0fb << 1), SEEK_SET); // offset 251 short
int bls = 0;
FORC4
bls += (imgdata.makernotes.canon.ChannelBlackLevel[c] = get2());
imgdata.makernotes.canon.AverageBlackLevel = bls / 4;
}
fseek(ifp, save1 + (0x1e3 << 1), SEEK_SET); // offset 483 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
break;
// 1DX / 5DmkIII / 6D / 100D / 650D / 700D / EOS M / 7DmkII / 750D / 760D
case 1312:
case 1313:
case 1316:
case 1506:
imgdata.makernotes.canon.CanonColorDataVer = 7;
imgdata.makernotes.canon.CanonColorDataSubVer = get2();
{
fseek(ifp, save1 + (0x44 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x49 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x80 << 1), SEEK_SET);
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0xd5 << 1), SEEK_SET);
Canon_WBCTpresets(0); // BCAT
fseek(ifp, save1 + (0x114 << 1), SEEK_SET); // offset 276 shorts
int bls = 0;
FORC4
bls += (imgdata.makernotes.canon.ChannelBlackLevel[c] = get2());
imgdata.makernotes.canon.AverageBlackLevel = bls / 4;
}
if (imgdata.makernotes.canon.CanonColorDataSubVer == 10)
{
fseek(ifp, save1 + (0x1fc << 1), SEEK_SET); // offset 508 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
else if (imgdata.makernotes.canon.CanonColorDataSubVer == 11)
{
fseek(ifp, save1 + (0x2dc << 1), SEEK_SET); // offset 732 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
break;
// 5DS / 5DS R / 80D / 1300D / 5D4 / 800D / 77D / 6D II / 200D
case 1560:
case 1592:
case 1353:
case 1602:
imgdata.makernotes.canon.CanonColorDataVer = 8;
imgdata.makernotes.canon.CanonColorDataSubVer = get2();
{
fseek(ifp, save1 + (0x44 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x49 << 1), SEEK_SET);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Measured][c ^ (c >> 1)] = get2();
fseek(ifp, save1 + (0x85 << 1), SEEK_SET);
Canon_WBpresets(2, 12);
fseek(ifp, save1 + (0x107 << 1), SEEK_SET);
Canon_WBCTpresets(0); // BCAT
fseek(ifp, save1 + (0x146 << 1), SEEK_SET); // offset 326 shorts
int bls = 0;
FORC4
bls += (imgdata.makernotes.canon.ChannelBlackLevel[c] = get2());
imgdata.makernotes.canon.AverageBlackLevel = bls / 4;
}
if (imgdata.makernotes.canon.CanonColorDataSubVer == 14) // 1300D
{
fseek(ifp, save1 + (0x230 << 1), SEEK_SET); // offset 560 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
else
{
fseek(ifp, save1 + (0x30e << 1), SEEK_SET); // offset 782 shorts
imgdata.makernotes.canon.NormalWhiteLevel = get2();
imgdata.makernotes.canon.SpecularWhiteLevel = get2();
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.canon.SpecularWhiteLevel;
}
break;
}
fseek(ifp, save1, SEEK_SET);
}
}
void CLASS setPentaxBodyFeatures(unsigned id)
{
imgdata.lens.makernotes.CamID = id;
switch (id)
{
case 0x12994:
case 0x12aa2:
case 0x12b1a:
case 0x12b60:
case 0x12b62:
case 0x12b7e:
case 0x12b80:
case 0x12b9c:
case 0x12b9d:
case 0x12ba2:
case 0x12c1e:
case 0x12c20:
case 0x12cd2:
case 0x12cd4:
case 0x12cfa:
case 0x12d72:
case 0x12d73:
case 0x12db8:
case 0x12dfe:
case 0x12e6c:
case 0x12e76:
case 0x12ef8:
case 0x12f52:
case 0x12f70:
case 0x12f71:
case 0x12fb6:
case 0x12fc0:
case 0x12fca:
case 0x1301a:
case 0x13024:
case 0x1309c:
case 0x13222:
case 0x1322c:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Pentax_K;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Pentax_K;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_APSC;
break;
case 0x13092:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Pentax_K;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Pentax_K;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_FF;
break;
case 0x12e08:
case 0x13010:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Pentax_645;
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_MF;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Pentax_645;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_MF;
break;
case 0x12ee4:
case 0x12f66:
case 0x12f7a:
case 0x1302e:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Pentax_Q;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Pentax_Q;
break;
default:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
return;
}
void CLASS PentaxISO(ushort c)
{
int code[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 50, 100, 200, 400, 800, 1600, 3200, 258, 259, 260, 261,
262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278};
double value[] = {50, 64, 80, 100, 125, 160, 200, 250, 320, 400, 500, 640,
800, 1000, 1250, 1600, 2000, 2500, 3200, 4000, 5000, 6400, 8000, 10000,
12800, 16000, 20000, 25600, 32000, 40000, 51200, 64000, 80000, 102400, 128000, 160000,
204800, 258000, 325000, 409600, 516000, 650000, 819200, 50, 100, 200, 400, 800,
1600, 3200, 50, 70, 100, 140, 200, 280, 400, 560, 800, 1100,
1600, 2200, 3200, 4500, 6400, 9000, 12800, 18000, 25600, 36000, 51200};
#define numel (sizeof(code) / sizeof(code[0]))
int i;
for (i = 0; i < numel; i++)
{
if (code[i] == c)
{
iso_speed = value[i];
return;
}
}
if (i == numel)
iso_speed = 65535.0f;
}
#undef numel
void CLASS PentaxLensInfo(unsigned id, unsigned len) // tag 0x0207
{
ushort iLensData = 0;
uchar *table_buf;
table_buf = (uchar *)malloc(MAX(len, 128));
fread(table_buf, len, 1, ifp);
if ((id < 0x12b9c) || (((id == 0x12b9c) || // K100D
(id == 0x12b9d) || // K110D
(id == 0x12ba2)) && // K100D Super
((!table_buf[20] || (table_buf[20] == 0xff)))))
{
iLensData = 3;
if (imgdata.lens.makernotes.LensID == -1)
imgdata.lens.makernotes.LensID = (((unsigned)table_buf[0]) << 8) + table_buf[1];
}
else
switch (len)
{
case 90: // LensInfo3
iLensData = 13;
if (imgdata.lens.makernotes.LensID == -1)
imgdata.lens.makernotes.LensID = ((unsigned)((table_buf[1] & 0x0f) + table_buf[3]) << 8) + table_buf[4];
break;
case 91: // LensInfo4
iLensData = 12;
if (imgdata.lens.makernotes.LensID == -1)
imgdata.lens.makernotes.LensID = ((unsigned)((table_buf[1] & 0x0f) + table_buf[3]) << 8) + table_buf[4];
break;
case 80: // LensInfo5
case 128:
iLensData = 15;
if (imgdata.lens.makernotes.LensID == -1)
imgdata.lens.makernotes.LensID = ((unsigned)((table_buf[1] & 0x0f) + table_buf[4]) << 8) + table_buf[5];
break;
default:
if (id >= 0x12b9c) // LensInfo2
{
iLensData = 4;
if (imgdata.lens.makernotes.LensID == -1)
imgdata.lens.makernotes.LensID = ((unsigned)((table_buf[0] & 0x0f) + table_buf[2]) << 8) + table_buf[3];
}
}
if (iLensData)
{
if (table_buf[iLensData + 9] && (fabs(imgdata.lens.makernotes.CurFocal) < 0.1f))
imgdata.lens.makernotes.CurFocal =
10 * (table_buf[iLensData + 9] >> 2) * libraw_powf64l(4, (table_buf[iLensData + 9] & 0x03) - 2);
if (table_buf[iLensData + 10] & 0xf0)
imgdata.lens.makernotes.MaxAp4CurFocal =
libraw_powf64l(2.0f, (float)((table_buf[iLensData + 10] & 0xf0) >> 4) / 4.0f);
if (table_buf[iLensData + 10] & 0x0f)
imgdata.lens.makernotes.MinAp4CurFocal =
libraw_powf64l(2.0f, (float)((table_buf[iLensData + 10] & 0x0f) + 10) / 4.0f);
if (iLensData != 12)
{
switch (table_buf[iLensData] & 0x06)
{
case 0:
imgdata.lens.makernotes.MinAp4MinFocal = 22.0f;
break;
case 2:
imgdata.lens.makernotes.MinAp4MinFocal = 32.0f;
break;
case 4:
imgdata.lens.makernotes.MinAp4MinFocal = 45.0f;
break;
case 6:
imgdata.lens.makernotes.MinAp4MinFocal = 16.0f;
break;
}
if (table_buf[iLensData] & 0x70)
imgdata.lens.makernotes.LensFStops = ((float)(((table_buf[iLensData] & 0x70) >> 4) ^ 0x07)) / 2.0f + 5.0f;
imgdata.lens.makernotes.MinFocusDistance = (float)(table_buf[iLensData + 3] & 0xf8);
imgdata.lens.makernotes.FocusRangeIndex = (float)(table_buf[iLensData + 3] & 0x07);
if ((table_buf[iLensData + 14] > 1) && (fabs(imgdata.lens.makernotes.MaxAp4CurFocal) < 0.7f))
imgdata.lens.makernotes.MaxAp4CurFocal =
libraw_powf64l(2.0f, (float)((table_buf[iLensData + 14] & 0x7f) - 1) / 32.0f);
}
else if ((id != 0x12e76) && // K-5
(table_buf[iLensData + 15] > 1) && (fabs(imgdata.lens.makernotes.MaxAp4CurFocal) < 0.7f))
{
imgdata.lens.makernotes.MaxAp4CurFocal =
libraw_powf64l(2.0f, (float)((table_buf[iLensData + 15] & 0x7f) - 1) / 32.0f);
}
}
free(table_buf);
return;
}
void CLASS setPhaseOneFeatures(unsigned id)
{
ushort i;
static const struct
{
ushort id;
char t_model[32];
} p1_unique[] = {
// Phase One section:
{1, "Hasselblad V"},
{10, "PhaseOne/Mamiya"},
{12, "Contax 645"},
{16, "Hasselblad V"},
{17, "Hasselblad V"},
{18, "Contax 645"},
{19, "PhaseOne/Mamiya"},
{20, "Hasselblad V"},
{21, "Contax 645"},
{22, "PhaseOne/Mamiya"},
{23, "Hasselblad V"},
{24, "Hasselblad H"},
{25, "PhaseOne/Mamiya"},
{32, "Contax 645"},
{34, "Hasselblad V"},
{35, "Hasselblad V"},
{36, "Hasselblad H"},
{37, "Contax 645"},
{38, "PhaseOne/Mamiya"},
{39, "Hasselblad V"},
{40, "Hasselblad H"},
{41, "Contax 645"},
{42, "PhaseOne/Mamiya"},
{44, "Hasselblad V"},
{45, "Hasselblad H"},
{46, "Contax 645"},
{47, "PhaseOne/Mamiya"},
{48, "Hasselblad V"},
{49, "Hasselblad H"},
{50, "Contax 645"},
{51, "PhaseOne/Mamiya"},
{52, "Hasselblad V"},
{53, "Hasselblad H"},
{54, "Contax 645"},
{55, "PhaseOne/Mamiya"},
{67, "Hasselblad V"},
{68, "Hasselblad H"},
{69, "Contax 645"},
{70, "PhaseOne/Mamiya"},
{71, "Hasselblad V"},
{72, "Hasselblad H"},
{73, "Contax 645"},
{74, "PhaseOne/Mamiya"},
{76, "Hasselblad V"},
{77, "Hasselblad H"},
{78, "Contax 645"},
{79, "PhaseOne/Mamiya"},
{80, "Hasselblad V"},
{81, "Hasselblad H"},
{82, "Contax 645"},
{83, "PhaseOne/Mamiya"},
{84, "Hasselblad V"},
{85, "Hasselblad H"},
{86, "Contax 645"},
{87, "PhaseOne/Mamiya"},
{99, "Hasselblad V"},
{100, "Hasselblad H"},
{101, "Contax 645"},
{102, "PhaseOne/Mamiya"},
{103, "Hasselblad V"},
{104, "Hasselblad H"},
{105, "PhaseOne/Mamiya"},
{106, "Contax 645"},
{112, "Hasselblad V"},
{113, "Hasselblad H"},
{114, "Contax 645"},
{115, "PhaseOne/Mamiya"},
{131, "Hasselblad V"},
{132, "Hasselblad H"},
{133, "Contax 645"},
{134, "PhaseOne/Mamiya"},
{135, "Hasselblad V"},
{136, "Hasselblad H"},
{137, "Contax 645"},
{138, "PhaseOne/Mamiya"},
{140, "Hasselblad V"},
{141, "Hasselblad H"},
{142, "Contax 645"},
{143, "PhaseOne/Mamiya"},
{148, "Hasselblad V"},
{149, "Hasselblad H"},
{150, "Contax 645"},
{151, "PhaseOne/Mamiya"},
{160, "A-250"},
{161, "A-260"},
{162, "A-280"},
{167, "Hasselblad V"},
{168, "Hasselblad H"},
{169, "Contax 645"},
{170, "PhaseOne/Mamiya"},
{172, "Hasselblad V"},
{173, "Hasselblad H"},
{174, "Contax 645"},
{175, "PhaseOne/Mamiya"},
{176, "Hasselblad V"},
{177, "Hasselblad H"},
{178, "Contax 645"},
{179, "PhaseOne/Mamiya"},
{180, "Hasselblad V"},
{181, "Hasselblad H"},
{182, "Contax 645"},
{183, "PhaseOne/Mamiya"},
{208, "Hasselblad V"},
{211, "PhaseOne/Mamiya"},
{448, "Phase One 645AF"},
{457, "Phase One 645DF"},
{471, "Phase One 645DF+"},
{704, "Phase One iXA"},
{705, "Phase One iXA - R"},
{706, "Phase One iXU 150"},
{707, "Phase One iXU 150 - NIR"},
{708, "Phase One iXU 180"},
{721, "Phase One iXR"},
// Leaf section:
{333, "Mamiya"},
{329, "Universal"},
{330, "Hasselblad H1/H2"},
{332, "Contax"},
{336, "AFi"},
{327, "Mamiya"},
{324, "Universal"},
{325, "Hasselblad H1/H2"},
{326, "Contax"},
{335, "AFi"},
{340, "Mamiya"},
{337, "Universal"},
{338, "Hasselblad H1/H2"},
{339, "Contax"},
{323, "Mamiya"},
{320, "Universal"},
{322, "Hasselblad H1/H2"},
{321, "Contax"},
{334, "AFi"},
{369, "Universal"},
{370, "Mamiya"},
{371, "Hasselblad H1/H2"},
{372, "Contax"},
{373, "Afi"},
};
imgdata.lens.makernotes.CamID = id;
if (id && !imgdata.lens.makernotes.body[0])
{
for (i = 0; i < sizeof p1_unique / sizeof *p1_unique; i++)
if (id == p1_unique[i].id)
{
strcpy(imgdata.lens.makernotes.body, p1_unique[i].t_model);
}
}
return;
}
void CLASS parseFujiMakernotes(unsigned tag, unsigned type)
{
switch (tag)
{
case 0x1002:
imgdata.makernotes.fuji.WB_Preset = get2();
break;
case 0x1011:
imgdata.other.FlashEC = getreal(type);
break;
case 0x1020:
imgdata.makernotes.fuji.Macro = get2();
break;
case 0x1021:
imgdata.makernotes.fuji.FocusMode = get2();
break;
case 0x1022:
imgdata.makernotes.fuji.AFMode = get2();
break;
case 0x1023:
imgdata.makernotes.fuji.FocusPixel[0] = get2();
imgdata.makernotes.fuji.FocusPixel[1] = get2();
break;
case 0x1034:
imgdata.makernotes.fuji.ExrMode = get2();
break;
case 0x1050:
imgdata.makernotes.fuji.ShutterType = get2();
break;
case 0x1400:
imgdata.makernotes.fuji.FujiDynamicRange = get2();
break;
case 0x1401:
imgdata.makernotes.fuji.FujiFilmMode = get2();
break;
case 0x1402:
imgdata.makernotes.fuji.FujiDynamicRangeSetting = get2();
break;
case 0x1403:
imgdata.makernotes.fuji.FujiDevelopmentDynamicRange = get2();
break;
case 0x140b:
imgdata.makernotes.fuji.FujiAutoDynamicRange = get2();
break;
case 0x1404:
imgdata.lens.makernotes.MinFocal = getreal(type);
break;
case 0x1405:
imgdata.lens.makernotes.MaxFocal = getreal(type);
break;
case 0x1406:
imgdata.lens.makernotes.MaxAp4MinFocal = getreal(type);
break;
case 0x1407:
imgdata.lens.makernotes.MaxAp4MaxFocal = getreal(type);
break;
case 0x1422:
imgdata.makernotes.fuji.ImageStabilization[0] = get2();
imgdata.makernotes.fuji.ImageStabilization[1] = get2();
imgdata.makernotes.fuji.ImageStabilization[2] = get2();
imgdata.shootinginfo.ImageStabilization =
(imgdata.makernotes.fuji.ImageStabilization[0] << 9) + imgdata.makernotes.fuji.ImageStabilization[1];
break;
case 0x1431:
imgdata.makernotes.fuji.Rating = get4();
break;
case 0x3820:
imgdata.makernotes.fuji.FrameRate = get2();
break;
case 0x3821:
imgdata.makernotes.fuji.FrameWidth = get2();
break;
case 0x3822:
imgdata.makernotes.fuji.FrameHeight = get2();
break;
}
return;
}
void CLASS setSonyBodyFeatures(unsigned id)
{
ushort idx;
static const struct
{
ushort scf[8];
/*
scf[0] camera id
scf[1] camera format
scf[2] camera mount: Minolta A, Sony E, fixed,
scf[3] camera type: DSLR, NEX, SLT, ILCE, ILCA, DSC
scf[4] lens mount
scf[5] tag 0x2010 group (0 if not used)
scf[6] offset of Sony ISO in 0x2010 table, 0xffff if not valid
scf[7] offset of ImageCount3 in 0x9050 table, 0xffff if not valid
*/
} SonyCamFeatures[] = {
{256, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{257, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{258, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{259, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{260, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{261, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{262, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{263, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{264, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{265, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{266, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{267, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{268, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{269, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{270, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{271, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{272, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{273, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{274, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{275, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{276, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{277, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{278, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 0, 0xffff, 0xffff},
{279, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 0, 0xffff, 0xffff},
{280, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 0, 0xffff, 0xffff},
{281, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 0, 0xffff, 0xffff},
{282, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{283, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_DSLR, 0, 0, 0xffff, 0xffff},
{284, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 0, 0xffff, 0xffff},
{285, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 0, 0xffff, 0xffff},
{286, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 2, 0x1218, 0x01bd},
{287, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 2, 0x1218, 0x01bd},
{288, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 1, 0x113e, 0x01bd},
{289, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 2, 0x1218, 0x01bd},
{290, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 2, 0x1218, 0x01bd},
{291, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 3, 0x11f4, 0x01bd},
{292, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 3, 0x11f4, 0x01bd},
{293, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 3, 0x11f4, 0x01bd},
{294, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 5, 0x1254, 0x01aa},
{295, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 5, 0x1254, 0x01aa},
{296, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 5, 0x1254, 0x01aa},
{297, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 5, 0x1254, 0xffff},
{298, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 5, 0x1258, 0xffff},
{299, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 5, 0x1254, 0x01aa},
{300, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 5, 0x1254, 0x01aa},
{301, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{302, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 5, 0x1280, 0x01aa},
{303, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_SLT, 0, 5, 0x1280, 0x01aa},
{304, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{305, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 5, 0x1280, 0x01aa},
{306, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0xffff},
{307, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_NEX, 0, 5, 0x1254, 0x01aa},
{308, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 6, 0x113c, 0xffff},
{309, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 7, 0x0344, 0xffff},
{310, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 5, 0x1258, 0xffff},
{311, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0xffff},
{312, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0xffff},
{313, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0x01aa},
{314, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{315, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{316, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{317, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 7, 0x0344, 0xffff},
{318, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0xffff},
{319, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_ILCA, 0, 7, 0x0344, 0x01a0},
{320, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{321, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{322, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{323, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{324, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{325, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{326, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{327, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{328, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{329, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{330, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{331, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{332, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{333, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{334, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{335, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{336, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{337, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{338, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{339, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0x01a0},
{340, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0xffff},
{341, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 8, 0x0346, 0xffff},
{342, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 8, 0x0346, 0xffff},
{343, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{344, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 8, 0x0346, 0xffff},
{345, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{346, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 7, 0x0344, 0x01a0},
{347, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 8, 0x0346, 0x01cb},
{348, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{349, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{350, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 8, 0x0346, 0x01cb},
{351, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{352, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{353, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_ILCA, 0, 7, 0x0344, 0x01a0},
{354, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Minolta_A, LIBRAW_SONY_ILCA, 0, 8, 0x0346, 0x01cd},
{355, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 8, 0x0346, 0xffff},
{356, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 8, 0x0346, 0xffff},
{357, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 8, 0x0346, 0x01cd},
{358, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 9, 0x0320, 0x019f},
{359, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{360, LIBRAW_FORMAT_APSC, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 8, 0x0346, 0x01cd},
{361, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{362, LIBRAW_FORMAT_FF, LIBRAW_MOUNT_Sony_E, LIBRAW_SONY_ILCE, 0, 9, 0x0320, 0x019f},
{363, 0, 0, 0, 0, 0, 0xffff, 0xffff},
{364, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 8, 0x0346, 0xffff},
{365, LIBRAW_FORMAT_1INCH, LIBRAW_MOUNT_FixedLens, LIBRAW_SONY_DSC, LIBRAW_MOUNT_FixedLens, 9, 0x0320, 0xffff},
};
imgdata.lens.makernotes.CamID = id;
if (id == 2)
{
imgdata.lens.makernotes.CameraMount = imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.makernotes.sony.SonyCameraType = LIBRAW_SONY_DSC;
imgdata.makernotes.sony.group2010 = 0;
imgdata.makernotes.sony.real_iso_offset = 0xffff;
imgdata.makernotes.sony.ImageCount3_offset = 0xffff;
return;
}
else
idx = id - 256;
if ((idx >= 0) && (idx < sizeof SonyCamFeatures / sizeof *SonyCamFeatures))
{
if (!SonyCamFeatures[idx].scf[2])
return;
imgdata.lens.makernotes.CameraFormat = SonyCamFeatures[idx].scf[1];
imgdata.lens.makernotes.CameraMount = SonyCamFeatures[idx].scf[2];
imgdata.makernotes.sony.SonyCameraType = SonyCamFeatures[idx].scf[3];
if (SonyCamFeatures[idx].scf[4])
imgdata.lens.makernotes.LensMount = SonyCamFeatures[idx].scf[4];
imgdata.makernotes.sony.group2010 = SonyCamFeatures[idx].scf[5];
imgdata.makernotes.sony.real_iso_offset = SonyCamFeatures[idx].scf[6];
imgdata.makernotes.sony.ImageCount3_offset = SonyCamFeatures[idx].scf[7];
}
char *sbstr = strstr(software, " v");
if (sbstr != NULL)
{
sbstr += 2;
imgdata.makernotes.sony.firmware = atof(sbstr);
if ((id == 306) || (id == 311))
{
if (imgdata.makernotes.sony.firmware < 1.2f)
imgdata.makernotes.sony.ImageCount3_offset = 0x01aa;
else
imgdata.makernotes.sony.ImageCount3_offset = 0x01c0;
}
else if (id == 312)
{
if (imgdata.makernotes.sony.firmware < 2.0f)
imgdata.makernotes.sony.ImageCount3_offset = 0x01aa;
else
imgdata.makernotes.sony.ImageCount3_offset = 0x01c0;
}
else if ((id == 318) || (id == 340))
{
if (imgdata.makernotes.sony.firmware < 1.2f)
imgdata.makernotes.sony.ImageCount3_offset = 0x01a0;
else
imgdata.makernotes.sony.ImageCount3_offset = 0x01b6;
}
}
}
void CLASS parseSonyLensType2(uchar a, uchar b)
{
ushort lid2;
lid2 = (((ushort)a) << 8) | ((ushort)b);
if (!lid2)
return;
if (lid2 < 0x100)
{
if ((imgdata.lens.makernotes.AdapterID != 0x4900) && (imgdata.lens.makernotes.AdapterID != 0xEF00))
{
imgdata.lens.makernotes.AdapterID = lid2;
switch (lid2)
{
case 1:
case 2:
case 3:
case 6:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Minolta_A;
break;
case 44:
case 78:
case 239:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
break;
}
}
}
else
imgdata.lens.makernotes.LensID = lid2;
if ((lid2 >= 50481) && (lid2 < 50500))
{
strcpy(imgdata.lens.makernotes.Adapter, "MC-11");
imgdata.lens.makernotes.AdapterID = 0x4900;
}
return;
}
#define strnXcat(buf, string) strncat(buf, string, LIM(sizeof(buf) - strbuflen(buf) - 1, 0, sizeof(buf)))
void CLASS parseSonyLensFeatures(uchar a, uchar b)
{
ushort features;
features = (((ushort)a) << 8) | ((ushort)b);
if ((imgdata.lens.makernotes.LensMount == LIBRAW_MOUNT_Canon_EF) ||
(imgdata.lens.makernotes.LensMount != LIBRAW_MOUNT_Sigma_X3F) || !features)
return;
imgdata.lens.makernotes.LensFeatures_pre[0] = 0;
imgdata.lens.makernotes.LensFeatures_suf[0] = 0;
if ((features & 0x0200) && (features & 0x0100))
strcpy(imgdata.lens.makernotes.LensFeatures_pre, "E");
else if (features & 0x0200)
strcpy(imgdata.lens.makernotes.LensFeatures_pre, "FE");
else if (features & 0x0100)
strcpy(imgdata.lens.makernotes.LensFeatures_pre, "DT");
if (!imgdata.lens.makernotes.LensFormat && !imgdata.lens.makernotes.LensMount)
{
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_FF;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Minolta_A;
if ((features & 0x0200) && (features & 0x0100))
{
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_APSC;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Sony_E;
}
else if (features & 0x0200)
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Sony_E;
}
else if (features & 0x0100)
{
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_APSC;
}
}
if (features & 0x4000)
strnXcat(imgdata.lens.makernotes.LensFeatures_pre, " PZ");
if (features & 0x0008)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " G");
else if (features & 0x0004)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " ZA");
if ((features & 0x0020) && (features & 0x0040))
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " Macro");
else if (features & 0x0020)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " STF");
else if (features & 0x0040)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " Reflex");
else if (features & 0x0080)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " Fisheye");
if (features & 0x0001)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " SSM");
else if (features & 0x0002)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " SAM");
if (features & 0x8000)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " OSS");
if (features & 0x2000)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " LE");
if (features & 0x0800)
strnXcat(imgdata.lens.makernotes.LensFeatures_suf, " II");
if (imgdata.lens.makernotes.LensFeatures_suf[0] == ' ')
memmove(imgdata.lens.makernotes.LensFeatures_suf, imgdata.lens.makernotes.LensFeatures_suf + 1,
strbuflen(imgdata.lens.makernotes.LensFeatures_suf) - 1);
return;
}
#undef strnXcat
void CLASS process_Sony_0x0116(uchar *buf, ushort len, unsigned id)
{
short bufx;
if (((id == 257) || (id == 262) || (id == 269) || (id == 270)) && (len >= 2))
bufx = buf[1];
else if ((id >= 273) && (len >= 3))
bufx = buf[2];
else
return;
imgdata.other.BatteryTemperature = (float)(bufx - 32) / 1.8f;
}
void CLASS process_Sony_0x2010(uchar *buf, ushort len)
{
if ((!imgdata.makernotes.sony.group2010) || (imgdata.makernotes.sony.real_iso_offset == 0xffff) ||
(len < (imgdata.makernotes.sony.real_iso_offset + 2)))
return;
if (imgdata.other.real_ISO < 0.1f)
{
uchar s[2];
s[0] = SonySubstitution[buf[imgdata.makernotes.sony.real_iso_offset]];
s[1] = SonySubstitution[buf[imgdata.makernotes.sony.real_iso_offset + 1]];
imgdata.other.real_ISO = 100.0f * libraw_powf64l(2.0f, (16 - ((float)sget2(s)) / 256.0f));
}
}
void CLASS process_Sony_0x9050(uchar *buf, ushort len, unsigned id)
{
ushort lid;
uchar s[4];
int c;
if ((imgdata.lens.makernotes.CameraMount != LIBRAW_MOUNT_Sony_E) &&
(imgdata.lens.makernotes.CameraMount != LIBRAW_MOUNT_FixedLens))
{
if (len < 2)
return;
if (buf[0])
imgdata.lens.makernotes.MaxAp4CurFocal =
my_roundf(libraw_powf64l(2.0f, ((float)SonySubstitution[buf[0]] / 8.0 - 1.06f) / 2.0f) * 10.0f) / 10.0f;
if (buf[1])
imgdata.lens.makernotes.MinAp4CurFocal =
my_roundf(libraw_powf64l(2.0f, ((float)SonySubstitution[buf[1]] / 8.0 - 1.06f) / 2.0f) * 10.0f) / 10.0f;
}
if (imgdata.lens.makernotes.CameraMount != LIBRAW_MOUNT_FixedLens)
{
if (len <= 0x106)
return;
if (buf[0x3d] | buf[0x3c])
{
lid = SonySubstitution[buf[0x3d]] << 8 | SonySubstitution[buf[0x3c]];
imgdata.lens.makernotes.CurAp = libraw_powf64l(2.0f, ((float)lid / 256.0f - 16.0f) / 2.0f);
}
if (buf[0x105] && (imgdata.lens.makernotes.LensMount != LIBRAW_MOUNT_Canon_EF) &&
(imgdata.lens.makernotes.LensMount != LIBRAW_MOUNT_Sigma_X3F))
imgdata.lens.makernotes.LensMount = SonySubstitution[buf[0x105]];
if (buf[0x106])
imgdata.lens.makernotes.LensFormat = SonySubstitution[buf[0x106]];
}
if (imgdata.lens.makernotes.CameraMount == LIBRAW_MOUNT_Sony_E)
{
if (len <= 0x108)
return;
parseSonyLensType2(SonySubstitution[buf[0x0108]], // LensType2 - Sony lens ids
SonySubstitution[buf[0x0107]]);
}
if (len <= 0x10a)
return;
if ((imgdata.lens.makernotes.LensID == -1) && (imgdata.lens.makernotes.CameraMount == LIBRAW_MOUNT_Minolta_A) &&
(buf[0x010a] | buf[0x0109]))
{
imgdata.lens.makernotes.LensID = // LensType - Minolta/Sony lens ids
SonySubstitution[buf[0x010a]] << 8 | SonySubstitution[buf[0x0109]];
if ((imgdata.lens.makernotes.LensID > 0x4900) && (imgdata.lens.makernotes.LensID <= 0x5900))
{
imgdata.lens.makernotes.AdapterID = 0x4900;
imgdata.lens.makernotes.LensID -= imgdata.lens.makernotes.AdapterID;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Sigma_X3F;
strcpy(imgdata.lens.makernotes.Adapter, "MC-11");
}
else if ((imgdata.lens.makernotes.LensID > 0xEF00) && (imgdata.lens.makernotes.LensID < 0xFFFF) &&
(imgdata.lens.makernotes.LensID != 0xFF00))
{
imgdata.lens.makernotes.AdapterID = 0xEF00;
imgdata.lens.makernotes.LensID -= imgdata.lens.makernotes.AdapterID;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
}
}
if ((id >= 286) && (id <= 293))
{
if (len <= 0x116)
return;
// "SLT-A65", "SLT-A77", "NEX-7", "NEX-VG20E",
// "SLT-A37", "SLT-A57", "NEX-F3", "Lunar"
parseSonyLensFeatures(SonySubstitution[buf[0x115]], SonySubstitution[buf[0x116]]);
}
else if (imgdata.lens.makernotes.CameraMount != LIBRAW_MOUNT_FixedLens)
{
if (len <= 0x117)
return;
parseSonyLensFeatures(SonySubstitution[buf[0x116]], SonySubstitution[buf[0x117]]);
}
if ((id == 347) || (id == 350) || (id == 354) || (id == 357) || (id == 358) || (id == 360) || (id == 362))
{
if (len <= 0x8d)
return;
unsigned long long b88 = SonySubstitution[buf[0x88]];
unsigned long long b89 = SonySubstitution[buf[0x89]];
unsigned long long b8a = SonySubstitution[buf[0x8a]];
unsigned long long b8b = SonySubstitution[buf[0x8b]];
unsigned long long b8c = SonySubstitution[buf[0x8c]];
unsigned long long b8d = SonySubstitution[buf[0x8d]];
sprintf(imgdata.shootinginfo.InternalBodySerial, "%06llx",
(b88 << 40) + (b89 << 32) + (b8a << 24) + (b8b << 16) + (b8c << 8) + b8d);
}
else if (imgdata.lens.makernotes.CameraMount == LIBRAW_MOUNT_Minolta_A)
{
if (len <= 0xf4)
return;
unsigned long long bf0 = SonySubstitution[buf[0xf0]];
unsigned long long bf1 = SonySubstitution[buf[0xf1]];
unsigned long long bf2 = SonySubstitution[buf[0xf2]];
unsigned long long bf3 = SonySubstitution[buf[0xf3]];
unsigned long long bf4 = SonySubstitution[buf[0xf4]];
sprintf(imgdata.shootinginfo.InternalBodySerial, "%05llx",
(bf0 << 32) + (bf1 << 24) + (bf2 << 16) + (bf3 << 8) + bf4);
}
else if ((imgdata.lens.makernotes.CameraMount == LIBRAW_MOUNT_Sony_E) && (id != 288) && (id != 289) && (id != 290))
{
if (len <= 0x7f)
return;
unsigned b7c = SonySubstitution[buf[0x7c]];
unsigned b7d = SonySubstitution[buf[0x7d]];
unsigned b7e = SonySubstitution[buf[0x7e]];
unsigned b7f = SonySubstitution[buf[0x7f]];
sprintf(imgdata.shootinginfo.InternalBodySerial, "%04x", (b7c << 24) + (b7d << 16) + (b7e << 8) + b7f);
}
if ((imgdata.makernotes.sony.ImageCount3_offset != 0xffff) &&
(len >= (imgdata.makernotes.sony.ImageCount3_offset + 4)))
{
FORC4 s[c] = SonySubstitution[buf[imgdata.makernotes.sony.ImageCount3_offset + c]];
imgdata.makernotes.sony.ImageCount3 = sget4(s);
}
if (id == 362)
{
for (c = 0; c < 6; c++)
{
imgdata.makernotes.sony.TimeStamp[c] = SonySubstitution[buf[0x0066 + c]];
}
}
return;
}
void CLASS process_Sony_0x9400(uchar *buf, ushort len, unsigned id)
{
uchar s[4];
int c;
short bufx = buf[0];
if (((bufx == 0x23) || (bufx == 0x24) || (bufx == 0x26)) && (len >= 0x1f))
{ // 0x9400 'c' version
if ((id == 358) || (id == 362) || (id == 365))
{
imgdata.makernotes.sony.ShotNumberSincePowerUp = SonySubstitution[buf[0x0a]];
}
else
{
FORC4 s[c] = SonySubstitution[buf[0x0a + c]];
imgdata.makernotes.sony.ShotNumberSincePowerUp = sget4(s);
}
imgdata.makernotes.sony.Sony0x9400_version = 0xc;
imgdata.makernotes.sony.Sony0x9400_ReleaseMode2 = SonySubstitution[buf[0x09]];
FORC4 s[c] = SonySubstitution[buf[0x12 + c]];
imgdata.makernotes.sony.Sony0x9400_SequenceImageNumber = sget4(s);
imgdata.makernotes.sony.Sony0x9400_SequenceLength1 = SonySubstitution[buf[0x16]]; // shots
FORC4 s[c] = SonySubstitution[buf[0x1a + c]];
imgdata.makernotes.sony.Sony0x9400_SequenceFileNumber = sget4(s);
imgdata.makernotes.sony.Sony0x9400_SequenceLength2 = SonySubstitution[buf[0x1e]]; // files
}
else if ((bufx == 0x0c) && (len >= 0x1f))
{ // 0x9400 'b' version
imgdata.makernotes.sony.Sony0x9400_version = 0xb;
FORC4 s[c] = SonySubstitution[buf[0x08 + c]];
imgdata.makernotes.sony.Sony0x9400_SequenceImageNumber = sget4(s);
FORC4 s[c] = SonySubstitution[buf[0x0c + c]];
imgdata.makernotes.sony.Sony0x9400_SequenceFileNumber = sget4(s);
imgdata.makernotes.sony.Sony0x9400_ReleaseMode2 = SonySubstitution[buf[0x10]];
imgdata.makernotes.sony.Sony0x9400_SequenceLength1 = SonySubstitution[buf[0x1e]];
}
else if ((bufx == 0x0a) && (len >= 0x23))
{ // 0x9400 'a' version
imgdata.makernotes.sony.Sony0x9400_version = 0xa;
FORC4 s[c] = SonySubstitution[buf[0x08 + c]];
imgdata.makernotes.sony.Sony0x9400_SequenceImageNumber = sget4(s);
FORC4 s[c] = SonySubstitution[buf[0x0c + c]];
imgdata.makernotes.sony.Sony0x9400_SequenceFileNumber = sget4(s);
imgdata.makernotes.sony.Sony0x9400_ReleaseMode2 = SonySubstitution[buf[0x10]];
imgdata.makernotes.sony.Sony0x9400_SequenceLength1 = SonySubstitution[buf[0x22]];
}
else
return;
}
void CLASS process_Sony_0x9402(uchar *buf, ushort len)
{
if ((imgdata.makernotes.sony.SonyCameraType == LIBRAW_SONY_SLT) ||
(imgdata.makernotes.sony.SonyCameraType == LIBRAW_SONY_ILCA))
return;
if (len < 5)
return;
short bufx = buf[0x00];
if ((bufx == 0x05) || (bufx == 0xff) || (buf[0x02] != 0xff))
return;
imgdata.other.AmbientTemperature = (float)((short)SonySubstitution[buf[0x04]]);
return;
}
void CLASS process_Sony_0x9403(uchar *buf, ushort len)
{
if (len < 6)
return;
short bufx = SonySubstitution[buf[4]];
if ((bufx == 0x00) || (bufx == 0x94))
return;
imgdata.other.SensorTemperature = (float)((short)SonySubstitution[buf[5]]);
return;
}
void CLASS process_Sony_0x9406(uchar *buf, ushort len)
{
if (len < 6)
return;
short bufx = buf[0];
if ((bufx != 0x01) && (bufx != 0x08) && (bufx != 0x1b))
return;
bufx = buf[2];
if ((bufx != 0x08) && (bufx != 0x1b))
return;
imgdata.other.BatteryTemperature = (float)(SonySubstitution[buf[5]] - 32) / 1.8f;
return;
}
void CLASS process_Sony_0x940c(uchar *buf, ushort len)
{
if ((imgdata.makernotes.sony.SonyCameraType != LIBRAW_SONY_ILCE) &&
(imgdata.makernotes.sony.SonyCameraType != LIBRAW_SONY_NEX))
return;
if (len <= 0x000a)
return;
ushort lid2;
if ((imgdata.lens.makernotes.LensMount != LIBRAW_MOUNT_Canon_EF) &&
(imgdata.lens.makernotes.LensMount != LIBRAW_MOUNT_Sigma_X3F))
{
switch (SonySubstitution[buf[0x0008]])
{
case 1:
case 5:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Minolta_A;
break;
case 4:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Sony_E;
break;
}
}
lid2 = (((ushort)SonySubstitution[buf[0x000a]]) << 8) | ((ushort)SonySubstitution[buf[0x0009]]);
if ((lid2 > 0) && (lid2 < 32784))
parseSonyLensType2(SonySubstitution[buf[0x000a]], // LensType2 - Sony lens ids
SonySubstitution[buf[0x0009]]);
return;
}
void CLASS process_Sony_0x940e(uchar *buf, ushort len, unsigned id)
{
if (((id == 286) || (id == 287) || (id == 294)) && (len >= 0x017e))
{
imgdata.makernotes.sony.AFMicroAdjValue = SonySubstitution[buf[0x017d]];
}
else if ((imgdata.makernotes.sony.SonyCameraType == LIBRAW_SONY_ILCA) && (len >= 0x0051))
{
imgdata.makernotes.sony.AFMicroAdjValue = SonySubstitution[buf[0x0050]];
}
else
return;
if (imgdata.makernotes.sony.AFMicroAdjValue != 0)
imgdata.makernotes.sony.AFMicroAdjOn = 1;
}
void CLASS parseSonyMakernotes(unsigned tag, unsigned type, unsigned len, unsigned dng_writer, uchar *&table_buf_0x0116,
ushort &table_buf_0x0116_len, uchar *&table_buf_0x2010, ushort &table_buf_0x2010_len,
uchar *&table_buf_0x9050, ushort &table_buf_0x9050_len, uchar *&table_buf_0x9400,
ushort &table_buf_0x9400_len, uchar *&table_buf_0x9402, ushort &table_buf_0x9402_len,
uchar *&table_buf_0x9403, ushort &table_buf_0x9403_len, uchar *&table_buf_0x9406,
ushort &table_buf_0x9406_len, uchar *&table_buf_0x940c, ushort &table_buf_0x940c_len,
uchar *&table_buf_0x940e, ushort &table_buf_0x940e_len)
{
ushort lid;
uchar *table_buf;
if (tag == 0xb001) // Sony ModelID
{
unique_id = get2();
setSonyBodyFeatures(unique_id);
if (table_buf_0x0116_len)
{
process_Sony_0x0116(table_buf_0x0116, table_buf_0x0116_len, unique_id);
free(table_buf_0x0116);
table_buf_0x0116_len = 0;
}
if (table_buf_0x2010_len)
{
process_Sony_0x2010(table_buf_0x2010, table_buf_0x2010_len);
free(table_buf_0x2010);
table_buf_0x2010_len = 0;
}
if (table_buf_0x9050_len)
{
process_Sony_0x9050(table_buf_0x9050, table_buf_0x9050_len, unique_id);
free(table_buf_0x9050);
table_buf_0x9050_len = 0;
}
if (table_buf_0x9400_len)
{
process_Sony_0x9400(table_buf_0x9400, table_buf_0x9400_len, unique_id);
free(table_buf_0x9400);
table_buf_0x9400_len = 0;
}
if (table_buf_0x9402_len)
{
process_Sony_0x9402(table_buf_0x9402, table_buf_0x9402_len);
free(table_buf_0x9402);
table_buf_0x9402_len = 0;
}
if (table_buf_0x9403_len)
{
process_Sony_0x9403(table_buf_0x9403, table_buf_0x9403_len);
free(table_buf_0x9403);
table_buf_0x9403_len = 0;
}
if (table_buf_0x9406_len)
{
process_Sony_0x9406(table_buf_0x9406, table_buf_0x9406_len);
free(table_buf_0x9406);
table_buf_0x9406_len = 0;
}
if (table_buf_0x940c_len)
{
process_Sony_0x940c(table_buf_0x940c, table_buf_0x940c_len);
free(table_buf_0x940c);
table_buf_0x940c_len = 0;
}
if (table_buf_0x940e_len)
{
process_Sony_0x940e(table_buf_0x940e, table_buf_0x940e_len, unique_id);
free(table_buf_0x940e);
table_buf_0x940e_len = 0;
}
}
else if ((tag == 0x0010) && // CameraInfo
strncasecmp(model, "DSLR-A100", 9) && strncasecmp(model, "NEX-5C", 6) && !strncasecmp(make, "SONY", 4) &&
((len == 368) || // a700
(len == 5478) || // a850, a900
(len == 5506) || // a200, a300, a350
(len == 6118) || // a230, a290, a330, a380, a390
// a450, a500, a550, a560, a580
// a33, a35, a55
// NEX3, NEX5, NEX5C, NEXC3, VG10E
(len == 15360)))
{
table_buf = (uchar *)malloc(len);
fread(table_buf, len, 1, ifp);
if (memcmp(table_buf, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) &&
memcmp(table_buf, "\x00\x00\x00\x00\x00\x00\x00\x00", 8))
{
switch (len)
{
case 368:
case 5478:
// a700, a850, a900: CameraInfo
if ((!dng_writer) ||
(saneSonyCameraInfo(table_buf[0], table_buf[3], table_buf[2], table_buf[5], table_buf[4], table_buf[7])))
{
if (table_buf[0] | table_buf[3])
imgdata.lens.makernotes.MinFocal = bcd2dec(table_buf[0]) * 100 + bcd2dec(table_buf[3]);
if (table_buf[2] | table_buf[5])
imgdata.lens.makernotes.MaxFocal = bcd2dec(table_buf[2]) * 100 + bcd2dec(table_buf[5]);
if (table_buf[4])
imgdata.lens.makernotes.MaxAp4MinFocal = bcd2dec(table_buf[4]) / 10.0f;
if (table_buf[4])
imgdata.lens.makernotes.MaxAp4MaxFocal = bcd2dec(table_buf[7]) / 10.0f;
parseSonyLensFeatures(table_buf[1], table_buf[6]);
if (len == 5478)
{
imgdata.makernotes.sony.AFMicroAdjValue = table_buf[304] - 20;
imgdata.makernotes.sony.AFMicroAdjOn = (((table_buf[305] & 0x80) == 0x80) ? 1 : 0);
imgdata.makernotes.sony.AFMicroAdjRegisteredLenses = table_buf[305] & 0x7f;
}
}
break;
default:
// CameraInfo2 & 3
if ((!dng_writer) ||
(saneSonyCameraInfo(table_buf[1], table_buf[2], table_buf[3], table_buf[4], table_buf[5], table_buf[6])))
{
if (table_buf[1] | table_buf[2])
imgdata.lens.makernotes.MinFocal = bcd2dec(table_buf[1]) * 100 + bcd2dec(table_buf[2]);
if (table_buf[3] | table_buf[4])
imgdata.lens.makernotes.MaxFocal = bcd2dec(table_buf[3]) * 100 + bcd2dec(table_buf[4]);
if (table_buf[5])
imgdata.lens.makernotes.MaxAp4MinFocal = bcd2dec(table_buf[5]) / 10.0f;
if (table_buf[6])
imgdata.lens.makernotes.MaxAp4MaxFocal = bcd2dec(table_buf[6]) / 10.0f;
parseSonyLensFeatures(table_buf[0], table_buf[7]);
}
}
}
free(table_buf);
}
else if ((!dng_writer) && (tag == 0x0020) && // WBInfoA100, needs 0xb028 processing
!strncasecmp(model, "DSLR-A100", 9))
{
fseek(ifp, 0x49dc, SEEK_CUR);
stmread(imgdata.shootinginfo.InternalBodySerial, 12, ifp);
}
else if (tag == 0x0104)
{
imgdata.other.FlashEC = getreal(type);
}
else if (tag == 0x0105) // Teleconverter
{
imgdata.lens.makernotes.TeleconverterID = get2();
}
else if (tag == 0x0114 && len < 256000) // CameraSettings
{
table_buf = (uchar *)malloc(len);
fread(table_buf, len, 1, ifp);
switch (len)
{
case 280:
case 364:
case 332:
// CameraSettings and CameraSettings2 are big endian
if (table_buf[2] | table_buf[3])
{
lid = (((ushort)table_buf[2]) << 8) | ((ushort)table_buf[3]);
imgdata.lens.makernotes.CurAp = libraw_powf64l(2.0f, ((float)lid / 8.0f - 1.0f) / 2.0f);
}
break;
case 1536:
case 2048:
// CameraSettings3 are little endian
parseSonyLensType2(table_buf[1016], table_buf[1015]);
if (imgdata.lens.makernotes.LensMount != LIBRAW_MOUNT_Canon_EF)
{
switch (table_buf[153])
{
case 16:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Minolta_A;
break;
case 17:
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Sony_E;
break;
}
}
break;
}
free(table_buf);
}
else if ((tag == 0x3000) && (len < 256000))
{
uchar *table_buf_0x3000;
table_buf_0x3000 = (uchar *)malloc(len);
fread(table_buf_0x3000, len, 1, ifp);
for (int i = 0; i < 20; i++)
imgdata.makernotes.sony.SonyDateTime[i] = table_buf_0x3000[6 + i];
}
else if (tag == 0x0116 && len < 256000)
{
table_buf_0x0116 = (uchar *)malloc(len);
table_buf_0x0116_len = len;
fread(table_buf_0x0116, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x0116(table_buf_0x0116, table_buf_0x0116_len, imgdata.lens.makernotes.CamID);
free(table_buf_0x0116);
table_buf_0x0116_len = 0;
}
}
else if (tag == 0x2010 && len < 256000)
{
table_buf_0x2010 = (uchar *)malloc(len);
table_buf_0x2010_len = len;
fread(table_buf_0x2010, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x2010(table_buf_0x2010, table_buf_0x2010_len);
free(table_buf_0x2010);
table_buf_0x2010_len = 0;
}
}
else if (tag == 0x201a)
{
imgdata.makernotes.sony.ElectronicFrontCurtainShutter = get4();
}
else if (tag == 0x201b)
{
uchar uc;
fread(&uc, 1, 1, ifp);
imgdata.shootinginfo.FocusMode = (short)uc;
}
else if (tag == 0x202c)
{
imgdata.makernotes.sony.MeteringMode2 = get2();
}
else if (tag == 0x9050 && len < 256000) // little endian
{
table_buf_0x9050 = (uchar *)malloc(len);
table_buf_0x9050_len = len;
fread(table_buf_0x9050, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x9050(table_buf_0x9050, table_buf_0x9050_len, imgdata.lens.makernotes.CamID);
free(table_buf_0x9050);
table_buf_0x9050_len = 0;
}
}
else if (tag == 0x9400 && len < 256000)
{
table_buf_0x9400 = (uchar *)malloc(len);
table_buf_0x9400_len = len;
fread(table_buf_0x9400, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x9400(table_buf_0x9400, table_buf_0x9400_len, unique_id);
free(table_buf_0x9400);
table_buf_0x9400_len = 0;
}
}
else if (tag == 0x9402 && len < 256000)
{
table_buf_0x9402 = (uchar *)malloc(len);
table_buf_0x9402_len = len;
fread(table_buf_0x9402, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x9402(table_buf_0x9402, table_buf_0x9402_len);
free(table_buf_0x9402);
table_buf_0x9402_len = 0;
}
}
else if (tag == 0x9403 && len < 256000)
{
table_buf_0x9403 = (uchar *)malloc(len);
table_buf_0x9403_len = len;
fread(table_buf_0x9403, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x9403(table_buf_0x9403, table_buf_0x9403_len);
free(table_buf_0x9403);
table_buf_0x9403_len = 0;
}
}
else if ((tag == 0x9405) && (len < 256000) && (len > 0x64))
{
uchar *table_buf_0x9405;
table_buf_0x9405 = (uchar *)malloc(len);
fread(table_buf_0x9405, len, 1, ifp);
uchar bufx = table_buf_0x9405[0x0];
if (imgdata.other.real_ISO < 0.1f)
{
if ((bufx == 0x25) || (bufx == 0x3a) || (bufx == 0x76) || (bufx == 0x7e) || (bufx == 0x8b) || (bufx == 0x9a) ||
(bufx == 0xb3) || (bufx == 0xe1))
{
uchar s[2];
s[0] = SonySubstitution[table_buf_0x9405[0x04]];
s[1] = SonySubstitution[table_buf_0x9405[0x05]];
imgdata.other.real_ISO = 100.0f * libraw_powf64l(2.0f, (16 - ((float)sget2(s)) / 256.0f));
}
}
free(table_buf_0x9405);
}
else if (tag == 0x9406 && len < 256000)
{
table_buf_0x9406 = (uchar *)malloc(len);
table_buf_0x9406_len = len;
fread(table_buf_0x9406, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x9406(table_buf_0x9406, table_buf_0x9406_len);
free(table_buf_0x9406);
table_buf_0x9406_len = 0;
}
}
else if (tag == 0x940c && len < 256000)
{
table_buf_0x940c = (uchar *)malloc(len);
table_buf_0x940c_len = len;
fread(table_buf_0x940c, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x940c(table_buf_0x940c, table_buf_0x940c_len);
free(table_buf_0x940c);
table_buf_0x940c_len = 0;
}
}
else if (tag == 0x940e && len < 256000)
{
table_buf_0x940e = (uchar *)malloc(len);
table_buf_0x940e_len = len;
fread(table_buf_0x940e, len, 1, ifp);
if (imgdata.lens.makernotes.CamID)
{
process_Sony_0x940e(table_buf_0x940e, table_buf_0x940e_len, imgdata.lens.makernotes.CamID);
free(table_buf_0x940e);
table_buf_0x940e_len = 0;
}
}
else if (((tag == 0xb027) || (tag == 0x010c)) && (imgdata.lens.makernotes.LensID == -1))
{
imgdata.lens.makernotes.LensID = get4();
if ((imgdata.lens.makernotes.LensID > 0x4900) && (imgdata.lens.makernotes.LensID <= 0x5900))
{
imgdata.lens.makernotes.AdapterID = 0x4900;
imgdata.lens.makernotes.LensID -= imgdata.lens.makernotes.AdapterID;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Sigma_X3F;
strcpy(imgdata.lens.makernotes.Adapter, "MC-11");
}
else if ((imgdata.lens.makernotes.LensID > 0xEF00) && (imgdata.lens.makernotes.LensID < 0xFFFF) &&
(imgdata.lens.makernotes.LensID != 0xFF00))
{
imgdata.lens.makernotes.AdapterID = 0xEF00;
imgdata.lens.makernotes.LensID -= imgdata.lens.makernotes.AdapterID;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Canon_EF;
}
if (tag == 0x010c)
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Minolta_A;
}
else if (tag == 0xb02a && len < 256000) // Sony LensSpec
{
table_buf = (uchar *)malloc(len);
fread(table_buf, len, 1, ifp);
if ((!dng_writer) ||
(saneSonyCameraInfo(table_buf[1], table_buf[2], table_buf[3], table_buf[4], table_buf[5], table_buf[6])))
{
if (table_buf[1] | table_buf[2])
imgdata.lens.makernotes.MinFocal = bcd2dec(table_buf[1]) * 100 + bcd2dec(table_buf[2]);
if (table_buf[3] | table_buf[4])
imgdata.lens.makernotes.MaxFocal = bcd2dec(table_buf[3]) * 100 + bcd2dec(table_buf[4]);
if (table_buf[5])
imgdata.lens.makernotes.MaxAp4MinFocal = bcd2dec(table_buf[5]) / 10.0f;
if (table_buf[6])
imgdata.lens.makernotes.MaxAp4MaxFocal = bcd2dec(table_buf[6]) / 10.0f;
parseSonyLensFeatures(table_buf[0], table_buf[7]);
}
free(table_buf);
}
else if ((tag == 0xb02b) && !imgdata.sizes.raw_crop.cwidth && (len == 2))
{
imgdata.sizes.raw_crop.cheight = get4();
imgdata.sizes.raw_crop.cwidth = get4();
}
}
void CLASS parse_makernote_0xc634(int base, int uptag, unsigned dng_writer)
{
unsigned ver97 = 0, offset = 0, entries, tag, type, len, save, c;
unsigned i;
uchar NikonKey, ci, cj, ck;
unsigned serial = 0;
unsigned custom_serial = 0;
unsigned NikonLensDataVersion = 0;
unsigned lenNikonLensData = 0;
unsigned NikonFlashInfoVersion = 0;
uchar *CanonCameraInfo;
unsigned lenCanonCameraInfo = 0;
unsigned typeCanonCameraInfo = 0;
uchar *table_buf;
uchar *table_buf_0x0116;
ushort table_buf_0x0116_len = 0;
uchar *table_buf_0x2010;
ushort table_buf_0x2010_len = 0;
uchar *table_buf_0x9050;
ushort table_buf_0x9050_len = 0;
uchar *table_buf_0x9400;
ushort table_buf_0x9400_len = 0;
uchar *table_buf_0x9402;
ushort table_buf_0x9402_len = 0;
uchar *table_buf_0x9403;
ushort table_buf_0x9403_len = 0;
uchar *table_buf_0x9406;
ushort table_buf_0x9406_len = 0;
uchar *table_buf_0x940c;
ushort table_buf_0x940c_len = 0;
uchar *table_buf_0x940e;
ushort table_buf_0x940e_len = 0;
short morder, sorder = order;
char buf[10];
INT64 fsize = ifp->size();
fread(buf, 1, 10, ifp);
/*
printf("===>>buf: 0x");
for (int i = 0; i < sizeof buf; i ++) {
printf("%02x", buf[i]);
}
putchar('\n');
*/
if (!strcmp(buf, "Nikon"))
{
base = ftell(ifp);
order = get2();
if (get2() != 42)
goto quit;
offset = get4();
fseek(ifp, offset - 8, SEEK_CUR);
}
else if (!strcmp(buf, "OLYMPUS") || !strcmp(buf, "PENTAX ") ||
(!strncmp(make, "SAMSUNG", 7) && (dng_writer == CameraDNG)))
{
base = ftell(ifp) - 10;
fseek(ifp, -2, SEEK_CUR);
order = get2();
if (buf[0] == 'O')
get2();
}
else if (!strncmp(buf, "SONY", 4) || !strcmp(buf, "Panasonic"))
{
goto nf;
}
else if (!strncmp(buf, "FUJIFILM", 8))
{
base = ftell(ifp) - 10;
nf:
order = 0x4949;
fseek(ifp, 2, SEEK_CUR);
}
else if (!strcmp(buf, "OLYMP") || !strcmp(buf, "LEICA") || !strcmp(buf, "Ricoh") || !strcmp(buf, "EPSON"))
fseek(ifp, -2, SEEK_CUR);
else if (!strcmp(buf, "AOC") || !strcmp(buf, "QVC"))
fseek(ifp, -4, SEEK_CUR);
else
{
fseek(ifp, -10, SEEK_CUR);
if ((!strncmp(make, "SAMSUNG", 7) && (dng_writer == AdobeDNG)))
base = ftell(ifp);
}
entries = get2();
if (entries > 1000)
return;
morder = order;
while (entries--)
{
order = morder;
tiff_get(base, &tag, &type, &len, &save);
INT64 pos = ifp->tell();
if (len > 8 && pos + len > 2 * fsize)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue;
}
tag |= uptag << 16;
if (len > 100 * 1024 * 1024)
goto next; // 100Mb tag? No!
if (!strncmp(make, "Canon", 5))
{
if (tag == 0x000d && len < 256000) // camera info
{
if (type != 4)
{
CanonCameraInfo = (uchar *)malloc(MAX(16, len));
fread(CanonCameraInfo, len, 1, ifp);
}
else
{
CanonCameraInfo = (uchar *)malloc(MAX(16, len * 4));
fread(CanonCameraInfo, len, 4, ifp);
}
lenCanonCameraInfo = len;
typeCanonCameraInfo = type;
}
else if (tag == 0x10) // Canon ModelID
{
unique_id = get4();
unique_id = setCanonBodyFeatures(unique_id);
if (lenCanonCameraInfo)
{
processCanonCameraInfo(unique_id, CanonCameraInfo, lenCanonCameraInfo, typeCanonCameraInfo);
free(CanonCameraInfo);
CanonCameraInfo = 0;
lenCanonCameraInfo = 0;
}
}
else
parseCanonMakernotes(tag, type, len);
}
else if (!strncmp(make, "FUJI", 4))
parseFujiMakernotes(tag, type);
else if (!strncasecmp(make, "LEICA", 5))
{
if ((tag == 0x0320) && (type == 9) && (len == 1) && !strncasecmp(make, "Leica Camera AG", 15) &&
!strncmp(buf, "LEICA", 5) && (buf[5] == 0) && (buf[6] == 0) && (buf[7] == 0))
imgdata.other.CameraTemperature = getreal(type);
if (tag == 0x34003402)
imgdata.other.CameraTemperature = getreal(type);
if (((tag == 0x035e) || (tag == 0x035f)) && (type == 10) && (len == 9))
{
int ind = tag == 0x035e ? 0 : 1;
for (int j = 0; j < 3; j++)
FORCC imgdata.color.dng_color[ind].forwardmatrix[j][c] = getreal(type);
imgdata.color.dng_color[ind].parsedfields |= LIBRAW_DNGFM_FORWARDMATRIX;
}
if ((tag == 0x0303) && (type != 4))
{
stmread(imgdata.lens.makernotes.Lens, len, ifp);
}
if ((tag == 0x3405) || (tag == 0x0310) || (tag == 0x34003405))
{
imgdata.lens.makernotes.LensID = get4();
imgdata.lens.makernotes.LensID =
((imgdata.lens.makernotes.LensID >> 2) << 8) | (imgdata.lens.makernotes.LensID & 0x3);
if (imgdata.lens.makernotes.LensID != -1)
{
if ((model[0] == 'M') || !strncasecmp(model, "LEICA M", 7))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_M;
if (imgdata.lens.makernotes.LensID)
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Leica_M;
}
else if ((model[0] == 'S') || !strncasecmp(model, "LEICA S", 7))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_S;
if (imgdata.lens.makernotes.Lens[0])
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Leica_S;
}
}
}
else if (((tag == 0x0313) || (tag == 0x34003406)) && (fabs(imgdata.lens.makernotes.CurAp) < 0.17f) &&
((type == 10) || (type == 5)))
{
imgdata.lens.makernotes.CurAp = getreal(type);
if (imgdata.lens.makernotes.CurAp > 126.3)
imgdata.lens.makernotes.CurAp = 0.0f;
}
else if (tag == 0x3400)
{
parse_makernote(base, 0x3400);
}
}
else if (!strncmp(make, "NIKON", 5))
{
if (tag == 0x1d) // serial number
while ((c = fgetc(ifp)) && c != EOF)
{
if ((!custom_serial) && (!isdigit(c)))
{
if ((strbuflen(model) == 3) && (!strcmp(model, "D50")))
{
custom_serial = 34;
}
else
{
custom_serial = 96;
}
}
serial = serial * 10 + (isdigit(c) ? c - '0' : c % 10);
}
else if (tag == 0x000a)
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
else if (tag == 0x0082) // lens attachment
{
stmread(imgdata.lens.makernotes.Attachment, len, ifp);
}
else if (tag == 0x0083) // lens type
{
imgdata.lens.nikon.NikonLensType = fgetc(ifp);
}
else if (tag == 0x0084) // lens
{
imgdata.lens.makernotes.MinFocal = getreal(type);
imgdata.lens.makernotes.MaxFocal = getreal(type);
imgdata.lens.makernotes.MaxAp4MinFocal = getreal(type);
imgdata.lens.makernotes.MaxAp4MaxFocal = getreal(type);
}
else if (tag == 0x008b) // lens f-stops
{
uchar a, b, c;
a = fgetc(ifp);
b = fgetc(ifp);
c = fgetc(ifp);
if (c)
{
imgdata.lens.nikon.NikonLensFStops = a * b * (12 / c);
imgdata.lens.makernotes.LensFStops = (float)imgdata.lens.nikon.NikonLensFStops / 12.0f;
}
}
else if (tag == 0x0093)
{
imgdata.makernotes.nikon.NEFCompression = i = get2();
if ((i == 7) || (i == 9))
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
}
else if (tag == 0x0097)
{
for (i = 0; i < 4; i++)
ver97 = ver97 * 10 + fgetc(ifp) - '0';
if (ver97 == 601) // Coolpix A
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
}
else if (tag == 0x0098) // contains lens data
{
for (i = 0; i < 4; i++)
{
NikonLensDataVersion = NikonLensDataVersion * 10 + fgetc(ifp) - '0';
}
switch (NikonLensDataVersion)
{
case 100:
lenNikonLensData = 9;
break;
case 101:
case 201: // encrypted, starting from v.201
case 202:
case 203:
lenNikonLensData = 15;
break;
case 204:
lenNikonLensData = 16;
break;
case 400:
lenNikonLensData = 459;
break;
case 401:
lenNikonLensData = 590;
break;
case 402:
lenNikonLensData = 509;
break;
case 403:
lenNikonLensData = 879;
break;
}
if (lenNikonLensData)
{
table_buf = (uchar *)malloc(lenNikonLensData);
fread(table_buf, lenNikonLensData, 1, ifp);
if ((NikonLensDataVersion < 201) && lenNikonLensData)
{
processNikonLensData(table_buf, lenNikonLensData);
free(table_buf);
lenNikonLensData = 0;
}
}
}
else if (tag == 0xa7) // shutter count
{
NikonKey = fgetc(ifp) ^ fgetc(ifp) ^ fgetc(ifp) ^ fgetc(ifp);
if ((NikonLensDataVersion > 200) && lenNikonLensData)
{
if (custom_serial)
{
ci = xlat[0][custom_serial];
}
else
{
ci = xlat[0][serial & 0xff];
}
cj = xlat[1][NikonKey];
ck = 0x60;
for (i = 0; i < lenNikonLensData; i++)
table_buf[i] ^= (cj += ci * ck++);
processNikonLensData(table_buf, lenNikonLensData);
lenNikonLensData = 0;
free(table_buf);
}
}
else if (tag == 0x00a8) // contains flash data
{
for (i = 0; i < 4; i++)
{
NikonFlashInfoVersion = NikonFlashInfoVersion * 10 + fgetc(ifp) - '0';
}
}
else if (tag == 0x00b0)
{
get4(); // ME tag version, 4 symbols
imgdata.makernotes.nikon.ExposureMode = get4();
imgdata.makernotes.nikon.nMEshots = get4();
imgdata.makernotes.nikon.MEgainOn = get4();
}
else if (tag == 0x00b9)
{
uchar uc;
int8_t sc;
fread(&uc, 1, 1, ifp);
imgdata.makernotes.nikon.AFFineTune = uc;
fread(&uc, 1, 1, ifp);
imgdata.makernotes.nikon.AFFineTuneIndex = uc;
fread(&sc, 1, 1, ifp);
imgdata.makernotes.nikon.AFFineTuneAdj = sc;
}
else if (tag == 37 && (!iso_speed || iso_speed == 65535))
{
unsigned char cc;
fread(&cc, 1, 1, ifp);
iso_speed = (int)(100.0 * libraw_powf64l(2.0, (double)(cc) / 12.0 - 5.0));
break;
}
}
else if (!strncmp(make, "OLYMPUS", 7))
{
short nWB, tWB;
int SubDirOffsetValid = strncmp(model, "E-300", 5) && strncmp(model, "E-330", 5) && strncmp(model, "E-400", 5) &&
strncmp(model, "E-500", 5) && strncmp(model, "E-1", 3);
if ((tag == 0x2010) || (tag == 0x2020) || (tag == 0x2030) || (tag == 0x2031) || (tag == 0x2040) ||
(tag == 0x2050) || (tag == 0x3000))
{
fseek(ifp, save - 4, SEEK_SET);
fseek(ifp, base + get4(), SEEK_SET);
parse_makernote_0xc634(base, tag, dng_writer);
}
if (!SubDirOffsetValid && ((len > 4) || (((type == 3) || (type == 8)) && (len > 2)) ||
(((type == 4) || (type == 9)) && (len > 1)) || (type == 5) || (type > 9)))
goto skip_Oly_broken_tags;
if ((tag >= 0x20400101) && (tag <= 0x20400111))
{
if ((tag == 0x20400101) && (len == 2) && (!strncasecmp(model, "E-410", 5) || !strncasecmp(model, "E-510", 5)))
{
int i;
for (i = 0; i < 64; i++)
{
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = imgdata.color.WB_Coeffs[i][1] =
imgdata.color.WB_Coeffs[i][3] = 0x100;
}
for (i = 64; i < 256; i++)
{
imgdata.color.WB_Coeffs[i][1] = imgdata.color.WB_Coeffs[i][3] = 0x100;
}
}
nWB = tag - 0x20400101;
tWB = Oly_wb_list2[nWB << 1];
ushort CT = Oly_wb_list2[(nWB << 1) | 1];
int wb[4];
wb[0] = get2();
wb[2] = get2();
if (tWB != 0x100)
{
imgdata.color.WB_Coeffs[tWB][0] = wb[0];
imgdata.color.WB_Coeffs[tWB][2] = wb[2];
}
if (CT)
{
imgdata.color.WBCT_Coeffs[nWB - 1][0] = CT;
imgdata.color.WBCT_Coeffs[nWB - 1][1] = wb[0];
imgdata.color.WBCT_Coeffs[nWB - 1][3] = wb[2];
}
if (len == 4)
{
wb[1] = get2();
wb[3] = get2();
if (tWB != 0x100)
{
imgdata.color.WB_Coeffs[tWB][1] = wb[1];
imgdata.color.WB_Coeffs[tWB][3] = wb[3];
}
if (CT)
{
imgdata.color.WBCT_Coeffs[nWB - 1][2] = wb[1];
imgdata.color.WBCT_Coeffs[nWB - 1][4] = wb[3];
}
}
}
else if ((tag >= 0x20400112) && (tag <= 0x2040011e))
{
nWB = tag - 0x20400112;
int wbG = get2();
tWB = Oly_wb_list2[nWB << 1];
if (nWB)
imgdata.color.WBCT_Coeffs[nWB - 1][2] = imgdata.color.WBCT_Coeffs[nWB - 1][4] = wbG;
if (tWB != 0x100)
imgdata.color.WB_Coeffs[tWB][1] = imgdata.color.WB_Coeffs[tWB][3] = wbG;
}
else if (tag == 0x2040011f)
{
int wbG = get2();
if (imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][0])
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] = wbG;
FORC4 if (imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + c][0])
imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + c][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + c][3] =
wbG;
}
else if (tag == 0x20400121)
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][2] = get2();
if (len == 4)
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] = get2();
}
}
else if ((tag == 0x30000110) && strcmp(software, "v757-71"))
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][2] = get2();
if (len == 2)
{
for (int i = 0; i < 256; i++)
imgdata.color.WB_Coeffs[i][1] = imgdata.color.WB_Coeffs[i][3] = 0x100;
}
}
else if ((((tag >= 0x30000120) && (tag <= 0x30000124)) || ((tag >= 0x30000130) && (tag <= 0x30000133))) &&
strcmp(software, "v757-71"))
{
int wb_ind;
if (tag <= 0x30000124)
wb_ind = tag - 0x30000120;
else
wb_ind = tag - 0x30000130 + 5;
imgdata.color.WB_Coeffs[Oly_wb_list1[wb_ind]][0] = get2();
imgdata.color.WB_Coeffs[Oly_wb_list1[wb_ind]][2] = get2();
}
else
{
switch (tag)
{
case 0x0207:
case 0x20100100:
{
uchar sOlyID[8];
fread(sOlyID, MIN(len, 7), 1, ifp);
sOlyID[7] = 0;
OlyID = sOlyID[0];
i = 1;
while (i < 7 && sOlyID[i])
{
OlyID = OlyID << 8 | sOlyID[i];
i++;
}
setOlympusBodyFeatures(OlyID);
}
break;
case 0x1002:
imgdata.lens.makernotes.CurAp = libraw_powf64l(2.0f, getreal(type) / 2);
break;
case 0x20100102:
stmread(imgdata.shootinginfo.InternalBodySerial, len, ifp);
break;
case 0x20100201:
imgdata.lens.makernotes.LensID = (unsigned long long)fgetc(ifp) << 16 |
(unsigned long long)(fgetc(ifp), fgetc(ifp)) << 8 |
(unsigned long long)fgetc(ifp);
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FT;
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_FT;
if (((imgdata.lens.makernotes.LensID < 0x20000) || (imgdata.lens.makernotes.LensID > 0x4ffff)) &&
(imgdata.lens.makernotes.LensID & 0x10))
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_mFT;
}
break;
case 0x20100202:
if ((!imgdata.lens.LensSerial[0]))
stmread(imgdata.lens.LensSerial, len, ifp);
break;
case 0x20100203:
stmread(imgdata.lens.makernotes.Lens, len, ifp);
break;
case 0x20100205:
imgdata.lens.makernotes.MaxAp4MinFocal = libraw_powf64l(sqrt(2.0f), get2() / 256.0f);
break;
case 0x20100206:
imgdata.lens.makernotes.MaxAp4MaxFocal = libraw_powf64l(sqrt(2.0f), get2() / 256.0f);
break;
case 0x20100207:
imgdata.lens.makernotes.MinFocal = (float)get2();
break;
case 0x20100208:
imgdata.lens.makernotes.MaxFocal = (float)get2();
if (imgdata.lens.makernotes.MaxFocal > 1000.0f)
imgdata.lens.makernotes.MaxFocal = imgdata.lens.makernotes.MinFocal;
break;
case 0x2010020a:
imgdata.lens.makernotes.MaxAp4CurFocal = libraw_powf64l(sqrt(2.0f), get2() / 256.0f);
break;
case 0x20100301:
imgdata.lens.makernotes.TeleconverterID = fgetc(ifp) << 8;
fgetc(ifp);
imgdata.lens.makernotes.TeleconverterID = imgdata.lens.makernotes.TeleconverterID | fgetc(ifp);
break;
case 0x20100303:
stmread(imgdata.lens.makernotes.Teleconverter, len, ifp);
break;
case 0x20100403:
stmread(imgdata.lens.makernotes.Attachment, len, ifp);
break;
case 0x20200306:
{
uchar uc;
fread(&uc, 1, 1, ifp);
imgdata.makernotes.olympus.AFFineTune = uc;
}
break;
case 0x20200307:
FORC3 imgdata.makernotes.olympus.AFFineTuneAdj[c] = get2();
break;
case 0x20200401:
imgdata.other.FlashEC = getreal(type);
break;
case 0x1007:
imgdata.other.SensorTemperature = (float)get2();
break;
case 0x1008:
imgdata.other.LensTemperature = (float)get2();
break;
case 0x20401306:
{
int temp = get2();
if ((temp != 0) && (temp != 100))
{
if (temp < 61)
imgdata.other.CameraTemperature = (float)temp;
else
imgdata.other.CameraTemperature = (float)(temp - 32) / 1.8f;
if ((OlyID == 0x4434353933ULL) && // TG-5
(imgdata.other.exifAmbientTemperature > -273.15f))
imgdata.other.CameraTemperature += imgdata.other.exifAmbientTemperature;
}
}
break;
case 0x20501500:
if (OlyID != 0x0ULL)
{
short temp = get2();
if ((OlyID == 0x4434303430ULL) || // E-1
(OlyID == 0x5330303336ULL) || // E-M5
(len != 1))
imgdata.other.SensorTemperature = (float)temp;
else if ((temp != -32768) && (temp != 0))
{
if (temp > 199)
imgdata.other.SensorTemperature = 86.474958f - 0.120228f * (float)temp;
else
imgdata.other.SensorTemperature = (float)temp;
}
}
break;
}
}
skip_Oly_broken_tags:;
}
else if (!strncmp(make, "PENTAX", 6) || !strncmp(model, "PENTAX", 6) ||
(!strncmp(make, "SAMSUNG", 7) && (dng_writer == CameraDNG)))
{
if (tag == 0x0005)
{
unique_id = get4();
setPentaxBodyFeatures(unique_id);
}
else if (tag == 0x000d)
{
imgdata.makernotes.pentax.FocusMode = get2();
}
else if (tag == 0x000e)
{
imgdata.makernotes.pentax.AFPointSelected = get2();
}
else if (tag == 0x000f)
{
imgdata.makernotes.pentax.AFPointsInFocus = getint(type);
}
else if (tag == 0x0010)
{
imgdata.makernotes.pentax.FocusPosition = get2();
}
else if (tag == 0x0013)
{
imgdata.lens.makernotes.CurAp = (float)get2() / 10.0f;
}
else if (tag == 0x0014)
{
PentaxISO(get2());
}
else if (tag == 0x001d)
{
imgdata.lens.makernotes.CurFocal = (float)get4() / 100.0f;
}
else if (tag == 0x0034)
{
uchar uc;
FORC4
{
fread(&uc, 1, 1, ifp);
imgdata.makernotes.pentax.DriveMode[c] = uc;
}
}
else if (tag == 0x0038)
{
imgdata.sizes.raw_crop.cleft = get2();
imgdata.sizes.raw_crop.ctop = get2();
}
else if (tag == 0x0039)
{
imgdata.sizes.raw_crop.cwidth = get2();
imgdata.sizes.raw_crop.cheight = get2();
}
else if (tag == 0x003f)
{
imgdata.lens.makernotes.LensID = fgetc(ifp) << 8 | fgetc(ifp);
}
else if (tag == 0x0047)
{
imgdata.other.CameraTemperature = (float)fgetc(ifp);
}
else if (tag == 0x004d)
{
if (type == 9)
imgdata.other.FlashEC = getreal(type) / 256.0f;
else
imgdata.other.FlashEC = (float)((signed short)fgetc(ifp)) / 6.0f;
}
else if (tag == 0x0072)
{
imgdata.makernotes.pentax.AFAdjustment = get2();
}
else if (tag == 0x007e)
{
imgdata.color.linear_max[0] = imgdata.color.linear_max[1] = imgdata.color.linear_max[2] =
imgdata.color.linear_max[3] = (long)(-1) * get4();
}
else if (tag == 0x0207)
{
if (len < 65535) // Safety belt
PentaxLensInfo(imgdata.lens.makernotes.CamID, len);
}
else if ((tag >= 0x020d) && (tag <= 0x0214))
{
FORC4 imgdata.color.WB_Coeffs[Pentax_wb_list1[tag - 0x020d]][c ^ (c >> 1)] = get2();
}
else if (tag == 0x0221)
{
int nWB = get2();
if (nWB <= sizeof(imgdata.color.WBCT_Coeffs) / sizeof(imgdata.color.WBCT_Coeffs[0]))
for (int i = 0; i < nWB; i++)
{
imgdata.color.WBCT_Coeffs[i][0] = (unsigned)0xcfc6 - get2();
fseek(ifp, 2, SEEK_CUR);
imgdata.color.WBCT_Coeffs[i][1] = get2();
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = 0x2000;
imgdata.color.WBCT_Coeffs[i][3] = get2();
}
}
else if (tag == 0x0215)
{
fseek(ifp, 16, SEEK_CUR);
sprintf(imgdata.shootinginfo.InternalBodySerial, "%d", get4());
}
else if (tag == 0x0229)
{
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
}
else if (tag == 0x022d)
{
int wb_ind;
getc(ifp);
for (int wb_cnt = 0; wb_cnt < nPentax_wb_list2; wb_cnt++)
{
wb_ind = getc(ifp);
if (wb_ind < nPentax_wb_list2)
FORC4 imgdata.color.WB_Coeffs[Pentax_wb_list2[wb_ind]][c ^ (c >> 1)] = get2();
}
}
else if (tag == 0x0239) // Q-series lens info (LensInfoQ)
{
char LensInfo[20];
fseek(ifp, 12, SEEK_CUR);
stread(imgdata.lens.makernotes.Lens, 30, ifp);
strcat(imgdata.lens.makernotes.Lens, " ");
stread(LensInfo, 20, ifp);
strcat(imgdata.lens.makernotes.Lens, LensInfo);
}
}
else if (!strncmp(make, "SAMSUNG", 7) && (dng_writer == AdobeDNG))
{
if (tag == 0x0002)
{
if (get4() == 0x2000)
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Samsung_NX;
}
else if (!strncmp(model, "NX mini", 7))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Samsung_NX_M;
}
else
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
}
}
else if (tag == 0x0003)
{
imgdata.lens.makernotes.CamID = unique_id = get4();
}
else if (tag == 0x0043)
{
int temp = get4();
if (temp)
{
imgdata.other.CameraTemperature = (float)temp;
if (get4() == 10)
imgdata.other.CameraTemperature /= 10.0f;
}
}
else if (tag == 0xa003)
{
imgdata.lens.makernotes.LensID = get2();
if (imgdata.lens.makernotes.LensID)
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Samsung_NX;
}
else if (tag == 0xa005)
{
stmread(imgdata.lens.InternalLensSerial, len, ifp);
}
else if (tag == 0xa019)
{
imgdata.lens.makernotes.CurAp = getreal(type);
}
else if (tag == 0xa01a)
{
imgdata.lens.makernotes.FocalLengthIn35mmFormat = get4() / 10.0f;
if (imgdata.lens.makernotes.FocalLengthIn35mmFormat < 10.0f)
imgdata.lens.makernotes.FocalLengthIn35mmFormat *= 10.0f;
}
}
else if (!strncasecmp(make, "SONY", 4) || !strncasecmp(make, "Konica", 6) || !strncasecmp(make, "Minolta", 7) ||
(!strncasecmp(make, "Hasselblad", 10) &&
(!strncasecmp(model, "Stellar", 7) || !strncasecmp(model, "Lunar", 5) ||
!strncasecmp(model, "Lusso", 5) || !strncasecmp(model, "HV", 2))))
{
parseSonyMakernotes(tag, type, len, AdobeDNG, table_buf_0x0116, table_buf_0x0116_len, table_buf_0x2010,
table_buf_0x2010_len, table_buf_0x9050, table_buf_0x9050_len, table_buf_0x9400,
table_buf_0x9400_len, table_buf_0x9402, table_buf_0x9402_len, table_buf_0x9403,
table_buf_0x9403_len, table_buf_0x9406, table_buf_0x9406_len, table_buf_0x940c,
table_buf_0x940c_len, table_buf_0x940e, table_buf_0x940e_len);
}
next:
fseek(ifp, save, SEEK_SET);
}
quit:
order = sorder;
}
#else
void CLASS parse_makernote_0xc634(int base, int uptag, unsigned dng_writer)
{ /*placeholder */
}
#endif
void CLASS parse_makernote(int base, int uptag)
{
unsigned offset = 0, entries, tag, type, len, save, c;
unsigned ver97 = 0, serial = 0, i, wbi = 0, wb[4] = {0, 0, 0, 0};
uchar buf97[324], ci, cj, ck;
short morder, sorder = order;
char buf[10];
unsigned SamsungKey[11];
uchar NikonKey;
#ifdef LIBRAW_LIBRARY_BUILD
unsigned custom_serial = 0;
unsigned NikonLensDataVersion = 0;
unsigned lenNikonLensData = 0;
unsigned NikonFlashInfoVersion = 0;
uchar *CanonCameraInfo;
unsigned lenCanonCameraInfo = 0;
unsigned typeCanonCameraInfo = 0;
uchar *table_buf;
uchar *table_buf_0x0116;
ushort table_buf_0x0116_len = 0;
uchar *table_buf_0x2010;
ushort table_buf_0x2010_len = 0;
uchar *table_buf_0x9050;
ushort table_buf_0x9050_len = 0;
uchar *table_buf_0x9400;
ushort table_buf_0x9400_len = 0;
uchar *table_buf_0x9402;
ushort table_buf_0x9402_len = 0;
uchar *table_buf_0x9403;
ushort table_buf_0x9403_len = 0;
uchar *table_buf_0x9406;
ushort table_buf_0x9406_len = 0;
uchar *table_buf_0x940c;
ushort table_buf_0x940c_len = 0;
uchar *table_buf_0x940e;
ushort table_buf_0x940e_len = 0;
INT64 fsize = ifp->size();
#endif
/*
The MakerNote might have its own TIFF header (possibly with
its own byte-order!), or it might just be a table.
*/
if (!strncmp(make, "Nokia", 5))
return;
fread(buf, 1, 10, ifp);
/*
printf("===>>buf: 0x");
for (int i = 0; i < sizeof buf; i ++) {
printf("%02x", buf[i]);
}
putchar('\n');
*/
if (!strncmp(buf, "KDK", 3) || /* these aren't TIFF tables */
!strncmp(buf, "VER", 3) || !strncmp(buf, "IIII", 4) || !strncmp(buf, "MMMM", 4))
return;
if (!strncmp(buf, "KC", 2) || /* Konica KD-400Z, KD-510Z */
!strncmp(buf, "MLY", 3))
{ /* Minolta DiMAGE G series */
order = 0x4d4d;
while ((i = ftell(ifp)) < data_offset && i < 16384)
{
wb[0] = wb[2];
wb[2] = wb[1];
wb[1] = wb[3];
wb[3] = get2();
if (wb[1] == 256 && wb[3] == 256 && wb[0] > 256 && wb[0] < 640 && wb[2] > 256 && wb[2] < 640)
FORC4 cam_mul[c] = wb[c];
}
goto quit;
}
if (!strcmp(buf, "Nikon"))
{
base = ftell(ifp);
order = get2();
if (get2() != 42)
goto quit;
offset = get4();
fseek(ifp, offset - 8, SEEK_CUR);
}
else if (!strcmp(buf, "OLYMPUS") || !strcmp(buf, "PENTAX "))
{
base = ftell(ifp) - 10;
fseek(ifp, -2, SEEK_CUR);
order = get2();
if (buf[0] == 'O')
get2();
}
else if (!strncmp(buf, "SONY", 4) || !strcmp(buf, "Panasonic"))
{
goto nf;
}
else if (!strncmp(buf, "FUJIFILM", 8))
{
base = ftell(ifp) - 10;
nf:
order = 0x4949;
fseek(ifp, 2, SEEK_CUR);
}
else if (!strcmp(buf, "OLYMP") || !strcmp(buf, "LEICA") || !strcmp(buf, "Ricoh") || !strcmp(buf, "EPSON"))
fseek(ifp, -2, SEEK_CUR);
else if (!strcmp(buf, "AOC") || !strcmp(buf, "QVC"))
fseek(ifp, -4, SEEK_CUR);
else
{
fseek(ifp, -10, SEEK_CUR);
if (!strncmp(make, "SAMSUNG", 7))
base = ftell(ifp);
}
// adjust pos & base for Leica M8/M9/M Mono tags and dir in tag 0x3400
if (!strncasecmp(make, "LEICA", 5))
{
if (!strncmp(model, "M8", 2) || !strncasecmp(model, "Leica M8", 8) || !strncasecmp(model, "LEICA X", 7))
{
base = ftell(ifp) - 8;
}
else if (!strncasecmp(model, "LEICA M (Typ 240)", 17))
{
base = 0;
}
else if (!strncmp(model, "M9", 2) || !strncasecmp(model, "Leica M9", 8) || !strncasecmp(model, "M Monochrom", 11) ||
!strncasecmp(model, "Leica M Monochrom", 11))
{
if (!uptag)
{
base = ftell(ifp) - 10;
fseek(ifp, 8, SEEK_CUR);
}
else if (uptag == 0x3400)
{
fseek(ifp, 10, SEEK_CUR);
base += 10;
}
}
else if (!strncasecmp(model, "LEICA T", 7))
{
base = ftell(ifp) - 8;
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_T;
#endif
}
#ifdef LIBRAW_LIBRARY_BUILD
else if (!strncasecmp(model, "LEICA SL", 8))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_SL;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_FF;
}
#endif
}
entries = get2();
if (entries > 1000)
return;
morder = order;
while (entries--)
{
order = morder;
tiff_get(base, &tag, &type, &len, &save);
tag |= uptag << 16;
#ifdef LIBRAW_LIBRARY_BUILD
INT64 _pos = ftell(ifp);
if (len > 8 && _pos + len > 2 * fsize)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue;
}
if (!strncasecmp(model, "KODAK P880", 10) || !strncasecmp(model, "KODAK P850", 10) ||
!strncasecmp(model, "KODAK P712", 10))
{
if (tag == 0xf90b)
{
imgdata.makernotes.kodak.clipBlack = get2();
}
else if (tag == 0xf90c)
{
imgdata.makernotes.kodak.clipWhite = get2();
}
}
if (!strncmp(make, "Canon", 5))
{
if (tag == 0x000d && len < 256000) // camera info
{
if (type != 4)
{
CanonCameraInfo = (uchar *)malloc(MAX(16, len));
fread(CanonCameraInfo, len, 1, ifp);
}
else
{
CanonCameraInfo = (uchar *)malloc(MAX(16, len * 4));
fread(CanonCameraInfo, len, 4, ifp);
}
lenCanonCameraInfo = len;
typeCanonCameraInfo = type;
}
else if (tag == 0x10) // Canon ModelID
{
unique_id = get4();
unique_id = setCanonBodyFeatures(unique_id);
if (lenCanonCameraInfo)
{
processCanonCameraInfo(unique_id, CanonCameraInfo, lenCanonCameraInfo, typeCanonCameraInfo);
free(CanonCameraInfo);
CanonCameraInfo = 0;
lenCanonCameraInfo = 0;
}
}
else
parseCanonMakernotes(tag, type, len);
}
else if (!strncmp(make, "FUJI", 4))
{
if (tag == 0x0010)
{
char FujiSerial[sizeof(imgdata.shootinginfo.InternalBodySerial)];
char *words[4];
char yy[2], mm[3], dd[3], ystr[16], ynum[16];
int year, nwords, ynum_len;
unsigned c;
stmread(FujiSerial, len, ifp);
nwords = getwords(FujiSerial, words, 4, sizeof(imgdata.shootinginfo.InternalBodySerial));
for (int i = 0; i < nwords; i++)
{
mm[2] = dd[2] = 0;
if (strnlen(words[i], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) < 18)
if (i == 0)
strncpy(imgdata.shootinginfo.InternalBodySerial, words[0],
sizeof(imgdata.shootinginfo.InternalBodySerial) - 1);
else
{
char tbuf[sizeof(imgdata.shootinginfo.InternalBodySerial)];
snprintf(tbuf, sizeof(tbuf), "%s %s", imgdata.shootinginfo.InternalBodySerial, words[i]);
strncpy(imgdata.shootinginfo.InternalBodySerial, tbuf,
sizeof(imgdata.shootinginfo.InternalBodySerial) - 1);
}
else
{
strncpy(dd, words[i] + strnlen(words[i], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 14, 2);
strncpy(mm, words[i] + strnlen(words[i], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 16, 2);
strncpy(yy, words[i] + strnlen(words[i], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 18, 2);
year = (yy[0] - '0') * 10 + (yy[1] - '0');
if (year < 70)
year += 2000;
else
year += 1900;
ynum_len = (int)strnlen(words[i], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 18;
strncpy(ynum, words[i], ynum_len);
ynum[ynum_len] = 0;
for (int j = 0; ynum[j] && ynum[j + 1] && sscanf(ynum + j, "%2x", &c); j += 2)
ystr[j / 2] = c;
ystr[ynum_len / 2 + 1] = 0;
strcpy(model2, ystr);
if (i == 0)
{
char tbuf[sizeof(imgdata.shootinginfo.InternalBodySerial)];
if (nwords == 1)
snprintf(tbuf, sizeof(tbuf), "%s %s %d:%s:%s",
words[0] + strnlen(words[0], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 12, ystr,
year, mm, dd);
else
snprintf(tbuf, sizeof(tbuf), "%s %d:%s:%s %s", ystr, year, mm, dd,
words[0] + strnlen(words[0], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 12);
strncpy(imgdata.shootinginfo.InternalBodySerial, tbuf,
sizeof(imgdata.shootinginfo.InternalBodySerial) - 1);
}
else
{
char tbuf[sizeof(imgdata.shootinginfo.InternalBodySerial)];
snprintf(tbuf, sizeof(tbuf), "%s %s %d:%s:%s %s", imgdata.shootinginfo.InternalBodySerial, ystr, year, mm,
dd, words[i] + strnlen(words[i], sizeof(imgdata.shootinginfo.InternalBodySerial) - 1) - 12);
strncpy(imgdata.shootinginfo.InternalBodySerial, tbuf,
sizeof(imgdata.shootinginfo.InternalBodySerial) - 1);
}
}
}
}
else
parseFujiMakernotes(tag, type);
}
else if (!strncasecmp(model, "Hasselblad X1D", 14) || !strncasecmp(model, "Hasselblad H6D", 14) ||
!strncasecmp(model, "Hasselblad A6D", 14))
{
if (tag == 0x0045)
{
imgdata.makernotes.hasselblad.BaseISO = get4();
}
else if (tag == 0x0046)
{
imgdata.makernotes.hasselblad.Gain = getreal(type);
}
}
else if (!strncasecmp(make, "LEICA", 5))
{
if (((tag == 0x035e) || (tag == 0x035f)) && (type == 10) && (len == 9))
{
int ind = tag == 0x035e ? 0 : 1;
for (int j = 0; j < 3; j++)
FORCC imgdata.color.dng_color[ind].forwardmatrix[j][c] = getreal(type);
imgdata.color.dng_color[ind].parsedfields |= LIBRAW_DNGFM_FORWARDMATRIX;
}
if (tag == 0x34003402)
imgdata.other.CameraTemperature = getreal(type);
if ((tag == 0x0320) && (type == 9) && (len == 1) && !strncasecmp(make, "Leica Camera AG", 15) &&
!strncmp(buf, "LEICA", 5) && (buf[5] == 0) && (buf[6] == 0) && (buf[7] == 0))
imgdata.other.CameraTemperature = getreal(type);
if ((tag == 0x0303) && (type != 4))
{
stmread(imgdata.lens.makernotes.Lens, len, ifp);
}
if ((tag == 0x3405) || (tag == 0x0310) || (tag == 0x34003405))
{
imgdata.lens.makernotes.LensID = get4();
imgdata.lens.makernotes.LensID =
((imgdata.lens.makernotes.LensID >> 2) << 8) | (imgdata.lens.makernotes.LensID & 0x3);
if (imgdata.lens.makernotes.LensID != -1)
{
if ((model[0] == 'M') || !strncasecmp(model, "LEICA M", 7))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_M;
if (imgdata.lens.makernotes.LensID)
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Leica_M;
}
else if ((model[0] == 'S') || !strncasecmp(model, "LEICA S", 7))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_S;
if (imgdata.lens.makernotes.Lens[0])
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Leica_S;
}
}
}
else if (((tag == 0x0313) || (tag == 0x34003406)) && (fabs(imgdata.lens.makernotes.CurAp) < 0.17f) &&
((type == 10) || (type == 5)))
{
imgdata.lens.makernotes.CurAp = getreal(type);
if (imgdata.lens.makernotes.CurAp > 126.3)
imgdata.lens.makernotes.CurAp = 0.0f;
}
else if (tag == 0x3400)
{
parse_makernote(base, 0x3400);
}
}
else if (!strncmp(make, "NIKON", 5))
{
if (tag == 0x000a)
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
else if (tag == 0x0012)
{
char a, b, c;
a = fgetc(ifp);
b = fgetc(ifp);
c = fgetc(ifp);
if (c)
imgdata.other.FlashEC = (float)(a * b) / (float)c;
}
else if (tag == 0x003b) // all 1s for regular exposures
{
imgdata.makernotes.nikon.ME_WB[0] = getreal(type);
imgdata.makernotes.nikon.ME_WB[2] = getreal(type);
imgdata.makernotes.nikon.ME_WB[1] = getreal(type);
imgdata.makernotes.nikon.ME_WB[3] = getreal(type);
}
else if (tag == 0x0045)
{
imgdata.sizes.raw_crop.cleft = get2();
imgdata.sizes.raw_crop.ctop = get2();
imgdata.sizes.raw_crop.cwidth = get2();
imgdata.sizes.raw_crop.cheight = get2();
}
else if (tag == 0x0082) // lens attachment
{
stmread(imgdata.lens.makernotes.Attachment, len, ifp);
}
else if (tag == 0x0083) // lens type
{
imgdata.lens.nikon.NikonLensType = fgetc(ifp);
}
else if (tag == 0x0084) // lens
{
imgdata.lens.makernotes.MinFocal = getreal(type);
imgdata.lens.makernotes.MaxFocal = getreal(type);
imgdata.lens.makernotes.MaxAp4MinFocal = getreal(type);
imgdata.lens.makernotes.MaxAp4MaxFocal = getreal(type);
}
else if (tag == 0x008b) // lens f-stops
{
uchar a, b, c;
a = fgetc(ifp);
b = fgetc(ifp);
c = fgetc(ifp);
if (c)
{
imgdata.lens.nikon.NikonLensFStops = a * b * (12 / c);
imgdata.lens.makernotes.LensFStops = (float)imgdata.lens.nikon.NikonLensFStops / 12.0f;
}
}
else if (tag == 0x0093) // Nikon compression
{
imgdata.makernotes.nikon.NEFCompression = i = get2();
if ((i == 7) || (i == 9))
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
}
else if (tag == 0x0098) // contains lens data
{
for (i = 0; i < 4; i++)
{
NikonLensDataVersion = NikonLensDataVersion * 10 + fgetc(ifp) - '0';
}
switch (NikonLensDataVersion)
{
case 100:
lenNikonLensData = 9;
break;
case 101:
case 201: // encrypted, starting from v.201
case 202:
case 203:
lenNikonLensData = 15;
break;
case 204:
lenNikonLensData = 16;
break;
case 400:
lenNikonLensData = 459;
break;
case 401:
lenNikonLensData = 590;
break;
case 402:
lenNikonLensData = 509;
break;
case 403:
lenNikonLensData = 879;
break;
}
if (lenNikonLensData > 0)
{
table_buf = (uchar *)malloc(lenNikonLensData);
fread(table_buf, lenNikonLensData, 1, ifp);
if ((NikonLensDataVersion < 201) && lenNikonLensData)
{
processNikonLensData(table_buf, lenNikonLensData);
free(table_buf);
lenNikonLensData = 0;
}
}
}
else if (tag == 0x00a0)
{
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
}
else if (tag == 0x00a8) // contains flash data
{
for (i = 0; i < 4; i++)
{
NikonFlashInfoVersion = NikonFlashInfoVersion * 10 + fgetc(ifp) - '0';
}
}
else if (tag == 0x00b0)
{
get4(); // ME tag version, 4 symbols
imgdata.makernotes.nikon.ExposureMode = get4();
imgdata.makernotes.nikon.nMEshots = get4();
imgdata.makernotes.nikon.MEgainOn = get4();
}
else if (tag == 0x00b9)
{
uchar uc;
int8_t sc;
fread(&uc, 1, 1, ifp);
imgdata.makernotes.nikon.AFFineTune = uc;
fread(&uc, 1, 1, ifp);
imgdata.makernotes.nikon.AFFineTuneIndex = uc;
fread(&sc, 1, 1, ifp);
imgdata.makernotes.nikon.AFFineTuneAdj = sc;
}
}
else if (!strncmp(make, "OLYMPUS", 7))
{
switch (tag)
{
case 0x0404:
case 0x101a:
case 0x20100101:
if (!imgdata.shootinginfo.BodySerial[0])
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
break;
case 0x20100102:
if (!imgdata.shootinginfo.InternalBodySerial[0])
stmread(imgdata.shootinginfo.InternalBodySerial, len, ifp);
break;
case 0x0207:
case 0x20100100:
{
uchar sOlyID[8];
fread(sOlyID, MIN(len, 7), 1, ifp);
sOlyID[7] = 0;
OlyID = sOlyID[0];
i = 1;
while (i < 7 && sOlyID[i])
{
OlyID = OlyID << 8 | sOlyID[i];
i++;
}
setOlympusBodyFeatures(OlyID);
}
break;
case 0x1002:
imgdata.lens.makernotes.CurAp = libraw_powf64l(2.0f, getreal(type) / 2);
break;
case 0x20400612:
case 0x30000612:
imgdata.sizes.raw_crop.cleft = get2();
break;
case 0x20400613:
case 0x30000613:
imgdata.sizes.raw_crop.ctop = get2();
break;
case 0x20400614:
case 0x30000614:
imgdata.sizes.raw_crop.cwidth = get2();
break;
case 0x20400615:
case 0x30000615:
imgdata.sizes.raw_crop.cheight = get2();
break;
case 0x20401112:
imgdata.makernotes.olympus.OlympusCropID = get2();
break;
case 0x20401113:
FORC4 imgdata.makernotes.olympus.OlympusFrame[c] = get2();
break;
case 0x20100201:
{
unsigned long long oly_lensid[3];
oly_lensid[0] = fgetc(ifp);
fgetc(ifp);
oly_lensid[1] = fgetc(ifp);
oly_lensid[2] = fgetc(ifp);
imgdata.lens.makernotes.LensID = (oly_lensid[0] << 16) | (oly_lensid[1] << 8) | oly_lensid[2];
}
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FT;
imgdata.lens.makernotes.LensFormat = LIBRAW_FORMAT_FT;
if (((imgdata.lens.makernotes.LensID < 0x20000) || (imgdata.lens.makernotes.LensID > 0x4ffff)) &&
(imgdata.lens.makernotes.LensID & 0x10))
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_mFT;
}
break;
case 0x20100202:
stmread(imgdata.lens.LensSerial, len, ifp);
break;
case 0x20100203:
stmread(imgdata.lens.makernotes.Lens, len, ifp);
break;
case 0x20100205:
imgdata.lens.makernotes.MaxAp4MinFocal = libraw_powf64l(sqrt(2.0f), get2() / 256.0f);
break;
case 0x20100206:
imgdata.lens.makernotes.MaxAp4MaxFocal = libraw_powf64l(sqrt(2.0f), get2() / 256.0f);
break;
case 0x20100207:
imgdata.lens.makernotes.MinFocal = (float)get2();
break;
case 0x20100208:
imgdata.lens.makernotes.MaxFocal = (float)get2();
if (imgdata.lens.makernotes.MaxFocal > 1000.0f)
imgdata.lens.makernotes.MaxFocal = imgdata.lens.makernotes.MinFocal;
break;
case 0x2010020a:
imgdata.lens.makernotes.MaxAp4CurFocal = libraw_powf64l(sqrt(2.0f), get2() / 256.0f);
break;
case 0x20100301:
imgdata.lens.makernotes.TeleconverterID = fgetc(ifp) << 8;
fgetc(ifp);
imgdata.lens.makernotes.TeleconverterID = imgdata.lens.makernotes.TeleconverterID | fgetc(ifp);
break;
case 0x20100303:
stmread(imgdata.lens.makernotes.Teleconverter, len, ifp);
break;
case 0x20100403:
stmread(imgdata.lens.makernotes.Attachment, len, ifp);
break;
case 0x1007:
imgdata.other.SensorTemperature = (float)get2();
break;
case 0x1008:
imgdata.other.LensTemperature = (float)get2();
break;
case 0x20401306:
{
int temp = get2();
if ((temp != 0) && (temp != 100))
{
if (temp < 61)
imgdata.other.CameraTemperature = (float)temp;
else
imgdata.other.CameraTemperature = (float)(temp - 32) / 1.8f;
if ((OlyID == 0x4434353933ULL) && // TG-5
(imgdata.other.exifAmbientTemperature > -273.15f))
imgdata.other.CameraTemperature += imgdata.other.exifAmbientTemperature;
}
}
break;
case 0x20501500:
if (OlyID != 0x0ULL)
{
short temp = get2();
if ((OlyID == 0x4434303430ULL) || // E-1
(OlyID == 0x5330303336ULL) || // E-M5
(len != 1))
imgdata.other.SensorTemperature = (float)temp;
else if ((temp != -32768) && (temp != 0))
{
if (temp > 199)
imgdata.other.SensorTemperature = 86.474958f - 0.120228f * (float)temp;
else
imgdata.other.SensorTemperature = (float)temp;
}
}
break;
}
}
else if ((!strncmp(make, "PENTAX", 6) || !strncmp(make, "RICOH", 5)) && !strncmp(model, "GR", 2))
{
if (tag == 0x0005)
{
char buffer[17];
int count = 0;
fread(buffer, 16, 1, ifp);
buffer[16] = 0;
for (int i = 0; i < 16; i++)
{
// sprintf(imgdata.shootinginfo.InternalBodySerial+2*i, "%02x", buffer[i]);
if ((isspace(buffer[i])) || (buffer[i] == 0x2D) || (isalnum(buffer[i])))
count++;
}
if (count == 16)
{
sprintf(imgdata.shootinginfo.BodySerial, "%8s", buffer + 8);
buffer[8] = 0;
sprintf(imgdata.shootinginfo.InternalBodySerial, "%8s", buffer);
}
else
{
sprintf(imgdata.shootinginfo.BodySerial, "%02x%02x%02x%02x", buffer[4], buffer[5], buffer[6], buffer[7]);
sprintf(imgdata.shootinginfo.InternalBodySerial, "%02x%02x%02x%02x", buffer[8], buffer[9], buffer[10],
buffer[11]);
}
}
else if ((tag == 0x1001) && (type == 3))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_APSC;
imgdata.lens.makernotes.LensID = -1;
imgdata.lens.makernotes.FocalType = 1;
}
else if ((tag == 0x100b) && (type == 10))
{
imgdata.other.FlashEC = getreal(type);
}
else if ((tag == 0x1017) && (get2() == 2))
{
strcpy(imgdata.lens.makernotes.Attachment, "Wide-Angle Adapter");
}
else if (tag == 0x1500)
{
imgdata.lens.makernotes.CurFocal = getreal(type);
}
}
else if (!strncmp(make, "RICOH", 5) && strncmp(model, "PENTAX", 6))
{
if ((tag == 0x0005) && !strncmp(model, "GXR", 3))
{
char buffer[9];
buffer[8] = 0;
fread(buffer, 8, 1, ifp);
sprintf(imgdata.shootinginfo.InternalBodySerial, "%8s", buffer);
}
else if ((tag == 0x100b) && (type == 10))
{
imgdata.other.FlashEC = getreal(type);
}
else if ((tag == 0x1017) && (get2() == 2))
{
strcpy(imgdata.lens.makernotes.Attachment, "Wide-Angle Adapter");
}
else if (tag == 0x1500)
{
imgdata.lens.makernotes.CurFocal = getreal(type);
}
else if ((tag == 0x2001) && !strncmp(model, "GXR", 3))
{
short ntags, cur_tag;
fseek(ifp, 20, SEEK_CUR);
ntags = get2();
cur_tag = get2();
while (cur_tag != 0x002c)
{
fseek(ifp, 10, SEEK_CUR);
cur_tag = get2();
}
fseek(ifp, 6, SEEK_CUR);
fseek(ifp, get4() + 20, SEEK_SET);
stread(imgdata.shootinginfo.BodySerial, 12, ifp);
get2();
imgdata.lens.makernotes.LensID = getc(ifp) - '0';
switch (imgdata.lens.makernotes.LensID)
{
case 1:
case 2:
case 3:
case 5:
case 6:
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_RicohModule;
break;
case 8:
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Leica_M;
imgdata.lens.makernotes.CameraFormat = LIBRAW_FORMAT_APSC;
imgdata.lens.makernotes.LensID = -1;
break;
default:
imgdata.lens.makernotes.LensID = -1;
}
fseek(ifp, 17, SEEK_CUR);
stread(imgdata.lens.LensSerial, 12, ifp);
}
}
else if ((!strncmp(make, "PENTAX", 6) || !strncmp(model, "PENTAX", 6) ||
(!strncmp(make, "SAMSUNG", 7) && dng_version)) &&
strncmp(model, "GR", 2))
{
if (tag == 0x0005)
{
unique_id = get4();
setPentaxBodyFeatures(unique_id);
}
else if (tag == 0x000d)
{
imgdata.makernotes.pentax.FocusMode = get2();
}
else if (tag == 0x000e)
{
imgdata.makernotes.pentax.AFPointSelected = get2();
}
else if (tag == 0x000f)
{
imgdata.makernotes.pentax.AFPointsInFocus = getint(type);
}
else if (tag == 0x0010)
{
imgdata.makernotes.pentax.FocusPosition = get2();
}
else if (tag == 0x0013)
{
imgdata.lens.makernotes.CurAp = (float)get2() / 10.0f;
}
else if (tag == 0x0014)
{
PentaxISO(get2());
}
else if (tag == 0x001d)
{
imgdata.lens.makernotes.CurFocal = (float)get4() / 100.0f;
}
else if (tag == 0x0034)
{
uchar uc;
FORC4
{
fread(&uc, 1, 1, ifp);
imgdata.makernotes.pentax.DriveMode[c] = uc;
}
}
else if (tag == 0x0038)
{
imgdata.sizes.raw_crop.cleft = get2();
imgdata.sizes.raw_crop.ctop = get2();
}
else if (tag == 0x0039)
{
imgdata.sizes.raw_crop.cwidth = get2();
imgdata.sizes.raw_crop.cheight = get2();
}
else if (tag == 0x003f)
{
imgdata.lens.makernotes.LensID = fgetc(ifp) << 8 | fgetc(ifp);
}
else if (tag == 0x0047)
{
imgdata.other.CameraTemperature = (float)fgetc(ifp);
}
else if (tag == 0x004d)
{
if (type == 9)
imgdata.other.FlashEC = getreal(type) / 256.0f;
else
imgdata.other.FlashEC = (float)((signed short)fgetc(ifp)) / 6.0f;
}
else if (tag == 0x0072)
{
imgdata.makernotes.pentax.AFAdjustment = get2();
}
else if (tag == 0x007e)
{
imgdata.color.linear_max[0] = imgdata.color.linear_max[1] = imgdata.color.linear_max[2] =
imgdata.color.linear_max[3] = (long)(-1) * get4();
}
else if (tag == 0x0207)
{
if (len < 65535) // Safety belt
PentaxLensInfo(imgdata.lens.makernotes.CamID, len);
}
else if ((tag >= 0x020d) && (tag <= 0x0214))
{
FORC4 imgdata.color.WB_Coeffs[Pentax_wb_list1[tag - 0x020d]][c ^ (c >> 1)] = get2();
}
else if (tag == 0x0221)
{
int nWB = get2();
if (nWB <= sizeof(imgdata.color.WBCT_Coeffs) / sizeof(imgdata.color.WBCT_Coeffs[0]))
for (int i = 0; i < nWB; i++)
{
imgdata.color.WBCT_Coeffs[i][0] = (unsigned)0xcfc6 - get2();
fseek(ifp, 2, SEEK_CUR);
imgdata.color.WBCT_Coeffs[i][1] = get2();
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = 0x2000;
imgdata.color.WBCT_Coeffs[i][3] = get2();
}
}
else if (tag == 0x0215)
{
fseek(ifp, 16, SEEK_CUR);
sprintf(imgdata.shootinginfo.InternalBodySerial, "%d", get4());
}
else if (tag == 0x0229)
{
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
}
else if (tag == 0x022d)
{
int wb_ind;
getc(ifp);
for (int wb_cnt = 0; wb_cnt < nPentax_wb_list2; wb_cnt++)
{
wb_ind = getc(ifp);
if (wb_ind < nPentax_wb_list2)
FORC4 imgdata.color.WB_Coeffs[Pentax_wb_list2[wb_ind]][c ^ (c >> 1)] = get2();
}
}
else if (tag == 0x0239) // Q-series lens info (LensInfoQ)
{
char LensInfo[20];
fseek(ifp, 2, SEEK_CUR);
stread(imgdata.lens.makernotes.Lens, 30, ifp);
strcat(imgdata.lens.makernotes.Lens, " ");
stread(LensInfo, 20, ifp);
strcat(imgdata.lens.makernotes.Lens, LensInfo);
}
}
else if (!strncmp(make, "SAMSUNG", 7))
{
if (tag == 0x0002)
{
if (get4() == 0x2000)
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Samsung_NX;
}
else if (!strncmp(model, "NX mini", 7))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Samsung_NX_M;
}
else
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
}
}
else if (tag == 0x0003)
{
unique_id = imgdata.lens.makernotes.CamID = get4();
}
else if (tag == 0x0043)
{
int temp = get4();
if (temp)
{
imgdata.other.CameraTemperature = (float)temp;
if (get4() == 10)
imgdata.other.CameraTemperature /= 10.0f;
}
}
else if (tag == 0xa002)
{
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
}
else if (tag == 0xa003)
{
imgdata.lens.makernotes.LensID = get2();
if (imgdata.lens.makernotes.LensID)
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Samsung_NX;
}
else if (tag == 0xa005)
{
stmread(imgdata.lens.InternalLensSerial, len, ifp);
}
else if (tag == 0xa019)
{
imgdata.lens.makernotes.CurAp = getreal(type);
}
else if (tag == 0xa01a)
{
imgdata.lens.makernotes.FocalLengthIn35mmFormat = get4() / 10.0f;
if (imgdata.lens.makernotes.FocalLengthIn35mmFormat < 10.0f)
imgdata.lens.makernotes.FocalLengthIn35mmFormat *= 10.0f;
}
}
else if (!strncasecmp(make, "SONY", 4) || !strncasecmp(make, "Konica", 6) || !strncasecmp(make, "Minolta", 7) ||
(!strncasecmp(make, "Hasselblad", 10) &&
(!strncasecmp(model, "Stellar", 7) || !strncasecmp(model, "Lunar", 5) ||
!strncasecmp(model, "Lusso", 5) || !strncasecmp(model, "HV", 2))))
{
parseSonyMakernotes(tag, type, len, nonDNG, table_buf_0x0116, table_buf_0x0116_len, table_buf_0x2010,
table_buf_0x2010_len, table_buf_0x9050, table_buf_0x9050_len, table_buf_0x9400,
table_buf_0x9400_len, table_buf_0x9402, table_buf_0x9402_len, table_buf_0x9403,
table_buf_0x9403_len, table_buf_0x9406, table_buf_0x9406_len, table_buf_0x940c,
table_buf_0x940c_len, table_buf_0x940e, table_buf_0x940e_len);
}
fseek(ifp, _pos, SEEK_SET);
#endif
if (tag == 2 && strstr(make, "NIKON") && !iso_speed)
iso_speed = (get2(), get2());
if (tag == 37 && strstr(make, "NIKON") && (!iso_speed || iso_speed == 65535))
{
unsigned char cc;
fread(&cc, 1, 1, ifp);
iso_speed = int(100.0 * libraw_powf64l(2.0f, float(cc) / 12.0 - 5.0));
}
if (tag == 4 && len > 26 && len < 35)
{
if ((i = (get4(), get2())) != 0x7fff && (!iso_speed || iso_speed == 65535))
iso_speed = 50 * libraw_powf64l(2.0, i / 32.0 - 4);
#ifdef LIBRAW_LIBRARY_BUILD
get4();
#else
if ((i = (get2(), get2())) != 0x7fff && !aperture)
aperture = libraw_powf64l(2.0, i / 64.0);
#endif
if ((i = get2()) != 0xffff && !shutter)
shutter = libraw_powf64l(2.0, (short)i / -32.0);
wbi = (get2(), get2());
shot_order = (get2(), get2());
}
if ((tag == 4 || tag == 0x114) && !strncmp(make, "KONICA", 6))
{
fseek(ifp, tag == 4 ? 140 : 160, SEEK_CUR);
switch (get2())
{
case 72:
flip = 0;
break;
case 76:
flip = 6;
break;
case 82:
flip = 5;
break;
}
}
if (tag == 7 && type == 2 && len > 20)
fgets(model2, 64, ifp);
if (tag == 8 && type == 4)
shot_order = get4();
if (tag == 9 && !strncmp(make, "Canon", 5))
fread(artist, 64, 1, ifp);
if (tag == 0xc && len == 4)
FORC3 cam_mul[(c << 1 | c >> 1) & 3] = getreal(type);
if (tag == 0xd && type == 7 && get2() == 0xaaaa)
{
#if 0 /* Canon rotation data is handled by EXIF.Orientation */
for (c = i = 2; (ushort)c != 0xbbbb && i < len; i++)
c = c << 8 | fgetc(ifp);
while ((i += 4) < len - 5)
if (get4() == 257 && (i = len) && (c = (get4(), fgetc(ifp))) < 3)
flip = "065"[c] - '0';
#endif
}
#ifndef LIBRAW_LIBRARY_BUILD
if (tag == 0x10 && type == 4)
unique_id = get4();
#endif
#ifdef LIBRAW_LIBRARY_BUILD
INT64 _pos2 = ftell(ifp);
if (!strncasecmp(make, "Olympus", 7))
{
short nWB, tWB;
if ((tag == 0x20300108) || (tag == 0x20310109))
imgdata.makernotes.olympus.ColorSpace = get2();
if ((tag == 0x20400101) && (len == 2) && (!strncasecmp(model, "E-410", 5) || !strncasecmp(model, "E-510", 5)))
{
int i;
for (i = 0; i < 64; i++)
imgdata.color.WBCT_Coeffs[i][2] = imgdata.color.WBCT_Coeffs[i][4] = imgdata.color.WB_Coeffs[i][1] =
imgdata.color.WB_Coeffs[i][3] = 0x100;
for (i = 64; i < 256; i++)
imgdata.color.WB_Coeffs[i][1] = imgdata.color.WB_Coeffs[i][3] = 0x100;
}
if ((tag >= 0x20400101) && (tag <= 0x20400111))
{
nWB = tag - 0x20400101;
tWB = Oly_wb_list2[nWB << 1];
ushort CT = Oly_wb_list2[(nWB << 1) | 1];
int wb[4];
wb[0] = get2();
wb[2] = get2();
if (tWB != 0x100)
{
imgdata.color.WB_Coeffs[tWB][0] = wb[0];
imgdata.color.WB_Coeffs[tWB][2] = wb[2];
}
if (CT)
{
imgdata.color.WBCT_Coeffs[nWB - 1][0] = CT;
imgdata.color.WBCT_Coeffs[nWB - 1][1] = wb[0];
imgdata.color.WBCT_Coeffs[nWB - 1][3] = wb[2];
}
if (len == 4)
{
wb[1] = get2();
wb[3] = get2();
if (tWB != 0x100)
{
imgdata.color.WB_Coeffs[tWB][1] = wb[1];
imgdata.color.WB_Coeffs[tWB][3] = wb[3];
}
if (CT)
{
imgdata.color.WBCT_Coeffs[nWB - 1][2] = wb[1];
imgdata.color.WBCT_Coeffs[nWB - 1][4] = wb[3];
}
}
}
if ((tag >= 0x20400112) && (tag <= 0x2040011e))
{
nWB = tag - 0x20400112;
int wbG = get2();
tWB = Oly_wb_list2[nWB << 1];
if (nWB)
imgdata.color.WBCT_Coeffs[nWB - 1][2] = imgdata.color.WBCT_Coeffs[nWB - 1][4] = wbG;
if (tWB != 0x100)
imgdata.color.WB_Coeffs[tWB][1] = imgdata.color.WB_Coeffs[tWB][3] = wbG;
}
if (tag == 0x20400121)
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][2] = get2();
if (len == 4)
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] = get2();
}
}
if (tag == 0x2040011f)
{
int wbG = get2();
if (imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][0])
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] = wbG;
FORC4 if (imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + c][0])
imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + c][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + c][3] =
wbG;
}
if ((tag == 0x30000110) && strcmp(software, "v757-71"))
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][2] = get2();
if (len == 2)
{
for (int i = 0; i < 256; i++)
imgdata.color.WB_Coeffs[i][1] = imgdata.color.WB_Coeffs[i][3] = 0x100;
}
}
if ((((tag >= 0x30000120) && (tag <= 0x30000124)) || ((tag >= 0x30000130) && (tag <= 0x30000133))) &&
strcmp(software, "v757-71"))
{
int wb_ind;
if (tag <= 0x30000124)
wb_ind = tag - 0x30000120;
else
wb_ind = tag - 0x30000130 + 5;
imgdata.color.WB_Coeffs[Oly_wb_list1[wb_ind]][0] = get2();
imgdata.color.WB_Coeffs[Oly_wb_list1[wb_ind]][2] = get2();
}
if ((tag == 0x20400805) && (len == 2))
{
imgdata.makernotes.olympus.OlympusSensorCalibration[0] = getreal(type);
imgdata.makernotes.olympus.OlympusSensorCalibration[1] = getreal(type);
FORC4 imgdata.color.linear_max[c] = imgdata.makernotes.olympus.OlympusSensorCalibration[0];
}
if (tag == 0x20200306)
{
uchar uc;
fread(&uc, 1, 1, ifp);
imgdata.makernotes.olympus.AFFineTune = uc;
}
if (tag == 0x20200307)
{
FORC3 imgdata.makernotes.olympus.AFFineTuneAdj[c] = get2();
}
if (tag == 0x20200401)
{
imgdata.other.FlashEC = getreal(type);
}
}
fseek(ifp, _pos2, SEEK_SET);
#endif
if (tag == 0x11 && is_raw && !strncmp(make, "NIKON", 5))
{
fseek(ifp, get4() + base, SEEK_SET);
parse_tiff_ifd(base);
}
if (tag == 0x14 && type == 7)
{
if (len == 2560)
{
fseek(ifp, 1248, SEEK_CUR);
goto get2_256;
}
fread(buf, 1, 10, ifp);
if (!strncmp(buf, "NRW ", 4))
{
fseek(ifp, strcmp(buf + 4, "0100") ? 46 : 1546, SEEK_CUR);
cam_mul[0] = get4() << 2;
cam_mul[1] = get4() + get4();
cam_mul[2] = get4() << 2;
}
}
if (tag == 0x15 && type == 2 && is_raw)
fread(model, 64, 1, ifp);
if (strstr(make, "PENTAX"))
{
if (tag == 0x1b)
tag = 0x1018;
if (tag == 0x1c)
tag = 0x1017;
}
if (tag == 0x1d)
{
while ((c = fgetc(ifp)) && c != EOF)
#ifdef LIBRAW_LIBRARY_BUILD
{
if ((!custom_serial) && (!isdigit(c)))
{
if ((strbuflen(model) == 3) && (!strcmp(model, "D50")))
{
custom_serial = 34;
}
else
{
custom_serial = 96;
}
}
#endif
serial = serial * 10 + (isdigit(c) ? c - '0' : c % 10);
#ifdef LIBRAW_LIBRARY_BUILD
}
if (!imgdata.shootinginfo.BodySerial[0])
sprintf(imgdata.shootinginfo.BodySerial, "%d", serial);
#endif
}
if (tag == 0x29 && type == 1)
{ // Canon PowerShot G9
c = wbi < 18 ? "012347800000005896"[wbi] - '0' : 0;
fseek(ifp, 8 + c * 32, SEEK_CUR);
FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get4();
}
#ifndef LIBRAW_LIBRARY_BUILD
if (tag == 0x3d && type == 3 && len == 4)
FORC4 cblack[c ^ c >> 1] = get2() >> (14 - tiff_bps);
#endif
if (tag == 0x81 && type == 4)
{
data_offset = get4();
fseek(ifp, data_offset + 41, SEEK_SET);
raw_height = get2() * 2;
raw_width = get2();
filters = 0x61616161;
}
if ((tag == 0x81 && type == 7) || (tag == 0x100 && type == 7) || (tag == 0x280 && type == 1))
{
thumb_offset = ftell(ifp);
thumb_length = len;
}
if (tag == 0x88 && type == 4 && (thumb_offset = get4()))
thumb_offset += base;
if (tag == 0x89 && type == 4)
thumb_length = get4();
if (tag == 0x8c || tag == 0x96)
meta_offset = ftell(ifp);
if (tag == 0x97)
{
for (i = 0; i < 4; i++)
ver97 = ver97 * 10 + fgetc(ifp) - '0';
switch (ver97)
{
case 100:
fseek(ifp, 68, SEEK_CUR);
FORC4 cam_mul[(c >> 1) | ((c & 1) << 1)] = get2();
break;
case 102:
fseek(ifp, 6, SEEK_CUR);
FORC4 cam_mul[c ^ (c >> 1)] = get2();
break;
case 103:
fseek(ifp, 16, SEEK_CUR);
FORC4 cam_mul[c] = get2();
}
if (ver97 >= 200)
{
if (ver97 != 205)
fseek(ifp, 280, SEEK_CUR);
fread(buf97, 324, 1, ifp);
}
}
if ((tag == 0xa1) && (type == 7) && strncasecmp(make, "Samsung", 7))
{
order = 0x4949;
fseek(ifp, 140, SEEK_CUR);
FORC3 cam_mul[c] = get4();
}
if (tag == 0xa4 && type == 3)
{
fseek(ifp, wbi * 48, SEEK_CUR);
FORC3 cam_mul[c] = get2();
}
if (tag == 0xa7)
{ // shutter count
NikonKey = fgetc(ifp) ^ fgetc(ifp) ^ fgetc(ifp) ^ fgetc(ifp);
if ((unsigned)(ver97 - 200) < 17)
{
ci = xlat[0][serial & 0xff];
cj = xlat[1][NikonKey];
ck = 0x60;
for (i = 0; i < 324; i++)
buf97[i] ^= (cj += ci * ck++);
i = "66666>666;6A;:;55"[ver97 - 200] - '0';
FORC4 cam_mul[c ^ (c >> 1) ^ (i & 1)] = sget2(buf97 + (i & -2) + c * 2);
}
#ifdef LIBRAW_LIBRARY_BUILD
if ((NikonLensDataVersion > 200) && lenNikonLensData)
{
if (custom_serial)
{
ci = xlat[0][custom_serial];
}
else
{
ci = xlat[0][serial & 0xff];
}
cj = xlat[1][NikonKey];
ck = 0x60;
for (i = 0; i < lenNikonLensData; i++)
table_buf[i] ^= (cj += ci * ck++);
processNikonLensData(table_buf, lenNikonLensData);
lenNikonLensData = 0;
free(table_buf);
}
if (ver97 == 601) // Coolpix A
{
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
}
#endif
}
if (tag == 0xb001 && type == 3) // Sony ModelID
{
unique_id = get2();
}
if (tag == 0x200 && len == 3)
shot_order = (get4(), get4());
if (tag == 0x200 && len == 4) // Pentax black level
FORC4 cblack[c ^ c >> 1] = get2();
if (tag == 0x201 && len == 4) // Pentax As Shot WB
FORC4 cam_mul[c ^ (c >> 1)] = get2();
if (tag == 0x220 && type == 7)
meta_offset = ftell(ifp);
if (tag == 0x401 && type == 4 && len == 4)
FORC4 cblack[c ^ c >> 1] = get4();
#ifdef LIBRAW_LIBRARY_BUILD
// not corrected for file bitcount, to be patched in open_datastream
if (tag == 0x03d && strstr(make, "NIKON") && len == 4)
{
FORC4 cblack[c ^ c >> 1] = get2();
i = cblack[3];
FORC3 if (i > cblack[c]) i = cblack[c];
FORC4 cblack[c] -= i;
black += i;
}
#endif
if (tag == 0xe01)
{ /* Nikon Capture Note */
#ifdef LIBRAW_LIBRARY_BUILD
int loopc = 0;
#endif
order = 0x4949;
fseek(ifp, 22, SEEK_CUR);
for (offset = 22; offset + 22 < len; offset += 22 + i)
{
#ifdef LIBRAW_LIBRARY_BUILD
if (loopc++ > 1024)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
tag = get4();
fseek(ifp, 14, SEEK_CUR);
i = get4() - 4;
if (tag == 0x76a43207)
flip = get2();
else
fseek(ifp, i, SEEK_CUR);
}
}
if (tag == 0xe80 && len == 256 && type == 7)
{
fseek(ifp, 48, SEEK_CUR);
cam_mul[0] = get2() * 508 * 1.078 / 0x10000;
cam_mul[2] = get2() * 382 * 1.173 / 0x10000;
}
if (tag == 0xf00 && type == 7)
{
if (len == 614)
fseek(ifp, 176, SEEK_CUR);
else if (len == 734 || len == 1502)
fseek(ifp, 148, SEEK_CUR);
else
goto next;
goto get2_256;
}
if (((tag == 0x1011 && len == 9) || tag == 0x20400200) && strcmp(software, "v757-71"))
for (i = 0; i < 3; i++)
{
#ifdef LIBRAW_LIBRARY_BUILD
if (!imgdata.makernotes.olympus.ColorSpace)
{
FORC3 cmatrix[i][c] = ((short)get2()) / 256.0;
}
else
{
FORC3 imgdata.color.ccm[i][c] = ((short)get2()) / 256.0;
}
#else
FORC3 cmatrix[i][c] = ((short)get2()) / 256.0;
#endif
}
if ((tag == 0x1012 || tag == 0x20400600) && len == 4)
FORC4 cblack[c ^ c >> 1] = get2();
if (tag == 0x1017 || tag == 0x20400100)
cam_mul[0] = get2() / 256.0;
if (tag == 0x1018 || tag == 0x20400100)
cam_mul[2] = get2() / 256.0;
if (tag == 0x2011 && len == 2)
{
get2_256:
order = 0x4d4d;
cam_mul[0] = get2() / 256.0;
cam_mul[2] = get2() / 256.0;
}
if ((tag | 0x70) == 0x2070 && (type == 4 || type == 13))
fseek(ifp, get4() + base, SEEK_SET);
#ifdef LIBRAW_LIBRARY_BUILD
// IB start
if (tag == 0x2010)
{
INT64 _pos3 = ftell(ifp);
parse_makernote(base, 0x2010);
fseek(ifp, _pos3, SEEK_SET);
}
if (((tag == 0x2020) || (tag == 0x3000) || (tag == 0x2030) || (tag == 0x2031) || (tag == 0x2050)) &&
((type == 7) || (type == 13)) && !strncasecmp(make, "Olympus", 7))
{
INT64 _pos3 = ftell(ifp);
parse_makernote(base, tag);
fseek(ifp, _pos3, SEEK_SET);
}
// IB end
#endif
if ((tag == 0x2020) && ((type == 7) || (type == 13)) && !strncmp(buf, "OLYMP", 5))
parse_thumb_note(base, 257, 258);
if (tag == 0x2040)
parse_makernote(base, 0x2040);
if (tag == 0xb028)
{
fseek(ifp, get4() + base, SEEK_SET);
parse_thumb_note(base, 136, 137);
}
if (tag == 0x4001 && len > 500 && len < 100000)
{
i = len == 582 ? 50 : len == 653 ? 68 : len == 5120 ? 142 : 126;
fseek(ifp, i, SEEK_CUR);
FORC4 cam_mul[c ^ (c >> 1)] = get2();
for (i += 18; i <= len; i += 10)
{
get2();
FORC4 sraw_mul[c ^ (c >> 1)] = get2();
if (sraw_mul[1] == 1170)
break;
}
}
if (!strncasecmp(make, "Samsung", 7))
{
if (tag == 0xa020) // get the full Samsung encryption key
for (i = 0; i < 11; i++)
SamsungKey[i] = get4();
if (tag == 0xa021) // get and decode Samsung cam_mul array
FORC4 cam_mul[c ^ (c >> 1)] = get4() - SamsungKey[c];
#ifdef LIBRAW_LIBRARY_BUILD
if (tag == 0xa022)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get4() - SamsungKey[c + 4];
if (imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][0] < (imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][1] >> 1))
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][1] >> 4;
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][3] >> 4;
}
}
if (tag == 0xa023)
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][0] = get4() - SamsungKey[8];
imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][1] = get4() - SamsungKey[9];
imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][3] = get4() - SamsungKey[10];
imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][2] = get4() - SamsungKey[0];
if (imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][0] < (imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][1] >> 1))
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][1] >> 4;
imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Ill_A][3] >> 4;
}
}
if (tag == 0xa024)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][c ^ (c >> 1)] = get4() - SamsungKey[c + 1];
if (imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][0] < (imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][1] >> 1))
{
imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][1] >> 4;
imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_D65][3] >> 4;
}
}
/*
if (tag == 0xa025) {
i = get4();
imgdata.color.linear_max[0] = imgdata.color.linear_max[1] = imgdata.color.linear_max[2] =
imgdata.color.linear_max[3] = i - SamsungKey[0]; printf ("Samsung 0xa025 %d\n", i); }
*/
if (tag == 0xa030 && len == 9)
for (i = 0; i < 3; i++)
FORC3 imgdata.color.ccm[i][c] = (float)((short)((get4() + SamsungKey[i * 3 + c]))) / 256.0;
#endif
if (tag == 0xa031 && len == 9) // get and decode Samsung color matrix
for (i = 0; i < 3; i++)
FORC3 cmatrix[i][c] = (float)((short)((get4() + SamsungKey[i * 3 + c]))) / 256.0;
if (tag == 0xa028)
FORC4 cblack[c ^ (c >> 1)] = get4() - SamsungKey[c];
}
else
{
// Somebody else use 0xa021 and 0xa028?
if (tag == 0xa021)
FORC4 cam_mul[c ^ (c >> 1)] = get4();
if (tag == 0xa028)
FORC4 cam_mul[c ^ (c >> 1)] -= get4();
}
#ifdef LIBRAW_LIBRARY_BUILD
if (tag == 0x4021 && (imgdata.makernotes.canon.multishot[0] = get4()) &&
(imgdata.makernotes.canon.multishot[1] = get4()))
{
if (len >= 4)
{
imgdata.makernotes.canon.multishot[2] = get4();
imgdata.makernotes.canon.multishot[3] = get4();
}
FORC4 cam_mul[c] = 1024;
}
#else
if (tag == 0x4021 && get4() && get4())
FORC4 cam_mul[c] = 1024;
#endif
next:
fseek(ifp, save, SEEK_SET);
}
quit:
order = sorder;
}
/*
Since the TIFF DateTime string has no timezone information,
assume that the camera's clock was set to Universal Time.
*/
void CLASS get_timestamp(int reversed)
{
struct tm t;
char str[20];
int i;
str[19] = 0;
if (reversed)
for (i = 19; i--;)
str[i] = fgetc(ifp);
else
fread(str, 19, 1, ifp);
memset(&t, 0, sizeof t);
if (sscanf(str, "%d:%d:%d %d:%d:%d", &t.tm_year, &t.tm_mon, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec) != 6)
return;
t.tm_year -= 1900;
t.tm_mon -= 1;
t.tm_isdst = -1;
if (mktime(&t) > 0)
timestamp = mktime(&t);
}
void CLASS parse_exif(int base)
{
unsigned kodak, entries, tag, type, len, save, c;
double expo, ape;
kodak = !strncmp(make, "EASTMAN", 7) && tiff_nifds < 3;
entries = get2();
if (!strncmp(make, "Hasselblad", 10) && (tiff_nifds > 3) && (entries > 512))
return;
#ifdef LIBRAW_LIBRARY_BUILD
INT64 fsize = ifp->size();
#endif
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
#ifdef LIBRAW_LIBRARY_BUILD
INT64 savepos = ftell(ifp);
if (len > 8 && savepos + len > fsize * 2)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue;
}
if (callbacks.exif_cb)
{
callbacks.exif_cb(callbacks.exifparser_data, tag, type, len, order, ifp);
fseek(ifp, savepos, SEEK_SET);
}
#endif
switch (tag)
{
#ifdef LIBRAW_LIBRARY_BUILD
case 0x9400:
imgdata.other.exifAmbientTemperature = getreal(type);
if ((imgdata.other.CameraTemperature > -273.15f) && (OlyID == 0x4434353933ULL)) // TG-5
imgdata.other.CameraTemperature += imgdata.other.exifAmbientTemperature;
break;
case 0x9401:
imgdata.other.exifHumidity = getreal(type);
break;
case 0x9402:
imgdata.other.exifPressure = getreal(type);
break;
case 0x9403:
imgdata.other.exifWaterDepth = getreal(type);
break;
case 0x9404:
imgdata.other.exifAcceleration = getreal(type);
break;
case 0x9405:
imgdata.other.exifCameraElevationAngle = getreal(type);
break;
case 0xa405: // FocalLengthIn35mmFormat
imgdata.lens.FocalLengthIn35mmFormat = get2();
break;
case 0xa431: // BodySerialNumber
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
break;
case 0xa432: // LensInfo, 42034dec, Lens Specification per EXIF standard
imgdata.lens.MinFocal = getreal(type);
imgdata.lens.MaxFocal = getreal(type);
imgdata.lens.MaxAp4MinFocal = getreal(type);
imgdata.lens.MaxAp4MaxFocal = getreal(type);
break;
case 0xa435: // LensSerialNumber
stmread(imgdata.lens.LensSerial, len, ifp);
break;
case 0xc630: // DNG LensInfo, Lens Specification per EXIF standard
imgdata.lens.dng.MinFocal = getreal(type);
imgdata.lens.dng.MaxFocal = getreal(type);
imgdata.lens.dng.MaxAp4MinFocal = getreal(type);
imgdata.lens.dng.MaxAp4MaxFocal = getreal(type);
break;
case 0xa433: // LensMake
stmread(imgdata.lens.LensMake, len, ifp);
break;
case 0xa434: // LensModel
stmread(imgdata.lens.Lens, len, ifp);
if (!strncmp(imgdata.lens.Lens, "----", 4))
imgdata.lens.Lens[0] = 0;
break;
case 0x9205:
imgdata.lens.EXIF_MaxAp = libraw_powf64l(2.0f, (getreal(type) / 2.0f));
break;
#endif
case 33434:
tiff_ifd[tiff_nifds - 1].t_shutter = shutter = getreal(type);
break;
case 33437:
aperture = getreal(type);
break; // 0x829d FNumber
case 34855:
iso_speed = get2();
break;
case 34865:
if (iso_speed == 0xffff && !strncasecmp(make, "FUJI", 4))
iso_speed = getreal(type);
break;
case 34866:
if (iso_speed == 0xffff && (!strncasecmp(make, "SONY", 4) || !strncasecmp(make, "CANON", 5)))
iso_speed = getreal(type);
break;
case 36867:
case 36868:
get_timestamp(0);
break;
case 37377:
if ((expo = -getreal(type)) < 128 && shutter == 0.)
tiff_ifd[tiff_nifds - 1].t_shutter = shutter = libraw_powf64l(2.0, expo);
break;
case 37378: // 0x9202 ApertureValue
if ((fabs(ape = getreal(type)) < 256.0) && (!aperture))
aperture = libraw_powf64l(2.0, ape / 2);
break;
case 37385:
flash_used = getreal(type);
break;
case 37386:
focal_len = getreal(type);
break;
case 37500: // tag 0x927c
#ifdef LIBRAW_LIBRARY_BUILD
if (((make[0] == '\0') && (!strncmp(model, "ov5647", 6))) ||
((!strncmp(make, "RaspberryPi", 11)) && (!strncmp(model, "RP_OV5647", 9))) ||
((!strncmp(make, "RaspberryPi", 11)) && (!strncmp(model, "RP_imx219", 9))))
{
char mn_text[512];
char *pos;
char ccms[512];
ushort l;
float num;
fgets(mn_text, MIN(len,511), ifp);
mn_text[511] = 0;
pos = strstr(mn_text, "gain_r=");
if (pos)
cam_mul[0] = atof(pos + 7);
pos = strstr(mn_text, "gain_b=");
if (pos)
cam_mul[2] = atof(pos + 7);
if ((cam_mul[0] > 0.001f) && (cam_mul[2] > 0.001f))
cam_mul[1] = cam_mul[3] = 1.0f;
else
cam_mul[0] = cam_mul[2] = 0.0f;
pos = strstr(mn_text, "ccm=");
if(pos)
{
pos +=4;
char *pos2 = strstr(pos, " ");
if(pos2)
{
l = pos2 - pos;
memcpy(ccms, pos, l);
ccms[l] = '\0';
#if defined WIN32 || defined(__MINGW32__)
// Win32 strtok is already thread-safe
pos = strtok(ccms, ",");
#else
char *last=0;
pos = strtok_r(ccms, ",",&last);
#endif
if(pos)
{
for (l = 0; l < 4; l++)
{
num = 0.0;
for (c = 0; c < 3; c++)
{
imgdata.color.ccm[l][c] = (float)atoi(pos);
num += imgdata.color.ccm[l][c];
#if defined WIN32 || defined(__MINGW32__)
pos = strtok(NULL, ",");
#else
pos = strtok_r(NULL, ",",&last);
#endif
if(!pos) goto end; // broken
}
if (num > 0.01)
FORC3 imgdata.color.ccm[l][c] = imgdata.color.ccm[l][c] / num;
}
}
}
}
end:;
}
else
#endif
parse_makernote(base, 0);
break;
case 40962:
if (kodak)
raw_width = get4();
break;
case 40963:
if (kodak)
raw_height = get4();
break;
case 41730:
if (get4() == 0x20002)
for (exif_cfa = c = 0; c < 8; c += 2)
exif_cfa |= fgetc(ifp) * 0x01010101U << c;
}
fseek(ifp, save, SEEK_SET);
}
}
#ifdef LIBRAW_LIBRARY_BUILD
void CLASS parse_gps_libraw(int base)
{
unsigned entries, tag, type, len, save, c;
entries = get2();
if (entries > 200)
return;
if (entries > 0)
imgdata.other.parsed_gps.gpsparsed = 1;
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
if (len > 1024)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue; // no GPS tags are 1k or larger
}
switch (tag)
{
case 1:
imgdata.other.parsed_gps.latref = getc(ifp);
break;
case 3:
imgdata.other.parsed_gps.longref = getc(ifp);
break;
case 5:
imgdata.other.parsed_gps.altref = getc(ifp);
break;
case 2:
if (len == 3)
FORC(3) imgdata.other.parsed_gps.latitude[c] = getreal(type);
break;
case 4:
if (len == 3)
FORC(3) imgdata.other.parsed_gps.longtitude[c] = getreal(type);
break;
case 7:
if (len == 3)
FORC(3) imgdata.other.parsed_gps.gpstimestamp[c] = getreal(type);
break;
case 6:
imgdata.other.parsed_gps.altitude = getreal(type);
break;
case 9:
imgdata.other.parsed_gps.gpsstatus = getc(ifp);
break;
}
fseek(ifp, save, SEEK_SET);
}
}
#endif
void CLASS parse_gps(int base)
{
unsigned entries, tag, type, len, save, c;
entries = get2();
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
if (len > 1024)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue; // no GPS tags are 1k or larger
}
switch (tag)
{
case 1:
case 3:
case 5:
gpsdata[29 + tag / 2] = getc(ifp);
break;
case 2:
case 4:
case 7:
FORC(6) gpsdata[tag / 3 * 6 + c] = get4();
break;
case 6:
FORC(2) gpsdata[18 + c] = get4();
break;
case 18:
case 29:
fgets((char *)(gpsdata + 14 + tag / 3), MIN(len, 12), ifp);
}
fseek(ifp, save, SEEK_SET);
}
}
void CLASS romm_coeff(float romm_cam[3][3])
{
static const float rgb_romm[3][3] = /* ROMM == Kodak ProPhoto */
{{2.034193, -0.727420, -0.306766}, {-0.228811, 1.231729, -0.002922}, {-0.008565, -0.153273, 1.161839}};
int i, j, k;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
for (cmatrix[i][j] = k = 0; k < 3; k++)
cmatrix[i][j] += rgb_romm[i][k] * romm_cam[k][j];
}
void CLASS parse_mos(int offset)
{
char data[40];
int skip, from, i, c, neut[4], planes = 0, frot = 0;
static const char *mod[] = {"",
"DCB2",
"Volare",
"Cantare",
"CMost",
"Valeo 6",
"Valeo 11",
"Valeo 22",
"Valeo 11p",
"Valeo 17",
"",
"Aptus 17",
"Aptus 22",
"Aptus 75",
"Aptus 65",
"Aptus 54S",
"Aptus 65S",
"Aptus 75S",
"AFi 5",
"AFi 6",
"AFi 7",
"AFi-II 7",
"Aptus-II 7",
"",
"Aptus-II 6",
"",
"",
"Aptus-II 10",
"Aptus-II 5",
"",
"",
"",
"",
"Aptus-II 10R",
"Aptus-II 8",
"",
"Aptus-II 12",
"",
"AFi-II 12"};
float romm_cam[3][3];
fseek(ifp, offset, SEEK_SET);
while (1)
{
if (get4() != 0x504b5453)
break;
get4();
fread(data, 1, 40, ifp);
skip = get4();
from = ftell(ifp);
// IB start
#ifdef LIBRAW_LIBRARY_BUILD
if (!strcmp(data, "CameraObj_camera_type"))
{
stmread(imgdata.lens.makernotes.body, skip, ifp);
}
if (!strcmp(data, "back_serial_number"))
{
char buffer[sizeof(imgdata.shootinginfo.BodySerial)];
char *words[4];
int nwords;
stmread(buffer, skip, ifp);
nwords = getwords(buffer, words, 4, sizeof(imgdata.shootinginfo.BodySerial));
strcpy(imgdata.shootinginfo.BodySerial, words[0]);
}
if (!strcmp(data, "CaptProf_serial_number"))
{
char buffer[sizeof(imgdata.shootinginfo.InternalBodySerial)];
char *words[4];
int nwords;
stmread(buffer, skip, ifp);
nwords = getwords(buffer, words, 4, sizeof(imgdata.shootinginfo.InternalBodySerial));
strcpy(imgdata.shootinginfo.InternalBodySerial, words[0]);
}
#endif
// IB end
if (!strcmp(data, "JPEG_preview_data"))
{
thumb_offset = from;
thumb_length = skip;
}
if (!strcmp(data, "icc_camera_profile"))
{
profile_offset = from;
profile_length = skip;
}
if (!strcmp(data, "ShootObj_back_type"))
{
fscanf(ifp, "%d", &i);
if ((unsigned)i < sizeof mod / sizeof(*mod))
strcpy(model, mod[i]);
}
if (!strcmp(data, "icc_camera_to_tone_matrix"))
{
for (i = 0; i < 9; i++)
((float *)romm_cam)[i] = int_to_float(get4());
romm_coeff(romm_cam);
}
if (!strcmp(data, "CaptProf_color_matrix"))
{
for (i = 0; i < 9; i++)
fscanf(ifp, "%f", (float *)romm_cam + i);
romm_coeff(romm_cam);
}
if (!strcmp(data, "CaptProf_number_of_planes"))
fscanf(ifp, "%d", &planes);
if (!strcmp(data, "CaptProf_raw_data_rotation"))
fscanf(ifp, "%d", &flip);
if (!strcmp(data, "CaptProf_mosaic_pattern"))
FORC4
{
fscanf(ifp, "%d", &i);
if (i == 1)
frot = c ^ (c >> 1);
}
if (!strcmp(data, "ImgProf_rotation_angle"))
{
fscanf(ifp, "%d", &i);
flip = i - flip;
}
if (!strcmp(data, "NeutObj_neutrals") && !cam_mul[0])
{
FORC4 fscanf(ifp, "%d", neut + c);
FORC3 cam_mul[c] = (float)neut[0] / neut[c + 1];
}
if (!strcmp(data, "Rows_data"))
load_flags = get4();
parse_mos(from);
fseek(ifp, skip + from, SEEK_SET);
}
if (planes)
filters = (planes == 1) * 0x01010101U * (uchar) "\x94\x61\x16\x49"[(flip / 90 + frot) & 3];
}
void CLASS linear_table(unsigned len)
{
int i;
if (len > 0x10000)
len = 0x10000;
else if(len < 1)
return;
read_shorts(curve, len);
for (i = len; i < 0x10000; i++)
curve[i] = curve[i - 1];
maximum = curve[len < 0x1000 ? 0xfff : len - 1];
}
#ifdef LIBRAW_LIBRARY_BUILD
void CLASS Kodak_WB_0x08tags(int wb, unsigned type)
{
float mul[3] = {1, 1, 1}, num, mul2;
int c;
FORC3 mul[c] = (num = getreal(type)) == 0 ? 1 : num;
imgdata.color.WB_Coeffs[wb][1] = imgdata.color.WB_Coeffs[wb][3] = mul[1];
mul2 = mul[1] * mul[1];
imgdata.color.WB_Coeffs[wb][0] = mul2 / mul[0];
imgdata.color.WB_Coeffs[wb][2] = mul2 / mul[2];
return;
}
/* Thanks to Alexey Danilchenko for wb as-shot parsing code */
void CLASS parse_kodak_ifd(int base)
{
unsigned entries, tag, type, len, save;
int j, c, wbi = -2, romm_camTemp[9], romm_camScale[3];
float mul[3] = {1, 1, 1}, num;
static const int wbtag[] = {64037, 64040, 64039, 64041, -1, -1, 64042};
// int a_blck = 0;
entries = get2();
if (entries > 1024)
return;
INT64 fsize = ifp->size();
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
INT64 savepos = ftell(ifp);
if (len > 8 && len + savepos > 2 * fsize)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue;
}
if (callbacks.exif_cb)
{
callbacks.exif_cb(callbacks.exifparser_data, tag | 0x20000, type, len, order, ifp);
fseek(ifp, savepos, SEEK_SET);
}
if (tag == 1003)
imgdata.sizes.raw_crop.cleft = get2();
if (tag == 1004)
imgdata.sizes.raw_crop.ctop = get2();
if (tag == 1005)
imgdata.sizes.raw_crop.cwidth = get2();
if (tag == 1006)
imgdata.sizes.raw_crop.cheight = get2();
if (tag == 1007)
imgdata.makernotes.kodak.BlackLevelTop = get2();
if (tag == 1008)
imgdata.makernotes.kodak.BlackLevelBottom = get2();
if (tag == 1011)
imgdata.other.FlashEC = getreal(type);
if (tag == 1020)
wbi = getint(type);
if (tag == 1021 && len == 72)
{ /* WB set in software */
fseek(ifp, 40, SEEK_CUR);
FORC3 cam_mul[c] = 2048.0 / fMAX(1.0f, get2());
wbi = -2;
}
if ((tag == 1030) && (len == 1))
imgdata.other.CameraTemperature = getreal(type);
if ((tag == 1043) && (len == 1))
imgdata.other.SensorTemperature = getreal(type);
if ((tag == 0x03ef) && (!strcmp(model, "EOS D2000C")))
black = get2();
if ((tag == 0x03f0) && (!strcmp(model, "EOS D2000C")))
{
if (black) // already set by tag 0x03ef
black = (black + get2()) / 2;
else
black = get2();
}
INT64 _pos2 = ftell(ifp);
if (tag == 0x0848)
Kodak_WB_0x08tags(LIBRAW_WBI_Daylight, type);
if (tag == 0x0849)
Kodak_WB_0x08tags(LIBRAW_WBI_Tungsten, type);
if (tag == 0x084a)
Kodak_WB_0x08tags(LIBRAW_WBI_Fluorescent, type);
if (tag == 0x084b)
Kodak_WB_0x08tags(LIBRAW_WBI_Flash, type);
if (tag == 0x084c)
Kodak_WB_0x08tags(LIBRAW_WBI_Custom, type);
if (tag == 0x084d)
Kodak_WB_0x08tags(LIBRAW_WBI_Auto, type);
if (tag == 0x0e93)
imgdata.color.linear_max[0] = imgdata.color.linear_max[1] = imgdata.color.linear_max[2] =
imgdata.color.linear_max[3] = get2();
if (tag == 0x09ce)
stmread(imgdata.shootinginfo.InternalBodySerial, len, ifp);
if (tag == 0xfa00)
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
if (tag == 0xfa27)
{
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][c] = get4();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][1];
}
if (tag == 0xfa28)
{
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][c] = get4();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][1];
}
if (tag == 0xfa29)
{
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][c] = get4();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][1];
}
if (tag == 0xfa2a)
{
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][c] = get4();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][1];
}
fseek(ifp, _pos2, SEEK_SET);
if (((tag == 0x07e4) || (tag == 0xfb01)) && (len == 9))
{
short validM = 0;
if (type == 10)
{
for (j = 0; j < 9; j++)
{
((float *)imgdata.makernotes.kodak.romm_camDaylight)[j] = getreal(type);
}
validM = 1;
}
else if (type == 9)
{
FORC3
{
romm_camScale[c] = 0;
for (j = 0; j < 3; j++)
{
romm_camTemp[c * 3 + j] = get4();
romm_camScale[c] += romm_camTemp[c * 3 + j];
}
}
if ((romm_camScale[0] > 0x1fff) && (romm_camScale[1] > 0x1fff) && (romm_camScale[2] > 0x1fff))
{
FORC3 for (j = 0; j < 3; j++)
{
((float *)imgdata.makernotes.kodak.romm_camDaylight)[c * 3 + j] =
((float)romm_camTemp[c * 3 + j]) / ((float)romm_camScale[c]);
}
validM = 1;
}
}
if (validM)
{
romm_coeff(imgdata.makernotes.kodak.romm_camDaylight);
}
}
if (((tag == 0x07e5) || (tag == 0xfb02)) && (len == 9))
{
if (type == 10)
{
for (j = 0; j < 9; j++)
{
((float *)imgdata.makernotes.kodak.romm_camTungsten)[j] = getreal(type);
}
}
else if (type == 9)
{
FORC3
{
romm_camScale[c] = 0;
for (j = 0; j < 3; j++)
{
romm_camTemp[c * 3 + j] = get4();
romm_camScale[c] += romm_camTemp[c * 3 + j];
}
}
if ((romm_camScale[0] > 0x1fff) && (romm_camScale[1] > 0x1fff) && (romm_camScale[2] > 0x1fff))
{
FORC3 for (j = 0; j < 3; j++)
{
((float *)imgdata.makernotes.kodak.romm_camTungsten)[c * 3 + j] =
((float)romm_camTemp[c * 3 + j]) / ((float)romm_camScale[c]);
}
}
}
}
if (((tag == 0x07e6) || (tag == 0xfb03)) && (len == 9))
{
if (type == 10)
{
for (j = 0; j < 9; j++)
{
((float *)imgdata.makernotes.kodak.romm_camFluorescent)[j] = getreal(type);
}
}
else if (type == 9)
{
FORC3
{
romm_camScale[c] = 0;
for (j = 0; j < 3; j++)
{
romm_camTemp[c * 3 + j] = get4();
romm_camScale[c] += romm_camTemp[c * 3 + j];
}
}
if ((romm_camScale[0] > 0x1fff) && (romm_camScale[1] > 0x1fff) && (romm_camScale[2] > 0x1fff))
{
FORC3 for (j = 0; j < 3; j++)
{
((float *)imgdata.makernotes.kodak.romm_camFluorescent)[c * 3 + j] =
((float)romm_camTemp[c * 3 + j]) / ((float)romm_camScale[c]);
}
}
}
}
if (((tag == 0x07e7) || (tag == 0xfb04)) && (len == 9))
{
if (type == 10)
{
for (j = 0; j < 9; j++)
{
((float *)imgdata.makernotes.kodak.romm_camFlash)[j] = getreal(type);
}
}
else if (type == 9)
{
FORC3
{
romm_camScale[c] = 0;
for (j = 0; j < 3; j++)
{
romm_camTemp[c * 3 + j] = get4();
romm_camScale[c] += romm_camTemp[c * 3 + j];
}
}
if ((romm_camScale[0] > 0x1fff) && (romm_camScale[1] > 0x1fff) && (romm_camScale[2] > 0x1fff))
{
FORC3 for (j = 0; j < 3; j++)
{
((float *)imgdata.makernotes.kodak.romm_camFlash)[c * 3 + j] =
((float)romm_camTemp[c * 3 + j]) / ((float)romm_camScale[c]);
}
}
}
}
if (((tag == 0x07e8) || (tag == 0xfb05)) && (len == 9))
{
if (type == 10)
{
for (j = 0; j < 9; j++)
{
((float *)imgdata.makernotes.kodak.romm_camCustom)[j] = getreal(type);
}
}
else if (type == 9)
{
FORC3
{
romm_camScale[c] = 0;
for (j = 0; j < 3; j++)
{
romm_camTemp[c * 3 + j] = get4();
romm_camScale[c] += romm_camTemp[c * 3 + j];
}
}
if ((romm_camScale[0] > 0x1fff) && (romm_camScale[1] > 0x1fff) && (romm_camScale[2] > 0x1fff))
{
FORC3 for (j = 0; j < 3; j++)
{
((float *)imgdata.makernotes.kodak.romm_camCustom)[c * 3 + j] =
((float)romm_camTemp[c * 3 + j]) / ((float)romm_camScale[c]);
}
}
}
}
if (((tag == 0x07e9) || (tag == 0xfb06)) && (len == 9))
{
if (type == 10)
{
for (j = 0; j < 9; j++)
{
((float *)imgdata.makernotes.kodak.romm_camAuto)[j] = getreal(type);
}
}
else if (type == 9)
{
FORC3
{
romm_camScale[c] = 0;
for (j = 0; j < 3; j++)
{
romm_camTemp[c * 3 + j] = get4();
romm_camScale[c] += romm_camTemp[c * 3 + j];
}
}
if ((romm_camScale[0] > 0x1fff) && (romm_camScale[1] > 0x1fff) && (romm_camScale[2] > 0x1fff))
{
FORC3 for (j = 0; j < 3; j++)
{
((float *)imgdata.makernotes.kodak.romm_camAuto)[c * 3 + j] =
((float)romm_camTemp[c * 3 + j]) / ((float)romm_camScale[c]);
}
}
}
}
if (tag == 2120 + wbi || (wbi < 0 && tag == 2125)) /* use Auto WB if illuminant index is not set */
{
FORC3 mul[c] = (num = getreal(type)) == 0 ? 1 : num;
FORC3 cam_mul[c] = mul[1] / mul[c]; /* normalise against green */
}
if (tag == 2317)
linear_table(len);
if (tag == 0x903)
iso_speed = getreal(type);
// if (tag == 6020) iso_speed = getint(type);
if (tag == 64013)
wbi = fgetc(ifp);
if ((unsigned)wbi < 7 && tag == wbtag[wbi])
FORC3 cam_mul[c] = get4();
if (tag == 0xfa13)
width = getint(type);
if (tag == 0xfa14)
height = (getint(type) + 1) & -2;
/*
height = getint(type);
if (tag == 0xfa16)
raw_width = get2();
if (tag == 0xfa17)
raw_height = get2();
*/
if (tag == 0xfa18)
{
imgdata.makernotes.kodak.offset_left = getint(8);
if (type != 8)
imgdata.makernotes.kodak.offset_left += 1;
}
if (tag == 0xfa19)
{
imgdata.makernotes.kodak.offset_top = getint(8);
if (type != 8)
imgdata.makernotes.kodak.offset_top += 1;
}
if (tag == 0xfa31)
imgdata.sizes.raw_crop.cwidth = get2();
if (tag == 0xfa32)
imgdata.sizes.raw_crop.cheight = get2();
if (tag == 0xfa3e)
imgdata.sizes.raw_crop.cleft = get2();
if (tag == 0xfa3f)
imgdata.sizes.raw_crop.ctop = get2();
fseek(ifp, save, SEEK_SET);
}
}
#else
void CLASS parse_kodak_ifd(int base)
{
unsigned entries, tag, type, len, save;
int i, c, wbi = -2, wbtemp = 6500;
float mul[3] = {1, 1, 1}, num;
static const int wbtag[] = {64037, 64040, 64039, 64041, -1, -1, 64042};
entries = get2();
if (entries > 1024)
return;
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
if (tag == 1020)
wbi = getint(type);
if (tag == 1021 && len == 72)
{ /* WB set in software */
fseek(ifp, 40, SEEK_CUR);
FORC3 cam_mul[c] = 2048.0 / fMAX(1.0, get2());
wbi = -2;
}
if (tag == 2118)
wbtemp = getint(type);
if (tag == 2120 + wbi && wbi >= 0)
FORC3 cam_mul[c] = 2048.0 / fMAX(1.0, getreal(type));
if (tag == 2130 + wbi)
FORC3 mul[c] = getreal(type);
if (tag == 2140 + wbi && wbi >= 0)
FORC3
{
for (num = i = 0; i < 4; i++)
num += getreal(type) * pow(wbtemp / 100.0, i);
cam_mul[c] = 2048 / fMAX(1.0, (num * mul[c]));
}
if (tag == 2317)
linear_table(len);
if (tag == 6020)
iso_speed = getint(type);
if (tag == 64013)
wbi = fgetc(ifp);
if ((unsigned)wbi < 7 && tag == wbtag[wbi])
FORC3 cam_mul[c] = get4();
if (tag == 64019)
width = getint(type);
if (tag == 64020)
height = (getint(type) + 1) & -2;
fseek(ifp, save, SEEK_SET);
}
}
#endif
int CLASS parse_tiff_ifd(int base)
{
unsigned entries, tag, type, len, plen = 16, save;
int ifd, use_cm = 0, cfa, i, j, c, ima_len = 0;
char *cbuf, *cp;
uchar cfa_pat[16], cfa_pc[] = {0, 1, 2, 3}, tab[256];
double fm[3][4], cc[4][4], cm[4][3], cam_xyz[4][3], num;
double ab[] = {1, 1, 1, 1}, asn[] = {0, 0, 0, 0}, xyz[] = {1, 1, 1};
unsigned sony_curve[] = {0, 0, 0, 0, 0, 4095};
unsigned *buf, sony_offset = 0, sony_length = 0, sony_key = 0;
struct jhead jh;
int pana_raw = 0;
#ifndef LIBRAW_LIBRARY_BUILD
FILE *sfp;
#endif
if (tiff_nifds >= sizeof tiff_ifd / sizeof tiff_ifd[0])
return 1;
ifd = tiff_nifds++;
for (j = 0; j < 4; j++)
for (i = 0; i < 4; i++)
cc[j][i] = i == j;
entries = get2();
if (entries > 512)
return 1;
#ifdef LIBRAW_LIBRARY_BUILD
INT64 fsize = ifp->size();
#endif
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
#ifdef LIBRAW_LIBRARY_BUILD
INT64 savepos = ftell(ifp);
if (len > 8 && savepos + len > 2 * fsize)
{
fseek(ifp, save, SEEK_SET); // Recover tiff-read position!!
continue;
}
if (callbacks.exif_cb)
{
callbacks.exif_cb(callbacks.exifparser_data, tag | (pana_raw ? 0x30000 : ((ifd + 1) << 20)), type, len, order,
ifp);
fseek(ifp, savepos, SEEK_SET);
}
#endif
#ifdef LIBRAW_LIBRARY_BUILD
if (!strncasecmp(make, "SONY", 4) ||
(!strncasecmp(make, "Hasselblad", 10) &&
(!strncasecmp(model, "Stellar", 7) || !strncasecmp(model, "Lunar", 5) || !strncasecmp(model, "HV", 2))))
{
switch (tag)
{
case 0x7300: // SR2 black level
for (int i = 0; i < 4 && i < len; i++)
cblack[i] = get2();
break;
case 0x7302:
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c < 2)] = get2();
break;
case 0x7312:
{
int i, lc[4];
FORC4 lc[c] = get2();
i = (lc[1] == 1024 && lc[2] == 1024) << 1;
SWAP(lc[i], lc[i + 1]);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c] = lc[c];
}
break;
case 0x7480:
case 0x7820:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][1];
break;
case 0x7481:
case 0x7821:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][1];
break;
case 0x7482:
case 0x7822:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][1];
break;
case 0x7483:
case 0x7823:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1];
break;
case 0x7484:
case 0x7824:
imgdata.color.WBCT_Coeffs[0][0] = 4500;
FORC3 imgdata.color.WBCT_Coeffs[0][c + 1] = get2();
imgdata.color.WBCT_Coeffs[0][4] = imgdata.color.WBCT_Coeffs[0][2];
break;
case 0x7486:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][1];
break;
case 0x7825:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][1];
break;
case 0x7826:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][1];
break;
case 0x7827:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][1];
break;
case 0x7828:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][1];
break;
case 0x7829:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][c] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][1];
break;
case 0x782a:
imgdata.color.WBCT_Coeffs[1][0] = 8500;
FORC3 imgdata.color.WBCT_Coeffs[1][c + 1] = get2();
imgdata.color.WBCT_Coeffs[1][4] = imgdata.color.WBCT_Coeffs[1][2];
break;
case 0x782b:
imgdata.color.WBCT_Coeffs[2][0] = 6000;
FORC3 imgdata.color.WBCT_Coeffs[2][c + 1] = get2();
imgdata.color.WBCT_Coeffs[2][4] = imgdata.color.WBCT_Coeffs[2][2];
break;
case 0x782c:
imgdata.color.WBCT_Coeffs[3][0] = 3200;
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_StudioTungsten][c] = imgdata.color.WBCT_Coeffs[3][c + 1] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_StudioTungsten][3] = imgdata.color.WBCT_Coeffs[3][4] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_StudioTungsten][1];
break;
case 0x782d:
imgdata.color.WBCT_Coeffs[4][0] = 2500;
FORC3 imgdata.color.WBCT_Coeffs[4][c + 1] = get2();
imgdata.color.WBCT_Coeffs[4][4] = imgdata.color.WBCT_Coeffs[4][2];
break;
case 0x787f:
if (len == 3)
{
FORC3 imgdata.color.linear_max[c] = get2();
imgdata.color.linear_max[3] = imgdata.color.linear_max[1];
}
else if (len == 1)
{
imgdata.color.linear_max[0] = imgdata.color.linear_max[1] = imgdata.color.linear_max[2] =
imgdata.color.linear_max[3] = getreal(type); // Is non-short possible here??
}
break;
}
}
#endif
switch (tag)
{
case 1:
if (len == 4)
pana_raw = get4();
break;
case 5:
width = get2();
break;
case 6:
height = get2();
break;
case 7:
width += get2();
break;
case 9:
if ((i = get2()))
filters = i;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 10:
if (pana_raw && len == 1 && type == 3)
{
pana_bpp = get2();
}
break;
#endif
case 14:
case 15:
case 16:
#ifdef LIBRAW_LIBRARY_BUILD
if (pana_raw)
{
imgdata.color.linear_max[tag - 14] = get2();
if (tag == 15)
imgdata.color.linear_max[3] = imgdata.color.linear_max[1];
}
#endif
break;
case 17:
case 18:
if (type == 3 && len == 1)
cam_mul[(tag - 17) * 2] = get2() / 256.0;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 19:
if (pana_raw)
{
ushort nWB, cnt, tWB;
nWB = get2();
if (nWB > 0x100)
break;
for (cnt = 0; cnt < nWB; cnt++)
{
tWB = get2();
if (tWB < 0x100)
{
imgdata.color.WB_Coeffs[tWB][0] = get2();
imgdata.color.WB_Coeffs[tWB][2] = get2();
imgdata.color.WB_Coeffs[tWB][1] = imgdata.color.WB_Coeffs[tWB][3] = 0x100;
}
else
get4();
}
}
break;
case 0x0120:
if (pana_raw)
{
unsigned sorder = order;
unsigned long sbase = base;
base = ftell(ifp);
order = get2();
fseek(ifp, 2, SEEK_CUR);
fseek(ifp, get4()-8, SEEK_CUR);
parse_tiff_ifd (base);
base = sbase;
order = sorder;
}
break;
case 0x2009:
if ((pana_encoding == 4) || (pana_encoding == 5))
{
int n = MIN (8, len);
int permut[8] = {3, 2, 1, 0, 3+4, 2+4, 1+4, 0+4};
imgdata.makernotes.panasonic.BlackLevelDim = len;
for (int i=0; i < n; i++)
{
imgdata.makernotes.panasonic.BlackLevel[permut[i]] =
(float) (get2()) / (float) (powf(2.f, 14.f-pana_bpp));
}
}
break;
#endif
case 23:
if (type == 3)
iso_speed = get2();
break;
case 28:
case 29:
case 30:
#ifdef LIBRAW_LIBRARY_BUILD
if (pana_raw && len == 1 && type == 3)
{
pana_black[tag - 28] = get2();
}
else
#endif
{
cblack[tag - 28] = get2();
cblack[3] = cblack[1];
}
break;
case 36:
case 37:
case 38:
cam_mul[tag - 36] = get2();
break;
case 39:
#ifdef LIBRAW_LIBRARY_BUILD
if (pana_raw)
{
ushort nWB, cnt, tWB;
nWB = get2();
if (nWB > 0x100)
break;
for (cnt = 0; cnt < nWB; cnt++)
{
tWB = get2();
if (tWB < 0x100)
{
imgdata.color.WB_Coeffs[tWB][0] = get2();
imgdata.color.WB_Coeffs[tWB][1] = imgdata.color.WB_Coeffs[tWB][3] = get2();
imgdata.color.WB_Coeffs[tWB][2] = get2();
}
else
fseek(ifp, 6, SEEK_CUR);
}
}
break;
#endif
if (len < 50 || cam_mul[0])
break;
fseek(ifp, 12, SEEK_CUR);
FORC3 cam_mul[c] = get2();
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 45:
if (pana_raw && len == 1 && type == 3)
{
pana_encoding = get2();
}
break;
#endif
case 46:
if (type != 7 || fgetc(ifp) != 0xff || fgetc(ifp) != 0xd8)
break;
thumb_offset = ftell(ifp) - 2;
thumb_length = len;
break;
case 61440: /* Fuji HS10 table */
fseek(ifp, get4() + base, SEEK_SET);
parse_tiff_ifd(base);
break;
case 2:
case 256:
case 61441: /* ImageWidth */
tiff_ifd[ifd].t_width = getint(type);
break;
case 3:
case 257:
case 61442: /* ImageHeight */
tiff_ifd[ifd].t_height = getint(type);
break;
case 258: /* BitsPerSample */
case 61443:
tiff_ifd[ifd].samples = len & 7;
tiff_ifd[ifd].bps = getint(type);
if (tiff_bps < tiff_ifd[ifd].bps)
tiff_bps = tiff_ifd[ifd].bps;
break;
case 61446:
raw_height = 0;
if (tiff_ifd[ifd].bps > 12)
break;
load_raw = &CLASS packed_load_raw;
load_flags = get4() ? 24 : 80;
break;
case 259: /* Compression */
tiff_ifd[ifd].comp = getint(type);
break;
case 262: /* PhotometricInterpretation */
tiff_ifd[ifd].phint = get2();
break;
case 270: /* ImageDescription */
fread(desc, 512, 1, ifp);
break;
case 271: /* Make */
fgets(make, 64, ifp);
break;
case 272: /* Model */
fgets(model, 64, ifp);
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 278:
tiff_ifd[ifd].rows_per_strip = getint(type);
break;
#endif
case 280: /* Panasonic RW2 offset */
if (type != 4)
break;
load_raw = &CLASS panasonic_load_raw;
load_flags = 0x2008;
case 273: /* StripOffset */
#ifdef LIBRAW_LIBRARY_BUILD
if (len > 1 && len < 16384)
{
off_t sav = ftell(ifp);
tiff_ifd[ifd].strip_offsets = (int *)calloc(len, sizeof(int));
tiff_ifd[ifd].strip_offsets_count = len;
for (int i = 0; i < len; i++)
tiff_ifd[ifd].strip_offsets[i] = get4() + base;
fseek(ifp, sav, SEEK_SET); // restore position
}
/* fallback */
#endif
case 513: /* JpegIFOffset */
case 61447:
tiff_ifd[ifd].offset = get4() + base;
if (!tiff_ifd[ifd].bps && tiff_ifd[ifd].offset > 0)
{
fseek(ifp, tiff_ifd[ifd].offset, SEEK_SET);
if (ljpeg_start(&jh, 1))
{
tiff_ifd[ifd].comp = 6;
tiff_ifd[ifd].t_width = jh.wide;
tiff_ifd[ifd].t_height = jh.high;
tiff_ifd[ifd].bps = jh.bits;
tiff_ifd[ifd].samples = jh.clrs;
if (!(jh.sraw || (jh.clrs & 1)))
tiff_ifd[ifd].t_width *= jh.clrs;
if ((tiff_ifd[ifd].t_width > 4 * tiff_ifd[ifd].t_height) & ~jh.clrs)
{
tiff_ifd[ifd].t_width /= 2;
tiff_ifd[ifd].t_height *= 2;
}
i = order;
parse_tiff(tiff_ifd[ifd].offset + 12);
order = i;
}
}
break;
case 274: /* Orientation */
tiff_ifd[ifd].t_flip = "50132467"[get2() & 7] - '0';
break;
case 277: /* SamplesPerPixel */
tiff_ifd[ifd].samples = getint(type) & 7;
break;
case 279: /* StripByteCounts */
#ifdef LIBRAW_LIBRARY_BUILD
if (len > 1 && len < 16384)
{
off_t sav = ftell(ifp);
tiff_ifd[ifd].strip_byte_counts = (int *)calloc(len, sizeof(int));
tiff_ifd[ifd].strip_byte_counts_count = len;
for (int i = 0; i < len; i++)
tiff_ifd[ifd].strip_byte_counts[i] = get4();
fseek(ifp, sav, SEEK_SET); // restore position
}
/* fallback */
#endif
case 514:
case 61448:
tiff_ifd[ifd].bytes = get4();
break;
case 61454: // FujiFilm "As Shot"
FORC3 cam_mul[(4 - c) % 3] = getint(type);
break;
case 305:
case 11: /* Software */
if ((pana_raw) && (tag == 11) && (type == 3))
{
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.makernotes.panasonic.Compression = get2();
#endif
break;
}
fgets(software, 64, ifp);
if (!strncmp(software, "Adobe", 5) || !strncmp(software, "dcraw", 5) || !strncmp(software, "UFRaw", 5) ||
!strncmp(software, "Bibble", 6) || !strcmp(software, "Digital Photo Professional"))
is_raw = 0;
break;
case 306: /* DateTime */
get_timestamp(0);
break;
case 315: /* Artist */
fread(artist, 64, 1, ifp);
break;
case 317:
tiff_ifd[ifd].predictor = getint(type);
break;
case 322: /* TileWidth */
tiff_ifd[ifd].t_tile_width = getint(type);
break;
case 323: /* TileLength */
tiff_ifd[ifd].t_tile_length = getint(type);
break;
case 324: /* TileOffsets */
tiff_ifd[ifd].offset = len > 1 ? ftell(ifp) : get4();
if (len == 1)
tiff_ifd[ifd].t_tile_width = tiff_ifd[ifd].t_tile_length = 0;
if (len == 4)
{
load_raw = &CLASS sinar_4shot_load_raw;
is_raw = 5;
}
break;
case 325:
tiff_ifd[ifd].bytes = len > 1 ? ftell(ifp) : get4();
break;
case 330: /* SubIFDs */
if (!strcmp(model, "DSLR-A100") && tiff_ifd[ifd].t_width == 3872)
{
load_raw = &CLASS sony_arw_load_raw;
data_offset = get4() + base;
ifd++;
#ifdef LIBRAW_LIBRARY_BUILD
if (ifd >= sizeof tiff_ifd / sizeof tiff_ifd[0])
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
break;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (!strncmp(make, "Hasselblad", 10) && libraw_internal_data.unpacker_data.hasselblad_parser_flag)
{
fseek(ifp, ftell(ifp) + 4, SEEK_SET);
fseek(ifp, get4() + base, SEEK_SET);
parse_tiff_ifd(base);
break;
}
#endif
if (len > 1000)
len = 1000; /* 1000 SubIFDs is enough */
while (len--)
{
i = ftell(ifp);
fseek(ifp, get4() + base, SEEK_SET);
if (parse_tiff_ifd(base))
break;
fseek(ifp, i + 4, SEEK_SET);
}
break;
case 339:
tiff_ifd[ifd].sample_format = getint(type);
break;
case 400:
strcpy(make, "Sarnoff");
maximum = 0xfff;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 700:
if ((type == 1 || type == 2 || type == 6 || type == 7) && len > 1 && len < 5100000)
{
xmpdata = (char *)malloc(xmplen = len + 1);
fread(xmpdata, len, 1, ifp);
xmpdata[len] = 0;
}
break;
#endif
case 28688:
FORC4 sony_curve[c + 1] = get2() >> 2 & 0xfff;
for (i = 0; i < 5; i++)
for (j = sony_curve[i] + 1; j <= sony_curve[i + 1]; j++)
curve[j] = curve[j - 1] + (1 << i);
break;
case 29184:
sony_offset = get4();
break;
case 29185:
sony_length = get4();
break;
case 29217:
sony_key = get4();
break;
case 29264:
parse_minolta(ftell(ifp));
raw_width = 0;
break;
case 29443:
FORC4 cam_mul[c ^ (c < 2)] = get2();
break;
case 29459:
FORC4 cam_mul[c] = get2();
i = (cam_mul[1] == 1024 && cam_mul[2] == 1024) << 1;
SWAP(cam_mul[i], cam_mul[i + 1])
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 30720: // Sony matrix, Sony_SR2SubIFD_0x7800
for (i = 0; i < 3; i++)
{
float num = 0.0;
for (c = 0; c < 3; c++)
{
imgdata.color.ccm[i][c] = (float)((short)get2());
num += imgdata.color.ccm[i][c];
}
if (num > 0.01)
FORC3 imgdata.color.ccm[i][c] = imgdata.color.ccm[i][c] / num;
}
break;
#endif
case 29456: // Sony black level, Sony_SR2SubIFD_0x7310, no more needs to be divided by 4
FORC4 cblack[c ^ c >> 1] = get2();
i = cblack[3];
FORC3 if (i > cblack[c]) i = cblack[c];
FORC4 cblack[c] -= i;
black = i;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("...Sony black: %u cblack: %u %u %u %u\n"), black, cblack[0], cblack[1], cblack[2],
cblack[3]);
#endif
break;
case 33405: /* Model2 */
fgets(model2, 64, ifp);
break;
case 33421: /* CFARepeatPatternDim */
if (get2() == 6 && get2() == 6)
filters = 9;
break;
case 33422: /* CFAPattern */
if (filters == 9)
{
FORC(36)((char *)xtrans)[c] = fgetc(ifp) & 3;
break;
}
case 64777: /* Kodak P-series */
if (len == 36)
{
filters = 9;
colors = 3;
FORC(36) xtrans[0][c] = fgetc(ifp) & 3;
}
else if (len > 0)
{
if ((plen = len) > 16)
plen = 16;
fread(cfa_pat, 1, plen, ifp);
for (colors = cfa = i = 0; i < plen && colors < 4; i++)
{
if(cfa_pat[i] > 31) continue; // Skip wrong data
colors += !(cfa & (1 << cfa_pat[i]));
cfa |= 1 << cfa_pat[i];
}
if (cfa == 070)
memcpy(cfa_pc, "\003\004\005", 3); /* CMY */
if (cfa == 072)
memcpy(cfa_pc, "\005\003\004\001", 4); /* GMCY */
goto guess_cfa_pc;
}
break;
case 33424:
case 65024:
fseek(ifp, get4() + base, SEEK_SET);
parse_kodak_ifd(base);
break;
case 33434: /* ExposureTime */
tiff_ifd[ifd].t_shutter = shutter = getreal(type);
break;
case 33437: /* FNumber */
aperture = getreal(type);
break;
#ifdef LIBRAW_LIBRARY_BUILD
// IB start
case 0x9400:
imgdata.other.exifAmbientTemperature = getreal(type);
if ((imgdata.other.CameraTemperature > -273.15f) && (OlyID == 0x4434353933ULL)) // TG-5
imgdata.other.CameraTemperature += imgdata.other.exifAmbientTemperature;
break;
case 0x9401:
imgdata.other.exifHumidity = getreal(type);
break;
case 0x9402:
imgdata.other.exifPressure = getreal(type);
break;
case 0x9403:
imgdata.other.exifWaterDepth = getreal(type);
break;
case 0x9404:
imgdata.other.exifAcceleration = getreal(type);
break;
case 0x9405:
imgdata.other.exifCameraElevationAngle = getreal(type);
break;
case 0xa405: // FocalLengthIn35mmFormat
imgdata.lens.FocalLengthIn35mmFormat = get2();
break;
case 0xa431: // BodySerialNumber
case 0xc62f:
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
break;
case 0xa432: // LensInfo, 42034dec, Lens Specification per EXIF standard
imgdata.lens.MinFocal = getreal(type);
imgdata.lens.MaxFocal = getreal(type);
imgdata.lens.MaxAp4MinFocal = getreal(type);
imgdata.lens.MaxAp4MaxFocal = getreal(type);
break;
case 0xa435: // LensSerialNumber
stmread(imgdata.lens.LensSerial, len, ifp);
break;
case 0xc630: // DNG LensInfo, Lens Specification per EXIF standard
imgdata.lens.MinFocal = getreal(type);
imgdata.lens.MaxFocal = getreal(type);
imgdata.lens.MaxAp4MinFocal = getreal(type);
imgdata.lens.MaxAp4MaxFocal = getreal(type);
break;
case 0xa433: // LensMake
stmread(imgdata.lens.LensMake, len, ifp);
break;
case 0xa434: // LensModel
stmread(imgdata.lens.Lens, len, ifp);
if (!strncmp(imgdata.lens.Lens, "----", 4))
imgdata.lens.Lens[0] = 0;
break;
case 0x9205:
imgdata.lens.EXIF_MaxAp = libraw_powf64l(2.0f, (getreal(type) / 2.0f));
break;
// IB end
#endif
case 34306: /* Leaf white balance */
FORC4
{
int q = get2();
if(q > 0) cam_mul[c ^ 1] = 4096.0 / q;
}
break;
case 34307: /* Leaf CatchLight color matrix */
fread(software, 1, 7, ifp);
if (strncmp(software, "MATRIX", 6))
break;
colors = 4;
for (raw_color = i = 0; i < 3; i++)
{
FORC4 fscanf(ifp, "%f", &rgb_cam[i][c ^ 1]);
if (!use_camera_wb)
continue;
num = 0;
FORC4 num += rgb_cam[i][c];
FORC4 rgb_cam[i][c] /= MAX(1, num);
}
break;
case 34310: /* Leaf metadata */
parse_mos(ftell(ifp));
case 34303:
strcpy(make, "Leaf");
break;
case 34665: /* EXIF tag */
fseek(ifp, get4() + base, SEEK_SET);
parse_exif(base);
break;
case 34853: /* GPSInfo tag */
{
unsigned pos;
fseek(ifp, pos = (get4() + base), SEEK_SET);
parse_gps(base);
#ifdef LIBRAW_LIBRARY_BUILD
fseek(ifp, pos, SEEK_SET);
parse_gps_libraw(base);
#endif
}
break;
case 34675: /* InterColorProfile */
case 50831: /* AsShotICCProfile */
profile_offset = ftell(ifp);
profile_length = len;
break;
case 37122: /* CompressedBitsPerPixel */
kodak_cbpp = get4();
break;
case 37386: /* FocalLength */
focal_len = getreal(type);
break;
case 37393: /* ImageNumber */
shot_order = getint(type);
break;
case 37400: /* old Kodak KDC tag */
for (raw_color = i = 0; i < 3; i++)
{
getreal(type);
FORC3 rgb_cam[i][c] = getreal(type);
}
break;
case 40976:
strip_offset = get4();
switch (tiff_ifd[ifd].comp)
{
case 32770:
load_raw = &CLASS samsung_load_raw;
break;
case 32772:
load_raw = &CLASS samsung2_load_raw;
break;
case 32773:
load_raw = &CLASS samsung3_load_raw;
break;
}
break;
case 46275: /* Imacon tags */
strcpy(make, "Imacon");
data_offset = ftell(ifp);
ima_len = len;
break;
case 46279:
if (!ima_len)
break;
fseek(ifp, 38, SEEK_CUR);
case 46274:
fseek(ifp, 40, SEEK_CUR);
raw_width = get4();
raw_height = get4();
left_margin = get4() & 7;
width = raw_width - left_margin - (get4() & 7);
top_margin = get4() & 7;
height = raw_height - top_margin - (get4() & 7);
if (raw_width == 7262 && ima_len == 234317952)
{
height = 5412;
width = 7216;
left_margin = 7;
filters = 0;
}
else if (raw_width == 7262)
{
height = 5444;
width = 7244;
left_margin = 7;
}
fseek(ifp, 52, SEEK_CUR);
FORC3 cam_mul[c] = getreal(11);
fseek(ifp, 114, SEEK_CUR);
flip = (get2() >> 7) * 90;
if (width * height * 6 == ima_len)
{
if (flip % 180 == 90)
SWAP(width, height);
raw_width = width;
raw_height = height;
left_margin = top_margin = filters = flip = 0;
}
sprintf(model, "Ixpress %d-Mp", height * width / 1000000);
load_raw = &CLASS imacon_full_load_raw;
if (filters)
{
if (left_margin & 1)
filters = 0x61616161;
load_raw = &CLASS unpacked_load_raw;
}
maximum = 0xffff;
break;
case 50454: /* Sinar tag */
case 50455:
if (len < 1 || len > 2560000 || !(cbuf = (char *)malloc(len)))
break;
#ifndef LIBRAW_LIBRARY_BUILD
fread(cbuf, 1, len, ifp);
#else
if (fread(cbuf, 1, len, ifp) != len)
throw LIBRAW_EXCEPTION_IO_CORRUPT; // cbuf to be free'ed in recycle
#endif
cbuf[len - 1] = 0;
for (cp = cbuf - 1; cp && cp < cbuf + len; cp = strchr(cp, '\n'))
if (!strncmp(++cp, "Neutral ", 8))
sscanf(cp + 8, "%f %f %f", cam_mul, cam_mul + 1, cam_mul + 2);
free(cbuf);
break;
case 50458:
if (!make[0])
strcpy(make, "Hasselblad");
break;
case 50459: /* Hasselblad tag */
#ifdef LIBRAW_LIBRARY_BUILD
libraw_internal_data.unpacker_data.hasselblad_parser_flag = 1;
#endif
i = order;
j = ftell(ifp);
c = tiff_nifds;
order = get2();
fseek(ifp, j + (get2(), get4()), SEEK_SET);
parse_tiff_ifd(j);
maximum = 0xffff;
tiff_nifds = c;
order = i;
break;
case 50706: /* DNGVersion */
FORC4 dng_version = (dng_version << 8) + fgetc(ifp);
if (!make[0])
strcpy(make, "DNG");
is_raw = 1;
break;
case 50708: /* UniqueCameraModel */
#ifdef LIBRAW_LIBRARY_BUILD
stmread(imgdata.color.UniqueCameraModel, len, ifp);
imgdata.color.UniqueCameraModel[sizeof(imgdata.color.UniqueCameraModel) - 1] = 0;
#endif
if (model[0])
break;
#ifndef LIBRAW_LIBRARY_BUILD
fgets(make, 64, ifp);
#else
strncpy(make, imgdata.color.UniqueCameraModel, MIN(len, sizeof(imgdata.color.UniqueCameraModel)));
#endif
if ((cp = strchr(make, ' ')))
{
strcpy(model, cp + 1);
*cp = 0;
}
break;
case 50710: /* CFAPlaneColor */
if (filters == 9)
break;
if (len > 4)
len = 4;
colors = len;
fread(cfa_pc, 1, colors, ifp);
guess_cfa_pc:
FORCC tab[cfa_pc[c]] = c;
cdesc[c] = 0;
for (i = 16; i--;)
filters = filters << 2 | tab[cfa_pat[i % plen]];
filters -= !filters;
break;
case 50711: /* CFALayout */
if (get2() == 2)
fuji_width = 1;
break;
case 291:
case 50712: /* LinearizationTable */
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_LINTABLE;
tiff_ifd[ifd].lineartable_offset = ftell(ifp);
tiff_ifd[ifd].lineartable_len = len;
#endif
linear_table(len);
break;
case 50713: /* BlackLevelRepeatDim */
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_BLACK;
tiff_ifd[ifd].dng_levels.dng_cblack[4] =
#endif
cblack[4] = get2();
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.dng_cblack[5] =
#endif
cblack[5] = get2();
if (cblack[4] * cblack[5] > (sizeof(cblack) / sizeof(cblack[0]) - 6))
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.dng_cblack[4] = tiff_ifd[ifd].dng_levels.dng_cblack[5] =
#endif
cblack[4] = cblack[5] = 1;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 0xf00d:
if (strcmp(model, "X-A3") &&
strcmp(model, "X-A10") &&
strcmp(model, "X-A5") &&
strcmp(model, "X-A20"))
{
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][(4 - c) % 3] = getint(type);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][1];
}
break;
case 0xf00c:
if (strcmp(model, "X-A3") &&
strcmp(model, "X-A10") &&
strcmp(model, "X-A5") &&
strcmp(model, "X-A20"))
{
unsigned fwb[4];
FORC4 fwb[c] = get4();
if (fwb[3] < 0x100)
{
imgdata.color.WB_Coeffs[fwb[3]][0] = fwb[1];
imgdata.color.WB_Coeffs[fwb[3]][1] = imgdata.color.WB_Coeffs[fwb[3]][3] = fwb[0];
imgdata.color.WB_Coeffs[fwb[3]][2] = fwb[2];
if ((fwb[3] == 17) && (libraw_internal_data.unpacker_data.lenRAFData > 3) &&
(libraw_internal_data.unpacker_data.lenRAFData < 10240000))
{
INT64 f_save = ftell(ifp);
ushort *rafdata = (ushort *)malloc(sizeof(ushort) * libraw_internal_data.unpacker_data.lenRAFData);
fseek(ifp, libraw_internal_data.unpacker_data.posRAFData, SEEK_SET);
fread(rafdata, sizeof(ushort), libraw_internal_data.unpacker_data.lenRAFData, ifp);
fseek(ifp, f_save, SEEK_SET);
int fj, found = 0;
for (int fi = 0; fi < (libraw_internal_data.unpacker_data.lenRAFData - 3); fi++)
{
if ((fwb[0] == rafdata[fi]) && (fwb[1] == rafdata[fi + 1]) && (fwb[2] == rafdata[fi + 2]))
{
if (rafdata[fi - 15] != fwb[0])
continue;
for (int wb_ind = 0, ofst = fi - 15; wb_ind < nFuji_wb_list1; wb_ind++, ofst += 3)
{
imgdata.color.WB_Coeffs[Fuji_wb_list1[wb_ind]][1] =
imgdata.color.WB_Coeffs[Fuji_wb_list1[wb_ind]][3] = rafdata[ofst];
imgdata.color.WB_Coeffs[Fuji_wb_list1[wb_ind]][0] = rafdata[ofst + 1];
imgdata.color.WB_Coeffs[Fuji_wb_list1[wb_ind]][2] = rafdata[ofst + 2];
}
fi += 0x60;
for (fj = fi; fj < (fi + 15); fj += 3)
if (rafdata[fj] != rafdata[fi])
{
found = 1;
break;
}
if (found)
{
fj = fj - 93;
for (int iCCT = 0; iCCT < 31; iCCT++)
{
imgdata.color.WBCT_Coeffs[iCCT][0] = FujiCCT_K[iCCT];
imgdata.color.WBCT_Coeffs[iCCT][1] = rafdata[iCCT * 3 + 1 + fj];
imgdata.color.WBCT_Coeffs[iCCT][2] = imgdata.color.WBCT_Coeffs[iCCT][4] = rafdata[iCCT * 3 + fj];
imgdata.color.WBCT_Coeffs[iCCT][3] = rafdata[iCCT * 3 + 2 + fj];
}
}
free(rafdata);
break;
}
}
}
}
FORC4 fwb[c] = get4();
if (fwb[3] < 0x100)
{
imgdata.color.WB_Coeffs[fwb[3]][0] = fwb[1];
imgdata.color.WB_Coeffs[fwb[3]][1] = imgdata.color.WB_Coeffs[fwb[3]][3] = fwb[0];
imgdata.color.WB_Coeffs[fwb[3]][2] = fwb[2];
}
}
break;
#endif
#ifdef LIBRAW_LIBRARY_BUILD
case 50709:
stmread(imgdata.color.LocalizedCameraModel, len, ifp);
break;
#endif
case 61450:
cblack[4] = cblack[5] = MIN(sqrt((double)len), 64);
case 50714: /* BlackLevel */
#ifdef LIBRAW_LIBRARY_BUILD
if (tiff_ifd[ifd].samples > 1 && tiff_ifd[ifd].samples == len) // LinearDNG, per-channel black
{
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_BLACK;
for (i = 0; i < colors && i < 4 && i < len; i++)
tiff_ifd[ifd].dng_levels.dng_cblack[i] = cblack[i] = getreal(type) + 0.5;
tiff_ifd[ifd].dng_levels.dng_black = black = 0;
}
else
#endif
if ((cblack[4] * cblack[5] < 2) && len == 1)
{
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_BLACK;
tiff_ifd[ifd].dng_levels.dng_black =
#endif
black = getreal(type);
}
else if (cblack[4] * cblack[5] <= len)
{
FORC(cblack[4] * cblack[5])
cblack[6 + c] = getreal(type);
black = 0;
FORC4
cblack[c] = 0;
#ifdef LIBRAW_LIBRARY_BUILD
if (tag == 50714)
{
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_BLACK;
FORC(cblack[4] * cblack[5])
tiff_ifd[ifd].dng_levels.dng_cblack[6 + c] = cblack[6 + c];
tiff_ifd[ifd].dng_levels.dng_black = 0;
FORC4
tiff_ifd[ifd].dng_levels.dng_cblack[c] = 0;
}
#endif
}
break;
case 50715: /* BlackLevelDeltaH */
case 50716: /* BlackLevelDeltaV */
for (num = i = 0; i < len && i < 65536; i++)
num += getreal(type);
if(len>0)
{
black += num / len + 0.5;
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.dng_black += num / len + 0.5;
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_BLACK;
#endif
}
break;
case 50717: /* WhiteLevel */
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_WHITE;
tiff_ifd[ifd].dng_levels.dng_whitelevel[0] =
#endif
maximum = getint(type);
#ifdef LIBRAW_LIBRARY_BUILD
if (tiff_ifd[ifd].samples > 1) // Linear DNG case
for (i = 1; i < colors && i < 4 && i < len; i++)
tiff_ifd[ifd].dng_levels.dng_whitelevel[i] = getint(type);
#endif
break;
case 50718: /* DefaultScale */
{
float q1 = getreal(type);
float q2 = getreal(type);
if(q1 > 0.00001f && q2 > 0.00001f)
{
pixel_aspect = q1/q2;
if (pixel_aspect > 0.995 && pixel_aspect < 1.005)
pixel_aspect = 1.0;
}
}
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 50719: /* DefaultCropOrigin */
if (len == 2)
{
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_CROPORIGIN;
tiff_ifd[ifd].dng_levels.default_crop[0] = getreal(type);
tiff_ifd[ifd].dng_levels.default_crop[1] = getreal(type);
if (!strncasecmp(make, "SONY", 4))
{
imgdata.sizes.raw_crop.cleft = tiff_ifd[ifd].dng_levels.default_crop[0];
imgdata.sizes.raw_crop.ctop = tiff_ifd[ifd].dng_levels.default_crop[1];
}
}
break;
case 50720: /* DefaultCropSize */
if (len == 2)
{
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_CROPSIZE;
tiff_ifd[ifd].dng_levels.default_crop[2] = getreal(type);
tiff_ifd[ifd].dng_levels.default_crop[3] = getreal(type);
if (!strncasecmp(make, "SONY", 4))
{
imgdata.sizes.raw_crop.cwidth = tiff_ifd[ifd].dng_levels.default_crop[2];
imgdata.sizes.raw_crop.cheight = tiff_ifd[ifd].dng_levels.default_crop[3];
}
}
break;
case 0x74c7:
if ((len == 2) && !strncasecmp(make, "SONY", 4))
{
imgdata.makernotes.sony.raw_crop.cleft = get4();
imgdata.makernotes.sony.raw_crop.ctop = get4();
}
break;
case 0x74c8:
if ((len == 2) && !strncasecmp(make, "SONY", 4))
{
imgdata.makernotes.sony.raw_crop.cwidth = get4();
imgdata.makernotes.sony.raw_crop.cheight = get4();
}
break;
#endif
#ifdef LIBRAW_LIBRARY_BUILD
case 50778:
tiff_ifd[ifd].dng_color[0].illuminant = get2();
tiff_ifd[ifd].dng_color[0].parsedfields |= LIBRAW_DNGFM_ILLUMINANT;
break;
case 50779:
tiff_ifd[ifd].dng_color[1].illuminant = get2();
tiff_ifd[ifd].dng_color[1].parsedfields |= LIBRAW_DNGFM_ILLUMINANT;
break;
#endif
case 50721: /* ColorMatrix1 */
case 50722: /* ColorMatrix2 */
#ifdef LIBRAW_LIBRARY_BUILD
i = tag == 50721 ? 0 : 1;
tiff_ifd[ifd].dng_color[i].parsedfields |= LIBRAW_DNGFM_COLORMATRIX;
#endif
FORCC for (j = 0; j < 3; j++)
{
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_color[i].colormatrix[c][j] =
#endif
cm[c][j] = getreal(type);
}
use_cm = 1;
break;
case 0xc714: /* ForwardMatrix1 */
case 0xc715: /* ForwardMatrix2 */
#ifdef LIBRAW_LIBRARY_BUILD
i = tag == 0xc714 ? 0 : 1;
tiff_ifd[ifd].dng_color[i].parsedfields |= LIBRAW_DNGFM_FORWARDMATRIX;
#endif
for (j = 0; j < 3; j++)
FORCC
{
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_color[i].forwardmatrix[j][c] =
#endif
fm[j][c] = getreal(type);
}
break;
case 50723: /* CameraCalibration1 */
case 50724: /* CameraCalibration2 */
#ifdef LIBRAW_LIBRARY_BUILD
j = tag == 50723 ? 0 : 1;
tiff_ifd[ifd].dng_color[j].parsedfields |= LIBRAW_DNGFM_CALIBRATION;
#endif
for (i = 0; i < colors; i++)
FORCC
{
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_color[j].calibration[i][c] =
#endif
cc[i][c] = getreal(type);
}
break;
case 50727: /* AnalogBalance */
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_ANALOGBALANCE;
#endif
FORCC
{
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.analogbalance[c] =
#endif
ab[c] = getreal(type);
}
break;
case 50728: /* AsShotNeutral */
FORCC asn[c] = getreal(type);
break;
case 50729: /* AsShotWhiteXY */
xyz[0] = getreal(type);
xyz[1] = getreal(type);
xyz[2] = 1 - xyz[0] - xyz[1];
FORC3 xyz[c] /= d65_white[c];
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 50730: /* DNG: Baseline Exposure */
baseline_exposure = getreal(type);
break;
#endif
// IB start
case 50740: /* tag 0xc634 : DNG Adobe, DNG Pentax, Sony SR2, DNG Private */
#ifdef LIBRAW_LIBRARY_BUILD
{
char mbuf[64];
unsigned short makernote_found = 0;
INT64 curr_pos, start_pos = ftell(ifp);
unsigned MakN_order, m_sorder = order;
unsigned MakN_length;
unsigned pos_in_original_raw;
fread(mbuf, 1, 6, ifp);
if (!strcmp(mbuf, "Adobe"))
{
order = 0x4d4d; // Adobe header is always in "MM" / big endian
curr_pos = start_pos + 6;
while (curr_pos + 8 - start_pos <= len)
{
fread(mbuf, 1, 4, ifp);
curr_pos += 8;
if (!strncmp(mbuf, "MakN", 4))
{
makernote_found = 1;
MakN_length = get4();
MakN_order = get2();
pos_in_original_raw = get4();
order = MakN_order;
INT64 save_pos = ifp->tell();
parse_makernote_0xc634(curr_pos + 6 - pos_in_original_raw, 0, AdobeDNG);
curr_pos = save_pos + MakN_length - 6;
fseek(ifp, curr_pos, SEEK_SET);
fread(mbuf, 1, 4, ifp);
curr_pos += 8;
if (!strncmp(mbuf, "SR2 ", 4))
{
order = 0x4d4d;
MakN_length = get4();
MakN_order = get2();
pos_in_original_raw = get4();
order = MakN_order;
unsigned *buf_SR2;
uchar *cbuf_SR2;
unsigned icbuf_SR2;
unsigned entries, tag, type, len, save;
int ival;
unsigned SR2SubIFDOffset = 0;
unsigned SR2SubIFDLength = 0;
unsigned SR2SubIFDKey = 0;
int base = curr_pos + 6 - pos_in_original_raw;
entries = get2();
while (entries--)
{
tiff_get(base, &tag, &type, &len, &save);
if (tag == 0x7200)
{
SR2SubIFDOffset = get4();
}
else if (tag == 0x7201)
{
SR2SubIFDLength = get4();
}
else if (tag == 0x7221)
{
SR2SubIFDKey = get4();
}
fseek(ifp, save, SEEK_SET);
}
if (SR2SubIFDLength && (SR2SubIFDLength < 10240000) && (buf_SR2 = (unsigned *)malloc(SR2SubIFDLength+1024))) // 1024b for safety
{
fseek(ifp, SR2SubIFDOffset + base, SEEK_SET);
fread(buf_SR2, SR2SubIFDLength, 1, ifp);
sony_decrypt(buf_SR2, SR2SubIFDLength / 4, 1, SR2SubIFDKey);
cbuf_SR2 = (uchar *)buf_SR2;
entries = sget2(cbuf_SR2);
icbuf_SR2 = 2;
while (entries--)
{
tag = sget2(cbuf_SR2 + icbuf_SR2);
icbuf_SR2 += 2;
type = sget2(cbuf_SR2 + icbuf_SR2);
icbuf_SR2 += 2;
len = sget4(cbuf_SR2 + icbuf_SR2);
icbuf_SR2 += 4;
if (len * ("11124811248484"[type < 14 ? type : 0] - '0') > 4)
{
ival = sget4(cbuf_SR2 + icbuf_SR2) - SR2SubIFDOffset;
}
else
{
ival = icbuf_SR2;
}
if(ival > SR2SubIFDLength) // points out of orig. buffer size
break; // END processing. Generally we should check against SR2SubIFDLength minus 6 of 8, depending on tag, but we allocated extra 1024b for buffer, so this does not matter
icbuf_SR2 += 4;
switch (tag)
{
case 0x7302:
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c < 2)] = sget2(cbuf_SR2 + ival + 2 * c);
break;
case 0x7312:
{
int i, lc[4];
FORC4 lc[c] = sget2(cbuf_SR2 + ival + 2 * c);
i = (lc[1] == 1024 && lc[2] == 1024) << 1;
SWAP(lc[i], lc[i + 1]);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c] = lc[c];
}
break;
case 0x7480:
case 0x7820:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][1];
break;
case 0x7481:
case 0x7821:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][1];
break;
case 0x7482:
case 0x7822:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][1];
break;
case 0x7483:
case 0x7823:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1];
break;
case 0x7484:
case 0x7824:
imgdata.color.WBCT_Coeffs[0][0] = 4500;
FORC3 imgdata.color.WBCT_Coeffs[0][c + 1] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WBCT_Coeffs[0][4] = imgdata.color.WBCT_Coeffs[0][2];
break;
case 0x7486:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_Fluorescent][1];
break;
case 0x7825:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][1];
break;
case 0x7826:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][1];
break;
case 0x7827:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][1];
break;
case 0x7828:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][1];
break;
case 0x7829:
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][c] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][3] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][1];
break;
case 0x782a:
imgdata.color.WBCT_Coeffs[1][0] = 8500;
FORC3 imgdata.color.WBCT_Coeffs[1][c + 1] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WBCT_Coeffs[1][4] = imgdata.color.WBCT_Coeffs[1][2];
break;
case 0x782b:
imgdata.color.WBCT_Coeffs[2][0] = 6000;
FORC3 imgdata.color.WBCT_Coeffs[2][c + 1] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WBCT_Coeffs[2][4] = imgdata.color.WBCT_Coeffs[2][2];
break;
case 0x782c:
imgdata.color.WBCT_Coeffs[3][0] = 3200;
FORC3 imgdata.color.WB_Coeffs[LIBRAW_WBI_StudioTungsten][c] = imgdata.color.WBCT_Coeffs[3][c + 1] =
sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WB_Coeffs[LIBRAW_WBI_StudioTungsten][3] = imgdata.color.WBCT_Coeffs[3][4] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_StudioTungsten][1];
break;
case 0x782d:
imgdata.color.WBCT_Coeffs[4][0] = 2500;
FORC3 imgdata.color.WBCT_Coeffs[4][c + 1] = sget2(cbuf_SR2 + ival + 2 * c);
imgdata.color.WBCT_Coeffs[4][4] = imgdata.color.WBCT_Coeffs[4][2];
break;
}
}
free(buf_SR2);
}
} /* SR2 processed */
break;
}
}
}
else
{
fread(mbuf + 6, 1, 2, ifp);
if (!strcmp(mbuf, "PENTAX ") || !strcmp(mbuf, "SAMSUNG"))
{
makernote_found = 1;
fseek(ifp, start_pos, SEEK_SET);
parse_makernote_0xc634(base, 0, CameraDNG);
}
}
fseek(ifp, start_pos, SEEK_SET);
order = m_sorder;
}
// IB end
#endif
if (dng_version)
break;
parse_minolta(j = get4() + base);
fseek(ifp, j, SEEK_SET);
parse_tiff_ifd(base);
break;
case 50752:
read_shorts(cr2_slice, 3);
break;
case 50829: /* ActiveArea */
top_margin = getint(type);
left_margin = getint(type);
height = getint(type) - top_margin;
width = getint(type) - left_margin;
break;
case 50830: /* MaskedAreas */
for (i = 0; i < len && i < 32; i++)
((int *)mask)[i] = getint(type);
black = 0;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 50970: /* PreviewColorSpace */
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_PREVIEWCS;
tiff_ifd[ifd].dng_levels.preview_colorspace = getint(type);
break;
#endif
case 51009: /* OpcodeList2 */
#ifdef LIBRAW_LIBRARY_BUILD
tiff_ifd[ifd].dng_levels.parsedfields |= LIBRAW_DNGFM_OPCODE2;
tiff_ifd[ifd].opcode2_offset =
#endif
meta_offset = ftell(ifp);
break;
case 64772: /* Kodak P-series */
if (len < 13)
break;
fseek(ifp, 16, SEEK_CUR);
data_offset = get4();
fseek(ifp, 28, SEEK_CUR);
data_offset += get4();
load_raw = &CLASS packed_load_raw;
break;
case 65026:
if (type == 2)
fgets(model2, 64, ifp);
}
fseek(ifp, save, SEEK_SET);
}
if (sony_length && sony_length < 10240000 && (buf = (unsigned *)malloc(sony_length)))
{
fseek(ifp, sony_offset, SEEK_SET);
fread(buf, sony_length, 1, ifp);
sony_decrypt(buf, sony_length / 4, 1, sony_key);
#ifndef LIBRAW_LIBRARY_BUILD
sfp = ifp;
if ((ifp = tmpfile()))
{
fwrite(buf, sony_length, 1, ifp);
fseek(ifp, 0, SEEK_SET);
parse_tiff_ifd(-sony_offset);
fclose(ifp);
}
ifp = sfp;
#else
if (!ifp->tempbuffer_open(buf, sony_length))
{
parse_tiff_ifd(-sony_offset);
ifp->tempbuffer_close();
}
#endif
free(buf);
}
for (i = 0; i < colors; i++)
FORCC cc[i][c] *= ab[i];
if (use_cm)
{
FORCC for (i = 0; i < 3; i++) for (cam_xyz[c][i] = j = 0; j < colors; j++) cam_xyz[c][i] +=
cc[c][j] * cm[j][i] * xyz[i];
cam_xyz_coeff(cmatrix, cam_xyz);
}
if (asn[0])
{
cam_mul[3] = 0;
FORCC
if(fabs(asn[c])>0.0001)
cam_mul[c] = 1 / asn[c];
}
if (!use_cm)
FORCC if(fabs(cc[c][c])>0.0001) pre_mul[c] /= cc[c][c];
return 0;
}
int CLASS parse_tiff(int base)
{
int doff;
fseek(ifp, base, SEEK_SET);
order = get2();
if (order != 0x4949 && order != 0x4d4d)
return 0;
get2();
while ((doff = get4()))
{
fseek(ifp, doff + base, SEEK_SET);
if (parse_tiff_ifd(base))
break;
}
return 1;
}
void CLASS apply_tiff()
{
int max_samp = 0, ties = 0, raw = -1, thm = -1, i;
unsigned long long ns, os;
struct jhead jh;
thumb_misc = 16;
if (thumb_offset)
{
fseek(ifp, thumb_offset, SEEK_SET);
if (ljpeg_start(&jh, 1))
{
if ((unsigned)jh.bits < 17 && (unsigned)jh.wide < 0x10000 && (unsigned)jh.high < 0x10000)
{
thumb_misc = jh.bits;
thumb_width = jh.wide;
thumb_height = jh.high;
}
}
}
for (i = tiff_nifds; i--;)
{
if (tiff_ifd[i].t_shutter)
shutter = tiff_ifd[i].t_shutter;
tiff_ifd[i].t_shutter = shutter;
}
for (i = 0; i < tiff_nifds; i++)
{
if( tiff_ifd[i].t_width < 1 || tiff_ifd[i].t_width > 65535
|| tiff_ifd[i].t_height < 1 || tiff_ifd[i].t_height > 65535)
continue; /* wrong image dimensions */
if (max_samp < tiff_ifd[i].samples)
max_samp = tiff_ifd[i].samples;
if (max_samp > 3)
max_samp = 3;
os = raw_width * raw_height;
ns = tiff_ifd[i].t_width * tiff_ifd[i].t_height;
if (tiff_bps)
{
os *= tiff_bps;
ns *= tiff_ifd[i].bps;
}
if ((tiff_ifd[i].comp != 6 || tiff_ifd[i].samples != 3) &&
unsigned(tiff_ifd[i].t_width | tiff_ifd[i].t_height) < 0x10000 && (unsigned)tiff_ifd[i].bps < 33 &&
(unsigned)tiff_ifd[i].samples < 13 && ns && ((ns > os && (ties = 1)) || (ns == os && shot_select == ties++)))
{
raw_width = tiff_ifd[i].t_width;
raw_height = tiff_ifd[i].t_height;
tiff_bps = tiff_ifd[i].bps;
tiff_compress = tiff_ifd[i].comp;
data_offset = tiff_ifd[i].offset;
#ifdef LIBRAW_LIBRARY_BUILD
data_size = tiff_ifd[i].bytes;
#endif
tiff_flip = tiff_ifd[i].t_flip;
tiff_samples = tiff_ifd[i].samples;
tile_width = tiff_ifd[i].t_tile_width;
tile_length = tiff_ifd[i].t_tile_length;
shutter = tiff_ifd[i].t_shutter;
raw = i;
}
}
if (is_raw == 1 && ties)
is_raw = ties;
if (!tile_width)
tile_width = INT_MAX;
if (!tile_length)
tile_length = INT_MAX;
for (i = tiff_nifds; i--;)
if (tiff_ifd[i].t_flip)
tiff_flip = tiff_ifd[i].t_flip;
if (raw >= 0 && !load_raw)
switch (tiff_compress)
{
case 32767:
#ifdef LIBRAW_LIBRARY_BUILD
if (!dng_version && INT64(tiff_ifd[raw].bytes) == INT64(raw_width) * INT64(raw_height))
#else
if (tiff_ifd[raw].bytes == raw_width * raw_height)
#endif
{
tiff_bps = 14;
load_raw = &CLASS sony_arw2_load_raw;
break;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (!dng_version && !strncasecmp(make, "Sony", 4) && INT64(tiff_ifd[raw].bytes) == INT64(raw_width) * INT64(raw_height) * 2ULL)
#else
if (!strncasecmp(make, "Sony", 4) && tiff_ifd[raw].bytes == raw_width * raw_height * 2)
#endif
{
tiff_bps = 14;
load_raw = &CLASS unpacked_load_raw;
break;
}
#ifdef LIBRAW_LIBRARY_BUILD
if (INT64(tiff_ifd[raw].bytes) * 8ULL != INT64(raw_width) * INT64(raw_height) * INT64(tiff_bps))
#else
if (tiff_ifd[raw].bytes * 8 != raw_width * raw_height * tiff_bps)
#endif
{
raw_height += 8;
load_raw = &CLASS sony_arw_load_raw;
break;
}
load_flags = 79;
case 32769:
load_flags++;
case 32770:
case 32773:
goto slr;
case 0:
case 1:
#ifdef LIBRAW_LIBRARY_BUILD
// Sony 14-bit uncompressed
if (!dng_version && !strncasecmp(make, "Sony", 4) && INT64(tiff_ifd[raw].bytes) == INT64(raw_width) * INT64(raw_height) * 2ULL)
{
tiff_bps = 14;
load_raw = &CLASS unpacked_load_raw;
break;
}
if (!dng_version && !strncasecmp(make, "Sony", 4) && tiff_ifd[raw].samples == 4 &&
INT64(tiff_ifd[raw].bytes) == INT64(raw_width) * INT64(raw_height) * 8ULL) // Sony ARQ
{
tiff_bps = 14;
tiff_samples = 4;
load_raw = &CLASS sony_arq_load_raw;
filters = 0;
strcpy(cdesc, "RGBG");
break;
}
if (!strncasecmp(make, "Nikon", 5) && !strncmp(software, "Nikon Scan", 10))
{
load_raw = &CLASS nikon_coolscan_load_raw;
raw_color = 1;
filters = 0;
break;
}
if (!strncmp(make, "OLYMPUS", 7) && INT64(tiff_ifd[raw].bytes) * 2ULL == INT64(raw_width) * INT64(raw_height) * 3ULL)
#else
if (!strncmp(make, "OLYMPUS", 7) && tiff_ifd[raw].bytes * 2 == raw_width * raw_height * 3)
#endif
load_flags = 24;
#ifdef LIBRAW_LIBRARY_BUILD
if (!dng_version && INT64(tiff_ifd[raw].bytes) * 5ULL == INT64(raw_width) * INT64(raw_height) * 8ULL)
#else
if (tiff_ifd[raw].bytes * 5 == raw_width * raw_height * 8)
#endif
{
load_flags = 81;
tiff_bps = 12;
}
slr:
switch (tiff_bps)
{
case 8:
load_raw = &CLASS eight_bit_load_raw;
break;
case 12:
if (tiff_ifd[raw].phint == 2)
load_flags = 6;
load_raw = &CLASS packed_load_raw;
break;
case 14:
load_flags = 0;
case 16:
load_raw = &CLASS unpacked_load_raw;
#ifdef LIBRAW_LIBRARY_BUILD
if (!strncmp(make, "OLYMPUS", 7) && INT64(tiff_ifd[raw].bytes) * 7ULL > INT64(raw_width) * INT64(raw_height))
#else
if (!strncmp(make, "OLYMPUS", 7) && tiff_ifd[raw].bytes * 7 > raw_width * raw_height)
#endif
load_raw = &CLASS olympus_load_raw;
}
break;
case 6:
case 7:
case 99:
load_raw = &CLASS lossless_jpeg_load_raw;
break;
case 262:
load_raw = &CLASS kodak_262_load_raw;
break;
case 34713:
#ifdef LIBRAW_LIBRARY_BUILD
if ((INT64(raw_width) + 9ULL) / 10ULL * 16ULL * INT64(raw_height) == INT64(tiff_ifd[raw].bytes))
#else
if ((raw_width + 9) / 10 * 16 * raw_height == tiff_ifd[raw].bytes)
#endif
{
load_raw = &CLASS packed_load_raw;
load_flags = 1;
}
#ifdef LIBRAW_LIBRARY_BUILD
else if (INT64(raw_width) * INT64(raw_height) * 3ULL == INT64(tiff_ifd[raw].bytes) * 2ULL)
#else
else if (raw_width * raw_height * 3 == tiff_ifd[raw].bytes * 2)
#endif
{
load_raw = &CLASS packed_load_raw;
if (model[0] == 'N')
load_flags = 80;
}
#ifdef LIBRAW_LIBRARY_BUILD
else if (INT64(raw_width) * INT64(raw_height) * 3ULL == INT64(tiff_ifd[raw].bytes))
#else
else if (raw_width * raw_height * 3 == tiff_ifd[raw].bytes)
#endif
{
load_raw = &CLASS nikon_yuv_load_raw;
gamma_curve(1 / 2.4, 12.92, 1, 4095);
memset(cblack, 0, sizeof cblack);
filters = 0;
}
#ifdef LIBRAW_LIBRARY_BUILD
else if (INT64(raw_width) * INT64(raw_height) * 2ULL == INT64(tiff_ifd[raw].bytes))
#else
else if (raw_width * raw_height * 2 == tiff_ifd[raw].bytes)
#endif
{
load_raw = &CLASS unpacked_load_raw;
load_flags = 4;
order = 0x4d4d;
}
else
#ifdef LIBRAW_LIBRARY_BUILD
if (INT64(raw_width) * INT64(raw_height) * 3ULL == INT64(tiff_ifd[raw].bytes) * 2ULL)
{
load_raw = &CLASS packed_load_raw;
load_flags = 80;
}
else if (tiff_ifd[raw].rows_per_strip && tiff_ifd[raw].strip_offsets_count &&
tiff_ifd[raw].strip_offsets_count == tiff_ifd[raw].strip_byte_counts_count)
{
int fit = 1;
for (int i = 0; i < tiff_ifd[raw].strip_byte_counts_count - 1; i++) // all but last
if (INT64(tiff_ifd[raw].strip_byte_counts[i]) * 2ULL != INT64(tiff_ifd[raw].rows_per_strip) * INT64(raw_width) * 3ULL)
{
fit = 0;
break;
}
if (fit)
load_raw = &CLASS nikon_load_striped_packed_raw;
else
load_raw = &CLASS nikon_load_raw; // fallback
}
else
#endif
load_raw = &CLASS nikon_load_raw;
break;
case 65535:
load_raw = &CLASS pentax_load_raw;
break;
case 65000:
switch (tiff_ifd[raw].phint)
{
case 2:
load_raw = &CLASS kodak_rgb_load_raw;
filters = 0;
break;
case 6:
load_raw = &CLASS kodak_ycbcr_load_raw;
filters = 0;
break;
case 32803:
load_raw = &CLASS kodak_65000_load_raw;
}
case 32867:
case 34892:
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 8:
break;
#endif
default:
is_raw = 0;
}
if (!dng_version)
if (((tiff_samples == 3 && tiff_ifd[raw].bytes && tiff_bps != 14 && (tiff_compress & -16) != 32768) ||
(tiff_bps == 8 && strncmp(make, "Phase", 5) && strncmp(make, "Leaf", 4) && !strcasestr(make, "Kodak") &&
!strstr(model2, "DEBUG RAW"))) &&
strncmp(software, "Nikon Scan", 10))
is_raw = 0;
for (i = 0; i < tiff_nifds; i++)
if (i != raw &&
(tiff_ifd[i].samples == max_samp || (tiff_ifd[i].comp == 7 && tiff_ifd[i].samples == 1)) /* Allow 1-bps JPEGs */
&& tiff_ifd[i].bps > 0 && tiff_ifd[i].bps < 33 && tiff_ifd[i].phint != 32803 && tiff_ifd[i].phint != 34892 &&
unsigned(tiff_ifd[i].t_width | tiff_ifd[i].t_height) < 0x10000 &&
tiff_ifd[i].t_width * tiff_ifd[i].t_height / (SQR(tiff_ifd[i].bps) + 1) >
thumb_width * thumb_height / (SQR(thumb_misc) + 1) &&
tiff_ifd[i].comp != 34892)
{
thumb_width = tiff_ifd[i].t_width;
thumb_height = tiff_ifd[i].t_height;
thumb_offset = tiff_ifd[i].offset;
thumb_length = tiff_ifd[i].bytes;
thumb_misc = tiff_ifd[i].bps;
thm = i;
}
if (thm >= 0)
{
thumb_misc |= tiff_ifd[thm].samples << 5;
switch (tiff_ifd[thm].comp)
{
case 0:
write_thumb = &CLASS layer_thumb;
break;
case 1:
if (tiff_ifd[thm].bps <= 8)
write_thumb = &CLASS ppm_thumb;
else if (!strncmp(make, "Imacon", 6))
write_thumb = &CLASS ppm16_thumb;
else
thumb_load_raw = &CLASS kodak_thumb_load_raw;
break;
case 65000:
thumb_load_raw = tiff_ifd[thm].phint == 6 ? &CLASS kodak_ycbcr_load_raw : &CLASS kodak_rgb_load_raw;
}
}
}
void CLASS parse_minolta(int base)
{
int save, tag, len, offset, high = 0, wide = 0, i, c;
short sorder = order;
fseek(ifp, base, SEEK_SET);
if (fgetc(ifp) || fgetc(ifp) - 'M' || fgetc(ifp) - 'R')
return;
order = fgetc(ifp) * 0x101;
offset = base + get4() + 8;
#ifdef LIBRAW_LIBRARY_BUILD
if(offset>ifp->size()-8) // At least 8 bytes for tag/len
offset = ifp->size()-8;
#endif
while ((save = ftell(ifp)) < offset)
{
for (tag = i = 0; i < 4; i++)
tag = tag << 8 | fgetc(ifp);
len = get4();
if(len < 0)
return; // just ignore wrong len?? or raise bad file exception?
switch (tag)
{
case 0x505244: /* PRD */
fseek(ifp, 8, SEEK_CUR);
high = get2();
wide = get2();
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 0x524946: /* RIF */
if (!strncasecmp(model, "DSLR-A100", 9))
{
fseek(ifp, 8, SEEK_CUR);
imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][2] = get2();
get4();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_WW][0] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_WW][2] = get2();
imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Daylight][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Cloudy][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][1] = imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][1] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][1] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_W][3] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_WW][1] =
imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_WW][3] = 0x100;
}
break;
#endif
case 0x574247: /* WBG */
get4();
i = strcmp(model, "DiMAGE A200") ? 0 : 3;
FORC4 cam_mul[c ^ (c >> 1) ^ i] = get2();
break;
case 0x545457: /* TTW */
parse_tiff(ftell(ifp));
data_offset = offset;
}
fseek(ifp, save + len + 8, SEEK_SET);
}
raw_height = high;
raw_width = wide;
order = sorder;
}
/*
Many cameras have a "debug mode" that writes JPEG and raw
at the same time. The raw file has no header, so try to
to open the matching JPEG file and read its metadata.
*/
void CLASS parse_external_jpeg()
{
const char *file, *ext;
char *jname, *jfile, *jext;
#ifndef LIBRAW_LIBRARY_BUILD
FILE *save = ifp;
#else
#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
if (ifp->wfname())
{
std::wstring rawfile(ifp->wfname());
rawfile.replace(rawfile.length() - 3, 3, L"JPG");
if (!ifp->subfile_open(rawfile.c_str()))
{
parse_tiff(12);
thumb_offset = 0;
is_raw = 1;
ifp->subfile_close();
}
else
imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA;
return;
}
#endif
if (!ifp->fname())
{
imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA;
return;
}
#endif
ext = strrchr(ifname, '.');
file = strrchr(ifname, '/');
if (!file)
file = strrchr(ifname, '\\');
#ifndef LIBRAW_LIBRARY_BUILD
if (!file)
file = ifname - 1;
#else
if (!file)
file = (char *)ifname - 1;
#endif
file++;
if (!ext || strlen(ext) != 4 || ext - file != 8)
return;
jname = (char *)malloc(strlen(ifname) + 1);
merror(jname, "parse_external_jpeg()");
strcpy(jname, ifname);
jfile = file - ifname + jname;
jext = ext - ifname + jname;
if (strcasecmp(ext, ".jpg"))
{
strcpy(jext, isupper(ext[1]) ? ".JPG" : ".jpg");
if (isdigit(*file))
{
memcpy(jfile, file + 4, 4);
memcpy(jfile + 4, file, 4);
}
}
else
while (isdigit(*--jext))
{
if (*jext != '9')
{
(*jext)++;
break;
}
*jext = '0';
}
#ifndef LIBRAW_LIBRARY_BUILD
if (strcmp(jname, ifname))
{
if ((ifp = fopen(jname, "rb")))
{
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Reading metadata from %s ...\n"), jname);
#endif
parse_tiff(12);
thumb_offset = 0;
is_raw = 1;
fclose(ifp);
}
}
#else
if (strcmp(jname, ifname))
{
if (!ifp->subfile_open(jname))
{
parse_tiff(12);
thumb_offset = 0;
is_raw = 1;
ifp->subfile_close();
}
else
imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA;
}
#endif
if (!timestamp)
{
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.process_warnings |= LIBRAW_WARN_NO_METADATA;
#endif
#ifdef DCRAW_VERBOSE
fprintf(stderr, _("Failed to read metadata from %s\n"), jname);
#endif
}
free(jname);
#ifndef LIBRAW_LIBRARY_BUILD
ifp = save;
#endif
}
/*
CIFF block 0x1030 contains an 8x8 white sample.
Load this into white[][] for use in scale_colors().
*/
void CLASS ciff_block_1030()
{
static const ushort key[] = {0x410, 0x45f3};
int i, bpp, row, col, vbits = 0;
unsigned long bitbuf = 0;
if ((get2(), get4()) != 0x80008 || !get4())
return;
bpp = get2();
if (bpp != 10 && bpp != 12)
return;
for (i = row = 0; row < 8; row++)
for (col = 0; col < 8; col++)
{
if (vbits < bpp)
{
bitbuf = bitbuf << 16 | (get2() ^ key[i++ & 1]);
vbits += 16;
}
white[row][col] = bitbuf >> (vbits -= bpp) & ~(-1 << bpp);
}
}
/*
Parse a CIFF file, better known as Canon CRW format.
*/
void CLASS parse_ciff(int offset, int length, int depth)
{
int tboff, nrecs, c, type, len, save, wbi = -1;
ushort key[] = {0x410, 0x45f3};
fseek(ifp, offset + length - 4, SEEK_SET);
tboff = get4() + offset;
fseek(ifp, tboff, SEEK_SET);
nrecs = get2();
if ((nrecs | depth) > 127)
return;
while (nrecs--)
{
type = get2();
len = get4();
save = ftell(ifp) + 4;
fseek(ifp, offset + get4(), SEEK_SET);
if ((((type >> 8) + 8) | 8) == 0x38)
{
parse_ciff(ftell(ifp), len, depth + 1); /* Parse a sub-table */
}
#ifdef LIBRAW_LIBRARY_BUILD
if (type == 0x3004)
parse_ciff(ftell(ifp), len, depth + 1);
#endif
if (type == 0x0810)
fread(artist, 64, 1, ifp);
if (type == 0x080a)
{
fread(make, 64, 1, ifp);
fseek(ifp, strbuflen(make) - 63, SEEK_CUR);
fread(model, 64, 1, ifp);
}
if (type == 0x1810)
{
width = get4();
height = get4();
pixel_aspect = int_to_float(get4());
flip = get4();
}
if (type == 0x1835) /* Get the decoder table */
tiff_compress = get4();
if (type == 0x2007)
{
thumb_offset = ftell(ifp);
thumb_length = len;
}
if (type == 0x1818)
{
shutter = libraw_powf64l(2.0f, -int_to_float((get4(), get4())));
aperture = libraw_powf64l(2.0f, int_to_float(get4()) / 2);
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.lens.makernotes.CurAp = aperture;
#endif
}
if (type == 0x102a)
{
// iso_speed = pow (2.0, (get4(),get2())/32.0 - 4) * 50;
iso_speed = libraw_powf64l(2.0f, ((get2(), get2()) + get2()) / 32.0f - 5.0f) * 100.0f;
#ifdef LIBRAW_LIBRARY_BUILD
aperture = _CanonConvertAperture((get2(), get2()));
imgdata.lens.makernotes.CurAp = aperture;
#else
aperture = libraw_powf64l(2.0, (get2(), (short)get2()) / 64.0);
#endif
shutter = libraw_powf64l(2.0, -((short)get2()) / 32.0);
wbi = (get2(), get2());
if (wbi > 17)
wbi = 0;
fseek(ifp, 32, SEEK_CUR);
if (shutter > 1e6)
shutter = get2() / 10.0;
}
if (type == 0x102c)
{
if (get2() > 512)
{ /* Pro90, G1 */
fseek(ifp, 118, SEEK_CUR);
FORC4 cam_mul[c ^ 2] = get2();
}
else
{ /* G2, S30, S40 */
fseek(ifp, 98, SEEK_CUR);
FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get2();
}
}
#ifdef LIBRAW_LIBRARY_BUILD
if (type == 0x10a9)
{
INT64 o = ftell(ifp);
fseek(ifp, (0x1 << 1), SEEK_CUR);
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ (c >> 1)] = get2();
Canon_WBpresets(0, 0);
fseek(ifp, o, SEEK_SET);
}
if (type == 0x102d)
{
INT64 o = ftell(ifp);
Canon_CameraSettings();
fseek(ifp, o, SEEK_SET);
}
if (type == 0x580b)
{
if (strcmp(model, "Canon EOS D30"))
sprintf(imgdata.shootinginfo.BodySerial, "%d", len);
else
sprintf(imgdata.shootinginfo.BodySerial, "%0x-%05d", len >> 16, len & 0xffff);
}
#endif
if (type == 0x0032)
{
if (len == 768)
{ /* EOS D30 */
fseek(ifp, 72, SEEK_CUR);
FORC4
{
ushort q = get2();
cam_mul[c ^ (c >> 1)] = 1024.0/ MAX(1,q);
}
if (!wbi)
cam_mul[0] = -1; /* use my auto white balance */
}
else if (!cam_mul[0])
{
if (get2() == key[0]) /* Pro1, G6, S60, S70 */
c = (strstr(model, "Pro1") ? "012346000000000000" : "01345:000000006008")[LIM(0, wbi, 17)] - '0' + 2;
else
{ /* G3, G5, S45, S50 */
c = "023457000000006000"[LIM(0, wbi, 17)] - '0';
key[0] = key[1] = 0;
}
fseek(ifp, 78 + c * 8, SEEK_CUR);
FORC4 cam_mul[c ^ (c >> 1) ^ 1] = get2() ^ key[c & 1];
if (!wbi)
cam_mul[0] = -1;
}
}
if (type == 0x10a9)
{ /* D60, 10D, 300D, and clones */
if (len > 66)
wbi = "0134567028"[LIM(0, wbi, 9)] - '0';
fseek(ifp, 2 + wbi * 8, SEEK_CUR);
FORC4 cam_mul[c ^ (c >> 1)] = get2();
}
if (type == 0x1030 && wbi >= 0 && (0x18040 >> wbi & 1))
ciff_block_1030(); /* all that don't have 0x10a9 */
if (type == 0x1031)
{
raw_width = (get2(), get2());
raw_height = get2();
}
if (type == 0x501c)
{
iso_speed = len & 0xffff;
}
if (type == 0x5029)
{
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.lens.makernotes.CurFocal = len >> 16;
imgdata.lens.makernotes.FocalType = len & 0xffff;
if (imgdata.lens.makernotes.FocalType == 2)
{
imgdata.lens.makernotes.CanonFocalUnits = 32;
if (imgdata.lens.makernotes.CanonFocalUnits > 1)
imgdata.lens.makernotes.CurFocal /= (float)imgdata.lens.makernotes.CanonFocalUnits;
}
focal_len = imgdata.lens.makernotes.CurFocal;
#else
focal_len = len >> 16;
if ((len & 0xffff) == 2)
focal_len /= 32;
#endif
}
if (type == 0x5813)
flash_used = int_to_float(len);
if (type == 0x5814)
canon_ev = int_to_float(len);
if (type == 0x5817)
shot_order = len;
if (type == 0x5834)
{
unique_id = len;
#ifdef LIBRAW_LIBRARY_BUILD
unique_id = setCanonBodyFeatures(unique_id);
#endif
}
if (type == 0x580e)
timestamp = len;
if (type == 0x180e)
timestamp = get4();
#ifdef LOCALTIME
if ((type | 0x4000) == 0x580e)
timestamp = mktime(gmtime(&timestamp));
#endif
fseek(ifp, save, SEEK_SET);
}
}
void CLASS parse_rollei()
{
char line[128], *val;
struct tm t;
fseek(ifp, 0, SEEK_SET);
memset(&t, 0, sizeof t);
do
{
fgets(line, 128, ifp);
if ((val = strchr(line, '=')))
*val++ = 0;
else
val = line + strbuflen(line);
if (!strcmp(line, "DAT"))
sscanf(val, "%d.%d.%d", &t.tm_mday, &t.tm_mon, &t.tm_year);
if (!strcmp(line, "TIM"))
sscanf(val, "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec);
if (!strcmp(line, "HDR"))
thumb_offset = atoi(val);
if (!strcmp(line, "X "))
raw_width = atoi(val);
if (!strcmp(line, "Y "))
raw_height = atoi(val);
if (!strcmp(line, "TX "))
thumb_width = atoi(val);
if (!strcmp(line, "TY "))
thumb_height = atoi(val);
} while (strncmp(line, "EOHD", 4));
data_offset = thumb_offset + thumb_width * thumb_height * 2;
t.tm_year -= 1900;
t.tm_mon -= 1;
if (mktime(&t) > 0)
timestamp = mktime(&t);
strcpy(make, "Rollei");
strcpy(model, "d530flex");
write_thumb = &CLASS rollei_thumb;
}
void CLASS parse_sinar_ia()
{
int entries, off;
char str[8], *cp;
order = 0x4949;
fseek(ifp, 4, SEEK_SET);
entries = get4();
fseek(ifp, get4(), SEEK_SET);
while (entries--)
{
off = get4();
get4();
fread(str, 8, 1, ifp);
if (!strcmp(str, "META"))
meta_offset = off;
if (!strcmp(str, "THUMB"))
thumb_offset = off;
if (!strcmp(str, "RAW0"))
data_offset = off;
}
fseek(ifp, meta_offset + 20, SEEK_SET);
fread(make, 64, 1, ifp);
make[63] = 0;
if ((cp = strchr(make, ' ')))
{
strcpy(model, cp + 1);
*cp = 0;
}
raw_width = get2();
raw_height = get2();
load_raw = &CLASS unpacked_load_raw;
thumb_width = (get4(), get2());
thumb_height = get2();
write_thumb = &CLASS ppm_thumb;
maximum = 0x3fff;
}
void CLASS parse_phase_one(int base)
{
unsigned entries, tag, type, len, data, save, i, c;
float romm_cam[3][3];
char *cp;
memset(&ph1, 0, sizeof ph1);
fseek(ifp, base, SEEK_SET);
order = get4() & 0xffff;
if (get4() >> 8 != 0x526177)
return; /* "Raw" */
fseek(ifp, get4() + base, SEEK_SET);
entries = get4();
get4();
while (entries--)
{
tag = get4();
type = get4();
len = get4();
data = get4();
save = ftell(ifp);
fseek(ifp, base + data, SEEK_SET);
switch (tag)
{
#ifdef LIBRAW_LIBRARY_BUILD
case 0x0102:
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
if ((imgdata.shootinginfo.BodySerial[0] == 0x4c) && (imgdata.shootinginfo.BodySerial[1] == 0x49))
{
unique_id =
(((imgdata.shootinginfo.BodySerial[0] & 0x3f) << 5) | (imgdata.shootinginfo.BodySerial[2] & 0x3f)) - 0x41;
}
else
{
unique_id =
(((imgdata.shootinginfo.BodySerial[0] & 0x3f) << 5) | (imgdata.shootinginfo.BodySerial[1] & 0x3f)) - 0x41;
}
setPhaseOneFeatures(unique_id);
break;
case 0x0211:
imgdata.other.SensorTemperature2 = int_to_float(data);
break;
case 0x0401:
if (type == 4)
imgdata.lens.makernotes.CurAp = libraw_powf64l(2.0f, (int_to_float(data) / 2.0f));
else
imgdata.lens.makernotes.CurAp = libraw_powf64l(2.0f, (getreal(type) / 2.0f));
break;
case 0x0403:
if (type == 4)
imgdata.lens.makernotes.CurFocal = int_to_float(data);
else
imgdata.lens.makernotes.CurFocal = getreal(type);
break;
case 0x0410:
stmread(imgdata.lens.makernotes.body, len, ifp);
break;
case 0x0412:
stmread(imgdata.lens.makernotes.Lens, len, ifp);
break;
case 0x0414:
if (type == 4)
{
imgdata.lens.makernotes.MaxAp4CurFocal = libraw_powf64l(2.0f, (int_to_float(data) / 2.0f));
}
else
{
imgdata.lens.makernotes.MaxAp4CurFocal = libraw_powf64l(2.0f, (getreal(type) / 2.0f));
}
break;
case 0x0415:
if (type == 4)
{
imgdata.lens.makernotes.MinAp4CurFocal = libraw_powf64l(2.0f, (int_to_float(data) / 2.0f));
}
else
{
imgdata.lens.makernotes.MinAp4CurFocal = libraw_powf64l(2.0f, (getreal(type) / 2.0f));
}
break;
case 0x0416:
if (type == 4)
{
imgdata.lens.makernotes.MinFocal = int_to_float(data);
}
else
{
imgdata.lens.makernotes.MinFocal = getreal(type);
}
if (imgdata.lens.makernotes.MinFocal > 1000.0f)
{
imgdata.lens.makernotes.MinFocal = 0.0f;
}
break;
case 0x0417:
if (type == 4)
{
imgdata.lens.makernotes.MaxFocal = int_to_float(data);
}
else
{
imgdata.lens.makernotes.MaxFocal = getreal(type);
}
break;
#endif
case 0x100:
flip = "0653"[data & 3] - '0';
break;
case 0x106:
for (i = 0; i < 9; i++)
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.color.P1_color[0].romm_cam[i] =
#endif
((float *)romm_cam)[i] = getreal(11);
romm_coeff(romm_cam);
break;
case 0x107:
FORC3 cam_mul[c] = getreal(11);
break;
case 0x108:
raw_width = data;
break;
case 0x109:
raw_height = data;
break;
case 0x10a:
left_margin = data;
break;
case 0x10b:
top_margin = data;
break;
case 0x10c:
width = data;
break;
case 0x10d:
height = data;
break;
case 0x10e:
ph1.format = data;
break;
case 0x10f:
data_offset = data + base;
break;
case 0x110:
meta_offset = data + base;
meta_length = len;
break;
case 0x112:
ph1.key_off = save - 4;
break;
case 0x210:
ph1.tag_210 = int_to_float(data);
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.other.SensorTemperature = ph1.tag_210;
#endif
break;
case 0x21a:
ph1.tag_21a = data;
break;
case 0x21c:
strip_offset = data + base;
break;
case 0x21d:
ph1.t_black = data;
break;
case 0x222:
ph1.split_col = data;
break;
case 0x223:
ph1.black_col = data + base;
break;
case 0x224:
ph1.split_row = data;
break;
case 0x225:
ph1.black_row = data + base;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 0x226:
for (i = 0; i < 9; i++)
imgdata.color.P1_color[1].romm_cam[i] = getreal(11);
break;
#endif
case 0x301:
model[63] = 0;
fread(model, 1, 63, ifp);
if ((cp = strstr(model, " camera")))
*cp = 0;
}
fseek(ifp, save, SEEK_SET);
}
#ifdef LIBRAW_LIBRARY_BUILD
if (!imgdata.lens.makernotes.body[0] && !imgdata.shootinginfo.BodySerial[0])
{
fseek(ifp, meta_offset, SEEK_SET);
order = get2();
fseek(ifp, 6, SEEK_CUR);
fseek(ifp, meta_offset + get4(), SEEK_SET);
entries = get4();
get4();
while (entries--)
{
tag = get4();
len = get4();
data = get4();
save = ftell(ifp);
fseek(ifp, meta_offset + data, SEEK_SET);
if (tag == 0x0407)
{
stmread(imgdata.shootinginfo.BodySerial, len, ifp);
if ((imgdata.shootinginfo.BodySerial[0] == 0x4c) && (imgdata.shootinginfo.BodySerial[1] == 0x49))
{
unique_id =
(((imgdata.shootinginfo.BodySerial[0] & 0x3f) << 5) | (imgdata.shootinginfo.BodySerial[2] & 0x3f)) - 0x41;
}
else
{
unique_id =
(((imgdata.shootinginfo.BodySerial[0] & 0x3f) << 5) | (imgdata.shootinginfo.BodySerial[1] & 0x3f)) - 0x41;
}
setPhaseOneFeatures(unique_id);
}
fseek(ifp, save, SEEK_SET);
}
}
#endif
load_raw = ph1.format < 3 ? &CLASS phase_one_load_raw : &CLASS phase_one_load_raw_c;
maximum = 0xffff;
strcpy(make, "Phase One");
if (model[0])
return;
switch (raw_height)
{
case 2060:
strcpy(model, "LightPhase");
break;
case 2682:
strcpy(model, "H 10");
break;
case 4128:
strcpy(model, "H 20");
break;
case 5488:
strcpy(model, "H 25");
break;
}
}
void CLASS parse_fuji(int offset)
{
unsigned entries, tag, len, save, c;
fseek(ifp, offset, SEEK_SET);
entries = get4();
if (entries > 255)
return;
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.process_warnings |= LIBRAW_WARN_PARSEFUJI_PROCESSED;
#endif
while (entries--)
{
tag = get2();
len = get2();
save = ftell(ifp);
if (tag == 0x100)
{
raw_height = get2();
raw_width = get2();
}
else if (tag == 0x121)
{
height = get2();
if ((width = get2()) == 4284)
width += 3;
}
else if (tag == 0x130)
{
fuji_layout = fgetc(ifp) >> 7;
fuji_width = !(fgetc(ifp) & 8);
}
else if (tag == 0x131)
{
filters = 9;
FORC(36)
{
int q = fgetc(ifp);
xtrans_abs[0][35 - c] = MAX(0, MIN(q, 2)); /* & 3;*/
}
}
else if (tag == 0x2ff0)
{
FORC4 cam_mul[c ^ 1] = get2();
// IB start
#ifdef LIBRAW_LIBRARY_BUILD
}
else if (tag == 0x110)
{
imgdata.sizes.raw_crop.ctop = get2();
imgdata.sizes.raw_crop.cleft = get2();
}
else if (tag == 0x111)
{
imgdata.sizes.raw_crop.cheight = get2();
imgdata.sizes.raw_crop.cwidth = get2();
}
else if ((tag == 0x122) && !strcmp(model, "DBP for GX680"))
{
int k = get2();
int l = get2(); /* margins? */
int m = get2(); /* margins? */
int n = get2();
// printf ("==>>0x122: height= %d l= %d m= %d width= %d\n", k, l, m, n);
}
else if (tag == 0x9650)
{
short a = (short)get2();
float b = fMAX(1.0f, get2());
imgdata.makernotes.fuji.FujiExpoMidPointShift = a / b;
}
else if (tag == 0x2f00)
{
int nWBs = get4();
nWBs = MIN(nWBs, 6);
for (int wb_ind = 0; wb_ind < nWBs; wb_ind++)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Custom1 + wb_ind][c ^ 1] = get2();
fseek(ifp, 8, SEEK_CUR);
}
}
else if (tag == 0x2000)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Auto][c ^ 1] = get2();
}
else if (tag == 0x2100)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FineWeather][c ^ 1] = get2();
}
else if (tag == 0x2200)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Shade][c ^ 1] = get2();
}
else if (tag == 0x2300)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_D][c ^ 1] = get2();
}
else if (tag == 0x2301)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_N][c ^ 1] = get2();
}
else if (tag == 0x2302)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_WW][c ^ 1] = get2();
}
else if (tag == 0x2310)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_FL_L][c ^ 1] = get2();
}
else if (tag == 0x2400)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Tungsten][c ^ 1] = get2();
}
else if (tag == 0x2410)
{
FORC4 imgdata.color.WB_Coeffs[LIBRAW_WBI_Flash][c ^ 1] = get2();
#endif
// IB end
}
else if (tag == 0xc000)
/* 0xc000 tag versions, second ushort; valid if the first ushort is 0
X100F 0x0259
X100T 0x0153
X-E2 0x014f 0x024f depends on firmware
X-A1 0x014e
XQ2 0x0150
XQ1 0x0150
X100S 0x0149 0x0249 depends on firmware
X30 0x0152
X20 0x0146
X-T10 0x0154
X-T2 0x0258
X-M1 0x014d
X-E2s 0x0355
X-A2 0x014e
X-T20 0x025b
GFX 50S 0x025a
X-T1 0x0151 0x0251 0x0351 depends on firmware
X70 0x0155
X-Pro2 0x0255
*/
{
c = order;
order = 0x4949;
if ((tag = get4()) > 10000)
tag = get4();
if (tag > 10000)
tag = get4();
width = tag;
height = get4();
#ifdef LIBRAW_LIBRARY_BUILD
if (!strcmp(model, "X-A3") ||
!strcmp(model, "X-A10") ||
!strcmp(model, "X-A5") ||
!strcmp(model, "X-A20"))
{
int wb[4];
int nWB, tWB, pWB;
int iCCT = 0;
int cnt;
fseek(ifp, save + 0x200, SEEK_SET);
for (int wb_ind = 0; wb_ind < 42; wb_ind++)
{
nWB = get4();
tWB = get4();
wb[0] = get4() << 1;
wb[1] = get4();
wb[3] = get4();
wb[2] = get4() << 1;
if (tWB && (iCCT < 255))
{
imgdata.color.WBCT_Coeffs[iCCT][0] = tWB;
for (cnt = 0; cnt < 4; cnt++)
imgdata.color.WBCT_Coeffs[iCCT][cnt + 1] = wb[cnt];
iCCT++;
}
if (nWB != 70)
{
for (pWB = 1; pWB < nFuji_wb_list2; pWB += 2)
{
if (Fuji_wb_list2[pWB] == nWB)
{
for (cnt = 0; cnt < 4; cnt++)
imgdata.color.WB_Coeffs[Fuji_wb_list2[pWB - 1]][cnt] = wb[cnt];
break;
}
}
}
}
}
else
{
libraw_internal_data.unpacker_data.posRAFData = save;
libraw_internal_data.unpacker_data.lenRAFData = (len >> 1);
}
#endif
order = c;
}
fseek(ifp, save + len, SEEK_SET);
}
height <<= fuji_layout;
width >>= fuji_layout;
}
int CLASS parse_jpeg(int offset)
{
int len, save, hlen, mark;
fseek(ifp, offset, SEEK_SET);
if (fgetc(ifp) != 0xff || fgetc(ifp) != 0xd8)
return 0;
while (fgetc(ifp) == 0xff && (mark = fgetc(ifp)) != 0xda)
{
order = 0x4d4d;
len = get2() - 2;
save = ftell(ifp);
if (mark == 0xc0 || mark == 0xc3 || mark == 0xc9)
{
fgetc(ifp);
raw_height = get2();
raw_width = get2();
}
order = get2();
hlen = get4();
if (get4() == 0x48454150
#ifdef LIBRAW_LIBRARY_BUILD
&& (save + hlen) >= 0 && (save + hlen) <= ifp->size()
#endif
) /* "HEAP" */
{
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
#endif
parse_ciff(save + hlen, len - hlen, 0);
}
if (parse_tiff(save + 6))
apply_tiff();
fseek(ifp, save + len, SEEK_SET);
}
return 1;
}
void CLASS parse_riff()
{
unsigned i, size, end;
char tag[4], date[64], month[64];
static const char mon[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
struct tm t;
order = 0x4949;
fread(tag, 4, 1, ifp);
size = get4();
end = ftell(ifp) + size;
if (!memcmp(tag, "RIFF", 4) || !memcmp(tag, "LIST", 4))
{
int maxloop = 1000;
get4();
while (ftell(ifp) + 7 < end && !feof(ifp) && maxloop--)
parse_riff();
}
else if (!memcmp(tag, "nctg", 4))
{
while (ftell(ifp) + 7 < end)
{
i = get2();
size = get2();
if ((i + 1) >> 1 == 10 && size == 20)
get_timestamp(0);
else
fseek(ifp, size, SEEK_CUR);
}
}
else if (!memcmp(tag, "IDIT", 4) && size < 64)
{
fread(date, 64, 1, ifp);
date[size] = 0;
memset(&t, 0, sizeof t);
if (sscanf(date, "%*s %s %d %d:%d:%d %d", month, &t.tm_mday, &t.tm_hour, &t.tm_min, &t.tm_sec, &t.tm_year) == 6)
{
for (i = 0; i < 12 && strcasecmp(mon[i], month); i++)
;
t.tm_mon = i;
t.tm_year -= 1900;
if (mktime(&t) > 0)
timestamp = mktime(&t);
}
}
else
fseek(ifp, size, SEEK_CUR);
}
void CLASS parse_qt(int end)
{
unsigned save, size;
char tag[4];
order = 0x4d4d;
while (ftell(ifp) + 7 < end)
{
save = ftell(ifp);
if ((size = get4()) < 8)
return;
if ((int)size < 0) return; // 2+GB is too much
if (save + size < save) return; // 32bit overflow
fread(tag, 4, 1, ifp);
if (!memcmp(tag, "moov", 4) || !memcmp(tag, "udta", 4) || !memcmp(tag, "CNTH", 4))
parse_qt(save + size);
if (!memcmp(tag, "CNDA", 4))
parse_jpeg(ftell(ifp));
fseek(ifp, save + size, SEEK_SET);
}
}
void CLASS parse_smal(int offset, int fsize)
{
int ver;
fseek(ifp, offset + 2, SEEK_SET);
order = 0x4949;
ver = fgetc(ifp);
if (ver == 6)
fseek(ifp, 5, SEEK_CUR);
if (get4() != fsize)
return;
if (ver > 6)
data_offset = get4();
raw_height = height = get2();
raw_width = width = get2();
strcpy(make, "SMaL");
sprintf(model, "v%d %dx%d", ver, width, height);
if (ver == 6)
load_raw = &CLASS smal_v6_load_raw;
if (ver == 9)
load_raw = &CLASS smal_v9_load_raw;
}
void CLASS parse_cine()
{
unsigned off_head, off_setup, off_image, i;
order = 0x4949;
fseek(ifp, 4, SEEK_SET);
is_raw = get2() == 2;
fseek(ifp, 14, SEEK_CUR);
is_raw *= get4();
off_head = get4();
off_setup = get4();
off_image = get4();
timestamp = get4();
if ((i = get4()))
timestamp = i;
fseek(ifp, off_head + 4, SEEK_SET);
raw_width = get4();
raw_height = get4();
switch (get2(), get2())
{
case 8:
load_raw = &CLASS eight_bit_load_raw;
break;
case 16:
load_raw = &CLASS unpacked_load_raw;
}
fseek(ifp, off_setup + 792, SEEK_SET);
strcpy(make, "CINE");
sprintf(model, "%d", get4());
fseek(ifp, 12, SEEK_CUR);
switch ((i = get4()) & 0xffffff)
{
case 3:
filters = 0x94949494;
break;
case 4:
filters = 0x49494949;
break;
default:
is_raw = 0;
}
fseek(ifp, 72, SEEK_CUR);
switch ((get4() + 3600) % 360)
{
case 270:
flip = 4;
break;
case 180:
flip = 1;
break;
case 90:
flip = 7;
break;
case 0:
flip = 2;
}
cam_mul[0] = getreal(11);
cam_mul[2] = getreal(11);
maximum = ~((~0u) << get4());
fseek(ifp, 668, SEEK_CUR);
shutter = get4() / 1000000000.0;
fseek(ifp, off_image, SEEK_SET);
if (shot_select < is_raw)
fseek(ifp, shot_select * 8, SEEK_CUR);
data_offset = (INT64)get4() + 8;
data_offset += (INT64)get4() << 32;
}
void CLASS parse_redcine()
{
unsigned i, len, rdvo;
order = 0x4d4d;
is_raw = 0;
fseek(ifp, 52, SEEK_SET);
width = get4();
height = get4();
fseek(ifp, 0, SEEK_END);
fseek(ifp, -(i = ftello(ifp) & 511), SEEK_CUR);
if (get4() != i || get4() != 0x52454f42)
{
#ifdef DCRAW_VERBOSE
fprintf(stderr, _("%s: Tail is missing, parsing from head...\n"), ifname);
#endif
fseek(ifp, 0, SEEK_SET);
while ((len = get4()) != EOF)
{
if (get4() == 0x52454456)
if (is_raw++ == shot_select)
data_offset = ftello(ifp) - 8;
fseek(ifp, len - 8, SEEK_CUR);
}
}
else
{
rdvo = get4();
fseek(ifp, 12, SEEK_CUR);
is_raw = get4();
fseeko(ifp, rdvo + 8 + shot_select * 4, SEEK_SET);
data_offset = get4();
}
}
/*
All matrices are from Adobe DNG Converter unless otherwise noted.
*/
void CLASS adobe_coeff(const char *t_make, const char *t_model
#ifdef LIBRAW_LIBRARY_BUILD
,
int internal_only
#endif
)
{
// clang-format off
static const struct
{
const char *prefix;
int t_black, t_maximum, trans[12];
} table[] = {
{ "AgfaPhoto DC-833m", 0, 0, /* DJC */
{ 11438,-3762,-1115,-2409,9914,2497,-1227,2295,5300 } },
{ "Apple QuickTake", 0, 0, /* DJC */
{ 21392,-5653,-3353,2406,8010,-415,7166,1427,2078 } },
{"Broadcom RPi IMX219", 66, 0x3ff,
{ 5302,1083,-728,-5320,14112,1699,-863,2371,5136 } }, /* LibRaw */
{ "Broadcom RPi OV5647", 16, 0x3ff,
{ 12782,-4059,-379,-478,9066,1413,1340,1513,5176 } }, /* DJC */
{ "Canon EOS D2000", 0, 0,
{ 24542,-10860,-3401,-1490,11370,-297,2858,-605,3225 } },
{ "Canon EOS D6000", 0, 0,
{ 20482,-7172,-3125,-1033,10410,-285,2542,226,3136 } },
{ "Canon EOS D30", 0, 0, /* updated */
{ 9900,-2771,-1324,-7072,14229,3140,-2790,3344,8861 } },
{ "Canon EOS D60", 0, 0xfa0, /* updated */
{ 6211,-1358,-896,-8557,15766,3012,-3001,3507,8567 } },
{ "Canon EOS 5DS", 0, 0x3c96,
{ 6250,-711,-808,-5153,12794,2636,-1249,2198,5610 } },
{ "Canon EOS 5D Mark IV", 0, 0,
{ 6446,-366,-864,-4436,12204,2513,-952,2496,6348 } },
{ "Canon EOS 5D Mark III", 0, 0x3c80,
{ 6722,-635,-963,-4287,12460,2028,-908,2162,5668 } },
{ "Canon EOS 5D Mark II", 0, 0x3cf0,
{ 4716,603,-830,-7798,15474,2480,-1496,1937,6651 } },
{ "Canon EOS 5D", 0, 0xe6c,
{ 6347,-479,-972,-8297,15954,2480,-1968,2131,7649 } },
{ "Canon EOS 6D Mark II", 0, 0x38de,
{ 6875,-970,-932,-4691,12459,2501,-874,1953,5809 } },
{ "Canon EOS 6D", 0, 0x3c82,
{7034, -804, -1014, -4420, 12564, 2058, -851, 1994, 5758 } },
{ "Canon EOS 77D", 0, 0,
{ 7377,-742,-998,-4235,11981,2549,-673,1918,5538 } },
{ "Canon EOS 7D Mark II", 0, 0x3510,
{ 7268,-1082,-969,-4186,11839,2663,-825,2029,5839 } },
{ "Canon EOS 7D", 0, 0x3510,
{ 6844,-996,-856,-3876,11761,2396,-593,1772,6198 } },
{ "Canon EOS 800D", 0, 0,
{ 6970,-512,-968,-4425,12161,2553,-739,1982,5601 } },
{ "Canon EOS 80D", 0, 0,
{ 7457,-671,-937,-4849,12495,2643,-1213,2354,5492 } },
{ "Canon EOS 10D", 0, 0xfa0, /* updated */
{ 8250,-2044,-1127,-8092,15606,2664,-2893,3453,8348 } },
{ "Canon EOS 200D", 0, 0,
{ 7377,-742,-998,-4235,11981,2549,-673,1918,5538 } },
{ "Canon EOS 20Da", 0, 0,
{ 14155,-5065,-1382,-6550,14633,2039,-1623,1824,6561 } },
{ "Canon EOS 20D", 0, 0xfff,
{ 6599,-537,-891,-8071,15783,2424,-1983,2234,7462 } },
{ "Canon EOS 30D", 0, 0,
{ 6257,-303,-1000,-7880,15621,2396,-1714,1904,7046 } },
{ "Canon EOS 40D", 0, 0x3f60,
{ 6071,-747,-856,-7653,15365,2441,-2025,2553,7315 } },
{ "Canon EOS 50D", 0, 0x3d93,
{ 4920,616,-593,-6493,13964,2784,-1774,3178,7005 } },
{ "Canon EOS 60Da", 0, 0x2ff7, /* added */
{ 17492,-7240,-2023,-1791,10323,1701,-186,1329,5406 } },
{ "Canon EOS 60D", 0, 0x2ff7,
{ 6719,-994,-925,-4408,12426,2211,-887,2129,6051 } },
{ "Canon EOS 70D", 0, 0x3bc7,
{ 7034,-804,-1014,-4420,12564,2058,-851,1994,5758 } },
{ "Canon EOS 100D", 0, 0x350f,
{ 6602,-841,-939,-4472,12458,2247,-975,2039,6148 } },
{ "Canon EOS 300D", 0, 0xfa0, /* updated */
{ 8250,-2044,-1127,-8092,15606,2664,-2893,3453,8348 } },
{ "Canon EOS 350D", 0, 0xfff,
{ 6018,-617,-965,-8645,15881,2975,-1530,1719,7642 } },
{ "Canon EOS 400D", 0, 0xe8e,
{ 7054,-1501,-990,-8156,15544,2812,-1278,1414,7796 } },
{ "Canon EOS 450D", 0, 0x390d,
{ 5784,-262,-821,-7539,15064,2672,-1982,2681,7427 } },
{ "Canon EOS 500D", 0, 0x3479,
{ 4763,712,-646,-6821,14399,2640,-1921,3276,6561 } },
{ "Canon EOS 550D", 0, 0x3dd7,
{ 6941,-1164,-857,-3825,11597,2534,-416,1540,6039 } },
{ "Canon EOS 600D", 0, 0x3510,
{ 6461,-907,-882,-4300,12184,2378,-819,1944,5931 } },
{ "Canon EOS 650D", 0, 0x354d,
{ 6602,-841,-939,-4472,12458,2247,-975,2039,6148 } },
{ "Canon EOS 750D", 0, 0x3c00,
{ 6362,-823,-847,-4426,12109,2616,-743,1857,5635 } },
{ "Canon EOS 760D", 0, 0x3c00,
{ 6362,-823,-847,-4426,12109,2616,-743,1857,5635 } },
{ "Canon EOS 700D", 0, 0x3c00,
{ 6602,-841,-939,-4472,12458,2247,-975,2039,6148 } },
{ "Canon EOS 1000D", 0, 0xe43,
{ 6771,-1139,-977,-7818,15123,2928,-1244,1437,7533 } },
{ "Canon EOS 1100D", 0, 0x3510,
{ 6444,-904,-893,-4563,12308,2535,-903,2016,6728 } },
{ "Canon EOS 1200D", 0, 0x37c2,
{ 6461,-907,-882,-4300,12184,2378,-819,1944,5931 } },
{ "Canon EOS 1300D", 0, 0x37c2,
{ 6939,-1016,-866,-4428,12473,2177,-1175,2178,6162 } },
{ "Canon EOS M6", 0, 0,
{ 8532,-701,-1167,-4095,11879,2508,-797,2424,7010 } },
{ "Canon EOS M5", 0, 0,
{ 8532,-701,-1167,-4095,11879,2508,-797,2424,7010 } },
{ "Canon EOS M3", 0, 0,
{ 6362,-823,-847,-4426,12109,2616,-743,1857,5635 } },
{ "Canon EOS M2", 0, 0, /* added */
{ 6400,-480,-888,-5294,13416,2047,-1296,2203,6137 } },
{ "Canon EOS M100", 0, 0,
{ 8532,-701,-1167,-4095,11879,2508,-797,2424,7010 } },
{ "Canon EOS M10", 0, 0,
{ 6400,-480,-888,-5294,13416,2047,-1296,2203,6137 } },
{ "Canon EOS M", 0, 0,
{ 6602,-841,-939,-4472,12458,2247,-975,2039,6148 } },
{ "Canon EOS-1Ds Mark III", 0, 0x3bb0,
{ 5859,-211,-930,-8255,16017,2353,-1732,1887,7448 } },
{ "Canon EOS-1Ds Mark II", 0, 0xe80,
{ 6517,-602,-867,-8180,15926,2378,-1618,1771,7633 } },
{ "Canon EOS-1D Mark IV", 0, 0x3bb0,
{ 6014,-220,-795,-4109,12014,2361,-561,1824,5787 } },
{ "Canon EOS-1D Mark III", 0, 0x3bb0,
{ 6291,-540,-976,-8350,16145,2311,-1714,1858,7326 } },
{ "Canon EOS-1D Mark II N", 0, 0xe80,
{ 6240,-466,-822,-8180,15825,2500,-1801,1938,8042 } },
{ "Canon EOS-1D Mark II", 0, 0xe80,
{ 6264,-582,-724,-8312,15948,2504,-1744,1919,8664 } },
{ "Canon EOS-1DS", 0, 0xe20, /* updated */
{ 3925,4060,-1739,-8973,16552,2545,-3287,3945,8243 } },
{ "Canon EOS-1D C", 0, 0x3c4e,
{ 6847,-614,-1014,-4669,12737,2139,-1197,2488,6846 } },
{ "Canon EOS-1D X Mark II", 0, 0x3c4e, /* updated */
{ 7596,-978,-967,-4808,12571,2503,-1398,2567,5752 } },
{ "Canon EOS-1D X", 0, 0x3c4e,
{ 6847,-614,-1014,-4669,12737,2139,-1197,2488,6846 } },
{ "Canon EOS-1D", 0, 0xe20,
{ 6806,-179,-1020,-8097,16415,1687,-3267,4236,7690 } },
{ "Canon EOS C500", 853, 0, /* DJC */
{ 17851,-10604,922,-7425,16662,763,-3660,3636,22278 } },
{"Canon PowerShot 600", 0, 0, /* added */
{ -3822,10019,1311,4085,-157,3386,-5341,10829,4812,-1969,10969,1126 } },
{ "Canon PowerShot A530", 0, 0,
{ 0 } }, /* don't want the A5 matrix */
{ "Canon PowerShot A50", 0, 0,
{ -5300,9846,1776,3436,684,3939,-5540,9879,6200,-1404,11175,217 } },
{ "Canon PowerShot A5", 0, 0,
{ -4801,9475,1952,2926,1611,4094,-5259,10164,5947,-1554,10883,547 } },
{ "Canon PowerShot G10", 0, 0,
{ 11093,-3906,-1028,-5047,12492,2879,-1003,1750,5561 } },
{ "Canon PowerShot G11", 0, 0,
{ 12177,-4817,-1069,-1612,9864,2049,-98,850,4471 } },
{ "Canon PowerShot G12", 0, 0,
{ 13244,-5501,-1248,-1508,9858,1935,-270,1083,4366 } },
{ "Canon PowerShot G15", 0, 0,
{ 7474,-2301,-567,-4056,11456,2975,-222,716,4181 } },
{ "Canon PowerShot G16", 0, 0, /* updated */
{ 8020,-2687,-682,-3704,11879,2052,-965,1921,5556 } },
{ "Canon PowerShot G1 X Mark III", 0, 0,
{ 8532,-701,-1167,-4095,11879,2508,-797,2424,7010 } },
{ "Canon PowerShot G1 X Mark II", 0, 0,
{ 7378,-1255,-1043,-4088,12251,2048,-876,1946,5805 } },
{ "Canon PowerShot G1 X", 0, 0,
{ 7378,-1255,-1043,-4088,12251,2048,-876,1946,5805 } },
{ "Canon PowerShot G1", 0, 0, /* updated */
{ -5686,10300,2223,4725,-1157,4383,-6128,10783,6163,-2688,12093,604 } },
{ "Canon PowerShot G2", 0, 0, /* updated */
{ 9194,-2787,-1059,-8098,15657,2608,-2610,3064,7867 } },
{ "Canon PowerShot G3 X", 0, 0,
{ 9701,-3857,-921,-3149,11537,1817,-786,1817,5147 } },
{ "Canon PowerShot G3", 0, 0, /* updated */
{ 9326,-2882,-1084,-7940,15447,2677,-2620,3090,7740 } },
{ "Canon PowerShot G5 X",0, 0,
{ 9602,-3823,-937,-2984,11495,1675,-407,1415,5049 } },
{ "Canon PowerShot G5", 0, 0, /* updated */
{ 9869,-2972,-942,-7314,15098,2369,-1898,2536,7282 } },
{ "Canon PowerShot G6", 0, 0,
{ 9877,-3775,-871,-7613,14807,3072,-1448,1305,7485 } },
{ "Canon PowerShot G7 X Mark II", 0, 0,
{ 9602,-3823,-937,-2984,11495,1675,-407,1415,5049 } },
{ "Canon PowerShot G7 X", 0, 0,
{ 9602,-3823,-937,-2984,11495,1675,-407,1415,5049 } },
{ "Canon PowerShot G9 X Mark II", 0, 0,
{ 10056,-4131,-944,-2576,11143,1625,-238,1294,5179 } },
{ "Canon PowerShot G9 X",0, 0,
{ 9602,-3823,-937,-2984,11495,1675,-407,1415,5049 } },
{ "Canon PowerShot G9", 0, 0,
{ 7368,-2141,-598,-5621,13254,2625,-1418,1696,5743 } },
{ "Canon PowerShot Pro1", 0, 0,
{ 10062,-3522,-999,-7643,15117,2730,-765,817,7323 } },
{ "Canon PowerShot Pro70", 34, 0, /* updated */
{ -5106,10695,1576,3820,53,4566,-6497,10736,6701,-3336,11887,1394 } },
{ "Canon PowerShot Pro90", 0, 0, /* updated */
{ -5912,10768,2288,4612,-989,4333,-6153,10897,5944,-2907,12288,624 } },
{ "Canon PowerShot S30", 0, 0, /* updated */
{ 10744,-3813,-1142,-7962,15966,2075,-2492,2805,7744 } },
{ "Canon PowerShot S40", 0, 0, /* updated */
{ 8606,-2573,-949,-8237,15489,2974,-2649,3076,9100 } },
{ "Canon PowerShot S45", 0, 0, /* updated */
{ 8251,-2410,-964,-8047,15430,2823,-2380,2824,8119 } },
{ "Canon PowerShot S50", 0, 0, /* updated */
{ 8979,-2658,-871,-7721,15500,2357,-1773,2366,6634 } },
{ "Canon PowerShot S60", 0, 0,
{ 8795,-2482,-797,-7804,15403,2573,-1422,1996,7082 } },
{ "Canon PowerShot S70", 0, 0,
{ 9976,-3810,-832,-7115,14463,2906,-901,989,7889 } },
{ "Canon PowerShot S90", 0, 0,
{ 12374,-5016,-1049,-1677,9902,2078,-83,852,4683 } },
{ "Canon PowerShot S95", 0, 0,
{ 13440,-5896,-1279,-1236,9598,1931,-180,1001,4651 } },
{ "Canon PowerShot S120", 0, 0,
{ 6961,-1685,-695,-4625,12945,1836,-1114,2152,5518 } },
{ "Canon PowerShot S110", 0, 0,
{ 8039,-2643,-654,-3783,11230,2930,-206,690,4194 } },
{ "Canon PowerShot S100", 0, 0,
{ 7968,-2565,-636,-2873,10697,2513,180,667,4211 } },
{ "Canon PowerShot SX1 IS", 0, 0,
{ 6578,-259,-502,-5974,13030,3309,-308,1058,4970 } },
{ "Canon PowerShot SX50 HS", 0, 0,
{ 12432,-4753,-1247,-2110,10691,1629,-412,1623,4926 } },
{ "Canon PowerShot SX60 HS", 0, 0,
{ 13161,-5451,-1344,-1989,10654,1531,-47,1271,4955 } },
{ "Canon PowerShot A3300", 0, 0, /* DJC */
{ 10826,-3654,-1023,-3215,11310,1906,0,999,4960 } },
{ "Canon PowerShot A470", 0, 0, /* DJC */
{ 12513,-4407,-1242,-2680,10276,2405,-878,2215,4734 } },
{ "Canon PowerShot A610", 0, 0, /* DJC */
{ 15591,-6402,-1592,-5365,13198,2168,-1300,1824,5075 } },
{ "Canon PowerShot A620", 0, 0, /* DJC */
{ 15265,-6193,-1558,-4125,12116,2010,-888,1639,5220 } },
{ "Canon PowerShot A630", 0, 0, /* DJC */
{ 14201,-5308,-1757,-6087,14472,1617,-2191,3105,5348 } },
{ "Canon PowerShot A640", 0, 0, /* DJC */
{ 13124,-5329,-1390,-3602,11658,1944,-1612,2863,4885 } },
{ "Canon PowerShot A650", 0, 0, /* DJC */
{ 9427,-3036,-959,-2581,10671,1911,-1039,1982,4430 } },
{ "Canon PowerShot A720", 0, 0, /* DJC */
{ 14573,-5482,-1546,-1266,9799,1468,-1040,1912,3810 } },
{ "Canon PowerShot D10", 127, 0, /* DJC */
{ 14052,-5229,-1156,-1325,9420,2252,-498,1957,4116 } },
{ "Canon PowerShot S3 IS", 0, 0, /* DJC */
{ 14062,-5199,-1446,-4712,12470,2243,-1286,2028,4836 } },
{ "Canon PowerShot SX110 IS", 0, 0, /* DJC */
{ 14134,-5576,-1527,-1991,10719,1273,-1158,1929,3581 } },
{ "Canon PowerShot SX220", 0, 0, /* DJC */
{ 13898,-5076,-1447,-1405,10109,1297,-244,1860,3687 } },
{ "Canon IXUS 160", 0, 0, /* DJC */
{ 11657,-3781,-1136,-3544,11262,2283,-160,1219,4700 } },
{ "Casio EX-F1", 0, 0, /* added */
{ 9084,-2016,-848,-6711,14351,2570,-1059,1725,6135 } },
{ "Casio EX-FH100", 0, 0, /* added */
{ 12771,-4179,-1558,-2149,10938,1375,-453,1751,4494 } },
{ "Casio EX-S20", 0, 0, /* DJC */
{ 11634,-3924,-1128,-4968,12954,2015,-1588,2648,7206 } },
{ "Casio EX-Z750", 0, 0, /* DJC */
{ 10819,-3873,-1099,-4903,13730,1175,-1755,3751,4632 } },
{ "Casio EX-Z10", 128, 0xfff, /* DJC */
{ 9790,-3338,-603,-2321,10222,2099,-344,1273,4799 } },
{ "CINE 650", 0, 0,
{ 3390,480,-500,-800,3610,340,-550,2336,1192 } },
{ "CINE 660", 0, 0,
{ 3390,480,-500,-800,3610,340,-550,2336,1192 } },
{ "CINE", 0, 0,
{ 20183,-4295,-423,-3940,15330,3985,-280,4870,9800 } },
{ "Contax N Digital", 0, 0xf1e,
{ 7777,1285,-1053,-9280,16543,2916,-3677,5679,7060 } },
{ "DXO ONE", 0, 0,
{ 6596,-2079,-562,-4782,13016,1933,-970,1581,5181 } },
{ "Epson R-D1", 0, 0,
{ 6827,-1878,-732,-8429,16012,2564,-704,592,7145 } },
{ "Fujifilm E550", 0, 0, /* updated */
{ 11044,-3888,-1120,-7248,15167,2208,-1531,2276,8069 } },
{ "Fujifilm E900", 0, 0,
{ 9183,-2526,-1078,-7461,15071,2574,-2022,2440,8639 } },
{ "Fujifilm F5", 0, 0,
{ 13690,-5358,-1474,-3369,11600,1998,-132,1554,4395 } },
{ "Fujifilm F6", 0, 0,
{ 13690,-5358,-1474,-3369,11600,1998,-132,1554,4395 } },
{ "Fujifilm F77", 0, 0xfe9,
{ 13690,-5358,-1474,-3369,11600,1998,-132,1554,4395 } },
{ "Fujifilm F7", 0, 0,
{ 10004,-3219,-1201,-7036,15047,2107,-1863,2565,7736 } },
{ "Fujifilm F810", 0, 0, /* added */
{ 11044,-3888,-1120,-7248,15167,2208,-1531,2276,8069 } },
{ "Fujifilm F8", 0, 0,
{ 13690,-5358,-1474,-3369,11600,1998,-132,1554,4395 } },
{ "Fujifilm S100FS", 514, 0,
{ 11521,-4355,-1065,-6524,13767,3058,-1466,1984,6045 } },
{ "Fujifilm S1", 0, 0,
{ 12297,-4882,-1202,-2106,10691,1623,-88,1312,4790 } },
{ "Fujifilm S20Pro", 0, 0,
{ 10004,-3219,-1201,-7036,15047,2107,-1863,2565,7736 } },
{ "Fujifilm S20", 512, 0x3fff,
{ 11401,-4498,-1312,-5088,12751,2613,-838,1568,5941 } },
{ "Fujifilm S2Pro", 128, 0, /* updated */
{ 12741,-4916,-1420,-8510,16791,1715,-1767,2302,7771 } },
{ "Fujifilm S3Pro", 0, 0,
{ 11807,-4612,-1294,-8927,16968,1988,-2120,2741,8006 } },
{ "Fujifilm S5Pro", 0, 0,
{ 12300,-5110,-1304,-9117,17143,1998,-1947,2448,8100 } },
{ "Fujifilm S5000", 0, 0,
{ 8754,-2732,-1019,-7204,15069,2276,-1702,2334,6982 } },
{ "Fujifilm S5100", 0, 0,
{ 11940,-4431,-1255,-6766,14428,2542,-993,1165,7421 } },
{ "Fujifilm S5500", 0, 0,
{ 11940,-4431,-1255,-6766,14428,2542,-993,1165,7421 } },
{ "Fujifilm S5200", 0, 0,
{ 9636,-2804,-988,-7442,15040,2589,-1803,2311,8621 } },
{ "Fujifilm S5600", 0, 0,
{ 9636,-2804,-988,-7442,15040,2589,-1803,2311,8621 } },
{ "Fujifilm S6", 0, 0,
{ 12628,-4887,-1401,-6861,14996,1962,-2198,2782,7091 } },
{ "Fujifilm S7000", 0, 0,
{ 10190,-3506,-1312,-7153,15051,2238,-2003,2399,7505 } },
{ "Fujifilm S9000", 0, 0,
{ 10491,-3423,-1145,-7385,15027,2538,-1809,2275,8692 } },
{ "Fujifilm S9500", 0, 0,
{ 10491,-3423,-1145,-7385,15027,2538,-1809,2275,8692 } },
{ "Fujifilm S9100", 0, 0,
{ 12343,-4515,-1285,-7165,14899,2435,-1895,2496,8800 } },
{ "Fujifilm S9600", 0, 0,
{ 12343,-4515,-1285,-7165,14899,2435,-1895,2496,8800 } },
{ "Fujifilm SL1000", 0, 0,
{ 11705,-4262,-1107,-2282,10791,1709,-555,1713,4945 } },
{ "Fujifilm IS-1", 0, 0,
{ 21461,-10807,-1441,-2332,10599,1999,289,875,7703 } },
{ "Fujifilm IS Pro", 0, 0,
{ 12300,-5110,-1304,-9117,17143,1998,-1947,2448,8100 } },
{ "Fujifilm HS10 HS11", 0, 0xf68,
{ 12440,-3954,-1183,-1123,9674,1708,-83,1614,4086 } },
{ "Fujifilm HS2", 0, 0,
{ 13690,-5358,-1474,-3369,11600,1998,-132,1554,4395 } },
{ "Fujifilm HS3", 0, 0,
{ 13690,-5358,-1474,-3369,11600,1998,-132,1554,4395 } },
{ "Fujifilm HS50EXR", 0, 0,
{ 12085,-4727,-953,-3257,11489,2002,-511,2046,4592 } },
{ "Fujifilm F900EXR", 0, 0,
{ 12085,-4727,-953,-3257,11489,2002,-511,2046,4592 } },
{ "Fujifilm X100S", 0, 0,
{ 10592,-4262,-1008,-3514,11355,2465,-870,2025,6386 } },
{ "Fujifilm X100F", 0, 0,
{ 11434,-4948,-1210,-3746,12042,1903,-666,1479,5235 } },
{ "Fujifilm X100T", 0, 0,
{ 10592,-4262,-1008,-3514,11355,2465,-870,2025,6386 } },
{ "Fujifilm X100", 0, 0,
{ 12161,-4457,-1069,-5034,12874,2400,-795,1724,6904 } },
{ "Fujifilm X10", 0, 0,
{ 13509,-6199,-1254,-4430,12733,1865,-331,1441,5022 } },
{ "Fujifilm X20", 0, 0,
{ 11768,-4971,-1133,-4904,12927,2183,-480,1723,4605 } },
{ "Fujifilm X30", 0, 0,
{ 12328,-5256,-1144,-4469,12927,1675,-87,1291,4351 } },
{ "Fujifilm X70", 0, 0,
{ 10450,-4329,-878,-3217,11105,2421,-752,1758,6519 } },
{ "Fujifilm X-Pro1", 0, 0,
{ 10413,-3996,-993,-3721,11640,2361,-733,1540,6011 } },
{ "Fujifilm X-Pro2", 0, 0,
{ 11434,-4948,-1210,-3746,12042,1903,-666,1479,5235 } },
{ "Fujifilm X-A10", 0, 0,
{ 11540,-4999,-991,-2949,10963,2278,-382,1049,5605} },
{ "Fujifilm X-A20", 0, 0, /* temp */
{ 11540,-4999,-991,-2949,10963,2278,-382,1049,5605} },
{ "Fujifilm X-A1", 0, 0,
{ 11086,-4555,-839,-3512,11310,2517,-815,1341,5940 } },
{ "Fujifilm X-A2", 0, 0,
{ 10763,-4560,-917,-3346,11311,2322,-475,1135,5843 } },
{ "Fujifilm X-A3", 0, 0,
{ 12407,-5222,-1086,-2971,11116,2120,-294,1029,5284 } },
{ "Fujifilm X-A5", 0, 0, /* temp */
{ 12407,-5222,-1086,-2971,11116,2120,-294,1029,5284 } },
{ "Fujifilm X-E1", 0, 0,
{ 10413,-3996,-993,-3721,11640,2361,-733,1540,6011 } },
{ "Fujifilm X-E2S", 0, 0,
{ 11562,-5118,-961,-3022,11007,2311,-525,1569,6097 } },
{ "Fujifilm X-E2", 0, 0,
{ 8458,-2451,-855,-4597,12447,2407,-1475,2482,6526 } },
{ "Fujifilm X-E3", 0, 0,
{ 11434,-4948,-1210,-3746,12042,1903,-666,1479,5235 } },
{ "Fujifilm XF1", 0, 0,
{ 13509,-6199,-1254,-4430,12733,1865,-331,1441,5022 } },
{ "Fujifilm X-M1", 0, 0,
{ 10413,-3996,-993,-3721,11640,2361,-733,1540,6011 } },
{ "Fujifilm X-S1", 0, 0,
{ 13509,-6199,-1254,-4430,12733,1865,-331,1441,5022 } },
{ "Fujifilm X-T20", 0, 0,
{ 11434,-4948,-1210,-3746,12042,1903,-666,1479,5235 } },
{ "Fujifilm X-T2", 0, 0,
{ 11434,-4948,-1210,-3746,12042,1903,-666,1479,5235 } },
{ "Fujifilm X-T10", 0, 0, /* updated */
{ 8458,-2451,-855,-4597,12447,2407,-1475,2482,6526 } },
{ "Fujifilm X-T1", 0, 0,
{ 8458,-2451,-855,-4597,12447,2407,-1475,2482,6526 } },
{ "Fujifilm X-H1", 0, 0,
{ 11434,-4948,-1210,-3746,12042,1903,-666,1479,5235 } },
{ "Fujifilm XQ1", 0, 0,
{ 9252,-2704,-1064,-5893,14265,1717,-1101,2341,4349 } },
{ "Fujifilm XQ2", 0, 0,
{ 9252,-2704,-1064,-5893,14265,1717,-1101,2341,4349 } },
{ "Fujifilm GFX 50S", 0, 0,
{ 11756,-4754,-874,-3056,11045,2305,-381,1457,6006 } },
{ "GITUP GIT2P", 4160, 0,
{ 8489, -2583,-1036,-8051,15583,2643,-1307,1407,7354 } },
{ "GITUP GIT2", 3200, 0,
{ 8489, -2583,-1036,-8051,15583,2643,-1307,1407,7354 } },
{ "Hasselblad HV", 0, 0, /* added */
{ 6344,-1612,-461,-4862,12476,2680,-864,1785,6898 } },
{ "Hasselblad Lunar", 0, 0,
{ 5491,-1192,-363,-4951,12342,2948,-911,1722,7192 } },
{ "Hasselblad Lusso", 0, 0, /* added */
{ 4912,-540,-201,-6129,13513,2906,-1563,2151,7182 } },
{ "Hasselblad Stellar", -800, 0,
{ 8651,-2754,-1057,-3464,12207,1373,-568,1398,4434 } },
{ "Hasselblad 500 mech.", 0, 0, /* added */
{ 8519,-3260,-280,-5081,13459,1738,-1449,2960,7809 } },
{ "Hasselblad CFV", 0, 0,
{ 8519,-3260,-280,-5081,13459,1738,-1449,2960,7809 } },
{ "Hasselblad H-16MP", 0, 0, /* LibRaw */
{ 17765,-5322,-1734,-6168,13354,2135,-264,2524,7440 } },
{ "Hasselblad H-22MP", 0, 0, /* LibRaw */
{ 17765,-5322,-1734,-6168,13354,2135,-264,2524,7440 } },
{ "Hasselblad H-31MP",0, 0, /* LibRaw */
{ 14480,-5448,-1686,-3534,13123,2260,384,2952,7232 } },
{ "Hasselblad 39-Coated", 0, 0, /* added */
{ 3857,452,-46,-6008,14477,1596,-2627,4481,5718 } },
{ "Hasselblad H-39MP",0, 0,
{ 3857,452,-46,-6008,14477,1596,-2627,4481,5718 } },
{ "Hasselblad H2D-39", 0, 0, /* added */
{ 3894,-110,287,-4672,12610,2295,-2092,4100,6196 } },
{ "Hasselblad H3D-50", 0, 0,
{ 3857,452,-46,-6008,14477,1596,-2627,4481,5718 } },
{ "Hasselblad H3D", 0, 0, /* added */
{ 3857,452,-46,-6008,14477,1596,-2627,4481,5718 } },
{ "Hasselblad H4D-40",0, 0, /* LibRaw */
{ 6325,-860,-957,-6559,15945,266,167,770,5936 } },
{ "Hasselblad H4D-50",0, 0, /* LibRaw */
{ 15283,-6272,-465,-2030,16031,478,-2379,390,7965 } },
{ "Hasselblad H4D-60",0, 0,
{ 9662,-684,-279,-4903,12293,2950,-344,1669,6024 } },
{ "Hasselblad H5D-50c",0, 0,
{ 4932,-835,141,-4878,11868,3437,-1138,1961,7067 } },
{ "Hasselblad H5D-50",0, 0,
{ 5656,-659,-346,-3923,12306,1791,-1602,3509,5442 } },
{ "Hasselblad H6D-100c",0, 0,
{ 5110,-1357,-308,-5573,12835,3077,-1279,2025,7010 } },
{ "Hasselblad X1D",0, 0,
{ 4932,-835,141,-4878,11868,3437,-1138,1961,7067 } },
{ "HTC One A9", 64, 1023, /* this is CM1 transposed */
{ 101, -20, -2, -11, 145, 41, -24, 1, 56 } },
{ "Imacon Ixpress", 0, 0, /* DJC */
{ 7025,-1415,-704,-5188,13765,1424,-1248,2742,6038 } },
{ "Kodak NC2000", 0, 0,
{ 13891,-6055,-803,-465,9919,642,2121,82,1291 } },
{ "Kodak DCS315C", -8, 0,
{ 17523,-4827,-2510,756,8546,-137,6113,1649,2250 } },
{ "Kodak DCS330C", -8, 0,
{ 20620,-7572,-2801,-103,10073,-396,3551,-233,2220 } },
{ "Kodak DCS420", 0, 0,
{ 10868,-1852,-644,-1537,11083,484,2343,628,2216 } },
{ "Kodak DCS460", 0, 0,
{ 10592,-2206,-967,-1944,11685,230,2206,670,1273 } },
{ "Kodak EOSDCS1", 0, 0,
{ 10592,-2206,-967,-1944,11685,230,2206,670,1273 } },
{ "Kodak EOSDCS3B", 0, 0,
{ 9898,-2700,-940,-2478,12219,206,1985,634,1031 } },
{ "Kodak DCS520C", -178, 0,
{ 24542,-10860,-3401,-1490,11370,-297,2858,-605,3225 } },
{ "Kodak DCS560C", -177, 0,
{ 20482,-7172,-3125,-1033,10410,-285,2542,226,3136 } },
{ "Kodak DCS620C", -177, 0,
{ 23617,-10175,-3149,-2054,11749,-272,2586,-489,3453 } },
{ "Kodak DCS620X", -176, 0,
{ 13095,-6231,154,12221,-21,-2137,895,4602,2258 } },
{ "Kodak DCS660C", -173, 0,
{ 18244,-6351,-2739,-791,11193,-521,3711,-129,2802 } },
{ "Kodak DCS720X", 0, 0,
{ 11775,-5884,950,9556,1846,-1286,-1019,6221,2728 } },
{ "Kodak DCS760C", 0, 0,
{ 16623,-6309,-1411,-4344,13923,323,2285,274,2926 } },
{ "Kodak DCS Pro SLR", 0, 0,
{ 5494,2393,-232,-6427,13850,2846,-1876,3997,5445 } },
{ "Kodak DCS Pro 14nx", 0, 0,
{ 5494,2393,-232,-6427,13850,2846,-1876,3997,5445 } },
{ "Kodak DCS Pro 14", 0, 0,
{ 7791,3128,-776,-8588,16458,2039,-2455,4006,6198 } },
{ "Photo Control Camerz ZDS 14", 0, 0,
{ 7791,3128,-776,-8588,16458,2039,-2455,4006,6198 } },
{ "Kodak ProBack645", 0, 0,
{ 16414,-6060,-1470,-3555,13037,473,2545,122,4948 } },
{ "Kodak ProBack", 0, 0,
{ 21179,-8316,-2918,-915,11019,-165,3477,-180,4210 } },
{ "Kodak P712", 0, 0,
{ 9658,-3314,-823,-5163,12695,2768,-1342,1843,6044 } },
{ "Kodak P850", 0, 0xf7c,
{ 10511,-3836,-1102,-6946,14587,2558,-1481,1792,6246 } },
{ "Kodak P880", 0, 0xfff,
{ 12805,-4662,-1376,-7480,15267,2360,-1626,2194,7904 } },
{ "Kodak EasyShare Z980", 0, 0,
{ 11313,-3559,-1101,-3893,11891,2257,-1214,2398,4908 } },
{ "Kodak EasyShare Z981", 0, 0,
{ 12729,-4717,-1188,-1367,9187,2582,274,860,4411 } },
{ "Kodak EasyShare Z990", 0, 0xfed,
{ 11749,-4048,-1309,-1867,10572,1489,-138,1449,4522 } },
{ "Kodak EASYSHARE Z1015", 0, 0xef1,
{ 11265,-4286,-992,-4694,12343,2647,-1090,1523,5447 } },
{ "Leaf C-Most", 0, 0, /* updated */
{ 3952,2189,449,-6701,14585,2275,-4536,7349,6536 } },
{ "Leaf Valeo 6", 0, 0,
{ 3952,2189,449,-6701,14585,2275,-4536,7349,6536 } },
{ "Leaf Aptus 54S", 0, 0,
{ 8236,1746,-1314,-8251,15953,2428,-3673,5786,5771 } },
{ "Leaf Aptus-II 8", 0, 0, /* added */
{ 7361,1257,-163,-6929,14061,3176,-1839,3454,5603 } },
{ "Leaf AFi-II 7", 0, 0, /* added */
{ 7691,-108,-339,-6185,13627,2833,-2046,3899,5952 } },
{ "Leaf Aptus-II 5", 0, 0, /* added */
{ 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } },
{ "Leaf Aptus 65", 0, 0,
{ 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } },
{ "Leaf AFi 65S", 0, 0, /* added */
{ 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } },
{ "Leaf Aptus 75", 0, 0,
{ 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } },
{ "Leaf AFi 75S", 0, 0, /* added */
{ 7914,1414,-1190,-8777,16582,2280,-2811,4605,5562 } },
{ "Leaf Credo 40", 0, 0,
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Leaf Credo 50", 0, 0,
{ 3984,0,0,0,10000,0,0,0,7666 } },
{ "Leaf Credo 60", 0, 0,
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Leaf Credo 80", 0, 0,
{ 6294,686,-712,-5435, 13417,2211,-1006,2435,5042 } },
{ "Leaf", 0, 0,
{ 8236,1746,-1314,-8251,15953,2428,-3673,5786,5771 } },
{ "Leica M10", 0, 0, /* added */
{ 9090,-3342,-740,-4006,13456,493,-569,2266,6871 } },
{ "Leica M9", 0, 0, /* added */
{ 6687,-1751,-291,-3556,11373,2492,-548,2204,7146 } },
{ "Leica M8", 0, 0, /* added */
{ 7675,-2196,-305,-5860,14119,1856,-2425,4006,6578 } },
{ "Leica M (Typ 240)", 0, 0, /* added */
{ 7199,-2140,-712,-4005,13327,649,-810,2521,6673 } },
{ "Leica M (Typ 262)", 0, 0,
{ 7199,-2140,-712,-4005,13327,649,-810,2521,6673 } },
{ "Leica SL (Typ 601)", 0, 0,
{ 11865,-4523,-1441,-5423,14458,935,-1587,2687,4830} },
{ "Leica S2", 0, 0, /* added */
{ 5627,-721,-447,-4423,12456,2192,-1048,2948,7379 } },
{"Leica S-E (Typ 006)", 0, 0, /* added */
{ 5749,-1072,-382,-4274,12432,2048,-1166,3104,7105 } },
{"Leica S (Typ 006)", 0, 0, /* added */
{ 5749,-1072,-382,-4274,12432,2048,-1166,3104,7105 } },
{ "Leica S (Typ 007)", 0, 0,
{ 6063,-2234,-231,-5210,13787,1500,-1043,2866,6997 } },
{ "Leica Q (Typ 116)", 0, 0, /* updated */
{ 10068,-4043,-1068,-5319,14268,1044,-765,1701,6522 } },
{ "Leica T (Typ 701)", 0, 0, /* added */
{ 6295 ,-1679 ,-475 ,-5586 ,13046 ,2837 ,-1410 ,1889 ,7075 } },
{ "Leica X2", 0, 0, /* added */
{ 8336,-2853,-699,-4425,11989,2760,-954,1625,6396 } },
{ "Leica X1", 0, 0, /* added */
{ 9055,-2611,-666,-4906,12652,2519,-555,1384,7417 } },
{ "Leica X", 0, 0, /* X(113), X-U(113), XV, X Vario(107) */ /* updated */
{ 9062,-3198,-828,-4065,11772,2603,-761,1468,6458 } },
{ "Mamiya M31", 0, 0, /* added */
{ 4516 ,-244 ,-36 ,-7020 ,14976 ,2174 ,-3206 ,4670 ,7087 } },
{ "Mamiya M22", 0, 0, /* added */
{ 2905 ,732 ,-237 ,-8135 ,16626 ,1476 ,-3038 ,4253 ,7517 } },
{ "Mamiya M18", 0, 0, /* added */
{ 6516 ,-2050 ,-507 ,-8217 ,16703 ,1479 ,-3492 ,4741 ,8489 } },
{ "Mamiya ZD", 0, 0,
{ 7645,2579,-1363,-8689,16717,2015,-3712,5941,5961 } },
{ "Micron 2010", 110, 0, /* DJC */
{ 16695,-3761,-2151,155,9682,163,3433,951,4904 } },
{ "Minolta DiMAGE 5", 0, 0xf7d, /* updated */
{ 9117,-3063,-973,-7949,15763,2306,-2752,3136,8093 } },
{ "Minolta DiMAGE 7Hi", 0, 0xf7d, /* updated */
{ 11555,-4064,-1256,-7903,15633,2409,-2811,3320,7358 } },
{ "Minolta DiMAGE 7i", 0, 0xf7d, /* added */
{ 11050,-3791,-1199,-7875,15585,2434,-2797,3359,7560 } },
{ "Minolta DiMAGE 7", 0, 0xf7d, /* updated */
{ 9258,-2879,-1008,-8076,15847,2351,-2806,3280,7821 } },
{ "Minolta DiMAGE A1", 0, 0xf8b, /* updated */
{ 9274,-2548,-1167,-8220,16324,1943,-2273,2721,8340 } },
{ "Minolta DiMAGE A200", 0, 0,
{ 8560,-2487,-986,-8112,15535,2771,-1209,1324,7743 } },
{ "Minolta DiMAGE A2", 0, 0xf8f,
{ 9097,-2726,-1053,-8073,15506,2762,-966,981,7763 } },
{ "Minolta DiMAGE Z2", 0, 0, /* DJC */
{ 11280,-3564,-1370,-4655,12374,2282,-1423,2168,5396 } },
{ "Minolta DYNAX 5", 0, 0xffb,
{ 10284,-3283,-1086,-7957,15762,2316,-829,882,6644 } },
{ "Minolta Maxxum 5D", 0, 0xffb, /* added */
{ 10284,-3283,-1086,-7957,15762,2316,-829,882,6644 } },
{ "Minolta ALPHA-5 DIGITAL", 0, 0xffb, /* added */
{ 10284,-3283,-1086,-7957,15762,2316,-829,882,6644 } },
{ "Minolta ALPHA SWEET DIGITAL", 0, 0xffb, /* added */
{ 10284,-3283,-1086,-7957,15762,2316,-829,882,6644 } },
{ "Minolta DYNAX 7", 0, 0xffb,
{ 10239,-3104,-1099,-8037,15727,2451,-927,925,6871 } },
{ "Minolta Maxxum 7D", 0, 0xffb, /* added */
{ 10239,-3104,-1099,-8037,15727,2451,-927,925,6871 } },
{ "Minolta ALPHA-7 DIGITAL", 0, 0xffb, /* added */
{ 10239,-3104,-1099,-8037,15727,2451,-927,925,6871 } },
{ "Motorola PIXL", 0, 0, /* DJC */
{ 8898,-989,-1033,-3292,11619,1674,-661,3178,5216 } },
{ "Nikon D100", 0, 0,
{ 5902,-933,-782,-8983,16719,2354,-1402,1455,6464 } },
{ "Nikon D1H", 0, 0, /* updated */
{ 7659,-2238,-935,-8942,16969,2004,-2701,3051,8690 } },
{ "Nikon D1X", 0, 0,
{ 7702,-2245,-975,-9114,17242,1875,-2679,3055,8521 } },
{ "Nikon D1", 0, 0, /* multiplied by 2.218750, 1.0, 1.148438 */
{ 16772,-4726,-2141,-7611,15713,1972,-2846,3494,9521 } },
{ "Nikon D200", 0, 0xfbc,
{ 8367,-2248,-763,-8758,16447,2422,-1527,1550,8053 } },
{ "Nikon D2H", 0, 0,
{ 5733,-911,-629,-7967,15987,2055,-3050,4013,7048 } },
{ "Nikon D2X", 0, 0, /* updated */
{ 10231,-2768,-1254,-8302,15900,2551,-797,681,7148 } },
{ "Nikon D3000", 0, 0,
{ 8736,-2458,-935,-9075,16894,2251,-1354,1242,8263 } },
{ "Nikon D3100", 0, 0,
{ 7911,-2167,-813,-5327,13150,2408,-1288,2483,7968 } },
{ "Nikon D3200", 0, 0xfb9,
{ 7013,-1408,-635,-5268,12902,2640,-1470,2801,7379 } },
{ "Nikon D3300", 0, 0,
{ 6988,-1384,-714,-5631,13410,2447,-1485,2204,7318 } },
{ "Nikon D3400", 0, 0,
{ 6988,-1384,-714,-5631,13410,2447,-1485,2204,7318 } },
{ "Nikon D300", 0, 0,
{ 9030,-1992,-715,-8465,16302,2255,-2689,3217,8069 } },
{ "Nikon D3X", 0, 0,
{ 7171,-1986,-648,-8085,15555,2718,-2170,2512,7457 } },
{ "Nikon D3S", 0, 0,
{ 8828,-2406,-694,-4874,12603,2541,-660,1509,7587 } },
{ "Nikon D3", 0, 0,
{ 8139,-2171,-663,-8747,16541,2295,-1925,2008,8093 } },
{ "Nikon D40X", 0, 0,
{ 8819,-2543,-911,-9025,16928,2151,-1329,1213,8449 } },
{ "Nikon D40", 0, 0,
{ 6992,-1668,-806,-8138,15748,2543,-874,850,7897 } },
{ "Nikon D4S", 0, 0,
{ 8598,-2848,-857,-5618,13606,2195,-1002,1773,7137 } },
{ "Nikon D4", 0, 0,
{ 8598,-2848,-857,-5618,13606,2195,-1002,1773,7137 } },
{ "Nikon Df", 0, 0,
{ 8598,-2848,-857,-5618,13606,2195,-1002,1773,7137 } },
{ "Nikon D5000", 0, 0xf00,
{ 7309,-1403,-519,-8474,16008,2622,-2433,2826,8064 } },
{ "Nikon D5100", 0, 0x3de6,
{ 8198,-2239,-724,-4871,12389,2798,-1043,2050,7181 } },
{ "Nikon D5200", 0, 0,
{ 8322,-3112,-1047,-6367,14342,2179,-988,1638,6394 } },
{ "Nikon D5300", 0, 0,
{ 6988,-1384,-714,-5631,13410,2447,-1485,2204,7318 } },
{ "Nikon D5500", 0, 0,
{ 8821,-2938,-785,-4178,12142,2287,-824,1651,6860 } },
{ "Nikon D5600", 0, 0,
{ 8821,-2938,-785,-4178,12142,2287,-824,1651,6860 } },
{ "Nikon D500", 0, 0,
{ 8813,-3210,-1036,-4703,12868,2021,-1054,1940,6129 } },
{ "Nikon D50", 0, 0,
{ 7732,-2422,-789,-8238,15884,2498,-859,783,7330 } },
{ "Nikon D5", 0, 0,
{ 9200,-3522,-992,-5755,13803,2117,-753,1486,6338 } },
{ "Nikon D600", 0, 0x3e07,
{ 8178,-2245,-609,-4857,12394,2776,-1207,2086,7298 } },
{ "Nikon D610",0, 0, /* updated */
{ 8178,-2245,-609,-4857,12394,2776,-1207,2086,7298 } },
{ "Nikon D60", 0, 0,
{ 8736,-2458,-935,-9075,16894,2251,-1354,1242,8263 } },
{ "Nikon D7000", 0, 0,
{ 8198,-2239,-724,-4871,12389,2798,-1043,2050,7181 } },
{ "Nikon D7100", 0, 0,
{ 8322,-3112,-1047,-6367,14342,2179,-988,1638,6394 } },
{ "Nikon D7200", 0, 0,
{ 8322,-3112,-1047,-6367,14342,2179,-988,1638,6394 } },
{ "Nikon D7500", 0, 0,
{ 8813,-3210,-1036,-4703,12868,2021,-1054,1940,6129 } },
{ "Nikon D750", -600, 0,
{ 9020,-2890,-715,-4535,12436,2348,-934,1919,7086 } },
{ "Nikon D700", 0, 0,
{ 8139,-2171,-663,-8747,16541,2295,-1925,2008,8093 } },
{ "Nikon D70", 0, 0,
{ 7732,-2422,-789,-8238,15884,2498,-859,783,7330 } },
{ "Nikon D850", 0, 0,
{ 10405,-3755,-1270,-5461,13787,1793,-1040,2015,6785 } },
{ "Nikon D810A", 0, 0,
{ 11973,-5685,-888,-1965,10326,1901,-115,1123,7169 } },
{ "Nikon D810", 0, 0,
{ 9369,-3195,-791,-4488,12430,2301,-893,1796,6872 } },
{ "Nikon D800", 0, 0,
{ 7866,-2108,-555,-4869,12483,2681,-1176,2069,7501 } },
{ "Nikon D80", 0, 0,
{ 8629,-2410,-883,-9055,16940,2171,-1490,1363,8520 } },
{ "Nikon D90", 0, 0xf00,
{ 7309,-1403,-519,-8474,16008,2622,-2434,2826,8064 } },
{ "Nikon E700", 0, 0x3dd, /* DJC */
{ -3746,10611,1665,9621,-1734,2114,-2389,7082,3064,3406,6116,-244 } },
{ "Nikon E800", 0, 0x3dd, /* DJC */
{ -3746,10611,1665,9621,-1734,2114,-2389,7082,3064,3406,6116,-244 } },
{ "Nikon E950", 0, 0x3dd, /* DJC */
{ -3746,10611,1665,9621,-1734,2114,-2389,7082,3064,3406,6116,-244 } },
{ "Nikon E995", 0, 0, /* copied from E5000 */
{ -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
{ "Nikon E2100", 0, 0, /* copied from Z2, new white balance */
{ 13142,-4152,-1596,-4655,12374,2282,-1769,2696,6711 } },
{ "Nikon E2500", 0, 0,
{ -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
{ "Nikon E3200", 0, 0, /* DJC */
{ 9846,-2085,-1019,-3278,11109,2170,-774,2134,5745 } },
{ "Nikon E4300", 0, 0, /* copied from Minolta DiMAGE Z2 */
{ 11280,-3564,-1370,-4655,12374,2282,-1423,2168,5396 } },
{ "Nikon E4500", 0, 0,
{ -5547,11762,2189,5814,-558,3342,-4924,9840,5949,688,9083,96 } },
{ "Nikon E5000", 0, 0, /* updated */
{ -6678,12805,2248,5725,-499,3375,-5903,10713,6034,-270,9976,134 } },
{ "Nikon E5400", 0, 0, /* updated */
{ 9349,-2988,-1001,-7918,15766,2266,-2097,2680,6839 } },
{ "Nikon E5700", 0, 0, /* updated */
{ -6475,12496,2428,5409,-16,3180,-5965,10912,5866,-177,9918,248 } },
{ "Nikon E8400", 0, 0,
{ 7842,-2320,-992,-8154,15718,2599,-1098,1342,7560 } },
{ "Nikon E8700", 0, 0,
{ 8489,-2583,-1036,-8051,15583,2643,-1307,1407,7354 } },
{ "Nikon E8800", 0, 0,
{ 7971,-2314,-913,-8451,15762,2894,-1442,1520,7610 } },
{ "Nikon COOLPIX A", 0, 0,
{ 8198,-2239,-724,-4871,12389,2798,-1043,2050,7181 } },
{ "Nikon COOLPIX B700", 0, 0,
{ 14387,-6014,-1299,-1357,9975,1616,467,1047,4744 } },
{ "Nikon COOLPIX P330", -200, 0,
{ 10321,-3920,-931,-2750,11146,1824,-442,1545,5539 } },
{ "Nikon COOLPIX P340", -200, 0,
{ 10321,-3920,-931,-2750,11146,1824,-442,1545,5539 } },
{ "Nikon COOLPIX Kalon", 0, 0, /* added */
{ 10321,-3920,-931,-2750,11146,1824,-442,1545,5539 } },
{ "Nikon COOLPIX Deneb", 0, 0, /* added */
{ 10321,-3920,-931,-2750,11146,1824,-442,1545,5539 } },
{ "Nikon COOLPIX P6000", 0, 0,
{ 9698,-3367,-914,-4706,12584,2368,-837,968,5801 } },
{ "Nikon COOLPIX P7000", 0, 0,
{ 11432,-3679,-1111,-3169,11239,2202,-791,1380,4455 } },
{ "Nikon COOLPIX P7100", 0, 0,
{ 11053,-4269,-1024,-1976,10182,2088,-526,1263,4469 } },
{ "Nikon COOLPIX P7700", -3200, 0,
{ 10321,-3920,-931,-2750,11146,1824,-442,1545,5539 } },
{ "Nikon COOLPIX P7800", -3200, 0,
{ 10321,-3920,-931,-2750,11146,1824,-442,1545,5539 } },
{ "Nikon 1 V3", -200, 0,
{ 5958,-1559,-571,-4021,11453,2939,-634,1548,5087 } },
{ "Nikon 1 J4", 0, 0,
{ 5958,-1559,-571,-4021,11453,2939,-634,1548,5087 } },
{ "Nikon 1 J5", 0, 0,
{ 7520,-2518,-645,-3844,12102,1945,-913,2249,6835 } },
{ "Nikon 1 S2", -200, 0,
{ 6612,-1342,-618,-3338,11055,2623,-174,1792,5075 } },
{ "Nikon 1 V2", 0, 0,
{ 6588,-1305,-693,-3277,10987,2634,-355,2016,5106 } },
{ "Nikon 1 J3", 0, 0, /* updated */
{ 6588,-1305,-693,-3277,10987,2634,-355,2016,5106 } },
{ "Nikon 1 AW1", 0, 0,
{ 6588,-1305,-693,-3277,10987,2634,-355,2016,5106 } },
{ "Nikon 1 ", 0, 0, /* J1, J2, S1, V1 */
{ 8994,-2667,-865,-4594,12324,2552,-699,1786,6260 } },
{ "Olympus AIR-A01", 0, 0xfe1,
{ 8992,-3093,-639,-2563,10721,2122,-437,1270,5473 } },
{ "Olympus C5050", 0, 0, /* updated */
{ 10633,-3234,-1285,-7460,15570,1967,-1917,2510,6299 } },
{ "Olympus C5060", 0, 0,
{ 10445,-3362,-1307,-7662,15690,2058,-1135,1176,7602 } },
{ "Olympus C7070", 0, 0,
{ 10252,-3531,-1095,-7114,14850,2436,-1451,1723,6365 } },
{ "Olympus C70", 0, 0,
{ 10793,-3791,-1146,-7498,15177,2488,-1390,1577,7321 } },
{ "Olympus C80", 0, 0,
{ 8606,-2509,-1014,-8238,15714,2703,-942,979,7760 } },
{ "Olympus E-10", 0, 0xffc, /* updated */
{ 12970,-4703,-1433,-7466,15843,1644,-2191,2451,6668 } },
{ "Olympus E-1", 0, 0,
{ 11846,-4767,-945,-7027,15878,1089,-2699,4122,8311 } },
{ "Olympus E-20", 0, 0xffc, /* updated */
{ 13414,-4950,-1517,-7166,15293,1960,-2325,2664,7212 } },
{ "Olympus E-300", 0, 0,
{ 7828,-1761,-348,-5788,14071,1830,-2853,4518,6557 } },
{ "Olympus E-330", 0, 0,
{ 8961,-2473,-1084,-7979,15990,2067,-2319,3035,8249 } },
{ "Olympus E-30", 0, 0xfbc,
{ 8144,-1861,-1111,-7763,15894,1929,-1865,2542,7607 } },
{ "Olympus E-3", 0, 0xf99,
{ 9487,-2875,-1115,-7533,15606,2010,-1618,2100,7389 } },
{ "Olympus E-400", 0, 0,
{ 6169,-1483,-21,-7107,14761,2536,-2904,3580,8568 } },
{ "Olympus E-410", 0, 0xf6a,
{ 8856,-2582,-1026,-7761,15766,2082,-2009,2575,7469 } },
{ "Olympus E-420", 0, 0xfd7,
{ 8746,-2425,-1095,-7594,15612,2073,-1780,2309,7416 } },
{ "Olympus E-450", 0, 0xfd2,
{ 8745,-2425,-1095,-7594,15613,2073,-1780,2309,7416 } },
{ "Olympus E-500", 0, 0,
{ 8136,-1968,-299,-5481,13742,1871,-2556,4205,6630 } },
{ "Olympus E-510", 0, 0xf6a,
{ 8785,-2529,-1033,-7639,15624,2112,-1783,2300,7817 } },
{ "Olympus E-520", 0, 0xfd2,
{ 8344,-2322,-1020,-7596,15635,2048,-1748,2269,7287 } },
{ "Olympus E-5", 0, 0xeec,
{ 11200,-3783,-1325,-4576,12593,2206,-695,1742,7504 } },
{ "Olympus E-600", 0, 0xfaf,
{ 8453,-2198,-1092,-7609,15681,2008,-1725,2337,7824 } },
{ "Olympus E-620", 0, 0xfaf,
{ 8453,-2198,-1092,-7609,15681,2008,-1725,2337,7824 } },
{ "Olympus E-P1", 0, 0xffd,
{ 8343,-2050,-1021,-7715,15705,2103,-1831,2380,8235 } },
{ "Olympus E-P2", 0, 0xffd,
{ 8343,-2050,-1021,-7715,15705,2103,-1831,2380,8235 } },
{ "Olympus E-P3", 0, 0,
{ 7575,-2159,-571,-3722,11341,2725,-1434,2819,6271 } },
{ "Olympus E-P5", 0, 0,
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus E-PL1s", 0, 0,
{ 11409,-3872,-1393,-4572,12757,2003,-709,1810,7415 } },
{ "Olympus E-PL1", 0, 0,
{ 11408,-4289,-1215,-4286,12385,2118,-387,1467,7787 } },
{ "Olympus E-PL2", 0, 0xcf3,
{ 15030,-5552,-1806,-3987,12387,1767,-592,1670,7023 } },
{ "Olympus E-PL3", 0, 0,
{ 7575,-2159,-571,-3722,11341,2725,-1434,2819,6271 } },
{ "Olympus E-PL5", 0, 0xfcb,
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus E-PL6", 0, 0,
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus E-PL7", 0, 0,
{ 9197,-3190,-659,-2606,10830,2039,-458,1250,5458 } },
{ "Olympus E-PL8", 0, 0,
{ 9197,-3190,-659,-2606,10830,2039,-458,1250,5458 } },
{ "Olympus E-PL9", 0, 0,
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus E-PM1", 0, 0,
{ 7575,-2159,-571,-3722,11341,2725,-1434,2819,6271 } },
{ "Olympus E-PM2", 0, 0,
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus E-M10", 0, 0, /* Same for E-M10MarkII, E-M10MarkIII */
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus E-M1MarkII", 0, 0,
{ 9383,-3170,-763,-2457,10702,2020,-384,1236,5552 } },
{ "Olympus E-M1", 0, 0,
{ 7687,-1984,-606,-4327,11928,2721,-1381,2339,6452 } },
{ "Olympus E-M5MarkII", 0, 0,
{ 9422,-3258,-711,-2655,10898,2015,-512,1354,5512 } },
{ "Olympus E-M5", 0, 0xfe1,
{ 8380,-2630,-639,-2887,10725,2496,-627,1427,5438 } },
{ "Olympus PEN-F",0, 0,
{ 9476,-3182,-765,-2613,10958,1893,-449,1315,5268 } },
{ "Olympus SP350", 0, 0,
{ 12078,-4836,-1069,-6671,14306,2578,-786,939,7418 } },
{ "Olympus SP3", 0, 0,
{ 11766,-4445,-1067,-6901,14421,2707,-1029,1217,7572 } },
{ "Olympus SP500UZ", 0, 0xfff,
{ 9493,-3415,-666,-5211,12334,3260,-1548,2262,6482 } },
{ "Olympus SP510UZ", 0, 0xffe,
{ 10593,-3607,-1010,-5881,13127,3084,-1200,1805,6721 } },
{ "Olympus SP550UZ", 0, 0xffe,
{ 11597,-4006,-1049,-5432,12799,2957,-1029,1750,6516 } },
{ "Olympus SP560UZ", 0, 0xff9,
{ 10915,-3677,-982,-5587,12986,2911,-1168,1968,6223 } },
{ "Olympus SP565UZ", 0, 0, /* added */
{ 11856,-4469,-1159,-4814,12368,2756,-993,1779,5589 } },
{ "Olympus SP570UZ", 0, 0,
{ 11522,-4044,-1146,-4736,12172,2904,-988,1829,6039 } },
{ "Olympus SH-2", 0, 0,
{ 10156,-3425,-1077,-2611,11177,1624,-385,1592,5080 } },
{ "Olympus SH-3", 0, 0, /* Alias of SH-2 */
{ 10156,-3425,-1077,-2611,11177,1624,-385,1592,5080 } },
{ "Olympus STYLUS1",0, 0, /* updated */
{ 8360,-2420,-880,-3928,12353,1739,-1381,2416,5173 } },
{ "Olympus TG-4", 0, 0,
{ 11426,-4159,-1126,-2066,10678,1593,-120,1327,4998 } },
{ "Olympus TG-5", 0, 0,
{ 10899,-3833,-1082,-2112,10736,1575,-267,1452,5269 } },
{ "Olympus XZ-10", 0, 0,
{ 9777,-3483,-925,-2886,11297,1800,-602,1663,5134 } },
{ "Olympus XZ-1", 0, 0,
{ 10901,-4095,-1074,-1141,9208,2293,-62,1417,5158 } },
{ "Olympus XZ-2", 0, 0,
{ 9777,-3483,-925,-2886,11297,1800,-602,1663,5134 } },
{ "OmniVision", 16, 0x3ff,
{ 12782,-4059,-379,-478,9066,1413,1340,1513,5176 } }, /* DJC */
{ "Pentax *ist DL2", 0, 0,
{ 10504,-2438,-1189,-8603,16207,2531,-1022,863,12242 } },
{ "Pentax *ist DL", 0, 0,
{ 10829,-2838,-1115,-8339,15817,2696,-837,680,11939 } },
{ "Pentax *ist DS2", 0, 0,
{ 10504,-2438,-1189,-8603,16207,2531,-1022,863,12242 } },
{ "Pentax *ist DS", 0, 0,
{ 10371,-2333,-1206,-8688,16231,2602,-1230,1116,11282 } },
{ "Pentax *ist D", 0, 0,
{ 9651,-2059,-1189,-8881,16512,2487,-1460,1345,10687 } },
{ "Pentax GR", 0, 0, /* added */
{ 5329,-1459,-390,-5407,12930,2768,-1119,1772,6046 } },
{ "Pentax K-01", 0, 0, /* added */
{ 8134,-2728,-645,-4365,11987,2694,-838,1509,6498 } },
{ "Pentax K10D", 0, 0, /* updated */
{ 9679,-2965,-811,-8622,16514,2182,-975,883,9793 } },
{ "Pentax K1", 0, 0,
{ 11095,-3157,-1324,-8377,15834,2720,-1108,947,11688 } },
{ "Pentax K20D", 0, 0,
{ 9427,-2714,-868,-7493,16092,1373,-2199,3264,7180 } },
{ "Pentax K200D", 0, 0,
{ 9186,-2678,-907,-8693,16517,2260,-1129,1094,8524 } },
{ "Pentax K2000", 0, 0, /* updated */
{ 9730,-2989,-970,-8527,16258,2381,-1060,970,8362 } },
{ "Pentax K-m", 0, 0, /* updated */
{ 9730,-2989,-970,-8527,16258,2381,-1060,970,8362 } },
{ "Pentax KP", 0, 0,
{ 7825,-2160,-1403,-4841,13555,1349,-1559,2449,5814 } },
{ "Pentax K-x", 0, 0,
{ 8843,-2837,-625,-5025,12644,2668,-411,1234,7410 } },
{ "Pentax K-r", 0, 0,
{ 9895,-3077,-850,-5304,13035,2521,-883,1768,6936 } },
{ "Pentax K-1", 0, 0, /* updated */
{ 8596,-2981,-639,-4202,12046,2431,-685,1424,6122 } },
{ "Pentax K-30", 0, 0, /* updated */
{ 8134,-2728,-645,-4365,11987,2694,-838,1509,6498 } },
{ "Pentax K-3 II", 0, 0, /* updated */
{ 7415,-2052,-721,-5186,12788,2682,-1446,2157,6773 } },
{ "Pentax K-3", 0, 0,
{ 7415,-2052,-721,-5186,12788,2682,-1446,2157,6773 } },
{ "Pentax K-5 II", 0, 0,
{ 8170,-2725,-639,-4440,12017,2744,-771,1465,6599 } },
{ "Pentax K-500", 0, 0, /* added */
{ 8109,-2740,-608,-4593,12175,2731,-1006,1515,6545 } },
{ "Pentax K-50", 0, 0, /* added */
{ 8109,-2740,-608,-4593,12175,2731,-1006,1515,6545 } },
{ "Pentax K-5", 0, 0,
{ 8713,-2833,-743,-4342,11900,2772,-722,1543,6247 } },
{ "Pentax K-70", 0, 0,
{ 8766,-3149,-747,-3976,11943,2292,-517,1259,5552 } },
{ "Pentax K-7", 0, 0,
{ 9142,-2947,-678,-8648,16967,1663,-2224,2898,8615 } },
{ "Pentax KP", 0, 0, /* temp */
{ 8626,-2607,-1155,-3995,12301,1881,-1039,1822,6925 } },
{ "Pentax K-S1", 0, 0,
{ 8512,-3211,-787,-4167,11966,2487,-638,1288,6054 } },
{ "Pentax K-S2", 0, 0,
{ 8662,-3280,-798,-3928,11771,2444,-586,1232,6054 } },
{ "Pentax Q-S1", 0, 0,
{ 12995,-5593,-1107,-1879,10139,2027,-64,1233,4919 } },
{ "Pentax Q7", 0, 0, /* added */
{ 10901,-3938,-1025,-2743,11210,1738,-823,1805,5344 } },
{ "Pentax Q10", 0, 0, /* updated */
{ 11562,-4183,-1172,-2357,10919,1641,-582,1726,5112 } },
{ "Pentax Q", 0, 0, /* added */
{ 11731,-4169,-1267,-2015,10727,1473,-217,1492,4870 } },
{ "Pentax MX-1", 0, 0, /* updated */
{ 9296,-3146,-888,-2860,11287,1783,-618,1698,5151 } },
{ "Pentax 645D", 0, 0x3e00,
{ 10646,-3593,-1158,-3329,11699,1831,-667,2874,6287 } },
{ "Pentax 645Z", 0, 0, /* updated */
{ 9519,-3591,-664,-4074,11725,2671,-624,1501,6653 } },
{ "Panasonic DMC-CM10", -15, 0,
{ 8770,-3194,-820,-2871,11281,1803,-513,1552,4434 } },
{ "Panasonic DMC-CM1", -15, 0,
{ 8770,-3194,-820,-2871,11281,1803,-513,1552,4434 } },
{ "Panasonic DC-FZ82", -15, 0, /* markets: FZ80 FZ82 */
{ 8550,-2908,-842,-3195,11529,1881,-338,1603,4631 } },
{ "Panasonic DC-FZ80", -15, 0, /* markets: FZ80 FZ82 */
{ 8550,-2908,-842,-3195,11529,1881,-338,1603,4631 } },
{ "Panasonic DMC-FZ8", 0, 0xf7f,
{ 8986,-2755,-802,-6341,13575,3077,-1476,2144,6379 } },
{ "Panasonic DMC-FZ18", 0, 0,
{ 9932,-3060,-935,-5809,13331,2753,-1267,2155,5575 } },
{ "Panasonic DMC-FZ28", -15, 0xf96,
{ 10109,-3488,-993,-5412,12812,2916,-1305,2140,5543 } },
{ "Panasonic DMC-FZ300", -15, 0xfff,
{ 8378,-2798,-769,-3068,11410,1877,-538,1792,4623 } },
{ "Panasonic DMC-FZ330", -15, 0xfff,
{ 8378,-2798,-769,-3068,11410,1877,-538,1792,4623 } },
{ "Panasonic DMC-FZ30", 0, 0xf94,
{ 10976,-4029,-1141,-7918,15491,2600,-1670,2071,8246 } },
{ "Panasonic DMC-FZ3", -15, 0,
{ 9938,-2780,-890,-4604,12393,2480,-1117,2304,4620 } },
{ "Panasonic DMC-FZ4", -15, 0, /* 40,42,45 */
{ 13639,-5535,-1371,-1698,9633,2430,316,1152,4108 } },
{ "Panasonic DMC-FZ50", 0, 0,
{ 7906,-2709,-594,-6231,13351,3220,-1922,2631,6537 } },
{ "Panasonic DMC-FZ7", -15, 0,
{ 11532,-4324,-1066,-2375,10847,1749,-564,1699,4351 } },
{ "Leica V-LUX1", 0, 0,
{ 7906,-2709,-594,-6231,13351,3220,-1922,2631,6537 } },
{ "Leica V-LUX 1", 0, 0,
{ 7906,-2709,-594,-6231,13351,3220,-1922,2631,6537 } },
{ "Panasonic DMC-L10", -15, 0xf96,
{ 8025,-1942,-1050,-7920,15904,2100,-2456,3005,7039 } },
{ "Panasonic DMC-L1", 0, 0xf7f,
{ 8054,-1885,-1025,-8349,16367,2040,-2805,3542,7629 } },
{ "Leica DIGILUX3", 0, 0xf7f, /* added */
{ 8054,-1885,-1025,-8349,16367,2040,-2805,3542,7629 } },
{ "Leica DIGILUX 3", 0, 0xf7f,
{ 8054,-1885,-1025,-8349,16367,2040,-2805,3542,7629 } },
{ "Panasonic DMC-LC1", 0, 0,
{ 11340,-4069,-1275,-7555,15266,2448,-2960,3426,7685 } },
{ "Leica DIGILUX2", 0, 0, /* added */
{ 11340,-4069,-1275,-7555,15266,2448,-2960,3426,7685 } },
{ "Leica DIGILUX 2", 0, 0,
{ 11340,-4069,-1275,-7555,15266,2448,-2960,3426,7685 } },
{ "Panasonic DMC-LX100", -15, 0,
{ 8844,-3538,-768,-3709,11762,2200,-698,1792,5220 } },
{ "Leica D-LUX (Typ 109)", -15, 0,
{ 8844,-3538,-768,-3709,11762,2200,-698,1792,5220 } },
{ "Panasonic DMC-LF1", -15, 0,
{ 9379,-3267,-816,-3227,11560,1881,-926,1928,5340 } },
{ "Leica C (Typ 112)", -15, 0,
{ 9379,-3267,-816,-3227,11560,1881,-926,1928,5340 } },
{ "Panasonic DMC-LX9", -15, 0, /* markets: LX9 LX10 LX15 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-LX10", -15, 0, /* markets: LX9 LX10 LX15 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-LX15", -15, 0, /* markets: LX9 LX10 LX15 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-LX1", 0, 0xf7f,
{ 10704,-4187,-1230,-8314,15952,2501,-920,945,8927 } },
{ "Leica D-Lux (Typ 109)", 0, 0xf7f,
{ 8844,-3538,-768,-3709,11762,2200,-698,1792,5220 } },
{ "Leica D-LUX2", 0, 0xf7f,
{ 10704,-4187,-1230,-8314,15952,2501,-920,945,8927 } },
{ "Leica D-LUX 2", 0, 0xf7f, /* added */
{ 10704,-4187,-1230,-8314,15952,2501,-920,945,8927 } },
{ "Panasonic DMC-LX2", 0, 0,
{ 8048,-2810,-623,-6450,13519,3272,-1700,2146,7049 } },
{ "Leica D-LUX3", 0, 0,
{ 8048,-2810,-623,-6450,13519,3272,-1700,2146,7049 } },
{ "Leica D-LUX 3", 0, 0, /* added */
{ 8048,-2810,-623,-6450,13519,3272,-1700,2146,7049 } },
{ "Panasonic DMC-LX3", -15, 0,
{ 8128,-2668,-655,-6134,13307,3161,-1782,2568,6083 } },
{ "Leica D-LUX 4", -15, 0,
{ 8128,-2668,-655,-6134,13307,3161,-1782,2568,6083 } },
{ "Panasonic DMC-LX5", -15, 0,
{ 10909,-4295,-948,-1333,9306,2399,22,1738,4582 } },
{ "Leica D-LUX 5", -15, 0,
{ 10909,-4295,-948,-1333,9306,2399,22,1738,4582 } },
{ "Panasonic DMC-LX7", -15, 0,
{ 10148,-3743,-991,-2837,11366,1659,-701,1893,4899 } },
{ "Leica D-LUX 6", -15, 0,
{ 10148,-3743,-991,-2837,11366,1659,-701,1893,4899 } },
{ "Panasonic DMC-FZ1000", -15, 0,
{ 7830,-2696,-763,-3325,11667,1866,-641,1712,4824 } },
{ "Leica V-LUX (Typ 114)", 15, 0,
{ 7830,-2696,-763,-3325,11667,1866,-641,1712,4824 } },
{ "Panasonic DMC-FZ100", -15, 0xfff,
{ 16197,-6146,-1761,-2393,10765,1869,366,2238,5248 } },
{ "Leica V-LUX 2", -15, 0xfff,
{ 16197,-6146,-1761,-2393,10765,1869,366,2238,5248 } },
{ "Panasonic DMC-FZ150", -15, 0xfff,
{ 11904,-4541,-1189,-2355,10899,1662,-296,1586,4289 } },
{ "Leica V-LUX 3", -15, 0xfff,
{ 11904,-4541,-1189,-2355,10899,1662,-296,1586,4289 } },
{ "Panasonic DMC-FZ2000", -15, 0, /* markets: DMC-FZ2000, DMC-FZ2500 ,FZH1 */
{ 7386,-2443,-743,-3437,11864,1757,-608,1660,4766 } },
{ "Panasonic DMC-FZ2500", -15, 0,
{ 7386,-2443,-743,-3437,11864,1757,-608,1660,4766 } },
{ "Panasonic DMC-FZH1", -15, 0,
{ 7386,-2443,-743,-3437,11864,1757,-608,1660,4766 } },
{ "Panasonic DMC-FZ200", -15, 0xfff,
{ 8112,-2563,-740,-3730,11784,2197,-941,2075,4933 } },
{ "Leica V-LUX 4", -15, 0xfff,
{ 8112,-2563,-740,-3730,11784,2197,-941,2075,4933 } },
{ "Panasonic DMC-FX150", -15, 0xfff,
{ 9082,-2907,-925,-6119,13377,3058,-1797,2641,5609 } },
{ "Panasonic DMC-FX180", -15, 0xfff, /* added */
{ 9082,-2907,-925,-6119,13377,3058,-1797,2641,5609 } },
{ "Panasonic DMC-G10", 0, 0,
{ 10113,-3400,-1114,-4765,12683,2317,-377,1437,6710 } },
{ "Panasonic DMC-G1", -15, 0xf94,
{ 8199,-2065,-1056,-8124,16156,2033,-2458,3022,7220 } },
{ "Panasonic DMC-G2", -15, 0xf3c,
{ 10113,-3400,-1114,-4765,12683,2317,-377,1437,6710 } },
{ "Panasonic DMC-G3", -15, 0xfff,
{ 6763,-1919,-863,-3868,11515,2684,-1216,2387,5879 } },
{ "Panasonic DMC-G5", -15, 0xfff,
{ 7798,-2562,-740,-3879,11584,2613,-1055,2248,5434 } },
{ "Panasonic DMC-G6", -15, 0xfff,
{ 8294,-2891,-651,-3869,11590,2595,-1183,2267,5352 } },
{ "Panasonic DMC-G7", -15, 0xfff,
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DMC-G8", -15, 0xfff, /* markets: DMC-G8, DMC-G80, DMC-G81, DMC-G85 */
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DC-G9", -15, 0,
{ 7685,-2375,-634,-3687,11700,2249,-748,1546,5111 } },
{ "Panasonic DMC-GF1", -15, 0xf92,
{ 7888,-1902,-1011,-8106,16085,2099,-2353,2866,7330 } },
{ "Panasonic DMC-GF2", -15, 0xfff,
{ 7888,-1902,-1011,-8106,16085,2099,-2353,2866,7330 } },
{ "Panasonic DMC-GF3", -15, 0xfff,
{ 9051,-2468,-1204,-5212,13276,2121,-1197,2510,6890 } },
{ "Panasonic DMC-GF5", -15, 0xfff,
{ 8228,-2945,-660,-3938,11792,2430,-1094,2278,5793 } },
{ "Panasonic DMC-GF6", -15, 0,
{ 8130,-2801,-946,-3520,11289,2552,-1314,2511,5791 } },
{ "Panasonic DMC-GF7", -15, 0,
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DMC-GF8", -15, 0,
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DMC-GH1", -15, 0xf92,
{ 6299,-1466,-532,-6535,13852,2969,-2331,3112,5984 } },
{ "Panasonic DMC-GH2", -15, 0xf95,
{ 7780,-2410,-806,-3913,11724,2484,-1018,2390,5298 } },
{ "Panasonic DMC-GH3", -15, 0,
{ 6559,-1752,-491,-3672,11407,2586,-962,1875,5130 } },
{ "Panasonic DMC-GH4", -15, 0,
{ 7122,-2108,-512,-3155,11201,2231,-541,1423,5045 } },
{ "Panasonic AG-GH4", -15, 0, /* added */
{ 7122,-2108,-512,-3155,11201,2231,-541,1423,5045 } },
{"Panasonic DC-GH5s", -15, 0,
{ 6929,-2355,-708,-4192,12534,1828,-1097,1989,5195 } },
{ "Panasonic DC-GH5", -15, 0,
{ 7641,-2336,-605,-3218,11299,2187,-485,1338,5121 } },
{ "Yuneec CGO4", -15, 0,
{ 7122,-2108,-512,-3155,11201,2231,-541,1423,5045 } },
{ "Panasonic DMC-GM1", -15, 0,
{ 6770,-1895,-744,-5232,13145,2303,-1664,2691,5703 } },
{ "Panasonic DMC-GM5", -15, 0,
{ 8238,-3244,-679,-3921,11814,2384,-836,2022,5852 } },
{ "Panasonic DMC-GX1", -15, 0,
{ 6763,-1919,-863,-3868,11515,2684,-1216,2387,5879 } },
{ "Panasonic DC-GF10", -15, 0, /* temp, markets: GF10, GF90 */
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DC-GF90", -15, 0, /* temp, markets: GF10, GF90 */
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DC-GX850", -15, 0, /* markets: GX850 GX800 GF9 */
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DC-GX800", -15, 0, /* markets: GX850 GX800 GF9 */
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DC-GF9", -15, 0, /* markets: GX850 GX800 GF9 */
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DMC-GX85", -15, 0, /* markets: GX85 GX80 GX7MK2 */
{ 7771,-3020,-629,-4029,11950,2345,-821,1977,6119 } },
{ "Panasonic DMC-GX80", -15, 0, /* markets: GX85 GX80 GX7MK2 */
{ 7771,-3020,-629,-4029,11950,2345,-821,1977,6119 } },
{ "Panasonic DMC-GX7MK2", -15, 0, /* markets: GX85 GX80 GX7MK2 */
{ 7771,-3020,-629,-4029,11950,2345,-821,1977,6119 } },
{ "Panasonic DMC-GX7", -15,0,
{ 7610,-2780,-576,-4614,12195,2733,-1375,2393,6490 } },
{ "Panasonic DMC-GX8", -15,0,
{ 7564,-2263,-606,-3148,11239,2177,-540,1435,4853 } },
{ "Panasonic DC-GX9", -15, 0, /* temp */
{ 7685,-2375,-634,-3687,11700,2249,-748,1546,5111 } },
{ "Panasonic DMC-TZ6", -15, 0, /* markets: ZS40 TZ60 TZ61 */
{ 8607,-2822,-808,-3755,11930,2049,-820,2060,5224 } },
{ "Panasonic DMC-TZ8", -15, 0, /* markets: ZS60 TZ80 TZ81 TZ82 TZ85 */
{ 8550,-2908,-842,-3195,11529,1881,-338,1603,4631 } },
{ "Panasonic DC-TZ90", -15, 0, /* markets: ZS70 TZ90 TZ91 TZ92 T93 */
{ 9052,-3117,-883,-3045,11346,1927,-205,1520,4730 } },
{ "Panasonic DC-TZ91", -15, 0, /* markets: ZS70 TZ90 TZ91 TZ92 T93 */
{ 9052,-3117,-883,-3045,11346,1927,-205,1520,4730 } },
{ "Panasonic DC-TZ92", -15, 0, /* markets: ZS70 TZ90 TZ91 TZ92 T93 */
{ 9052,-3117,-883,-3045,11346,1927,-205,1520,4730 } },
{ "Panasonic DC-T93", -15, 0, /* markets: ZS70 TZ90 TZ91 TZ92 T93 */
{ 9052,-3117,-883,-3045,11346,1927,-205,1520,4730 } },
{ "Panasonic DMC-ZS4", -15, 0, /* markets: ZS40 TZ60 TZ61 */
{ 8607,-2822,-808,-3755,11930,2049,-820,2060,5224 } },
{ "Panasonic DMC-TZ7", -15, 0, /* markets: ZS50 TZ70 TZ71 */
{ 8802,-3135,-789,-3151,11468,1904,-550,1745,4810 } },
{ "Panasonic DMC-ZS5", -15, 0, /* markets: ZS50 TZ70 TZ71 */
{ 8802,-3135,-789,-3151,11468,1904,-550,1745,4810 } },
{ "Panasonic DMC-ZS6", -15, 0, /* markets: ZS60 TZ80 TZ81 TZ85 */
{ 8550,-2908,-842,-3195,11529,1881,-338,1603,4631 } },
{ "Panasonic DC-ZS70", -15, 0, /* markets: ZS70 TZ90 TZ91 TZ92 T93 */
{ 9052,-3117,-883,-3045,11346,1927,-205,1520,4730 } },
{ "Panasonic DMC-ZS100", -15, 0, /* markets: ZS100 ZS110 TZ100 TZ101 TZ110 TX1 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-ZS110", -15, 0, /* markets: ZS100 ZS110 TZ100 TZ101 TZ110 TX1 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-TZ100", -15, 0, /* markets: ZS100 ZS110 TZ100 TZ101 TZ110 TX1 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-TZ101", -15, 0, /* markets: ZS100 ZS110 TZ100 TZ101 TZ110 TX1 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-TZ110", -15, 0, /* markets: ZS100 ZS110 TZ100 TZ101 TZ110 TX1 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DMC-TX1", -15, 0, /* markets: ZS100 ZS110 TZ100 TZ101 TZ110 TX1 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DC-ZS200", -15, 0, /* temp, markets: ZS200 TZ200 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Panasonic DC-TZ200", -15, 0, /* temp, markets: ZS200 TZ200 */
{ 7790,-2736,-755,-3452,11870,1769,-628,1647,4898 } },
{ "Phase One H 20", 0, 0, /* DJC */
{ 1313,1855,-109,-6715,15908,808,-327,1840,6020 } },
{ "Phase One H20", 0, 0, /* DJC */
{ 1313,1855,-109,-6715,15908,808,-327,1840,6020 } },
{ "Phase One H 25", 0, 0,
{ 2905,732,-237,-8134,16626,1476,-3038,4253,7517 } },
{ "Phase One H25", 0, 0, /* added */
{ 2905,732,-237,-8134,16626,1476,-3038,4253,7517 } },
{ "Phase One IQ280", 0, 0, /* added */
{ 6294,686,-712,-5435,13417,2211,-1006,2435,5042 } },
{ "Phase One IQ260", 0, 0, /* added */
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One IQ250",0, 0,
// {3984,0,0,0,10000,0,0,0,7666}},
{10325,845,-604,-4113,13385,481,-1791,4163,6924}}, /* emb */
{ "Phase One IQ180", 0, 0, /* added */
{ 6294,686,-712,-5435,13417,2211,-1006,2435,5042 } },
{ "Phase One IQ160", 0, 0, /* added */
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One IQ150", 0, 0, /* added */
{10325,845,-604,-4113,13385,481,-1791,4163,6924}}, /* temp */ /* emb */
// { 3984,0,0,0,10000,0,0,0,7666 } },
{ "Phase One IQ140", 0, 0, /* added */
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One P65", 0, 0,
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One P 65", 0, 0,
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One P45", 0, 0, /* added */
{ 5053,-24,-117,-5685,14077,1703,-2619,4491,5850 } },
{ "Phase One P 45", 0, 0, /* added */
{ 5053,-24,-117,-5685,14077,1703,-2619,4491,5850 } },
{ "Phase One P40", 0, 0,
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One P 40", 0, 0,
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One P30", 0, 0, /* added */
{ 4516,-244,-36,-7020,14976,2174,-3206,4670,7087 } },
{ "Phase One P 30", 0, 0, /* added */
{ 4516,-244,-36,-7020,14976,2174,-3206,4670,7087 } },
{ "Phase One P25", 0, 0, /* added */
{ 2905,732,-237,-8135,16626,1476,-3038,4253,7517 } },
{ "Phase One P 25", 0, 0, /* added */
{ 2905,732,-237,-8135,16626,1476,-3038,4253,7517 } },
{ "Phase One P21", 0, 0, /* added */
{ 6516,-2050,-507,-8217,16703,1479,-3492,4741,8489 } },
{ "Phase One P 21", 0, 0, /* added */
{ 6516,-2050,-507,-8217,16703,1479,-3492,4741,8489 } },
{ "Phase One P20", 0, 0, /* added */
{ 2905,732,-237,-8135,16626,1476,-3038,4253,7517 } },
{ "Phase One P20", 0, 0, /* added */
{ 2905,732,-237,-8135,16626,1476,-3038,4253,7517 } },
{ "Phase One P 2", 0, 0,
{ 2905,732,-237,-8134,16626,1476,-3038,4253,7517 } },
{ "Phase One P2", 0, 0,
{ 2905,732,-237,-8134,16626,1476,-3038,4253,7517 } },
{ "Phase One IQ3 100MP", 0, 0, /* added */
// {2423,0,0,0,9901,0,0,0,7989}},
{ 10999,354,-742,-4590,13342,937,-1060,2166,8120} }, /* emb */
{ "Phase One IQ3 80MP", 0, 0, /* added */
{ 6294,686,-712,-5435,13417,2211,-1006,2435,5042 } },
{ "Phase One IQ3 60MP", 0, 0, /* added */
{ 8035,435,-962,-6001,13872,2320,-1159,3065,5434 } },
{ "Phase One IQ3 50MP", 0, 0, /* added */
// { 3984,0,0,0,10000,0,0,0,7666 } },
{10058,1079,-587,-4135,12903,944,-916,2726,7480}}, /* emb */
{ "Photron BC2-HD", 0, 0, /* DJC */
{ 14603,-4122,-528,-1810,9794,2017,-297,2763,5936 } },
{ "Polaroid x530", 0, 0,
{ 13458,-2556,-510,-5444,15081,205,0,0,12120 } },
{ "Red One", 704, 0xffff, /* DJC */
{ 21014,-7891,-2613,-3056,12201,856,-2203,5125,8042 } },
{ "Ricoh S10 24-72mm F2.5-4.4 VC", 0, 0, /* added */
{ 10531,-4043,-878,-2038,10270,2052,-107,895,4577 } },
{ "Ricoh GR A12 50mm F2.5 MACRO", 0, 0, /* added */
{ 8849,-2560,-689,-5092,12831,2520,-507,1280,7104 } },
{ "Ricoh GR DIGITAL 3", 0, 0, /* added */
{ 8170,-2496,-655,-5147,13056,2312,-1367,1859,5265 } },
{ "Ricoh GR DIGITAL 4", 0, 0, /* added */
{ 8771,-3151,-837,-3097,11015,2389,-703,1343,4924 } },
{ "Ricoh GR II", 0, 0,
{ 4630,-834,-423,-4977,12805,2417,-638,1467,6115 } },
{ "Ricoh GR", 0, 0,
{ 3708,-543,-160,-5381,12254,3556,-1471,1929,8234 } },
{ "Ricoh GX200", 0, 0, /* added */
{ 8040,-2368,-626,-4659,12543,2363,-1125,1581,5660 } },
{ "Ricoh RICOH GX200", 0, 0, /* added */
{ 8040,-2368,-626,-4659,12543,2363,-1125,1581,5660 } },
{ "Ricoh GXR MOUNT A12", 0, 0, /* added */
{ 7834,-2182,-739,-5453,13409,2241,-952,2005,6620 } },
{ "Ricoh GXR A16", 0, 0, /* added */
{ 7837,-2538,-730,-4370,12184,2461,-868,1648,5830 } },
{ "Ricoh GXR A12", 0, 0, /* added */
{ 10228,-3159,-933,-5304,13158,2371,-943,1873,6685 } },
{ "Samsung EK-GN100", 0, 0, /* added */ /* Galaxy NX */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung EK-GN110", 0, 0, /* added */ /* Galaxy NX */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung EK-GN120", 0, 0, /* Galaxy NX */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung EK-KN120", 0, 0, /* added */ /* Galaxy NX */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung EX1", 0, 0x3e00,
{ 8898,-2498,-994,-3144,11328,2066,-760,1381,4576 } },
{ "Samsung EX2F", 0, 0x7ff,
{ 10648,-3897,-1055,-2022,10573,1668,-492,1611,4742 } },
{ "Samsung Galaxy S7 Edge", 0, 0, /* added */
{ 9927,-3704,-1024,-3935,12758,1257,-389,1512,4993 } },
{ "Samsung Galaxy S7", 0, 0, /* added */
{ 9927,-3704,-1024,-3935,12758,1257,-389,1512,4993 } },
{ "Samsung Galaxy NX", 0, 0, /* added */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung NX U", 0, 0, /* added */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung NX mini", 0, 0,
{ 5222,-1196,-550,-6540,14649,2009,-1666,2819,5657 } },
{ "Samsung NX3300", 0, 0,
{ 8060,-2933,-761,-4504,12890,1762,-630,1489,5227 } },
{ "Samsung NX3000", 0, 0,
{ 8060,-2933,-761,-4504,12890,1762,-630,1489,5227 } },
{ "Samsung NX30", 0, 0, /* used for NX30/NX300/NX300M */
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung NX2000", 0, 0,
{ 7557,-2522,-739,-4679,12949,1894,-840,1777,5311 } },
{ "Samsung NX2", 0, 0xfff, /* used for NX20/NX200/NX210 */
{ 6933,-2268,-753,-4921,13387,1647,-803,1641,6096 } },
{ "Samsung NX1000", 0, 0,
{ 6933,-2268,-753,-4921,13387,1647,-803,1641,6096 } },
{ "Samsung NX1100", 0, 0,
{ 6933,-2268,-753,-4921,13387,1647,-803,1641,6096 } },
{ "Samsung NX11", 0, 0,
{ 10332,-3234,-1168,-6111,14639,1520,-1352,2647,8331 } },
{ "Samsung NX10", 0, 0, /* used for NX10/NX100 */
{ 10332,-3234,-1168,-6111,14639,1520,-1352,2647,8331 } },
{ "Samsung NX500", 0, 0,
{ 10686,-4042,-1052,-3595,13238,276,-464,1259,5931 } },
{ "Samsung NX5", 0, 0,
{ 10332,-3234,-1168,-6111,14639,1520,-1352,2647,8331 } },
{ "Samsung NX1", 0, 0,
{ 10686,-4042,-1052,-3595,13238,276,-464,1259,5931 } },
{ "Samsung NXF1", 0, 0, /* added */
{ 5222,-1196,-550,-6540,14649,2009,-1666,2819,5657 } },
{ "Samsung WB2000", 0, 0xfff,
{ 12093,-3557,-1155,-1000,9534,1733,-22,1787,4576 } },
{ "Samsung GX10", 0, 0, /* added */ /* Pentax K10D */
{ 9679,-2965,-811,-8622,16514,2182,-975,883,9793 } },
{ "Samsung GX-10", 0, 0, /* added */ /* Pentax K10D */
{ 9679,-2965,-811,-8622,16514,2182,-975,883,9793 } },
{ "Samsung GX-1", 0, 0, /* used for GX-1L/GX-1S */
{ 10504,-2438,-1189,-8603,16207,2531,-1022,863,12242 } },
{ "Samsung GX20", 0, 0, /* copied from Pentax K20D */
{ 9427,-2714,-868,-7493,16092,1373,-2199,3264,7180 } },
{ "Samsung GX-20", 0, 0, /* added */ /* copied from Pentax K20D */
{ 9427,-2714,-868,-7493,16092,1373,-2199,3264,7180 } },
{ "Samsung S85", 0, 0, /* DJC */
{ 11885,-3968,-1473,-4214,12299,1916,-835,1655,5549 } },
// Foveon: LibRaw color data
{ "Sigma dp0 Quattro", 2047, 0,
{ 13801,-3390,-1016,5535,3802,877,1848,4245,3730 } },
{ "Sigma dp1 Quattro", 2047, 0,
{ 13801,-3390,-1016,5535,3802,877,1848,4245,3730 } },
{ "Sigma dp2 Quattro", 2047, 0,
{ 13801,-3390,-1016,5535,3802,877,1848,4245,3730 } },
{ "Sigma dp3 Quattro", 2047, 0,
{ 13801,-3390,-1016,5535,3802,877,1848,4245,3730 } },
{ "Sigma sd Quattro H", 256, 0,
{ 1295,108,-311, 256,828,-65,-28,750,254 } }, /* temp */
{ "Sigma sd Quattro", 2047, 0,
{ 1295,108,-311, 256,828,-65,-28,750,254 } }, /* temp */
{ "Sigma SD9", 15, 4095, /* updated */
{ 13564,-2537,-751,-5465,15154,194,-67,116,10425 } },
{ "Sigma SD10", 15, 16383, /* updated */
{ 6787,-1682,575,-3091,8357,160,217,-369,12314 } },
{ "Sigma SD14", 15, 16383, /* updated */
{ 13589,-2509,-739,-5440,15104,193,-61,105,10554 } },
{ "Sigma SD15", 15, 4095, /* updated */
{ 13556,-2537,-730,-5462,15144,195,-61,106,10577 } },
// Merills + SD1
{ "Sigma SD1", 31, 4095, /* LibRaw */
{ 5133,-1895,-353,4978,744,144,3837,3069,2777 } },
{ "Sigma DP1 Merrill", 31, 4095, /* LibRaw */
{ 5133,-1895,-353,4978,744,144,3837,3069,2777 } },
{ "Sigma DP2 Merrill", 31, 4095, /* LibRaw */
{ 5133,-1895,-353,4978,744,144,3837,3069,2777 } },
{ "Sigma DP3 Merrill", 31, 4095, /* LibRaw */
{ 5133,-1895,-353,4978,744,144,3837,3069,2777 } },
// Sigma DP (non-Merill Versions)
{ "Sigma DP1X", 0, 4095, /* updated */
{ 13704,-2452,-857,-5413,15073,186,-89,151,9820 } },
{ "Sigma DP1", 0, 4095, /* updated */
{ 12774,-2591,-394,-5333,14676,207,15,-21,12127 } },
{ "Sigma DP", 0, 4095, /* LibRaw */
// { 7401,-1169,-567,2059,3769,1510,664,3367,5328 } },
{ 13100,-3638,-847,6855,2369,580,2723,3218,3251 } },
{ "Sinar", 0, 0, /* DJC */
{ 16442,-2956,-2422,-2877,12128,750,-1136,6066,4559 } },
{ "Sony DSC-F828", 0, 0,
{ 7924,-1910,-777,-8226,15459,2998,-1517,2199,6818,-7242,11401,3481 } },
{ "Sony DSC-R1", 0, 0,
{ 8512,-2641,-694,-8042,15670,2526,-1821,2117,7414 } },
{ "Sony DSC-V3", 0, 0,
{ 7511,-2571,-692,-7894,15088,3060,-948,1111,8128 } },
{"Sony DSC-RX100M5", -800, 0,
{ 6596,-2079,-562,-4782,13016,1933,-970,1581,5181 } },
{ "Sony DSC-RX100M", -800, 0, /* used for M2/M3/M4 */
{ 6596,-2079,-562,-4782,13016,1933,-970,1581,5181 } },
{ "Sony DSC-RX100", 0, 0,
{ 8651,-2754,-1057,-3464,12207,1373,-568,1398,4434 } },
{"Sony DSC-RX10M4", -800, 0,
{ 7699,-2566,-629,-2967,11270,1928,-378,1286,4807 } },
{ "Sony DSC-RX10",0, 0, /* same for M2/M3 */
{ 6679,-1825,-745,-5047,13256,1953,-1580,2422,5183 } },
{ "Sony DSC-RX1RM2", 0, 0,
{ 6629,-1900,-483,-4618,12349,2550,-622,1381,6514 } },
{ "Sony DSC-RX1R", 0, 0, /* updated */
{ 6344,-1612,-462,-4863,12477,2681,-865,1786,6899 } },
{ "Sony DSC-RX1", 0, 0,
{ 6344,-1612,-462,-4863,12477,2681,-865,1786,6899 } },
{"Sony DSC-RX0", -800, 0, /* temp */
{ 9396,-3507,-843,-2497,11111,1572,-343,1355,5089 } },
{ "Sony DSLR-A100", 0, 0xfeb,
{ 9437,-2811,-774,-8405,16215,2290,-710,596,7181 } },
{ "Sony DSLR-A290", 0, 0,
{ 6038,-1484,-579,-9145,16746,2512,-875,746,7218 } },
{ "Sony DSLR-A2", 0, 0,
{ 9847,-3091,-928,-8485,16345,2225,-715,595,7103 } },
{ "Sony DSLR-A300", 0, 0,
{ 9847,-3091,-928,-8485,16345,2225,-715,595,7103 } },
{ "Sony DSLR-A330", 0, 0,
{ 9847,-3091,-929,-8485,16346,2225,-714,595,7103 } },
{ "Sony DSLR-A350", 0, 0xffc,
{ 6038,-1484,-578,-9146,16746,2513,-875,746,7217 } },
{ "Sony DSLR-A380", 0, 0,
{ 6038,-1484,-579,-9145,16746,2512,-875,746,7218 } },
{ "Sony DSLR-A390", 0, 0,
{ 6038,-1484,-579,-9145,16746,2512,-875,746,7218 } },
{ "Sony DSLR-A450", 0, 0xfeb,
{ 4950,-580,-103,-5228,12542,3029,-709,1435,7371 } },
{ "Sony DSLR-A580", 0, 16596,
{ 5932,-1492,-411,-4813,12285,2856,-741,1524,6739 } },
{ "Sony DSLR-A500", 0, 16596,
{ 6046,-1127,-278,-5574,13076,2786,-691,1419,7625 } },
{ "Sony DSLR-A550", 0, 16596,
{ 4950,-580,-103,-5228,12542,3029,-709,1435,7371 } },
{ "Sony DSLR-A5", 0, 0xfeb, /* Is there any cameras not covered above? */
{ 4950,-580,-103,-5228,12542,3029,-709,1435,7371 } },
{ "Sony DSLR-A700", 0, 0,
{ 5775,-805,-359,-8574,16295,2391,-1943,2341,7249 } },
{ "Sony DSLR-A850", 0, 0,
{ 5413,-1162,-365,-5665,13098,2866,-608,1179,8440 } },
{ "Sony DSLR-A900", 0, 0,
{ 5209,-1072,-397,-8845,16120,2919,-1618,1803,8654 } },
{ "Sony ILCA-68", 0, 0,
{ 6435,-1903,-536,-4722,12449,2550,-663,1363,6517 } },
{ "Sony ILCA-77M2", 0, 0,
{ 5991,-1732,-443,-4100,11989,2381,-704,1467,5992 } },
{ "Sony ILCA-99M2", 0, 0,
{ 6660,-1918,-471,-4613,12398,2485,-649,1433,6447 } },
{ "Sony ILCE-9", 0, 0,
{ 6389,-1703,-378,-4562,12265,2587,-670,1489,6550 } },
{ "Sony ILCE-7M2", 0, 0,
{ 5271,-712,-347,-6153,13653,2763,-1601,2366,7242 } },
{ "Sony ILCE-7SM2", 0, 0,
{ 5838,-1430,-246,-3497,11477,2297,-748,1885,5778 } },
{ "Sony ILCE-7S", 0, 0,
{ 5838,-1430,-246,-3497,11477,2297,-748,1885,5778 } },
{ "Sony ILCE-7RM3", 0, 0,
{ 6640,-1847,-503,-5238,13010,2474,-993,1673,6527 } },
{ "Sony ILCE-7RM2", 0, 0,
{ 6629,-1900,-483,-4618,12349,2550,-622,1381,6514 } },
{ "Sony ILCE-7R", 0, 0,
{ 4913,-541,-202,-6130,13513,2906,-1564,2151,7183 } },
{ "Sony ILCE-7", 0, 0,
{ 5271,-712,-347,-6153,13653,2763,-1601,2366,7242 } },
{ "Sony ILCE-6300", 0, 0,
{ 5973,-1695,-419,-3826,11797,2293,-639,1398,5789 } },
{ "Sony ILCE-6500", 0, 0,
{ 5973,-1695,-419,-3826,11797,2293,-639,1398,5789 } },
{ "Sony ILCE", 0, 0, /* 3000, 5000, 5100, 6000, and QX1 */
{ 5991,-1456,-455,-4764,12135,2980,-707,1425,6701 } },
{ "Sony MODEL-NAME", 0, 0, /* added */
{ 5491,-1192,-363,-4951,12342,2948,-911,1722,7192 } },
{ "Sony NEX-5N", 0, 0,
{ 5991,-1456,-455,-4764,12135,2980,-707,1425,6701 } },
{ "Sony NEX-5R", 0, 0,
{ 6129,-1545,-418,-4930,12490,2743,-977,1693,6615 } },
{ "Sony NEX-5T", 0, 0,
{ 6129,-1545,-418,-4930,12490,2743,-977,1693,6615 } },
{ "Sony NEX-3N", 0, 0,
{ 6129,-1545,-418,-4930,12490,2743,-977,1693,6615 } },
{ "Sony NEX-3", 0, 0,
{ 6549,-1550,-436,-4880,12435,2753,-854,1868,6976 } },
{ "Sony NEX-5", 0, 0,
{ 6549,-1550,-436,-4880,12435,2753,-854,1868,6976 } },
{ "Sony NEX-6", 0, 0,
{ 6129,-1545,-418,-4930,12490,2743,-977,1693,6615 } },
{ "Sony NEX-7", 0, 0,
{ 5491,-1192,-363,-4951,12342,2948,-911,1722,7192 } },
{ "Sony NEX-VG30", 0, 0, /* added */
{ 6129,-1545,-418,-4930,12490,2743,-977,1693,6615 } },
{ "Sony NEX-VG900", 0, 0, /* added */
{ 6344,-1612,-462,-4863,12477,2681,-865,1786,6899 } },
{ "Sony NEX", 0, 0, /* NEX-C3, NEX-F3, NEX-VG20 */
{ 5991,-1456,-455,-4764,12135,2980,-707,1425,6701 } },
{ "Sony SLT-A33", 0, 0,
{ 6069,-1221,-366,-5221,12779,2734,-1024,2066,6834 } },
{ "Sony SLT-A35", 0, 0,
{ 5986,-1618,-415,-4557,11820,3120,-681,1404,6971 } },
{ "Sony SLT-A37", 0, 0,
{ 5991,-1456,-455,-4764,12135,2980,-707,1425,6701 } },
{ "Sony SLT-A55", 0, 0,
{ 5932,-1492,-411,-4813,12285,2856,-741,1524,6739 } },
{ "Sony SLT-A57", 0, 0,
{ 5991,-1456,-455,-4764,12135,2980,-707,1425,6701 } },
{ "Sony SLT-A58", 0, 0,
{ 5991,-1456,-455,-4764,12135,2980,-707,1425,6701 } },
{ "Sony SLT-A65", 0, 0,
{ 5491,-1192,-363,-4951,12342,2948,-911,1722,7192 } },
{ "Sony SLT-A77", 0, 0,
{ 5491,-1192,-363,-4951,12342,2948,-911,1722,7192 } },
{ "Sony SLT-A99", 0, 0,
{ 6344,-1612,-462,-4863,12477,2681,-865,1786,6899 } },
};
// clang-format on
double cam_xyz[4][3];
char name[130];
int i, j;
if (colors > 4 || colors < 1)
return;
int bl4 = (cblack[0] + cblack[1] + cblack[2] + cblack[3]) / 4, bl64 = 0;
if (cblack[4] * cblack[5] > 0)
{
for (unsigned c = 0; c < 4096 && c < cblack[4] * cblack[5]; c++)
bl64 += cblack[c + 6];
bl64 /= cblack[4] * cblack[5];
}
int rblack = black + bl4 + bl64;
sprintf(name, "%s %s", t_make, t_model);
for (i = 0; i < sizeof table / sizeof *table; i++)
if (!strncasecmp(name, table[i].prefix, strlen(table[i].prefix)))
{
if (!dng_version)
{
if (table[i].t_black > 0)
{
black = (ushort)table[i].t_black;
memset(cblack, 0, sizeof(cblack));
}
else if (table[i].t_black < 0 && rblack == 0)
{
black = (ushort)(-table[i].t_black);
memset(cblack, 0, sizeof(cblack));
}
if (table[i].t_maximum)
maximum = (ushort)table[i].t_maximum;
}
if (table[i].trans[0])
{
for (raw_color = j = 0; j < 12; j++)
#ifdef LIBRAW_LIBRARY_BUILD
if (internal_only)
imgdata.color.cam_xyz[0][j] = table[i].trans[j] / 10000.0;
else
imgdata.color.cam_xyz[0][j] =
#endif
((double *)cam_xyz)[j] = table[i].trans[j] / 10000.0;
#ifdef LIBRAW_LIBRARY_BUILD
if (!internal_only)
#endif
cam_xyz_coeff(rgb_cam, cam_xyz);
}
break;
}
}
void CLASS simple_coeff(int index)
{
static const float table[][12] = {/* index 0 -- all Foveon cameras */
{1.4032, -0.2231, -0.1016, -0.5263, 1.4816, 0.017, -0.0112, 0.0183, 0.9113},
/* index 1 -- Kodak DC20 and DC25 */
{2.25, 0.75, -1.75, -0.25, -0.25, 0.75, 0.75, -0.25, -0.25, -1.75, 0.75, 2.25},
/* index 2 -- Logitech Fotoman Pixtura */
{1.893, -0.418, -0.476, -0.495, 1.773, -0.278, -1.017, -0.655, 2.672},
/* index 3 -- Nikon E880, E900, and E990 */
{-1.936280, 1.800443, -1.448486, 2.584324, 1.405365, -0.524955, -0.289090, 0.408680,
-1.204965, 1.082304, 2.941367, -1.818705}};
int i, c;
for (raw_color = i = 0; i < 3; i++)
FORCC rgb_cam[i][c] = table[index][i * colors + c];
}
short CLASS guess_byte_order(int words)
{
uchar test[4][2];
int t = 2, msb;
double diff, sum[2] = {0, 0};
fread(test[0], 2, 2, ifp);
for (words -= 2; words--;)
{
fread(test[t], 2, 1, ifp);
for (msb = 0; msb < 2; msb++)
{
diff = (test[t ^ 2][msb] << 8 | test[t ^ 2][!msb]) - (test[t][msb] << 8 | test[t][!msb]);
sum[msb] += diff * diff;
}
t = (t + 1) & 3;
}
return sum[0] < sum[1] ? 0x4d4d : 0x4949;
}
float CLASS find_green(int bps, int bite, int off0, int off1)
{
UINT64 bitbuf = 0;
int vbits, col, i, c;
ushort img[2][2064];
double sum[] = {0, 0};
if(width > 2064) return 0.f; // too wide
FORC(2)
{
fseek(ifp, c ? off1 : off0, SEEK_SET);
for (vbits = col = 0; col < width; col++)
{
for (vbits -= bps; vbits < 0; vbits += bite)
{
bitbuf <<= bite;
for (i = 0; i < bite; i += 8)
bitbuf |= (unsigned)(fgetc(ifp) << i);
}
img[c][col] = bitbuf << (64 - bps - vbits) >> (64 - bps);
}
}
FORC(width - 1)
{
sum[c & 1] += ABS(img[0][c] - img[1][c + 1]);
sum[~c & 1] += ABS(img[1][c] - img[0][c + 1]);
}
return 100 * log(sum[0] / sum[1]);
}
#ifdef LIBRAW_LIBRARY_BUILD
static void remove_trailing_spaces(char *string, size_t len)
{
if (len < 1)
return; // not needed, b/c sizeof of make/model is 64
string[len - 1] = 0;
if (len < 3)
return; // also not needed
len = strnlen(string, len - 1);
for (int i = len - 1; i >= 0; i--)
{
if (isspace((unsigned char)string[i]))
string[i] = 0;
else
break;
}
}
void CLASS initdata()
{
tiff_flip = flip = filters = UINT_MAX; /* unknown */
raw_height = raw_width = fuji_width = fuji_layout = cr2_slice[0] = 0;
maximum = height = width = top_margin = left_margin = 0;
cdesc[0] = desc[0] = artist[0] = make[0] = model[0] = model2[0] = 0;
iso_speed = shutter = aperture = focal_len = unique_id = 0;
tiff_nifds = 0;
memset(tiff_ifd, 0, sizeof tiff_ifd);
for (int i = 0; i < LIBRAW_IFD_MAXCOUNT; i++)
{
tiff_ifd[i].dng_color[0].illuminant = tiff_ifd[i].dng_color[1].illuminant = 0xffff;
for (int c = 0; c < 4; c++)
tiff_ifd[i].dng_levels.analogbalance[c] = 1.0f;
}
for (int i = 0; i < 0x10000; i++)
curve[i] = i;
memset(gpsdata, 0, sizeof gpsdata);
memset(cblack, 0, sizeof cblack);
memset(white, 0, sizeof white);
memset(mask, 0, sizeof mask);
thumb_offset = thumb_length = thumb_width = thumb_height = 0;
load_raw = thumb_load_raw = 0;
write_thumb = &CLASS jpeg_thumb;
data_offset = meta_offset = meta_length = tiff_bps = tiff_compress = 0;
kodak_cbpp = zero_after_ff = dng_version = load_flags = 0;
timestamp = shot_order = tiff_samples = black = is_foveon = 0;
mix_green = profile_length = data_error = zero_is_bad = 0;
pixel_aspect = is_raw = raw_color = 1;
tile_width = tile_length = 0;
}
#endif
/*
Identify which camera created this file, and set global variables
accordingly.
*/
void CLASS identify()
{
static const short pana[][6] = {
{3130, 1743, 4, 0, -6, 0}, {3130, 2055, 4, 0, -6, 0}, {3130, 2319, 4, 0, -6, 0},
{3170, 2103, 18, 0, -42, 20}, {3170, 2367, 18, 13, -42, -21}, {3177, 2367, 0, 0, -1, 0},
{3304, 2458, 0, 0, -1, 0}, {3330, 2463, 9, 0, -5, 0}, {3330, 2479, 9, 0, -17, 4},
{3370, 1899, 15, 0, -44, 20}, {3370, 2235, 15, 0, -44, 20}, {3370, 2511, 15, 10, -44, -21},
{3690, 2751, 3, 0, -8, -3}, {3710, 2751, 0, 0, -3, 0}, {3724, 2450, 0, 0, 0, -2},
{3770, 2487, 17, 0, -44, 19}, {3770, 2799, 17, 15, -44, -19}, {3880, 2170, 6, 0, -6, 0},
{4060, 3018, 0, 0, 0, -2}, {4290, 2391, 3, 0, -8, -1}, {4330, 2439, 17, 15, -44, -19},
{4508, 2962, 0, 0, -3, -4}, {4508, 3330, 0, 0, -3, -6},
};
static const ushort canon[][11] = {
{1944, 1416, 0, 0, 48, 0},
{2144, 1560, 4, 8, 52, 2, 0, 0, 0, 25},
{2224, 1456, 48, 6, 0, 2},
{2376, 1728, 12, 6, 52, 2},
{2672, 1968, 12, 6, 44, 2},
{3152, 2068, 64, 12, 0, 0, 16},
{3160, 2344, 44, 12, 4, 4},
{3344, 2484, 4, 6, 52, 6},
{3516, 2328, 42, 14, 0, 0},
{3596, 2360, 74, 12, 0, 0},
{3744, 2784, 52, 12, 8, 12},
{3944, 2622, 30, 18, 6, 2},
{3948, 2622, 42, 18, 0, 2},
{3984, 2622, 76, 20, 0, 2, 14},
{4104, 3048, 48, 12, 24, 12},
{4116, 2178, 4, 2, 0, 0},
{4152, 2772, 192, 12, 0, 0},
{4160, 3124, 104, 11, 8, 65},
{4176, 3062, 96, 17, 8, 0, 0, 16, 0, 7, 0x49},
{4192, 3062, 96, 17, 24, 0, 0, 16, 0, 0, 0x49},
{4312, 2876, 22, 18, 0, 2},
{4352, 2874, 62, 18, 0, 0},
{4476, 2954, 90, 34, 0, 0},
{4480, 3348, 12, 10, 36, 12, 0, 0, 0, 18, 0x49},
{4480, 3366, 80, 50, 0, 0},
{4496, 3366, 80, 50, 12, 0},
{4768, 3516, 96, 16, 0, 0, 0, 16},
{4832, 3204, 62, 26, 0, 0},
{4832, 3228, 62, 51, 0, 0},
{5108, 3349, 98, 13, 0, 0},
{5120, 3318, 142, 45, 62, 0},
{5280, 3528, 72, 52, 0, 0}, /* EOS M */
{5344, 3516, 142, 51, 0, 0},
{5344, 3584, 126, 100, 0, 2},
{5360, 3516, 158, 51, 0, 0},
{5568, 3708, 72, 38, 0, 0},
{5632, 3710, 96, 17, 0, 0, 0, 16, 0, 0, 0x49},
{5712, 3774, 62, 20, 10, 2},
{5792, 3804, 158, 51, 0, 0},
{5920, 3950, 122, 80, 2, 0},
{6096, 4056, 72, 34, 0, 0}, /* EOS M3 */
{6288, 4056, 266, 36, 0, 0}, /* EOS 80D */
{6384, 4224, 120, 44, 0, 0}, /* 6D II */
{6880, 4544, 136, 42, 0, 0}, /* EOS 5D4 */
{8896, 5920, 160, 64, 0, 0},
};
static const struct
{
ushort id;
char t_model[20];
} unique[] =
{
{0x001, "EOS-1D"},
{0x167, "EOS-1DS"},
{0x168, "EOS 10D"},
{0x169, "EOS-1D Mark III"},
{0x170, "EOS 300D"},
{0x174, "EOS-1D Mark II"},
{0x175, "EOS 20D"},
{0x176, "EOS 450D"},
{0x188, "EOS-1Ds Mark II"},
{0x189, "EOS 350D"},
{0x190, "EOS 40D"},
{0x213, "EOS 5D"},
{0x215, "EOS-1Ds Mark III"},
{0x218, "EOS 5D Mark II"},
{0x232, "EOS-1D Mark II N"},
{0x234, "EOS 30D"},
{0x236, "EOS 400D"},
{0x250, "EOS 7D"},
{0x252, "EOS 500D"},
{0x254, "EOS 1000D"},
{0x261, "EOS 50D"},
{0x269, "EOS-1D X"},
{0x270, "EOS 550D"},
{0x281, "EOS-1D Mark IV"},
{0x285, "EOS 5D Mark III"},
{0x286, "EOS 600D"},
{0x287, "EOS 60D"},
{0x288, "EOS 1100D"},
{0x289, "EOS 7D Mark II"},
{0x301, "EOS 650D"},
{0x302, "EOS 6D"},
{0x324, "EOS-1D C"},
{0x325, "EOS 70D"},
{0x326, "EOS 700D"},
{0x327, "EOS 1200D"},
{0x328, "EOS-1D X Mark II"},
{0x331, "EOS M"},
{0x335, "EOS M2"},
{0x374, "EOS M3"}, /* temp */
{0x384, "EOS M10"}, /* temp */
{0x394, "EOS M5"}, /* temp */
{0x398, "EOS M100"}, /* temp */
{0x346, "EOS 100D"},
{0x347, "EOS 760D"},
{0x349, "EOS 5D Mark IV"},
{0x350, "EOS 80D"},
{0x382, "EOS 5DS"},
{0x393, "EOS 750D"},
{0x401, "EOS 5DS R"},
{0x404, "EOS 1300D"},
{0x405, "EOS 800D"},
{0x406, "EOS 6D Mark II"},
{0x407, "EOS M6"},
{0x408, "EOS 77D"},
{0x417, "EOS 200D"},
},
sonique[] = {
{0x002, "DSC-R1"}, {0x100, "DSLR-A100"}, {0x101, "DSLR-A900"}, {0x102, "DSLR-A700"},
{0x103, "DSLR-A200"}, {0x104, "DSLR-A350"}, {0x105, "DSLR-A300"}, {0x106, "DSLR-A900"},
{0x107, "DSLR-A380"}, {0x108, "DSLR-A330"}, {0x109, "DSLR-A230"}, {0x10a, "DSLR-A290"},
{0x10d, "DSLR-A850"}, {0x10e, "DSLR-A850"}, {0x111, "DSLR-A550"}, {0x112, "DSLR-A500"},
{0x113, "DSLR-A450"}, {0x116, "NEX-5"}, {0x117, "NEX-3"}, {0x118, "SLT-A33"},
{0x119, "SLT-A55V"}, {0x11a, "DSLR-A560"}, {0x11b, "DSLR-A580"}, {0x11c, "NEX-C3"},
{0x11d, "SLT-A35"}, {0x11e, "SLT-A65V"}, {0x11f, "SLT-A77V"}, {0x120, "NEX-5N"},
{0x121, "NEX-7"}, {0x122, "NEX-VG20E"}, {0x123, "SLT-A37"}, {0x124, "SLT-A57"},
{0x125, "NEX-F3"}, {0x126, "SLT-A99V"}, {0x127, "NEX-6"}, {0x128, "NEX-5R"},
{0x129, "DSC-RX100"}, {0x12a, "DSC-RX1"}, {0x12b, "NEX-VG900"}, {0x12c, "NEX-VG30E"},
{0x12e, "ILCE-3000"}, {0x12f, "SLT-A58"}, {0x131, "NEX-3N"}, {0x132, "ILCE-7"},
{0x133, "NEX-5T"}, {0x134, "DSC-RX100M2"}, {0x135, "DSC-RX10"}, {0x136, "DSC-RX1R"},
{0x137, "ILCE-7R"}, {0x138, "ILCE-6000"}, {0x139, "ILCE-5000"}, {0x13d, "DSC-RX100M3"},
{0x13e, "ILCE-7S"}, {0x13f, "ILCA-77M2"}, {0x153, "ILCE-5100"}, {0x154, "ILCE-7M2"},
{0x155, "DSC-RX100M4"}, {0x156, "DSC-RX10M2"}, {0x158, "DSC-RX1RM2"}, {0x15a, "ILCE-QX1"},
{0x15b, "ILCE-7RM2"}, {0x15e, "ILCE-7SM2"}, {0x161, "ILCA-68"}, {0x162, "ILCA-99M2"},
{0x163, "DSC-RX10M3"}, {0x164, "DSC-RX100M5"}, {0x165, "ILCE-6300"}, {0x166, "ILCE-9"},
{0x168, "ILCE-6500"}, {0x16a, "ILCE-7RM3"}, {0x16c, "DSC-RX0"}, {0x16d, "DSC-RX10M4"},
};
#ifdef LIBRAW_LIBRARY_BUILD
static const libraw_custom_camera_t const_table[]
#else
static const struct
{
unsigned fsize;
ushort rw, rh;
uchar lm, tm, rm, bm, lf, cf, max, flags;
char t_make[10], t_model[20];
ushort offset;
} table[]
#endif
= {
{786432, 1024, 768, 0, 0, 0, 0, 0, 0x94, 0, 0, "AVT", "F-080C"},
{1447680, 1392, 1040, 0, 0, 0, 0, 0, 0x94, 0, 0, "AVT", "F-145C"},
{1920000, 1600, 1200, 0, 0, 0, 0, 0, 0x94, 0, 0, "AVT", "F-201C"},
{5067304, 2588, 1958, 0, 0, 0, 0, 0, 0x94, 0, 0, "AVT", "F-510C"},
{5067316, 2588, 1958, 0, 0, 0, 0, 0, 0x94, 0, 0, "AVT", "F-510C", 12},
{10134608, 2588, 1958, 0, 0, 0, 0, 9, 0x94, 0, 0, "AVT", "F-510C"},
{10134620, 2588, 1958, 0, 0, 0, 0, 9, 0x94, 0, 0, "AVT", "F-510C", 12},
{16157136, 3272, 2469, 0, 0, 0, 0, 9, 0x94, 0, 0, "AVT", "F-810C"},
{15980544, 3264, 2448, 0, 0, 0, 0, 8, 0x61, 0, 1, "AgfaPhoto", "DC-833m"},
{9631728, 2532, 1902, 0, 0, 0, 0, 96, 0x61, 0, 0, "Alcatel", "5035D"},
{31850496, 4608, 3456, 0, 0, 0, 0, 0, 0x94, 0, 0, "GITUP", "GIT2 4:3"},
{23887872, 4608, 2592, 0, 0, 0, 0, 0, 0x94, 0, 0, "GITUP", "GIT2 16:9"},
{32257024, 4624, 3488, 8, 2, 16, 2, 0, 0x94, 0, 0, "GITUP", "GIT2P 4:3"},
// Android Raw dumps id start
// File Size in bytes Horizontal Res Vertical Flag then bayer order eg 0x16 bbgr 0x94 rggb
{1540857, 2688, 1520, 0, 0, 0, 0, 1, 0x61, 0, 0, "Samsung", "S3"},
{2658304, 1212, 1096, 0, 0, 0, 0, 1, 0x16, 0, 0, "LG", "G3FrontMipi"},
{2842624, 1296, 1096, 0, 0, 0, 0, 1, 0x16, 0, 0, "LG", "G3FrontQCOM"},
{2969600, 1976, 1200, 0, 0, 0, 0, 1, 0x16, 0, 0, "Xiaomi", "MI3wMipi"},
{3170304, 1976, 1200, 0, 0, 0, 0, 1, 0x16, 0, 0, "Xiaomi", "MI3wQCOM"},
{3763584, 1584, 1184, 0, 0, 0, 0, 96, 0x61, 0, 0, "I_Mobile", "I_StyleQ6"},
{5107712, 2688, 1520, 0, 0, 0, 0, 1, 0x61, 0, 0, "OmniVisi", "UltraPixel1"},
{5382640, 2688, 1520, 0, 0, 0, 0, 1, 0x61, 0, 0, "OmniVisi", "UltraPixel2"},
{5664912, 2688, 1520, 0, 0, 0, 0, 1, 0x61, 0, 0, "OmniVisi", "4688"},
{5664912, 2688, 1520, 0, 0, 0, 0, 1, 0x61, 0, 0, "OmniVisi", "4688"},
{5364240, 2688, 1520, 0, 0, 0, 0, 1, 0x61, 0, 0, "OmniVisi", "4688"},
{6299648, 2592, 1944, 0, 0, 0, 0, 1, 0x16, 0, 0, "OmniVisi", "OV5648"},
{6721536, 2592, 1944, 0, 0, 0, 0, 0, 0x16, 0, 0, "OmniVisi", "OV56482"},
{6746112, 2592, 1944, 0, 0, 0, 0, 0, 0x16, 0, 0, "HTC", "OneSV"},
{9631728, 2532, 1902, 0, 0, 0, 0, 96, 0x61, 0, 0, "Sony", "5mp"},
{9830400, 2560, 1920, 0, 0, 0, 0, 96, 0x61, 0, 0, "NGM", "ForwardArt"},
{10186752, 3264, 2448, 0, 0, 0, 0, 1, 0x94, 0, 0, "Sony", "IMX219-mipi 8mp"},
{10223360, 2608, 1944, 0, 0, 0, 0, 96, 0x16, 0, 0, "Sony", "IMX"},
{10782464, 3282, 2448, 0, 0, 0, 0, 0, 0x16, 0, 0, "HTC", "MyTouch4GSlide"},
{10788864, 3282, 2448, 0, 0, 0, 0, 0, 0x16, 0, 0, "Xperia", "L"},
{15967488, 3264, 2446, 0, 0, 0, 0, 96, 0x16, 0, 0, "OmniVison", "OV8850"},
{16224256, 4208, 3082, 0, 0, 0, 0, 1, 0x16, 0, 0, "LG", "G3MipiL"},
{16424960, 4208, 3120, 0, 0, 0, 0, 1, 0x16, 0, 0, "IMX135", "MipiL"},
{17326080, 4164, 3120, 0, 0, 0, 0, 1, 0x16, 0, 0, "LG", "G3LQCom"},
{17522688, 4212, 3120, 0, 0, 0, 0, 0, 0x16, 0, 0, "Sony", "IMX135-QCOM"},
{19906560, 4608, 3456, 0, 0, 0, 0, 1, 0x16, 0, 0, "Gione", "E7mipi"},
{19976192, 5312, 2988, 0, 0, 0, 0, 1, 0x16, 0, 0, "LG", "G4"},
{20389888, 4632, 3480, 0, 0, 0, 0, 1, 0x16, 0, 0, "Xiaomi", "RedmiNote3Pro"},
{20500480, 4656, 3496, 0, 0, 0, 0, 1, 0x94, 0, 0, "Sony", "IMX298-mipi 16mp"},
{21233664, 4608, 3456, 0, 0, 0, 0, 1, 0x16, 0, 0, "Gione", "E7qcom"},
{26023936, 4192, 3104, 0, 0, 0, 0, 96, 0x94, 0, 0, "THL", "5000"},
{26257920, 4208, 3120, 0, 0, 0, 0, 96, 0x94, 0, 0, "Sony", "IMX214"},
{26357760, 4224, 3120, 0, 0, 0, 0, 96, 0x61, 0, 0, "OV", "13860"},
{41312256, 5248, 3936, 0, 0, 0, 0, 96, 0x61, 0, 0, "Meizu", "MX4"},
{42923008, 5344, 4016, 0, 0, 0, 0, 96, 0x61, 0, 0, "Sony", "IMX230"},
// Android Raw dumps id end
{20137344, 3664, 2748, 0, 0, 0, 0, 0x40, 0x49, 0, 0, "Aptina", "MT9J003", 0xffff},
{2868726, 1384, 1036, 0, 0, 0, 0, 64, 0x49, 0, 8, "Baumer", "TXG14", 1078},
{5298000, 2400, 1766, 12, 12, 44, 2, 40, 0x94, 0, 2, "Canon", "PowerShot SD300"},
{6553440, 2664, 1968, 4, 4, 44, 4, 40, 0x94, 0, 2, "Canon", "PowerShot A460"},
{6573120, 2672, 1968, 12, 8, 44, 0, 40, 0x94, 0, 2, "Canon", "PowerShot A610"},
{6653280, 2672, 1992, 10, 6, 42, 2, 40, 0x94, 0, 2, "Canon", "PowerShot A530"},
{7710960, 2888, 2136, 44, 8, 4, 0, 40, 0x94, 0, 2, "Canon", "PowerShot S3 IS"},
{9219600, 3152, 2340, 36, 12, 4, 0, 40, 0x94, 0, 2, "Canon", "PowerShot A620"},
{9243240, 3152, 2346, 12, 7, 44, 13, 40, 0x49, 0, 2, "Canon", "PowerShot A470"},
{10341600, 3336, 2480, 6, 5, 32, 3, 40, 0x94, 0, 2, "Canon", "PowerShot A720 IS"},
{10383120, 3344, 2484, 12, 6, 44, 6, 40, 0x94, 0, 2, "Canon", "PowerShot A630"},
{12945240, 3736, 2772, 12, 6, 52, 6, 40, 0x94, 0, 2, "Canon", "PowerShot A640"},
{15636240, 4104, 3048, 48, 12, 24, 12, 40, 0x94, 0, 2, "Canon", "PowerShot A650"},
{15467760, 3720, 2772, 6, 12, 30, 0, 40, 0x94, 0, 2, "Canon", "PowerShot SX110 IS"},
{15534576, 3728, 2778, 12, 9, 44, 9, 40, 0x94, 0, 2, "Canon", "PowerShot SX120 IS"},
{18653760, 4080, 3048, 24, 12, 24, 12, 40, 0x94, 0, 2, "Canon", "PowerShot SX20 IS"},
{18763488, 4104, 3048, 10, 22, 82, 22, 8, 0x49, 0, 0, "Canon", "PowerShot D10"},
{19131120, 4168, 3060, 92, 16, 4, 1, 40, 0x94, 0, 2, "Canon", "PowerShot SX220 HS"},
{21936096, 4464, 3276, 25, 10, 73, 12, 40, 0x16, 0, 2, "Canon", "PowerShot SX30 IS"},
{24724224, 4704, 3504, 8, 16, 56, 8, 40, 0x49, 0, 2, "Canon", "PowerShot A3300 IS"},
{30858240, 5248, 3920, 8, 16, 56, 16, 40, 0x94, 0, 2, "Canon", "IXUS 160"},
{1976352, 1632, 1211, 0, 2, 0, 1, 0, 0x94, 0, 1, "Casio", "QV-2000UX"},
{3217760, 2080, 1547, 0, 0, 10, 1, 0, 0x94, 0, 1, "Casio", "QV-3*00EX"},
{6218368, 2585, 1924, 0, 0, 9, 0, 0, 0x94, 0, 1, "Casio", "QV-5700"},
{7816704, 2867, 2181, 0, 0, 34, 36, 0, 0x16, 0, 1, "Casio", "EX-Z60"},
{2937856, 1621, 1208, 0, 0, 1, 0, 0, 0x94, 7, 13, "Casio", "EX-S20"},
{4948608, 2090, 1578, 0, 0, 32, 34, 0, 0x94, 7, 1, "Casio", "EX-S100"},
{6054400, 2346, 1720, 2, 0, 32, 0, 0, 0x94, 7, 1, "Casio", "QV-R41"},
{7426656, 2568, 1928, 0, 0, 0, 0, 0, 0x94, 0, 1, "Casio", "EX-P505"},
{7530816, 2602, 1929, 0, 0, 22, 0, 0, 0x94, 7, 1, "Casio", "QV-R51"},
{7542528, 2602, 1932, 0, 0, 32, 0, 0, 0x94, 7, 1, "Casio", "EX-Z50"},
{7562048, 2602, 1937, 0, 0, 25, 0, 0, 0x16, 7, 1, "Casio", "EX-Z500"},
{7753344, 2602, 1986, 0, 0, 32, 26, 0, 0x94, 7, 1, "Casio", "EX-Z55"},
{9313536, 2858, 2172, 0, 0, 14, 30, 0, 0x94, 7, 1, "Casio", "EX-P600"},
{10834368, 3114, 2319, 0, 0, 27, 0, 0, 0x94, 0, 1, "Casio", "EX-Z750"},
{10843712, 3114, 2321, 0, 0, 25, 0, 0, 0x94, 0, 1, "Casio", "EX-Z75"},
{10979200, 3114, 2350, 0, 0, 32, 32, 0, 0x94, 7, 1, "Casio", "EX-P700"},
{12310144, 3285, 2498, 0, 0, 6, 30, 0, 0x94, 0, 1, "Casio", "EX-Z850"},
{12489984, 3328, 2502, 0, 0, 47, 35, 0, 0x94, 0, 1, "Casio", "EX-Z8"},
{15499264, 3754, 2752, 0, 0, 82, 0, 0, 0x94, 0, 1, "Casio", "EX-Z1050"},
{18702336, 4096, 3044, 0, 0, 24, 0, 80, 0x94, 7, 1, "Casio", "EX-ZR100"},
{7684000, 2260, 1700, 0, 0, 0, 0, 13, 0x94, 0, 1, "Casio", "QV-4000"},
{787456, 1024, 769, 0, 1, 0, 0, 0, 0x49, 0, 0, "Creative", "PC-CAM 600"},
{28829184, 4384, 3288, 0, 0, 0, 0, 36, 0x61, 0, 0, "DJI"},
{15151104, 4608, 3288, 0, 0, 0, 0, 0, 0x94, 0, 0, "Matrix"},
{3840000, 1600, 1200, 0, 0, 0, 0, 65, 0x49, 0, 0, "Foculus", "531C"},
{307200, 640, 480, 0, 0, 0, 0, 0, 0x94, 0, 0, "Generic"},
{62464, 256, 244, 1, 1, 6, 1, 0, 0x8d, 0, 0, "Kodak", "DC20"},
{124928, 512, 244, 1, 1, 10, 1, 0, 0x8d, 0, 0, "Kodak", "DC20"},
{1652736, 1536, 1076, 0, 52, 0, 0, 0, 0x61, 0, 0, "Kodak", "DCS200"},
{4159302, 2338, 1779, 1, 33, 1, 2, 0, 0x94, 0, 0, "Kodak", "C330"},
{4162462, 2338, 1779, 1, 33, 1, 2, 0, 0x94, 0, 0, "Kodak", "C330", 3160},
{2247168, 1232, 912, 0, 0, 16, 0, 0, 0x00, 0, 0, "Kodak", "C330"},
{3370752, 1232, 912, 0, 0, 16, 0, 0, 0x00, 0, 0, "Kodak", "C330"},
{6163328, 2864, 2152, 0, 0, 0, 0, 0, 0x94, 0, 0, "Kodak", "C603"},
{6166488, 2864, 2152, 0, 0, 0, 0, 0, 0x94, 0, 0, "Kodak", "C603", 3160},
{460800, 640, 480, 0, 0, 0, 0, 0, 0x00, 0, 0, "Kodak", "C603"},
{9116448, 2848, 2134, 0, 0, 0, 0, 0, 0x00, 0, 0, "Kodak", "C603"},
{12241200, 4040, 3030, 2, 0, 0, 13, 0, 0x49, 0, 0, "Kodak", "12MP"},
{12272756, 4040, 3030, 2, 0, 0, 13, 0, 0x49, 0, 0, "Kodak", "12MP", 31556},
{18000000, 4000, 3000, 0, 0, 0, 0, 0, 0x00, 0, 0, "Kodak", "12MP"},
{614400, 640, 480, 0, 3, 0, 0, 64, 0x94, 0, 0, "Kodak", "KAI-0340"},
{15360000, 3200, 2400, 0, 0, 0, 0, 96, 0x16, 0, 0, "Lenovo", "A820"},
{3884928, 1608, 1207, 0, 0, 0, 0, 96, 0x16, 0, 0, "Micron", "2010", 3212},
{1138688, 1534, 986, 0, 0, 0, 0, 0, 0x61, 0, 0, "Minolta", "RD175", 513},
{1581060, 1305, 969, 0, 0, 18, 6, 6, 0x1e, 4, 1, "Nikon", "E900"},
{2465792, 1638, 1204, 0, 0, 22, 1, 6, 0x4b, 5, 1, "Nikon", "E950"},
{2940928, 1616, 1213, 0, 0, 0, 7, 30, 0x94, 0, 1, "Nikon", "E2100"},
{4771840, 2064, 1541, 0, 0, 0, 1, 6, 0xe1, 0, 1, "Nikon", "E990"},
{4775936, 2064, 1542, 0, 0, 0, 0, 30, 0x94, 0, 1, "Nikon", "E3700"},
{5865472, 2288, 1709, 0, 0, 0, 1, 6, 0xb4, 0, 1, "Nikon", "E4500"},
{5869568, 2288, 1710, 0, 0, 0, 0, 6, 0x16, 0, 1, "Nikon", "E4300"},
{7438336, 2576, 1925, 0, 0, 0, 1, 6, 0xb4, 0, 1, "Nikon", "E5000"},
{8998912, 2832, 2118, 0, 0, 0, 0, 30, 0x94, 7, 1, "Nikon", "COOLPIX S6"},
{5939200, 2304, 1718, 0, 0, 0, 0, 30, 0x16, 0, 0, "Olympus", "C770UZ"},
{3178560, 2064, 1540, 0, 0, 0, 0, 0, 0x94, 0, 1, "Pentax", "Optio S"},
{4841984, 2090, 1544, 0, 0, 22, 0, 0, 0x94, 7, 1, "Pentax", "Optio S"},
{6114240, 2346, 1737, 0, 0, 22, 0, 0, 0x94, 7, 1, "Pentax", "Optio S4"},
{10702848, 3072, 2322, 0, 0, 0, 21, 30, 0x94, 0, 1, "Pentax", "Optio 750Z"},
{4147200, 1920, 1080, 0, 0, 0, 0, 0, 0x49, 0, 0, "Photron", "BC2-HD"},
{4151666, 1920, 1080, 0, 0, 0, 0, 0, 0x49, 0, 0, "Photron", "BC2-HD", 8},
{13248000, 2208, 3000, 0, 0, 0, 0, 13, 0x61, 0, 0, "Pixelink", "A782"},
{6291456, 2048, 1536, 0, 0, 0, 0, 96, 0x61, 0, 0, "RoverShot", "3320AF"},
{311696, 644, 484, 0, 0, 0, 0, 0, 0x16, 0, 8, "ST Micro", "STV680 VGA"},
{16098048, 3288, 2448, 0, 0, 24, 0, 9, 0x94, 0, 1, "Samsung", "S85"},
{16215552, 3312, 2448, 0, 0, 48, 0, 9, 0x94, 0, 1, "Samsung", "S85"},
{20487168, 3648, 2808, 0, 0, 0, 0, 13, 0x94, 5, 1, "Samsung", "WB550"},
{24000000, 4000, 3000, 0, 0, 0, 0, 13, 0x94, 5, 1, "Samsung", "WB550"},
{12582980, 3072, 2048, 0, 0, 0, 0, 33, 0x61, 0, 0, "Sinar", "", 68},
{33292868, 4080, 4080, 0, 0, 0, 0, 33, 0x61, 0, 0, "Sinar", "", 68},
{44390468, 4080, 5440, 0, 0, 0, 0, 33, 0x61, 0, 0, "Sinar", "", 68},
{1409024, 1376, 1024, 0, 0, 1, 0, 0, 0x49, 0, 0, "Sony", "XCD-SX910CR"},
{2818048, 1376, 1024, 0, 0, 1, 0, 97, 0x49, 0, 0, "Sony", "XCD-SX910CR"},
};
#ifdef LIBRAW_LIBRARY_BUILD
libraw_custom_camera_t table[64 + sizeof(const_table) / sizeof(const_table[0])];
#endif
static const char *corp[] = {"AgfaPhoto", "Canon", "Casio", "Epson", "Fujifilm", "Mamiya", "Minolta",
"Motorola", "Kodak", "Konica", "Leica", "Nikon", "Nokia", "Olympus",
"Pentax", "Phase One", "Ricoh", "Samsung", "Sigma", "Sinar", "Sony"};
#ifdef LIBRAW_LIBRARY_BUILD
char head[64], *cp;
#else
char head[32], *cp;
#endif
int hlen, flen, fsize, zero_fsize = 1, i, c;
struct jhead jh;
#ifdef LIBRAW_LIBRARY_BUILD
unsigned camera_count = parse_custom_cameras(64, table, imgdata.params.custom_camera_strings);
for (int q = 0; q < sizeof(const_table) / sizeof(const_table[0]); q++)
memmove(&table[q + camera_count], &const_table[q], sizeof(const_table[0]));
camera_count += sizeof(const_table) / sizeof(const_table[0]);
#endif
tiff_flip = flip = filters = UINT_MAX; /* unknown */
raw_height = raw_width = fuji_width = fuji_layout = cr2_slice[0] = 0;
maximum = height = width = top_margin = left_margin = 0;
cdesc[0] = desc[0] = artist[0] = make[0] = model[0] = model2[0] = 0;
iso_speed = shutter = aperture = focal_len = unique_id = 0;
tiff_nifds = 0;
memset(tiff_ifd, 0, sizeof tiff_ifd);
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.other.CameraTemperature = imgdata.other.SensorTemperature = imgdata.other.SensorTemperature2 =
imgdata.other.LensTemperature = imgdata.other.AmbientTemperature = imgdata.other.BatteryTemperature =
imgdata.other.exifAmbientTemperature = -1000.0f;
for (i = 0; i < LIBRAW_IFD_MAXCOUNT; i++)
{
tiff_ifd[i].dng_color[0].illuminant = tiff_ifd[i].dng_color[1].illuminant = 0xffff;
for (int c = 0; c < 4; c++)
tiff_ifd[i].dng_levels.analogbalance[c] = 1.0f;
}
#endif
memset(gpsdata, 0, sizeof gpsdata);
memset(cblack, 0, sizeof cblack);
memset(white, 0, sizeof white);
memset(mask, 0, sizeof mask);
thumb_offset = thumb_length = thumb_width = thumb_height = 0;
load_raw = thumb_load_raw = 0;
write_thumb = &CLASS jpeg_thumb;
data_offset = meta_offset = meta_length = tiff_bps = tiff_compress = 0;
kodak_cbpp = zero_after_ff = dng_version = load_flags = 0;
timestamp = shot_order = tiff_samples = black = is_foveon = 0;
mix_green = profile_length = data_error = zero_is_bad = 0;
pixel_aspect = is_raw = raw_color = 1;
tile_width = tile_length = 0;
for (i = 0; i < 4; i++)
{
cam_mul[i] = i == 1;
pre_mul[i] = i < 3;
FORC3 cmatrix[c][i] = 0;
FORC3 rgb_cam[c][i] = c == i;
}
colors = 3;
for (i = 0; i < 0x10000; i++)
curve[i] = i;
order = get2();
hlen = get4();
fseek(ifp, 0, SEEK_SET);
#ifdef LIBRAW_LIBRARY_BUILD
if(fread(head, 1, 64, ifp) < 64) throw LIBRAW_EXCEPTION_IO_CORRUPT;
libraw_internal_data.unpacker_data.lenRAFData = libraw_internal_data.unpacker_data.posRAFData = 0;
#else
fread(head, 1, 32, ifp);
#endif
fseek(ifp, 0, SEEK_END);
flen = fsize = ftell(ifp);
if ((cp = (char *)memmem(head, 32, (char *)"MMMM", 4)) || (cp = (char *)memmem(head, 32, (char *)"IIII", 4)))
{
parse_phase_one(cp - head);
if (cp - head && parse_tiff(0))
apply_tiff();
}
else if (order == 0x4949 || order == 0x4d4d)
{
if (!memcmp(head + 6, "HEAPCCDR", 8))
{
data_offset = hlen;
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
#endif
parse_ciff(hlen, flen - hlen, 0);
load_raw = &CLASS canon_load_raw;
}
else if (parse_tiff(0))
apply_tiff();
}
else if (!memcmp(head, "\xff\xd8\xff\xe1", 4) && !memcmp(head + 6, "Exif", 4))
{
fseek(ifp, 4, SEEK_SET);
data_offset = 4 + get2();
fseek(ifp, data_offset, SEEK_SET);
if (fgetc(ifp) != 0xff)
parse_tiff(12);
thumb_offset = 0;
}
else if (!memcmp(head + 25, "ARECOYK", 7))
{
strcpy(make, "Contax");
strcpy(model, "N Digital");
fseek(ifp, 33, SEEK_SET);
get_timestamp(1);
fseek(ifp, 52, SEEK_SET);
switch (get4())
{
case 7:
iso_speed = 25;
break;
case 8:
iso_speed = 32;
break;
case 9:
iso_speed = 40;
break;
case 10:
iso_speed = 50;
break;
case 11:
iso_speed = 64;
break;
case 12:
iso_speed = 80;
break;
case 13:
iso_speed = 100;
break;
case 14:
iso_speed = 125;
break;
case 15:
iso_speed = 160;
break;
case 16:
iso_speed = 200;
break;
case 17:
iso_speed = 250;
break;
case 18:
iso_speed = 320;
break;
case 19:
iso_speed = 400;
break;
}
shutter = libraw_powf64l(2.0f, (((float)get4()) / 8.0f)) / 16000.0f;
FORC4 cam_mul[c ^ (c >> 1)] = get4();
fseek(ifp, 88, SEEK_SET);
aperture = libraw_powf64l(2.0f, ((float)get4()) / 16.0f);
fseek(ifp, 112, SEEK_SET);
focal_len = get4();
#ifdef LIBRAW_LIBRARY_BUILD
fseek(ifp, 104, SEEK_SET);
imgdata.lens.makernotes.MaxAp4CurFocal = libraw_powf64l(2.0f, ((float)get4()) / 16.0f);
fseek(ifp, 124, SEEK_SET);
stmread(imgdata.lens.makernotes.Lens, 32, ifp);
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_Contax_N;
if (imgdata.lens.makernotes.Lens[0])
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_Contax_N;
#endif
}
else if (!strcmp(head, "PXN"))
{
strcpy(make, "Logitech");
strcpy(model, "Fotoman Pixtura");
}
else if (!strcmp(head, "qktk"))
{
strcpy(make, "Apple");
strcpy(model, "QuickTake 100");
load_raw = &CLASS quicktake_100_load_raw;
}
else if (!strcmp(head, "qktn"))
{
strcpy(make, "Apple");
strcpy(model, "QuickTake 150");
load_raw = &CLASS kodak_radc_load_raw;
}
else if (!memcmp(head, "FUJIFILM", 8))
{
#ifdef LIBRAW_LIBRARY_BUILD
strncpy(model, head + 0x1c,0x20);
model[0x20]=0;
memcpy(model2, head + 0x3c, 4);
model2[4] = 0;
#endif
fseek(ifp, 84, SEEK_SET);
thumb_offset = get4();
thumb_length = get4();
fseek(ifp, 92, SEEK_SET);
parse_fuji(get4());
if (thumb_offset > 120)
{
fseek(ifp, 120, SEEK_SET);
is_raw += (i = get4()) ? 1 : 0;
if (is_raw == 2 && shot_select)
parse_fuji(i);
}
load_raw = &CLASS unpacked_load_raw;
fseek(ifp, 100 + 28 * (shot_select > 0), SEEK_SET);
parse_tiff(data_offset = get4());
parse_tiff(thumb_offset + 12);
apply_tiff();
}
else if (!memcmp(head, "RIFF", 4))
{
fseek(ifp, 0, SEEK_SET);
parse_riff();
}
else if (!memcmp(head + 4, "ftypqt ", 9))
{
fseek(ifp, 0, SEEK_SET);
parse_qt(fsize);
is_raw = 0;
}
else if (!memcmp(head, "\0\001\0\001\0@", 6))
{
fseek(ifp, 6, SEEK_SET);
fread(make, 1, 8, ifp);
fread(model, 1, 8, ifp);
fread(model2, 1, 16, ifp);
data_offset = get2();
get2();
raw_width = get2();
raw_height = get2();
load_raw = &CLASS nokia_load_raw;
filters = 0x61616161;
}
else if (!memcmp(head, "NOKIARAW", 8))
{
strcpy(make, "NOKIA");
order = 0x4949;
fseek(ifp, 300, SEEK_SET);
data_offset = get4();
i = get4(); // bytes count
width = get2();
height = get2();
#ifdef LIBRAW_LIBRARY_BUILD
// Data integrity check
if (width < 1 || width > 16000 || height < 1 || height > 16000 || i < (width * height) || i > (2 * width * height))
throw LIBRAW_EXCEPTION_IO_CORRUPT;
#endif
switch (tiff_bps = i * 8 / (width * height))
{
case 8:
load_raw = &CLASS eight_bit_load_raw;
break;
case 10:
load_raw = &CLASS nokia_load_raw;
break;
case 0:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
break;
}
raw_height = height + (top_margin = i / (width * tiff_bps / 8) - height);
mask[0][3] = 1;
filters = 0x61616161;
}
else if (!memcmp(head, "ARRI", 4))
{
order = 0x4949;
fseek(ifp, 20, SEEK_SET);
width = get4();
height = get4();
strcpy(make, "ARRI");
fseek(ifp, 668, SEEK_SET);
fread(model, 1, 64, ifp);
data_offset = 4096;
load_raw = &CLASS packed_load_raw;
load_flags = 88;
filters = 0x61616161;
}
else if (!memcmp(head, "XPDS", 4))
{
order = 0x4949;
fseek(ifp, 0x800, SEEK_SET);
fread(make, 1, 41, ifp);
raw_height = get2();
raw_width = get2();
fseek(ifp, 56, SEEK_CUR);
fread(model, 1, 30, ifp);
data_offset = 0x10000;
load_raw = &CLASS canon_rmf_load_raw;
gamma_curve(0, 12.25, 1, 1023);
}
else if (!memcmp(head + 4, "RED1", 4))
{
strcpy(make, "Red");
strcpy(model, "One");
parse_redcine();
load_raw = &CLASS redcine_load_raw;
gamma_curve(1 / 2.4, 12.92, 1, 4095);
filters = 0x49494949;
}
else if (!memcmp(head, "DSC-Image", 9))
parse_rollei();
else if (!memcmp(head, "PWAD", 4))
parse_sinar_ia();
else if (!memcmp(head, "\0MRM", 4))
parse_minolta(0);
else if (!memcmp(head, "FOVb", 4))
{
#ifdef LIBRAW_LIBRARY_BUILD
/* no foveon support for dcraw build from libraw source */
parse_x3f();
#endif
}
else if (!memcmp(head, "CI", 2))
parse_cine();
if (make[0] == 0)
#ifdef LIBRAW_LIBRARY_BUILD
for (zero_fsize = i = 0; i < camera_count; i++)
#else
for (zero_fsize = i = 0; i < sizeof table / sizeof *table; i++)
#endif
if (fsize == table[i].fsize)
{
strcpy(make, table[i].t_make);
#ifdef LIBRAW_LIBRARY_BUILD
if (!strncmp(make, "Canon", 5))
{
imgdata.lens.makernotes.CameraMount = LIBRAW_MOUNT_FixedLens;
imgdata.lens.makernotes.LensMount = LIBRAW_MOUNT_FixedLens;
}
#endif
strcpy(model, table[i].t_model);
flip = table[i].flags >> 2;
zero_is_bad = table[i].flags & 2;
if (table[i].flags & 1)
parse_external_jpeg();
data_offset = table[i].offset == 0xffff ? 0 : table[i].offset;
raw_width = table[i].rw;
raw_height = table[i].rh;
left_margin = table[i].lm;
top_margin = table[i].tm;
width = raw_width - left_margin - table[i].rm;
height = raw_height - top_margin - table[i].bm;
filters = 0x1010101U * table[i].cf;
colors = 4 - !((filters & filters >> 1) & 0x5555);
load_flags = table[i].lf;
switch (tiff_bps = (fsize - data_offset) * 8 / (raw_width * raw_height))
{
case 6:
load_raw = &CLASS minolta_rd175_load_raw;
break;
case 8:
load_raw = &CLASS eight_bit_load_raw;
break;
case 10:
if ((fsize - data_offset) / raw_height * 3 >= raw_width * 4)
{
load_raw = &CLASS android_loose_load_raw;
break;
}
else if (load_flags & 1)
{
load_raw = &CLASS android_tight_load_raw;
break;
}
case 12:
load_flags |= 128;
load_raw = &CLASS packed_load_raw;
break;
case 16:
order = 0x4949 | 0x404 * (load_flags & 1);
tiff_bps -= load_flags >> 4;
tiff_bps -= load_flags = load_flags >> 1 & 7;
load_raw = table[i].offset == 0xffff ? &CLASS unpacked_load_raw_reversed : &CLASS unpacked_load_raw;
}
maximum = (1 << tiff_bps) - (1 << table[i].max);
break;
}
if (zero_fsize)
fsize = 0;
if (make[0] == 0)
parse_smal(0, flen);
if (make[0] == 0)
{
parse_jpeg(0);
fseek(ifp, 0, SEEK_END);
int sz = ftell(ifp);
#ifdef LIBRAW_LIBRARY_BUILD
if (!strncmp(model, "RP_imx219", 9) && sz >= 0x9cb600 && !fseek(ifp, -0x9cb600, SEEK_END) &&
fread(head, 1, 0x20, ifp) && !strncmp(head, "BRCM", 4))
{
strcpy(make, "Broadcom");
strcpy(model, "RPi IMX219");
if (raw_height > raw_width)
flip = 5;
data_offset = ftell(ifp) + 0x8000 - 0x20;
parse_broadcom();
black = 66;
maximum = 0x3ff;
load_raw = &CLASS broadcom_load_raw;
thumb_offset = 0;
thumb_length = sz - 0x9cb600 - 1;
}
else if (!(strncmp(model, "ov5647", 6) && strncmp(model, "RP_OV5647", 9)) && sz >= 0x61b800 &&
!fseek(ifp, -0x61b800, SEEK_END) && fread(head, 1, 0x20, ifp) && !strncmp(head, "BRCM", 4))
{
strcpy(make, "Broadcom");
if (!strncmp(model, "ov5647", 6))
strcpy(model, "RPi OV5647 v.1");
else
strcpy(model, "RPi OV5647 v.2");
if (raw_height > raw_width)
flip = 5;
data_offset = ftell(ifp) + 0x8000 - 0x20;
parse_broadcom();
black = 16;
maximum = 0x3ff;
load_raw = &CLASS broadcom_load_raw;
thumb_offset = 0;
thumb_length = sz - 0x61b800 - 1;
#else
if (!(strncmp(model, "ov", 2) && strncmp(model, "RP_OV", 5)) && sz >= 6404096 && !fseek(ifp, -6404096, SEEK_END) &&
fread(head, 1, 32, ifp) && !strcmp(head, "BRCMn"))
{
strcpy(make, "OmniVision");
data_offset = ftell(ifp) + 0x8000 - 32;
width = raw_width;
raw_width = 2611;
load_raw = &CLASS nokia_load_raw;
filters = 0x16161616;
#endif
}
else
is_raw = 0;
}
#ifdef LIBRAW_LIBRARY_BUILD
// make sure strings are terminated
desc[511] = artist[63] = make[63] = model[63] = model2[63] = 0;
#endif
for (i = 0; i < sizeof corp / sizeof *corp; i++)
if (strcasestr(make, corp[i])) /* Simplify company names */
strcpy(make, corp[i]);
if ((!strncmp(make, "Kodak", 5) || !strncmp(make, "Leica", 5)) &&
((cp = strcasestr(model, " DIGITAL CAMERA")) || (cp = strstr(model, "FILE VERSION"))))
*cp = 0;
if (!strncasecmp(model, "PENTAX", 6))
strcpy(make, "Pentax");
#ifdef LIBRAW_LIBRARY_BUILD
remove_trailing_spaces(make, sizeof(make));
remove_trailing_spaces(model, sizeof(model));
#else
cp = make + strlen(make); /* Remove trailing spaces */
while (*--cp == ' ')
*cp = 0;
cp = model + strlen(model);
while (*--cp == ' ')
*cp = 0;
#endif
i = strbuflen(make); /* Remove make from model */
if (!strncasecmp(model, make, i) && model[i++] == ' ')
memmove(model, model + i, 64 - i);
if (!strncmp(model, "FinePix ", 8))
memmove(model, model + 8,strlen(model)-7);
if (!strncmp(model, "Digital Camera ", 15))
memmove(model, model + 15,strlen(model)-14);
desc[511] = artist[63] = make[63] = model[63] = model2[63] = 0;
if (!is_raw)
goto notraw;
if (!height)
height = raw_height;
if (!width)
width = raw_width;
if (height == 2624 && width == 3936) /* Pentax K10D and Samsung GX10 */
{
height = 2616;
width = 3896;
}
if (height == 3136 && width == 4864) /* Pentax K20D and Samsung GX20 */
{
height = 3124;
width = 4688;
filters = 0x16161616;
}
if (width == 4352 && (!strcmp(model, "K-r") || !strcmp(model, "K-x")))
{
width = 4309;
filters = 0x16161616;
}
if (width >= 4960 && !strncmp(model, "K-5", 3))
{
left_margin = 10;
width = 4950;
filters = 0x16161616;
}
if (width == 6080 && !strcmp(model, "K-70"))
{
height = 4016;
top_margin = 32;
width = 6020;
left_margin = 60;
}
if (width == 4736 && !strcmp(model, "K-7"))
{
height = 3122;
width = 4684;
filters = 0x16161616;
top_margin = 2;
}
if (width == 6080 && !strcmp(model, "K-3 II")) /* moved back */
{
left_margin = 4;
width = 6040;
}
if (width == 6112 && !strcmp(model, "KP"))
{
/* From DNG, maybe too strict */
left_margin = 54;
top_margin = 28;
width = 6028;
height = raw_height - top_margin;
}
if (width == 6080 && !strcmp(model, "K-3"))
{
left_margin = 4;
width = 6040;
}
if (width == 7424 && !strcmp(model, "645D"))
{
height = 5502;
width = 7328;
filters = 0x61616161;
top_margin = 29;
left_margin = 48;
}
if (height == 3014 && width == 4096) /* Ricoh GX200 */
width = 4014;
if (dng_version)
{
if (filters == UINT_MAX)
filters = 0;
if (filters)
is_raw *= tiff_samples;
else
colors = tiff_samples;
switch (tiff_compress)
{
case 0: /* Compression not set, assuming uncompressed */
case 1:
load_raw = &CLASS packed_dng_load_raw;
break;
case 7:
load_raw = &CLASS lossless_dng_load_raw;
break;
#ifdef LIBRAW_LIBRARY_BUILD
case 8:
load_raw = &CLASS deflate_dng_load_raw;
break;
#endif
case 34892:
load_raw = &CLASS lossy_dng_load_raw;
break;
default:
load_raw = 0;
}
if (!strncmp(make, "Canon", 5) && unique_id)
{
for (i = 0; i < sizeof unique / sizeof *unique; i++)
if (unique_id == 0x80000000 + unique[i].id)
{
strcpy(model, unique[i].t_model);
break;
}
}
if (!strncasecmp(make, "Sony", 4) && unique_id)
{
for (i = 0; i < sizeof sonique / sizeof *sonique; i++)
if (unique_id == sonique[i].id)
{
strcpy(model, sonique[i].t_model);
break;
}
}
goto dng_skip;
}
if (!strncmp(make, "Canon", 5) && !fsize && tiff_bps != 15)
{
if (!load_raw)
load_raw = &CLASS lossless_jpeg_load_raw;
for (i = 0; i < sizeof canon / sizeof *canon; i++)
if (raw_width == canon[i][0] && raw_height == canon[i][1])
{
width = raw_width - (left_margin = canon[i][2]);
height = raw_height - (top_margin = canon[i][3]);
width -= canon[i][4];
height -= canon[i][5];
mask[0][1] = canon[i][6];
mask[0][3] = -canon[i][7];
mask[1][1] = canon[i][8];
mask[1][3] = -canon[i][9];
if (canon[i][10])
filters = canon[i][10] * 0x01010101U;
}
if ((unique_id | 0x20000) == 0x2720000)
{
left_margin = 8;
top_margin = 16;
}
}
if (!strncmp(make, "Canon", 5) && unique_id)
{
for (i = 0; i < sizeof unique / sizeof *unique; i++)
if (unique_id == 0x80000000 + unique[i].id)
{
adobe_coeff("Canon", unique[i].t_model);
strcpy(model, unique[i].t_model);
}
}
if (!strncasecmp(make, "Sony", 4) && unique_id)
{
for (i = 0; i < sizeof sonique / sizeof *sonique; i++)
if (unique_id == sonique[i].id)
{
adobe_coeff("Sony", sonique[i].t_model);
strcpy(model, sonique[i].t_model);
}
}
if (!strncmp(make, "Nikon", 5))
{
if (!load_raw)
load_raw = &CLASS packed_load_raw;
if (model[0] == 'E')
load_flags |= !data_offset << 2 | 2;
}
/* Set parameters based on camera name (for non-DNG files). */
if (!strcmp(model, "KAI-0340") && find_green(16, 16, 3840, 5120) < 25)
{
height = 480;
top_margin = filters = 0;
strcpy(model, "C603");
}
#ifndef LIBRAW_LIBRARY_BUILD
if (!strcmp(make, "Sony") && raw_width > 3888 && !black && !cblack[0])
black = 128 << (tiff_bps - 12);
#else
/* Always 512 for arw2_load_raw */
if (!strcmp(make, "Sony") && raw_width > 3888 && !black && !cblack[0])
black = (load_raw == &LibRaw::sony_arw2_load_raw) ? 512 : (128 << (tiff_bps - 12));
#endif
if (is_foveon)
{
if (height * 2 < width)
pixel_aspect = 0.5;
if (height > width)
pixel_aspect = 2;
filters = 0;
}
else if (!strncmp(make, "Pentax", 6) && !strncmp(model, "K-1", 3))
{
top_margin = 18;
height = raw_height - top_margin;
if (raw_width == 7392)
{
left_margin = 6;
width = 7376;
}
}
else if (!strncmp(make, "Canon", 5) && tiff_bps == 15)
{
switch (width)
{
case 3344:
width -= 66;
case 3872:
width -= 6;
}
if (height > width)
{
SWAP(height, width);
SWAP(raw_height, raw_width);
}
if (width == 7200 && height == 3888)
{
raw_width = width = 6480;
raw_height = height = 4320;
}
filters = 0;
tiff_samples = colors = 3;
load_raw = &CLASS canon_sraw_load_raw;
}
else if (!strcmp(model, "PowerShot 600"))
{
height = 613;
width = 854;
raw_width = 896;
colors = 4;
filters = 0xe1e4e1e4;
load_raw = &CLASS canon_600_load_raw;
}
else if (!strcmp(model, "PowerShot A5") || !strcmp(model, "PowerShot A5 Zoom"))
{
height = 773;
width = 960;
raw_width = 992;
pixel_aspect = 256 / 235.0;
filters = 0x1e4e1e4e;
goto canon_a5;
}
else if (!strcmp(model, "PowerShot A50"))
{
height = 968;
width = 1290;
raw_width = 1320;
filters = 0x1b4e4b1e;
goto canon_a5;
}
else if (!strcmp(model, "PowerShot Pro70"))
{
height = 1024;
width = 1552;
filters = 0x1e4b4e1b;
canon_a5:
colors = 4;
tiff_bps = 10;
load_raw = &CLASS packed_load_raw;
load_flags = 40;
}
else if (!strcmp(model, "PowerShot Pro90 IS") || !strcmp(model, "PowerShot G1"))
{
colors = 4;
filters = 0xb4b4b4b4;
}
else if (!strcmp(model, "PowerShot A610"))
{
if (canon_s2is())
strcpy(model + 10, "S2 IS");
}
else if (!strcmp(model, "PowerShot SX220 HS"))
{
mask[1][3] = -4;
top_margin = 16;
left_margin = 92;
}
else if (!strcmp(model, "PowerShot S120"))
{
raw_width = 4192;
raw_height = 3062;
width = 4022;
height = 3016;
mask[0][0] = top_margin = 31;
mask[0][2] = top_margin + height;
left_margin = 120;
mask[0][1] = 23;
mask[0][3] = 72;
}
else if (!strcmp(model, "PowerShot G16"))
{
mask[0][0] = 0;
mask[0][2] = 80;
mask[0][1] = 0;
mask[0][3] = 16;
top_margin = 29;
left_margin = 120;
width = raw_width - left_margin - 48;
height = raw_height - top_margin - 14;
}
else if (!strcmp(model, "PowerShot SX50 HS"))
{
top_margin = 17;
}
else if (!strcmp(model, "EOS D2000C"))
{
filters = 0x61616161;
if (!black)
black = curve[200];
}
else if (!strcmp(model, "D1"))
{
cam_mul[0] *= 256 / 527.0;
cam_mul[2] *= 256 / 317.0;
}
else if (!strcmp(model, "D1X"))
{
width -= 4;
pixel_aspect = 0.5;
}
else if (!strcmp(model, "D40X") || !strcmp(model, "D60") || !strcmp(model, "D80") || !strcmp(model, "D3000"))
{
height -= 3;
width -= 4;
}
else if (!strcmp(model, "D3") || !strcmp(model, "D3S") || !strcmp(model, "D700"))
{
width -= 4;
left_margin = 2;
}
else if (!strcmp(model, "D3100"))
{
width -= 28;
left_margin = 6;
}
else if (!strcmp(model, "D5000") || !strcmp(model, "D90"))
{
width -= 42;
}
else if (!strcmp(model, "D5100") || !strcmp(model, "D7000") || !strcmp(model, "COOLPIX A"))
{
width -= 44;
}
else if (!strcmp(model, "D3200") || !strncmp(model, "D6", 2) || !strncmp(model, "D800", 4))
{
width -= 46;
}
else if (!strcmp(model, "D4") || !strcmp(model, "Df"))
{
width -= 52;
left_margin = 2;
}
else if (!strcmp(model, "D500"))
{
// Empty - to avoid width-1 below
}
else if (!strncmp(model, "D40", 3) || !strncmp(model, "D50", 3) || !strncmp(model, "D70", 3))
{
width--;
}
else if (!strcmp(model, "D100"))
{
if (load_flags)
raw_width = (width += 3) + 3;
}
else if (!strcmp(model, "D200"))
{
left_margin = 1;
width -= 4;
filters = 0x94949494;
}
else if (!strncmp(model, "D2H", 3))
{
left_margin = 6;
width -= 14;
}
else if (!strncmp(model, "D2X", 3))
{
if (width == 3264)
width -= 32;
else
width -= 8;
}
else if (!strncmp(model, "D300", 4))
{
width -= 32;
}
else if (!strncmp(make, "Nikon", 5) && raw_width == 4032)
{
if (!strcmp(model, "COOLPIX P7700"))
{
adobe_coeff("Nikon", "COOLPIX P7700");
maximum = 65504;
load_flags = 0;
}
else if (!strcmp(model, "COOLPIX P7800"))
{
adobe_coeff("Nikon", "COOLPIX P7800");
maximum = 65504;
load_flags = 0;
}
else if (!strcmp(model, "COOLPIX P340"))
load_flags = 0;
}
else if (!strncmp(model, "COOLPIX P", 9) && raw_width != 4032)
{
load_flags = 24;
filters = 0x94949494;
if (model[9] == '7' && (iso_speed >= 400 || iso_speed == 0) && !strstr(software, "V1.2"))
black = 255;
}
else if (!strncmp(model, "COOLPIX B700", 12))
{
load_flags = 24;
black = 200;
}
else if (!strncmp(model, "1 ", 2))
{
height -= 2;
}
else if (fsize == 1581060)
{
simple_coeff(3);
pre_mul[0] = 1.2085;
pre_mul[1] = 1.0943;
pre_mul[3] = 1.1103;
}
else if (fsize == 3178560)
{
cam_mul[0] *= 4;
cam_mul[2] *= 4;
}
else if (fsize == 4771840)
{
if (!timestamp && nikon_e995())
strcpy(model, "E995");
if (strcmp(model, "E995"))
{
filters = 0xb4b4b4b4;
simple_coeff(3);
pre_mul[0] = 1.196;
pre_mul[1] = 1.246;
pre_mul[2] = 1.018;
}
}
else if (fsize == 2940928)
{
if (!timestamp && !nikon_e2100())
strcpy(model, "E2500");
if (!strcmp(model, "E2500"))
{
height -= 2;
load_flags = 6;
colors = 4;
filters = 0x4b4b4b4b;
}
}
else if (fsize == 4775936)
{
if (!timestamp)
nikon_3700();
if (model[0] == 'E' && atoi(model + 1) < 3700)
filters = 0x49494949;
if (!strcmp(model, "Optio 33WR"))
{
flip = 1;
filters = 0x16161616;
}
if (make[0] == 'O')
{
i = find_green(12, 32, 1188864, 3576832);
c = find_green(12, 32, 2383920, 2387016);
if (abs(i) < abs(c))
{
SWAP(i, c);
load_flags = 24;
}
if (i < 0)
filters = 0x61616161;
}
}
else if (fsize == 5869568)
{
if (!timestamp && minolta_z2())
{
strcpy(make, "Minolta");
strcpy(model, "DiMAGE Z2");
}
load_flags = 6 + 24 * (make[0] == 'M');
}
else if (fsize == 6291456)
{
fseek(ifp, 0x300000, SEEK_SET);
if ((order = guess_byte_order(0x10000)) == 0x4d4d)
{
height -= (top_margin = 16);
width -= (left_margin = 28);
maximum = 0xf5c0;
strcpy(make, "ISG");
model[0] = 0;
}
}
else if (!strncmp(make, "Fujifilm", 8))
{
if (!strcmp(model, "X-A3") || !strcmp(model, "X-A10")
|| !strcmp(model, "X-A5") || !strcmp(model, "X-A20"))
{
left_margin = 0;
top_margin = 0;
width = raw_width;
height = raw_height;
}
if (!strcmp(model + 7, "S2Pro"))
{
strcpy(model, "S2Pro");
height = 2144;
width = 2880;
flip = 6;
}
else if (load_raw != &CLASS packed_load_raw && strncmp(model, "X-", 2) && filters >=1000) // Bayer and not X-models
maximum = (is_raw == 2 && shot_select) ? 0x2f00 : 0x3e00;
top_margin = (raw_height - height) >> 2 << 1;
left_margin = (raw_width - width) >> 2 << 1;
if (width == 2848 || width == 3664)
filters = 0x16161616;
if (width == 4032 || width == 4952)
left_margin = 0;
if (width == 3328 && (width -= 66))
left_margin = 34;
if (width == 4936)
left_margin = 4;
if (width == 6032)
left_margin = 0;
if (!strcmp(model, "HS50EXR") || !strcmp(model, "F900EXR"))
{
width += 2;
left_margin = 0;
filters = 0x16161616;
}
if (!strcmp(model, "GFX 50S"))
{
left_margin = 0;
top_margin = 0;
}
if (!strcmp(model, "S5500"))
{
height -= (top_margin = 6);
}
if (fuji_layout)
raw_width *= is_raw;
if (filters == 9)
FORC(36)((char *)xtrans)[c] = xtrans_abs[(c / 6 + top_margin) % 6][(c + left_margin) % 6];
}
else if (!strcmp(model, "KD-400Z"))
{
height = 1712;
width = 2312;
raw_width = 2336;
goto konica_400z;
}
else if (!strcmp(model, "KD-510Z"))
{
goto konica_510z;
}
else if (!strncasecmp(make, "Minolta", 7))
{
if (!load_raw && (maximum = 0xfff))
load_raw = &CLASS unpacked_load_raw;
if (!strncmp(model, "DiMAGE A", 8))
{
if (!strcmp(model, "DiMAGE A200"))
filters = 0x49494949;
tiff_bps = 12;
load_raw = &CLASS packed_load_raw;
}
else if (!strncmp(model, "ALPHA", 5) || !strncmp(model, "DYNAX", 5) || !strncmp(model, "MAXXUM", 6))
{
sprintf(model + 20, "DYNAX %-10s", model + 6 + (model[0] == 'M'));
adobe_coeff(make, model + 20);
load_raw = &CLASS packed_load_raw;
}
else if (!strncmp(model, "DiMAGE G", 8))
{
if (model[8] == '4')
{
height = 1716;
width = 2304;
}
else if (model[8] == '5')
{
konica_510z:
height = 1956;
width = 2607;
raw_width = 2624;
}
else if (model[8] == '6')
{
height = 2136;
width = 2848;
}
data_offset += 14;
filters = 0x61616161;
konica_400z:
load_raw = &CLASS unpacked_load_raw;
maximum = 0x3df;
order = 0x4d4d;
}
}
else if (!strcmp(model, "*ist D"))
{
load_raw = &CLASS unpacked_load_raw;
data_error = -1;
}
else if (!strcmp(model, "*ist DS"))
{
height -= 2;
}
else if (!strncmp(make, "Samsung", 7) && raw_width == 4704)
{
height -= top_margin = 8;
width -= 2 * (left_margin = 8);
load_flags = 32;
}
else if (!strncmp(make, "Samsung", 7) && !strcmp(model, "NX3000"))
{
top_margin = 38;
left_margin = 92;
width = 5456;
height = 3634;
filters = 0x61616161;
colors = 3;
}
else if (!strncmp(make, "Samsung", 7) && raw_height == 3714)
{
height -= top_margin = 18;
left_margin = raw_width - (width = 5536);
if (raw_width != 5600)
left_margin = top_margin = 0;
filters = 0x61616161;
colors = 3;
}
else if (!strncmp(make, "Samsung", 7) && raw_width == 5632)
{
order = 0x4949;
height = 3694;
top_margin = 2;
width = 5574 - (left_margin = 32 + tiff_bps);
if (tiff_bps == 12)
load_flags = 80;
}
else if (!strncmp(make, "Samsung", 7) && raw_width == 5664)
{
height -= top_margin = 17;
left_margin = 96;
width = 5544;
filters = 0x49494949;
}
else if (!strncmp(make, "Samsung", 7) && raw_width == 6496)
{
filters = 0x61616161;
#ifdef LIBRAW_LIBRARY_BUILD
if (!black && !cblack[0] && !cblack[1] && !cblack[2] && !cblack[3])
#endif
black = 1 << (tiff_bps - 7);
}
else if (!strcmp(model, "EX1"))
{
order = 0x4949;
height -= 20;
top_margin = 2;
if ((width -= 6) > 3682)
{
height -= 10;
width -= 46;
top_margin = 8;
}
}
else if (!strcmp(model, "WB2000"))
{
order = 0x4949;
height -= 3;
top_margin = 2;
if ((width -= 10) > 3718)
{
height -= 28;
width -= 56;
top_margin = 8;
}
}
else if (strstr(model, "WB550"))
{
strcpy(model, "WB550");
}
else if (!strcmp(model, "EX2F"))
{
height = 3030;
width = 4040;
top_margin = 15;
left_margin = 24;
order = 0x4949;
filters = 0x49494949;
load_raw = &CLASS unpacked_load_raw;
}
else if (!strcmp(model, "STV680 VGA"))
{
black = 16;
}
else if (!strcmp(model, "N95"))
{
height = raw_height - (top_margin = 2);
}
else if (!strcmp(model, "640x480"))
{
gamma_curve(0.45, 4.5, 1, 255);
}
else if (!strncmp(make, "Hasselblad", 10))
{
if (load_raw == &CLASS lossless_jpeg_load_raw)
load_raw = &CLASS hasselblad_load_raw;
if (raw_width == 7262)
{
height = 5444;
width = 7248;
top_margin = 4;
left_margin = 7;
filters = 0x61616161;
if (!strncasecmp(model, "H3D", 3))
{
adobe_coeff("Hasselblad", "H3DII-39");
strcpy(model, "H3DII-39");
}
}
else if (raw_width == 12000) // H6D 100c, A6D 100c
{
left_margin = 64;
width = 11608;
top_margin = 108;
height = raw_height - top_margin;
adobe_coeff("Hasselblad", "H6D-100c");
}
else if (raw_width == 7410 || raw_width == 8282)
{
height -= 84;
width -= 82;
top_margin = 4;
left_margin = 41;
filters = 0x61616161;
adobe_coeff("Hasselblad", "H4D-40");
strcpy(model, "H4D-40");
}
else if (raw_width == 8384) // X1D
{
top_margin = 96;
height -= 96;
left_margin = 48;
width -= 106;
adobe_coeff("Hasselblad", "X1D");
maximum = 0xffff;
tiff_bps = 16;
}
else if (raw_width == 9044)
{
if (black > 500)
{
top_margin = 12;
left_margin = 44;
width = 8956;
height = 6708;
memset(cblack, 0, sizeof(cblack));
adobe_coeff("Hasselblad", "H4D-60");
strcpy(model, "H4D-60");
black = 512;
}
else
{
height = 6716;
width = 8964;
top_margin = 8;
left_margin = 40;
black += load_flags = 256;
maximum = 0x8101;
strcpy(model, "H3DII-60");
}
}
else if (raw_width == 4090)
{
strcpy(model, "V96C");
height -= (top_margin = 6);
width -= (left_margin = 3) + 7;
filters = 0x61616161;
}
else if (raw_width == 8282 && raw_height == 6240)
{
if (!strncasecmp(model, "H5D", 3))
{
/* H5D 50*/
left_margin = 54;
top_margin = 16;
width = 8176;
height = 6132;
black = 256;
strcpy(model, "H5D-50");
}
else if (!strncasecmp(model, "H3D", 3))
{
black = 0;
left_margin = 54;
top_margin = 16;
width = 8176;
height = 6132;
memset(cblack, 0, sizeof(cblack));
adobe_coeff("Hasselblad", "H3D-50");
strcpy(model, "H3D-50");
}
}
else if (raw_width == 8374 && raw_height == 6304)
{
/* H5D 50c*/
left_margin = 52;
top_margin = 100;
width = 8272;
height = 6200;
black = 256;
strcpy(model, "H5D-50c");
}
if (tiff_samples > 1)
{
is_raw = tiff_samples + 1;
if (!shot_select && !half_size)
filters = 0;
}
}
else if (!strncmp(make, "Sinar", 5))
{
if (!load_raw)
load_raw = &CLASS unpacked_load_raw;
if (is_raw > 1 && !shot_select && !half_size)
filters = 0;
maximum = 0x3fff;
}
else if (!strncmp(make, "Leaf", 4))
{
maximum = 0x3fff;
fseek(ifp, data_offset, SEEK_SET);
if (ljpeg_start(&jh, 1) && jh.bits == 15)
maximum = 0x1fff;
if (tiff_samples > 1)
filters = 0;
if (tiff_samples > 1 || tile_length < raw_height)
{
load_raw = &CLASS leaf_hdr_load_raw;
raw_width = tile_width;
}
if ((width | height) == 2048)
{
if (tiff_samples == 1)
{
filters = 1;
strcpy(cdesc, "RBTG");
strcpy(model, "CatchLight");
top_margin = 8;
left_margin = 18;
height = 2032;
width = 2016;
}
else
{
strcpy(model, "DCB2");
top_margin = 10;
left_margin = 16;
height = 2028;
width = 2022;
}
}
else if (width + height == 3144 + 2060)
{
if (!model[0])
strcpy(model, "Cantare");
if (width > height)
{
top_margin = 6;
left_margin = 32;
height = 2048;
width = 3072;
filters = 0x61616161;
}
else
{
left_margin = 6;
top_margin = 32;
width = 2048;
height = 3072;
filters = 0x16161616;
}
if (!cam_mul[0] || model[0] == 'V')
filters = 0;
else
is_raw = tiff_samples;
}
else if (width == 2116)
{
strcpy(model, "Valeo 6");
height -= 2 * (top_margin = 30);
width -= 2 * (left_margin = 55);
filters = 0x49494949;
}
else if (width == 3171)
{
strcpy(model, "Valeo 6");
height -= 2 * (top_margin = 24);
width -= 2 * (left_margin = 24);
filters = 0x16161616;
}
}
else if (!strncmp(make, "Leica", 5) || !strncmp(make, "Panasonic", 9) || !strncasecmp(make, "YUNEEC", 6))
{
if (raw_width > 0 && ((flen - data_offset) / (raw_width * 8 / 7) == raw_height))
load_raw = &CLASS panasonic_load_raw;
if (!load_raw)
{
load_raw = &CLASS unpacked_load_raw;
load_flags = 4;
}
zero_is_bad = 1;
if ((height += 12) > raw_height)
height = raw_height;
for (i = 0; i < sizeof pana / sizeof *pana; i++)
if (raw_width == pana[i][0] && raw_height == pana[i][1])
{
left_margin = pana[i][2];
top_margin = pana[i][3];
width += pana[i][4];
height += pana[i][5];
}
filters = 0x01010101U * (uchar) "\x94\x61\x49\x16"[((filters - 1) ^ (left_margin & 1) ^ (top_margin << 1)) & 3];
}
else if (!strcmp(model, "C770UZ"))
{
height = 1718;
width = 2304;
filters = 0x16161616;
load_raw = &CLASS packed_load_raw;
load_flags = 30;
}
else if (!strncmp(make, "Olympus", 7))
{
height += height & 1;
if (exif_cfa)
filters = exif_cfa;
if (width == 4100)
width -= 4;
if (width == 4080)
width -= 24;
if (width == 9280)
{
width -= 6;
height -= 6;
}
if (load_raw == &CLASS unpacked_load_raw)
load_flags = 4;
tiff_bps = 12;
if (!strcmp(model, "E-300") || !strcmp(model, "E-500"))
{
width -= 20;
if (load_raw == &CLASS unpacked_load_raw)
{
maximum = 0xfc3;
memset(cblack, 0, sizeof cblack);
}
}
else if (!strcmp(model, "STYLUS1"))
{
width -= 14;
maximum = 0xfff;
}
else if (!strcmp(model, "E-330"))
{
width -= 30;
if (load_raw == &CLASS unpacked_load_raw)
maximum = 0xf79;
}
else if (!strcmp(model, "SP550UZ"))
{
thumb_length = flen - (thumb_offset = 0xa39800);
thumb_height = 480;
thumb_width = 640;
}
else if (!strcmp(model, "TG-4"))
{
width -= 16;
}
else if (!strcmp(model, "TG-5"))
{
width -= 26;
}
}
else if (!strcmp(model, "N Digital"))
{
height = 2047;
width = 3072;
filters = 0x61616161;
data_offset = 0x1a00;
load_raw = &CLASS packed_load_raw;
}
else if (!strcmp(model, "DSC-F828"))
{
width = 3288;
left_margin = 5;
mask[1][3] = -17;
data_offset = 862144;
load_raw = &CLASS sony_load_raw;
filters = 0x9c9c9c9c;
colors = 4;
strcpy(cdesc, "RGBE");
}
else if (!strcmp(model, "DSC-V3"))
{
width = 3109;
left_margin = 59;
mask[0][1] = 9;
data_offset = 787392;
load_raw = &CLASS sony_load_raw;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 3984)
{
width = 3925;
order = 0x4d4d;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 4288)
{
width -= 32;
}
else if (!strcmp(make, "Sony") && raw_width == 4600)
{
if (!strcmp(model, "DSLR-A350"))
height -= 4;
black = 0;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 4928)
{
if (height < 3280)
width -= 8;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 5504)
{ // ILCE-3000//5000
width -= height > 3664 ? 8 : 32;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 6048)
{
width -= 24;
if (strstr(model, "RX1") || strstr(model, "A99"))
width -= 6;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 7392)
{
width -= 30;
}
else if (!strncmp(make, "Sony", 4) && raw_width == 8000)
{
width -= 32;
}
else if (!strcmp(model, "DSLR-A100"))
{
if (width == 3880)
{
height--;
width = ++raw_width;
}
else
{
height -= 4;
width -= 4;
order = 0x4d4d;
load_flags = 2;
}
filters = 0x61616161;
}
else if (!strcmp(model, "PIXL"))
{
height -= top_margin = 4;
width -= left_margin = 32;
gamma_curve(0, 7, 1, 255);
}
else if (!strcmp(model, "C603") || !strcmp(model, "C330") || !strcmp(model, "12MP"))
{
order = 0x4949;
if (filters && data_offset)
{
fseek(ifp, data_offset < 4096 ? 168 : 5252, SEEK_SET);
read_shorts(curve, 256);
}
else
gamma_curve(0, 3.875, 1, 255);
load_raw = filters ? &CLASS eight_bit_load_raw
: strcmp(model, "C330") ? &CLASS kodak_c603_load_raw : &CLASS kodak_c330_load_raw;
load_flags = tiff_bps > 16;
tiff_bps = 8;
}
else if (!strncasecmp(model, "EasyShare", 9))
{
data_offset = data_offset < 0x15000 ? 0x15000 : 0x17000;
load_raw = &CLASS packed_load_raw;
}
else if (!strncasecmp(make, "Kodak", 5))
{
if (filters == UINT_MAX)
filters = 0x61616161;
if (!strncmp(model, "NC2000", 6) || !strncmp(model, "EOSDCS", 6) || !strncmp(model, "DCS4", 4))
{
width -= 4;
left_margin = 2;
if (model[6] == ' ')
model[6] = 0;
if (!strcmp(model, "DCS460A"))
goto bw;
}
else if (!strcmp(model, "DCS660M"))
{
black = 214;
goto bw;
}
else if (!strcmp(model, "DCS760M"))
{
bw:
colors = 1;
filters = 0;
}
if (!strcmp(model + 4, "20X"))
strcpy(cdesc, "MYCY");
if (strstr(model, "DC25"))
{
strcpy(model, "DC25");
data_offset = 15424;
}
if (!strncmp(model, "DC2", 3))
{
raw_height = 2 + (height = 242);
if (!strncmp(model, "DC290", 5))
iso_speed = 100;
if (!strncmp(model, "DC280", 5))
iso_speed = 70;
if (flen < 100000)
{
raw_width = 256;
width = 249;
pixel_aspect = (4.0 * height) / (3.0 * width);
}
else
{
raw_width = 512;
width = 501;
pixel_aspect = (493.0 * height) / (373.0 * width);
}
top_margin = left_margin = 1;
colors = 4;
filters = 0x8d8d8d8d;
simple_coeff(1);
pre_mul[1] = 1.179;
pre_mul[2] = 1.209;
pre_mul[3] = 1.036;
load_raw = &CLASS eight_bit_load_raw;
}
else if (!strcmp(model, "40"))
{
strcpy(model, "DC40");
height = 512;
width = 768;
data_offset = 1152;
load_raw = &CLASS kodak_radc_load_raw;
tiff_bps = 12;
}
else if (strstr(model, "DC50"))
{
strcpy(model, "DC50");
height = 512;
width = 768;
iso_speed = 84;
data_offset = 19712;
load_raw = &CLASS kodak_radc_load_raw;
}
else if (strstr(model, "DC120"))
{
strcpy(model, "DC120");
raw_height = height = 976;
raw_width = width = 848;
iso_speed = 160;
pixel_aspect = height / 0.75 / width;
load_raw = tiff_compress == 7 ? &CLASS kodak_jpeg_load_raw : &CLASS kodak_dc120_load_raw;
}
else if (!strcmp(model, "DCS200"))
{
thumb_height = 128;
thumb_width = 192;
thumb_offset = 6144;
thumb_misc = 360;
iso_speed = 140;
write_thumb = &CLASS layer_thumb;
black = 17;
}
}
else if (!strcmp(model, "Fotoman Pixtura"))
{
height = 512;
width = 768;
data_offset = 3632;
load_raw = &CLASS kodak_radc_load_raw;
filters = 0x61616161;
simple_coeff(2);
}
else if (!strncmp(model, "QuickTake", 9))
{
if (head[5])
strcpy(model + 10, "200");
fseek(ifp, 544, SEEK_SET);
height = get2();
width = get2();
data_offset = (get4(), get2()) == 30 ? 738 : 736;
if (height > width)
{
SWAP(height, width);
fseek(ifp, data_offset - 6, SEEK_SET);
flip = ~get2() & 3 ? 5 : 6;
}
filters = 0x61616161;
}
else if (!strncmp(make, "Rollei", 6) && !load_raw)
{
switch (raw_width)
{
case 1316:
height = 1030;
width = 1300;
top_margin = 1;
left_margin = 6;
break;
case 2568:
height = 1960;
width = 2560;
top_margin = 2;
left_margin = 8;
}
filters = 0x16161616;
load_raw = &CLASS rollei_load_raw;
}
else if (!strcmp(model, "GRAS-50S5C"))
{
height = 2048;
width = 2440;
load_raw = &CLASS unpacked_load_raw;
data_offset = 0;
filters = 0x49494949;
order = 0x4949;
maximum = 0xfffC;
}
else if (!strcmp(model, "BB-500CL"))
{
height = 2058;
width = 2448;
load_raw = &CLASS unpacked_load_raw;
data_offset = 0;
filters = 0x94949494;
order = 0x4949;
maximum = 0x3fff;
}
else if (!strcmp(model, "BB-500GE"))
{
height = 2058;
width = 2456;
load_raw = &CLASS unpacked_load_raw;
data_offset = 0;
filters = 0x94949494;
order = 0x4949;
maximum = 0x3fff;
}
else if (!strcmp(model, "SVS625CL"))
{
height = 2050;
width = 2448;
load_raw = &CLASS unpacked_load_raw;
data_offset = 0;
filters = 0x94949494;
order = 0x4949;
maximum = 0x0fff;
}
/* Early reject for damaged images */
if (!load_raw || height < 22 || width < 22 ||
#ifdef LIBRAW_LIBRARY_BUILD
(tiff_bps > 16 && load_raw != &LibRaw::deflate_dng_load_raw)
#else
tiff_bps > 16
#endif
|| tiff_samples > 4 || colors > 4 || colors < 1
/* alloc in unpack() may be fooled by size adjust */
|| ((int)width + (int)left_margin > 65535) || ((int)height + (int)top_margin > 65535))
{
is_raw = 0;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_IDENTIFY, 1, 2);
#endif
return;
}
if (!model[0])
sprintf(model, "%dx%d", width, height);
if (filters == UINT_MAX)
filters = 0x94949494;
if (thumb_offset && !thumb_height)
{
fseek(ifp, thumb_offset, SEEK_SET);
if (ljpeg_start(&jh, 1))
{
thumb_width = jh.wide;
thumb_height = jh.high;
}
}
dng_skip:
#ifdef LIBRAW_LIBRARY_BUILD
if (dng_version) /* Override black level by DNG tags */
{
/* copy DNG data from per-IFD field to color.dng */
int iifd = 0; // Active IFD we'll show to user.
for (; iifd < tiff_nifds; iifd++)
if (tiff_ifd[iifd].offset == data_offset) // found
break;
int pifd = -1;
for (int ii = 0; ii < tiff_nifds; ii++)
if (tiff_ifd[ii].offset == thumb_offset) // found
{
pifd = ii;
break;
}
#define CFAROUND(value, filters) filters ? (filters >= 1000 ? ((value + 1) / 2) * 2 : ((value + 5) / 6) * 6) : value
#define IFDCOLORINDEX(ifd, subset, bit) \
(tiff_ifd[ifd].dng_color[subset].parsedfields & bit) ? ifd \
: ((tiff_ifd[0].dng_color[subset].parsedfields & bit) ? 0 : -1)
#define IFDLEVELINDEX(ifd, bit) \
(tiff_ifd[ifd].dng_levels.parsedfields & bit) ? ifd : ((tiff_ifd[0].dng_levels.parsedfields & bit) ? 0 : -1)
#define COPYARR(to, from) memmove(&to, &from, sizeof(from))
if (iifd < tiff_nifds)
{
int sidx;
// Per field, not per structure
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_CHECK_DNG_ILLUMINANT)
{
int illidx[2], cmidx[2],calidx[2], abidx;
for(int i = 0; i < 2; i++)
{
illidx[i] = IFDCOLORINDEX(iifd, i, LIBRAW_DNGFM_ILLUMINANT);
cmidx[i] = IFDCOLORINDEX(iifd, i, LIBRAW_DNGFM_COLORMATRIX);
calidx[i] = IFDCOLORINDEX(iifd, i, LIBRAW_DNGFM_CALIBRATION);
}
abidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_ANALOGBALANCE);
// Data found, all in same ifd, illuminants are inited
if (illidx[0] >= 0 && illidx[0] < tiff_nifds && illidx[0] == illidx[1] && illidx[0] == cmidx[0] && illidx[0] == cmidx[1]
&& tiff_ifd[illidx[0]].dng_color[0].illuminant>0 && tiff_ifd[illidx[0]].dng_color[1].illuminant>0)
{
sidx = illidx[0]; // => selected IFD
double cc[4][4], cm[4][3], cam_xyz[4][3];
// CM -> Color Matrix
// CC -> Camera calibration
for (int j = 0; j < 4; j++) for (int i = 0; i < 4; i++) cc[j][i] = i == j;
int colidx = -1;
// IS D65 here?
for(int i = 0; i < 2; i++)
{
int ill = tiff_ifd[sidx].dng_color[i].illuminant;
if (tiff_ifd[sidx].dng_color[i].illuminant == LIBRAW_WBI_D65)
{
colidx = i; break;
}
}
// Other daylight-type ill
if(colidx<0)
for(int i = 0; i < 2; i++)
{
int ill = tiff_ifd[sidx].dng_color[i].illuminant;
if (ill == LIBRAW_WBI_Daylight || ill == LIBRAW_WBI_D55 || ill == LIBRAW_WBI_D75 || ill == LIBRAW_WBI_D50 || ill == LIBRAW_WBI_Flash)
{
colidx = i; break;
}
}
if(colidx>=0) // Selected
{
// Init camera matrix from DNG
FORCC for (int j = 0; j < 3; j++)
cm[c][j] = tiff_ifd[sidx].dng_color[colidx].colormatrix[c][j];
if(calidx[colidx] == sidx)
{
for (int i = 0; i < colors; i++)
FORCC
cc[i][c] = tiff_ifd[sidx].dng_color[colidx].calibration[i][c];
}
if(abidx == sidx)
for (int i = 0; i < colors; i++)
FORCC cc[i][c] *= tiff_ifd[sidx].dng_levels.analogbalance[i];
int j;
FORCC for (int i = 0; i < 3; i++) for (cam_xyz[c][i] = j = 0; j < colors; j++) cam_xyz[c][i] +=
cc[c][j] * cm[j][i];// add AsShotXY later * xyz[i];
cam_xyz_coeff(cmatrix, cam_xyz);
}
}
}
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_USE_DNG_DEFAULT_CROP)
{
sidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_CROPORIGIN);
int sidx2 = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_CROPSIZE);
if (sidx >= 0 && sidx == sidx2 && tiff_ifd[sidx].dng_levels.default_crop[2] > 0 &&
tiff_ifd[sidx].dng_levels.default_crop[3] > 0)
{
int lm = tiff_ifd[sidx].dng_levels.default_crop[0];
int lmm = CFAROUND(lm, filters);
int tm = tiff_ifd[sidx].dng_levels.default_crop[1];
int tmm = CFAROUND(tm, filters);
int ww = tiff_ifd[sidx].dng_levels.default_crop[2];
int hh = tiff_ifd[sidx].dng_levels.default_crop[3];
if (lmm > lm)
ww -= (lmm - lm);
if (tmm > tm)
hh -= (tmm - tm);
if (left_margin + lm + ww <= raw_width && top_margin + tm + hh <= raw_height)
{
left_margin += lmm;
top_margin += tmm;
width = ww;
height = hh;
}
}
}
if (!(imgdata.color.dng_color[0].parsedfields & LIBRAW_DNGFM_FORWARDMATRIX)) // Not set already (Leica makernotes)
{
sidx = IFDCOLORINDEX(iifd, 0, LIBRAW_DNGFM_FORWARDMATRIX);
if (sidx >= 0)
COPYARR(imgdata.color.dng_color[0].forwardmatrix, tiff_ifd[sidx].dng_color[0].forwardmatrix);
}
if (!(imgdata.color.dng_color[1].parsedfields & LIBRAW_DNGFM_FORWARDMATRIX)) // Not set already (Leica makernotes)
{
sidx = IFDCOLORINDEX(iifd, 1, LIBRAW_DNGFM_FORWARDMATRIX);
if (sidx >= 0)
COPYARR(imgdata.color.dng_color[1].forwardmatrix, tiff_ifd[sidx].dng_color[1].forwardmatrix);
}
for (int ss = 0; ss < 2; ss++)
{
sidx = IFDCOLORINDEX(iifd, ss, LIBRAW_DNGFM_COLORMATRIX);
if (sidx >= 0)
COPYARR(imgdata.color.dng_color[ss].colormatrix, tiff_ifd[sidx].dng_color[ss].colormatrix);
sidx = IFDCOLORINDEX(iifd, ss, LIBRAW_DNGFM_CALIBRATION);
if (sidx >= 0)
COPYARR(imgdata.color.dng_color[ss].calibration, tiff_ifd[sidx].dng_color[ss].calibration);
sidx = IFDCOLORINDEX(iifd, ss, LIBRAW_DNGFM_ILLUMINANT);
if (sidx >= 0)
imgdata.color.dng_color[ss].illuminant = tiff_ifd[sidx].dng_color[ss].illuminant;
}
// Levels
sidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_ANALOGBALANCE);
if (sidx >= 0)
COPYARR(imgdata.color.dng_levels.analogbalance, tiff_ifd[sidx].dng_levels.analogbalance);
sidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_WHITE);
if (sidx >= 0)
COPYARR(imgdata.color.dng_levels.dng_whitelevel, tiff_ifd[sidx].dng_levels.dng_whitelevel);
sidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_BLACK);
if (sidx >= 0)
{
imgdata.color.dng_levels.dng_black = tiff_ifd[sidx].dng_levels.dng_black;
COPYARR(imgdata.color.dng_levels.dng_cblack, tiff_ifd[sidx].dng_levels.dng_cblack);
}
if (pifd >= 0)
{
sidx = IFDLEVELINDEX(pifd, LIBRAW_DNGFM_PREVIEWCS);
if (sidx >= 0)
imgdata.color.dng_levels.preview_colorspace = tiff_ifd[sidx].dng_levels.preview_colorspace;
}
sidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_OPCODE2);
if (sidx >= 0)
meta_offset = tiff_ifd[sidx].opcode2_offset;
sidx = IFDLEVELINDEX(iifd, LIBRAW_DNGFM_LINTABLE);
INT64 linoff = -1;
int linlen = 0;
if (sidx >= 0)
{
linoff = tiff_ifd[sidx].lineartable_offset;
linlen = tiff_ifd[sidx].lineartable_len;
}
if (linoff >= 0 && linlen > 0)
{
INT64 pos = ftell(ifp);
fseek(ifp, linoff, SEEK_SET);
linear_table(linlen);
fseek(ifp, pos, SEEK_SET);
}
// Need to add curve too
}
/* Copy DNG black level to LibRaw's */
maximum = imgdata.color.dng_levels.dng_whitelevel[0];
black = imgdata.color.dng_levels.dng_black;
int ll = LIM(0, (sizeof(cblack) / sizeof(cblack[0])),
(sizeof(imgdata.color.dng_levels.dng_cblack) / sizeof(imgdata.color.dng_levels.dng_cblack[0])));
for (int i = 0; i < ll; i++)
cblack[i] = imgdata.color.dng_levels.dng_cblack[i];
}
#endif
/* Early reject for damaged images */
if (!load_raw || height < 22 || width < 22 ||
#ifdef LIBRAW_LIBRARY_BUILD
(tiff_bps > 16 && load_raw != &LibRaw::deflate_dng_load_raw)
#else
tiff_bps > 16
#endif
|| tiff_samples > 4 || colors > 4 || colors < 1)
{
is_raw = 0;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_IDENTIFY, 1, 2);
#endif
return;
}
{
// Check cam_mul range
int cmul_ok =1;
FORCC if(cam_mul[c] <= 0.001f) cmul_ok = 0;;
if(cmul_ok)
{
double cmin = cam_mul[0],cmax;
double cnorm[4];
FORCC cmin = MIN(cmin,cam_mul[c]);
FORCC cnorm[c] = cam_mul[c]/cmin;
cmax = cmin = cnorm[0];
FORCC
{
cmin = MIN(cmin,cnorm[c]);
cmax = MIN(cmax,cnorm[c]);
}
if(cmin <= 0.01f || cmax > 100.f)
cmul_ok = false;
}
if(!cmul_ok)
cam_mul[0] = cam_mul[3] = 0;
}
if ((use_camera_matrix & ((use_camera_wb || dng_version) | 0x2)) && cmatrix[0][0] > 0.125)
{
memcpy(rgb_cam, cmatrix, sizeof cmatrix);
raw_color = 0;
}
if (raw_color)
adobe_coeff(make, model);
#ifdef LIBRAW_LIBRARY_BUILD
else if (imgdata.color.cam_xyz[0][0] < 0.01)
adobe_coeff(make, model, 1);
#endif
if (load_raw == &CLASS kodak_radc_load_raw)
if (raw_color)
adobe_coeff("Apple", "Quicktake");
#ifdef LIBRAW_LIBRARY_BUILD
// Clear erorneus fuji_width if not set through parse_fuji or for DNG
if (fuji_width && !dng_version && !(imgdata.process_warnings & LIBRAW_WARN_PARSEFUJI_PROCESSED))
fuji_width = 0;
#endif
if (fuji_width)
{
fuji_width = width >> !fuji_layout;
filters = fuji_width & 1 ? 0x94949494 : 0x49494949;
width = (height >> fuji_layout) + fuji_width;
height = width - 1;
pixel_aspect = 1;
}
else
{
if (raw_height < height)
raw_height = height;
if (raw_width < width)
raw_width = width;
}
if (!tiff_bps)
tiff_bps = 12;
if (!maximum)
{
maximum = (1 << tiff_bps) - 1;
if (maximum < 0x10000 && curve[maximum] > 0 && load_raw == &CLASS sony_arw2_load_raw)
maximum = curve[maximum];
}
if (!load_raw || height < 22 || width < 22 ||
#ifdef LIBRAW_LIBRARY_BUILD
(tiff_bps > 16 && load_raw != &LibRaw::deflate_dng_load_raw)
#else
tiff_bps > 16
#endif
|| tiff_samples > 6 || colors > 4)
is_raw = 0;
if (raw_width < 22 || raw_width > 64000 || raw_height < 22 || raw_height > 64000)
is_raw = 0;
#ifdef NO_JASPER
if (load_raw == &CLASS redcine_load_raw)
{
#ifdef DCRAW_VERBOSE
fprintf(stderr, _("%s: You must link dcraw with %s!!\n"), ifname, "libjasper");
#endif
is_raw = 0;
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.process_warnings |= LIBRAW_WARN_NO_JASPER;
#endif
}
#endif
#ifdef NO_JPEG
if (load_raw == &CLASS kodak_jpeg_load_raw || load_raw == &CLASS lossy_dng_load_raw)
{
#ifdef DCRAW_VERBOSE
fprintf(stderr, _("%s: You must link dcraw with %s!!\n"), ifname, "libjpeg");
#endif
is_raw = 0;
#ifdef LIBRAW_LIBRARY_BUILD
imgdata.process_warnings |= LIBRAW_WARN_NO_JPEGLIB;
#endif
}
#endif
if (!cdesc[0])
strcpy(cdesc, colors == 3 ? "RGBG" : "GMCY");
if (!raw_height)
raw_height = height;
if (!raw_width)
raw_width = width;
if (filters > 999 && colors == 3)
filters |= ((filters >> 2 & 0x22222222) | (filters << 2 & 0x88888888)) & filters << 1;
notraw:
if (flip == UINT_MAX)
flip = tiff_flip;
if (flip == UINT_MAX)
flip = 0;
// Convert from degrees to bit-field if needed
if (flip > 89 || flip < -89)
{
switch ((flip + 3600) % 360)
{
case 270:
flip = 5;
break;
case 180:
flip = 3;
break;
case 90:
flip = 6;
break;
}
}
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_IDENTIFY, 1, 2);
#endif
}
void CLASS convert_to_rgb()
{
#ifndef LIBRAW_LIBRARY_BUILD
int row, col, c;
#endif
int i, j, k;
#ifndef LIBRAW_LIBRARY_BUILD
ushort *img;
float out[3];
#endif
float out_cam[3][4];
double num, inverse[3][3];
static const double xyzd50_srgb[3][3] = {
{0.436083, 0.385083, 0.143055}, {0.222507, 0.716888, 0.060608}, {0.013930, 0.097097, 0.714022}};
static const double rgb_rgb[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
static const double adobe_rgb[3][3] = {
{0.715146, 0.284856, 0.000000}, {0.000000, 1.000000, 0.000000}, {0.000000, 0.041166, 0.958839}};
static const double wide_rgb[3][3] = {
{0.593087, 0.404710, 0.002206}, {0.095413, 0.843149, 0.061439}, {0.011621, 0.069091, 0.919288}};
static const double prophoto_rgb[3][3] = {
{0.529317, 0.330092, 0.140588}, {0.098368, 0.873465, 0.028169}, {0.016879, 0.117663, 0.865457}};
static const double aces_rgb[3][3] = {
{0.432996, 0.375380, 0.189317}, {0.089427, 0.816523, 0.102989}, {0.019165, 0.118150, 0.941914}};
static const double(*out_rgb[])[3] = {rgb_rgb, adobe_rgb, wide_rgb, prophoto_rgb, xyz_rgb, aces_rgb};
static const char *name[] = {"sRGB", "Adobe RGB (1998)", "WideGamut D65", "ProPhoto D65", "XYZ", "ACES"};
static const unsigned phead[] = {1024, 0, 0x2100000, 0x6d6e7472, 0x52474220, 0x58595a20, 0,
0, 0, 0x61637370, 0, 0, 0x6e6f6e65, 0,
0, 0, 0, 0xf6d6, 0x10000, 0xd32d};
unsigned pbody[] = {10, 0x63707274, 0, 36, /* cprt */
0x64657363, 0, 40, /* desc */
0x77747074, 0, 20, /* wtpt */
0x626b7074, 0, 20, /* bkpt */
0x72545243, 0, 14, /* rTRC */
0x67545243, 0, 14, /* gTRC */
0x62545243, 0, 14, /* bTRC */
0x7258595a, 0, 20, /* rXYZ */
0x6758595a, 0, 20, /* gXYZ */
0x6258595a, 0, 20}; /* bXYZ */
static const unsigned pwhite[] = {0xf351, 0x10000, 0x116cc};
unsigned pcurve[] = {0x63757276, 0, 1, 0x1000000};
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_CONVERT_RGB, 0, 2);
#endif
gamma_curve(gamm[0], gamm[1], 0, 0);
memcpy(out_cam, rgb_cam, sizeof out_cam);
#ifndef LIBRAW_LIBRARY_BUILD
raw_color |= colors == 1 || document_mode || output_color < 1 || output_color > 6;
#else
raw_color |= colors == 1 || output_color < 1 || output_color > 6;
#endif
if (!raw_color)
{
oprof = (unsigned *)calloc(phead[0], 1);
merror(oprof, "convert_to_rgb()");
memcpy(oprof, phead, sizeof phead);
if (output_color == 5)
oprof[4] = oprof[5];
oprof[0] = 132 + 12 * pbody[0];
for (i = 0; i < pbody[0]; i++)
{
oprof[oprof[0] / 4] = i ? (i > 1 ? 0x58595a20 : 0x64657363) : 0x74657874;
pbody[i * 3 + 2] = oprof[0];
oprof[0] += (pbody[i * 3 + 3] + 3) & -4;
}
memcpy(oprof + 32, pbody, sizeof pbody);
oprof[pbody[5] / 4 + 2] = strlen(name[output_color - 1]) + 1;
memcpy((char *)oprof + pbody[8] + 8, pwhite, sizeof pwhite);
pcurve[3] = (short)(256 / gamm[5] + 0.5) << 16;
for (i = 4; i < 7; i++)
memcpy((char *)oprof + pbody[i * 3 + 2], pcurve, sizeof pcurve);
pseudoinverse((double(*)[3])out_rgb[output_color - 1], inverse, 3);
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
{
for (num = k = 0; k < 3; k++)
num += xyzd50_srgb[i][k] * inverse[j][k];
oprof[pbody[j * 3 + 23] / 4 + i + 2] = num * 0x10000 + 0.5;
}
for (i = 0; i < phead[0] / 4; i++)
oprof[i] = htonl(oprof[i]);
strcpy((char *)oprof + pbody[2] + 8, "auto-generated by dcraw");
strcpy((char *)oprof + pbody[5] + 12, name[output_color - 1]);
for (i = 0; i < 3; i++)
for (j = 0; j < colors; j++)
for (out_cam[i][j] = k = 0; k < 3; k++)
out_cam[i][j] += out_rgb[output_color - 1][i][k] * rgb_cam[k][j];
}
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, raw_color ? _("Building histograms...\n") : _("Converting to %s colorspace...\n"),
name[output_color - 1]);
#endif
#ifdef LIBRAW_LIBRARY_BUILD
convert_to_rgb_loop(out_cam);
#else
memset(histogram, 0, sizeof histogram);
for (img = image[0], row = 0; row < height; row++)
for (col = 0; col < width; col++, img += 4)
{
if (!raw_color)
{
out[0] = out[1] = out[2] = 0;
FORCC
{
out[0] += out_cam[0][c] * img[c];
out[1] += out_cam[1][c] * img[c];
out[2] += out_cam[2][c] * img[c];
}
FORC3 img[c] = CLIP((int)out[c]);
}
else if (document_mode)
img[0] = img[fcol(row, col)];
FORCC histogram[c][img[c] >> 3]++;
}
#endif
if (colors == 4 && output_color)
colors = 3;
#ifndef LIBRAW_LIBRARY_BUILD
if (document_mode && filters)
colors = 1;
#endif
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_CONVERT_RGB, 1, 2);
#endif
}
void CLASS fuji_rotate()
{
int i, row, col;
double step;
float r, c, fr, fc;
unsigned ur, uc;
ushort wide, high, (*img)[4], (*pix)[4];
if (!fuji_width)
return;
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Rotating image 45 degrees...\n"));
#endif
fuji_width = (fuji_width - 1 + shrink) >> shrink;
step = sqrt(0.5);
wide = fuji_width / step;
high = (height - fuji_width) / step;
img = (ushort(*)[4])calloc(high, wide * sizeof *img);
merror(img, "fuji_rotate()");
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_FUJI_ROTATE, 0, 2);
#endif
for (row = 0; row < high; row++)
for (col = 0; col < wide; col++)
{
ur = r = fuji_width + (row - col) * step;
uc = c = (row + col) * step;
if (ur > height - 2 || uc > width - 2)
continue;
fr = r - ur;
fc = c - uc;
pix = image + ur * width + uc;
for (i = 0; i < colors; i++)
img[row * wide + col][i] = (pix[0][i] * (1 - fc) + pix[1][i] * fc) * (1 - fr) +
(pix[width][i] * (1 - fc) + pix[width + 1][i] * fc) * fr;
}
free(image);
width = wide;
height = high;
image = img;
fuji_width = 0;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_FUJI_ROTATE, 1, 2);
#endif
}
void CLASS stretch()
{
ushort newdim, (*img)[4], *pix0, *pix1;
int row, col, c;
double rc, frac;
if (pixel_aspect == 1)
return;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_STRETCH, 0, 2);
#endif
#ifdef DCRAW_VERBOSE
if (verbose)
fprintf(stderr, _("Stretching the image...\n"));
#endif
if (pixel_aspect < 1)
{
newdim = height / pixel_aspect + 0.5;
img = (ushort(*)[4])calloc(width, newdim * sizeof *img);
merror(img, "stretch()");
for (rc = row = 0; row < newdim; row++, rc += pixel_aspect)
{
frac = rc - (c = rc);
pix0 = pix1 = image[c * width];
if (c + 1 < height)
pix1 += width * 4;
for (col = 0; col < width; col++, pix0 += 4, pix1 += 4)
FORCC img[row * width + col][c] = pix0[c] * (1 - frac) + pix1[c] * frac + 0.5;
}
height = newdim;
}
else
{
newdim = width * pixel_aspect + 0.5;
img = (ushort(*)[4])calloc(height, newdim * sizeof *img);
merror(img, "stretch()");
for (rc = col = 0; col < newdim; col++, rc += 1 / pixel_aspect)
{
frac = rc - (c = rc);
pix0 = pix1 = image[c];
if (c + 1 < width)
pix1 += 4;
for (row = 0; row < height; row++, pix0 += width * 4, pix1 += width * 4)
FORCC img[row * newdim + col][c] = pix0[c] * (1 - frac) + pix1[c] * frac + 0.5;
}
width = newdim;
}
free(image);
image = img;
#ifdef LIBRAW_LIBRARY_BUILD
RUN_CALLBACK(LIBRAW_PROGRESS_STRETCH, 1, 2);
#endif
}
int CLASS flip_index(int row, int col)
{
if (flip & 4)
SWAP(row, col);
if (flip & 2)
row = iheight - 1 - row;
if (flip & 1)
col = iwidth - 1 - col;
return row * iwidth + col;
}
void CLASS tiff_set(struct tiff_hdr *th, ushort *ntag, ushort tag, ushort type, int count, int val)
{
struct libraw_tiff_tag *tt;
int c;
tt = (struct libraw_tiff_tag *)(ntag + 1) + (*ntag)++;
tt->val.i = val;
if (type == 1 && count <= 4)
FORC(4) tt->val.c[c] = val >> (c << 3);
else if (type == 2)
{
count = strnlen((char *)th + val, count - 1) + 1;
if (count <= 4)
FORC(4) tt->val.c[c] = ((char *)th)[val + c];
}
else if (type == 3 && count <= 2)
FORC(2) tt->val.s[c] = val >> (c << 4);
tt->count = count;
tt->type = type;
tt->tag = tag;
}
#define TOFF(ptr) ((char *)(&(ptr)) - (char *)th)
void CLASS tiff_head(struct tiff_hdr *th, int full)
{
int c, psize = 0;
struct tm *t;
memset(th, 0, sizeof *th);
th->t_order = htonl(0x4d4d4949) >> 16;
th->magic = 42;
th->ifd = 10;
th->rat[0] = th->rat[2] = 300;
th->rat[1] = th->rat[3] = 1;
FORC(6) th->rat[4 + c] = 1000000;
th->rat[4] *= shutter;
th->rat[6] *= aperture;
th->rat[8] *= focal_len;
strncpy(th->t_desc, desc, 512);
strncpy(th->t_make, make, 64);
strncpy(th->t_model, model, 64);
strcpy(th->soft, "dcraw v" DCRAW_VERSION);
t = localtime(&timestamp);
sprintf(th->date, "%04d:%02d:%02d %02d:%02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour,
t->tm_min, t->tm_sec);
strncpy(th->t_artist, artist, 64);
if (full)
{
tiff_set(th, &th->ntag, 254, 4, 1, 0);
tiff_set(th, &th->ntag, 256, 4, 1, width);
tiff_set(th, &th->ntag, 257, 4, 1, height);
tiff_set(th, &th->ntag, 258, 3, colors, output_bps);
if (colors > 2)
th->tag[th->ntag - 1].val.i = TOFF(th->bps);
FORC4 th->bps[c] = output_bps;
tiff_set(th, &th->ntag, 259, 3, 1, 1);
tiff_set(th, &th->ntag, 262, 3, 1, 1 + (colors > 1));
}
tiff_set(th, &th->ntag, 270, 2, 512, TOFF(th->t_desc));
tiff_set(th, &th->ntag, 271, 2, 64, TOFF(th->t_make));
tiff_set(th, &th->ntag, 272, 2, 64, TOFF(th->t_model));
if (full)
{
if (oprof)
psize = ntohl(oprof[0]);
tiff_set(th, &th->ntag, 273, 4, 1, sizeof *th + psize);
tiff_set(th, &th->ntag, 277, 3, 1, colors);
tiff_set(th, &th->ntag, 278, 4, 1, height);
tiff_set(th, &th->ntag, 279, 4, 1, height * width * colors * output_bps / 8);
}
else
tiff_set(th, &th->ntag, 274, 3, 1, "12435867"[flip] - '0');
tiff_set(th, &th->ntag, 282, 5, 1, TOFF(th->rat[0]));
tiff_set(th, &th->ntag, 283, 5, 1, TOFF(th->rat[2]));
tiff_set(th, &th->ntag, 284, 3, 1, 1);
tiff_set(th, &th->ntag, 296, 3, 1, 2);
tiff_set(th, &th->ntag, 305, 2, 32, TOFF(th->soft));
tiff_set(th, &th->ntag, 306, 2, 20, TOFF(th->date));
tiff_set(th, &th->ntag, 315, 2, 64, TOFF(th->t_artist));
tiff_set(th, &th->ntag, 34665, 4, 1, TOFF(th->nexif));
if (psize)
tiff_set(th, &th->ntag, 34675, 7, psize, sizeof *th);
tiff_set(th, &th->nexif, 33434, 5, 1, TOFF(th->rat[4]));
tiff_set(th, &th->nexif, 33437, 5, 1, TOFF(th->rat[6]));
tiff_set(th, &th->nexif, 34855, 3, 1, iso_speed);
tiff_set(th, &th->nexif, 37386, 5, 1, TOFF(th->rat[8]));
if (gpsdata[1])
{
tiff_set(th, &th->ntag, 34853, 4, 1, TOFF(th->ngps));
tiff_set(th, &th->ngps, 0, 1, 4, 0x202);
tiff_set(th, &th->ngps, 1, 2, 2, gpsdata[29]);
tiff_set(th, &th->ngps, 2, 5, 3, TOFF(th->gps[0]));
tiff_set(th, &th->ngps, 3, 2, 2, gpsdata[30]);
tiff_set(th, &th->ngps, 4, 5, 3, TOFF(th->gps[6]));
tiff_set(th, &th->ngps, 5, 1, 1, gpsdata[31]);
tiff_set(th, &th->ngps, 6, 5, 1, TOFF(th->gps[18]));
tiff_set(th, &th->ngps, 7, 5, 3, TOFF(th->gps[12]));
tiff_set(th, &th->ngps, 18, 2, 12, TOFF(th->gps[20]));
tiff_set(th, &th->ngps, 29, 2, 12, TOFF(th->gps[23]));
memcpy(th->gps, gpsdata, sizeof th->gps);
}
}
#ifdef LIBRAW_LIBRARY_BUILD
void CLASS jpeg_thumb_writer(FILE *tfp, char *t_humb, int t_humb_length)
{
ushort exif[5];
struct tiff_hdr th;
fputc(0xff, tfp);
fputc(0xd8, tfp);
if (strcmp(t_humb + 6, "Exif"))
{
memcpy(exif, "\xff\xe1 Exif\0\0", 10);
exif[1] = htons(8 + sizeof th);
fwrite(exif, 1, sizeof exif, tfp);
tiff_head(&th, 0);
fwrite(&th, 1, sizeof th, tfp);
}
fwrite(t_humb + 2, 1, t_humb_length - 2, tfp);
}
void CLASS jpeg_thumb()
{
char *thumb;
thumb = (char *)malloc(thumb_length);
merror(thumb, "jpeg_thumb()");
fread(thumb, 1, thumb_length, ifp);
jpeg_thumb_writer(ofp, thumb, thumb_length);
free(thumb);
}
#else
void CLASS jpeg_thumb()
{
char *thumb;
ushort exif[5];
struct tiff_hdr th;
thumb = (char *)malloc(thumb_length);
merror(thumb, "jpeg_thumb()");
fread(thumb, 1, thumb_length, ifp);
fputc(0xff, ofp);
fputc(0xd8, ofp);
if (strcmp(thumb + 6, "Exif"))
{
memcpy(exif, "\xff\xe1 Exif\0\0", 10);
exif[1] = htons(8 + sizeof th);
fwrite(exif, 1, sizeof exif, ofp);
tiff_head(&th, 0);
fwrite(&th, 1, sizeof th, ofp);
}
fwrite(thumb + 2, 1, thumb_length - 2, ofp);
free(thumb);
}
#endif
void CLASS write_ppm_tiff()
{
struct tiff_hdr th;
uchar *ppm;
ushort *ppm2;
int c, row, col, soff, rstep, cstep;
int perc, val, total, t_white = 0x2000;
#ifdef LIBRAW_LIBRARY_BUILD
perc = width * height * auto_bright_thr;
#else
perc = width * height * 0.01; /* 99th percentile white level */
#endif
if (fuji_width)
perc /= 2;
if (!((highlight & ~2) || no_auto_bright))
for (t_white = c = 0; c < colors; c++)
{
for (val = 0x2000, total = 0; --val > 32;)
if ((total += histogram[c][val]) > perc)
break;
if (t_white < val)
t_white = val;
}
gamma_curve(gamm[0], gamm[1], 2, (t_white << 3) / bright);
iheight = height;
iwidth = width;
if (flip & 4)
SWAP(height, width);
ppm = (uchar *)calloc(width, colors * output_bps / 8);
ppm2 = (ushort *)ppm;
merror(ppm, "write_ppm_tiff()");
if (output_tiff)
{
tiff_head(&th, 1);
fwrite(&th, sizeof th, 1, ofp);
if (oprof)
fwrite(oprof, ntohl(oprof[0]), 1, ofp);
}
else if (colors > 3)
fprintf(ofp, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH %d\nMAXVAL %d\nTUPLTYPE %s\nENDHDR\n", width, height, colors,
(1 << output_bps) - 1, cdesc);
else
fprintf(ofp, "P%d\n%d %d\n%d\n", colors / 2 + 5, width, height, (1 << output_bps) - 1);
soff = flip_index(0, 0);
cstep = flip_index(0, 1) - soff;
rstep = flip_index(1, 0) - flip_index(0, width);
for (row = 0; row < height; row++, soff += rstep)
{
for (col = 0; col < width; col++, soff += cstep)
if (output_bps == 8)
FORCC ppm[col * colors + c] = curve[image[soff][c]] >> 8;
else
FORCC ppm2[col * colors + c] = curve[image[soff][c]];
if (output_bps == 16 && !output_tiff && htons(0x55aa) != 0x55aa)
swab((char *)ppm2, (char *)ppm2, width * colors * 2);
fwrite(ppm, colors * output_bps / 8, width, ofp);
}
free(ppm);
}
diff --git a/core/libs/rawengine/libraw/internal/libraw_x3f.cpp b/core/libs/rawengine/libraw/internal/libraw_x3f.cpp
index 49918e99d2..3db4630745 100644
--- a/core/libs/rawengine/libraw/internal/libraw_x3f.cpp
+++ b/core/libs/rawengine/libraw/internal/libraw_x3f.cpp
@@ -1,2462 +1,2462 @@
/* Library for accessing X3F Files
----------------------------------------------------------------
BSD-style License
----------------------------------------------------------------
* Copyright (c) 2010, Roland Karlsson (roland@proxel.se)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the organization nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ROLAND KARLSSON ''AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ROLAND KARLSSON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* From X3F_IO.H */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <stdio.h>
#include "../libraw/libraw_datastream.h"
#define SIZE_UNIQUE_IDENTIFIER 16
#define SIZE_WHITE_BALANCE 32
#define SIZE_COLOR_MODE 32
#define NUM_EXT_DATA_2_1 32
#define NUM_EXT_DATA_3_0 64
#define NUM_EXT_DATA NUM_EXT_DATA_3_0
#define X3F_VERSION(MAJ,MIN) (uint32_t)(((MAJ)<<16) + MIN)
#define X3F_VERSION_2_0 X3F_VERSION(2,0)
#define X3F_VERSION_2_1 X3F_VERSION(2,1)
#define X3F_VERSION_2_2 X3F_VERSION(2,2)
#define X3F_VERSION_2_3 X3F_VERSION(2,3)
#define X3F_VERSION_3_0 X3F_VERSION(3,0)
#define X3F_VERSION_4_0 X3F_VERSION(4,0)
/* Main file identifier */
#define X3F_FOVb (uint32_t)(0x62564f46)
/* Directory identifier */
#define X3F_SECd (uint32_t)(0x64434553)
/* Property section identifiers */
#define X3F_PROP (uint32_t)(0x504f5250)
#define X3F_SECp (uint32_t)(0x70434553)
/* Image section identifiers */
#define X3F_IMAG (uint32_t)(0x46414d49)
#define X3F_IMA2 (uint32_t)(0x32414d49)
#define X3F_SECi (uint32_t)(0x69434553)
/* CAMF section identifiers */
#define X3F_CAMF (uint32_t)(0x464d4143)
#define X3F_SECc (uint32_t)(0x63434553)
/* CAMF entry identifiers */
#define X3F_CMbP (uint32_t)(0x50624d43)
#define X3F_CMbT (uint32_t)(0x54624d43)
#define X3F_CMbM (uint32_t)(0x4d624d43)
#define X3F_CMb (uint32_t)(0x00624d43)
/* SDQ section identifiers ? - TODO */
#define X3F_SPPA (uint32_t)(0x41505053)
#define X3F_SECs (uint32_t)(0x73434553)
#define X3F_IMAGE_THUMB_PLAIN (uint32_t)(0x00020003)
#define X3F_IMAGE_THUMB_HUFFMAN (uint32_t)(0x0002000b)
#define X3F_IMAGE_THUMB_JPEG (uint32_t)(0x00020012)
#define X3F_IMAGE_THUMB_SDQ (uint32_t)(0x00020019) /* SDQ ? - TODO */
#define X3F_IMAGE_RAW_HUFFMAN_X530 (uint32_t)(0x00030005)
#define X3F_IMAGE_RAW_HUFFMAN_10BIT (uint32_t)(0x00030006)
#define X3F_IMAGE_RAW_TRUE (uint32_t)(0x0003001e)
#define X3F_IMAGE_RAW_MERRILL (uint32_t)(0x0001001e)
#define X3F_IMAGE_RAW_QUATTRO (uint32_t)(0x00010023)
#define X3F_IMAGE_RAW_SDQ (uint32_t)(0x00010025)
#define X3F_IMAGE_RAW_SDQH (uint32_t)(0x00010027)
#define X3F_IMAGE_RAW_SDQH2 (uint32_t)(0x00010029)
#define X3F_IMAGE_HEADER_SIZE 28
#define X3F_CAMF_HEADER_SIZE 28
#define X3F_PROPERTY_LIST_HEADER_SIZE 24
typedef uint16_t utf16_t;
typedef int bool_t;
typedef enum x3f_extended_types_e {
X3F_EXT_TYPE_NONE=0,
X3F_EXT_TYPE_EXPOSURE_ADJUST=1,
X3F_EXT_TYPE_CONTRAST_ADJUST=2,
X3F_EXT_TYPE_SHADOW_ADJUST=3,
X3F_EXT_TYPE_HIGHLIGHT_ADJUST=4,
X3F_EXT_TYPE_SATURATION_ADJUST=5,
X3F_EXT_TYPE_SHARPNESS_ADJUST=6,
X3F_EXT_TYPE_RED_ADJUST=7,
X3F_EXT_TYPE_GREEN_ADJUST=8,
X3F_EXT_TYPE_BLUE_ADJUST=9,
X3F_EXT_TYPE_FILL_LIGHT_ADJUST=10
} x3f_extended_types_t;
typedef struct x3f_property_s {
/* Read from file */
uint32_t name_offset;
uint32_t value_offset;
/* Computed */
utf16_t *name; /* 0x0000 terminated UTF 16 */
utf16_t *value; /* 0x0000 terminated UTF 16 */
} x3f_property_t;
typedef struct x3f_property_table_s {
uint32_t size;
x3f_property_t *element;
} x3f_property_table_t;
typedef struct x3f_property_list_s {
/* 2.0 Fields */
uint32_t num_properties;
uint32_t character_format;
uint32_t reserved;
uint32_t total_length;
x3f_property_table_t property_table;
void *data;
uint32_t data_size;
} x3f_property_list_t;
typedef struct x3f_table8_s {
uint32_t size;
uint8_t *element;
} x3f_table8_t;
typedef struct x3f_table16_s {
uint32_t size;
uint16_t *element;
} x3f_table16_t;
typedef struct x3f_table32_s {
uint32_t size;
uint32_t *element;
} x3f_table32_t;
typedef struct
{
uint8_t *data; /* Pointer to actual image data */
void *buf; /* Pointer to allocated buffer for free() */
uint32_t rows;
uint32_t columns;
uint32_t channels;
uint32_t row_stride;
} x3f_area8_t;
typedef struct
{
uint16_t *data; /* Pointer to actual image data */
void *buf; /* Pointer to allocated buffer for free() */
uint32_t rows;
uint32_t columns;
uint32_t channels;
uint32_t row_stride;
} x3f_area16_t;
#define UNDEFINED_LEAF 0xffffffff
typedef struct x3f_huffnode_s {
struct x3f_huffnode_s *branch[2];
uint32_t leaf;
} x3f_huffnode_t;
typedef struct x3f_hufftree_s {
uint32_t free_node_index; /* Free node index in huffman tree array */
x3f_huffnode_t *nodes; /* Coding tree */
} x3f_hufftree_t;
typedef struct x3f_true_huffman_element_s {
uint8_t code_size;
uint8_t code;
} x3f_true_huffman_element_t;
typedef struct x3f_true_huffman_s {
uint32_t size;
x3f_true_huffman_element_t *element;
} x3f_true_huffman_t;
/* 0=bottom, 1=middle, 2=top */
#define TRUE_PLANES 3
typedef struct x3f_true_s {
uint16_t seed[TRUE_PLANES]; /* Always 512,512,512 */
uint16_t unknown; /* Always 0 */
x3f_true_huffman_t table; /* Huffman table - zero
terminated. size is the number of
leaves plus 1.*/
x3f_table32_t plane_size; /* Size of the 3 planes */
uint8_t *plane_address[TRUE_PLANES]; /* computed offset to the planes */
x3f_hufftree_t tree; /* Coding tree */
x3f_area16_t x3rgb16; /* 3x16 bit X3-RGB data */
} x3f_true_t;
typedef struct x3f_quattro_s {
struct {
uint16_t columns;
uint16_t rows;
} plane[TRUE_PLANES];
uint32_t unknown;
bool_t quattro_layout;
x3f_area16_t top16; /* Container for the bigger top layer */
} x3f_quattro_t;
typedef struct x3f_huffman_s {
x3f_table16_t mapping; /* Value Mapping = X3F lossy compression */
x3f_table32_t table; /* Coding Table */
x3f_hufftree_t tree; /* Coding tree */
x3f_table32_t row_offsets; /* Row offsets */
x3f_area8_t rgb8; /* 3x8 bit RGB data */
x3f_area16_t x3rgb16; /* 3x16 bit X3-RGB data */
} x3f_huffman_t;
typedef struct x3f_image_data_s {
/* 2.0 Fields */
/* ------------------------------------------------------------------ */
/* Known combinations of type and format are:
1-6, 2-3, 2-11, 2-18, 3-6 */
uint32_t type; /* 1 = RAW X3 (SD1)
2 = thumbnail or maybe just RGB
3 = RAW X3 */
uint32_t format; /* 3 = 3x8 bit pixmap
6 = 3x10 bit huffman with map table
11 = 3x8 bit huffman
18 = JPEG */
uint32_t type_format; /* type<<16 + format */
/* ------------------------------------------------------------------ */
uint32_t columns; /* width / row size in pixels */
uint32_t rows; /* height */
uint32_t row_stride; /* row size in bytes */
/* NULL if not used */
x3f_huffman_t *huffman; /* Huffman help data */
x3f_true_t *tru; /* TRUE help data */
x3f_quattro_t *quattro; /* Quattro help data */
void *data; /* Take from file if NULL. Otherwise,
this is the actual data bytes in
the file. */
uint32_t data_size;
} x3f_image_data_t;
typedef struct camf_dim_entry_s {
uint32_t size;
uint32_t name_offset;
uint32_t n; /* 0,1,2,3... */
char *name;
} camf_dim_entry_t;
typedef enum {M_FLOAT, M_INT, M_UINT} matrix_type_t;
typedef struct camf_entry_s {
/* pointer into decoded data */
void *entry;
/* entry header */
uint32_t id;
uint32_t version;
uint32_t entry_size;
uint32_t name_offset;
uint32_t value_offset;
/* computed values */
char *name_address;
void *value_address;
uint32_t name_size;
uint32_t value_size;
/* extracted values for explicit CAMF entry types*/
uint32_t text_size;
char *text;
uint32_t property_num;
char **property_name;
uint8_t **property_value;
uint32_t matrix_dim;
camf_dim_entry_t *matrix_dim_entry;
/* Offset, pointer and size and type of raw data */
uint32_t matrix_type;
uint32_t matrix_data_off;
void *matrix_data;
uint32_t matrix_element_size;
/* Pointer and type of copied data */
matrix_type_t matrix_decoded_type;
void *matrix_decoded;
/* Help data to try to estimate element size */
uint32_t matrix_elements;
uint32_t matrix_used_space;
double matrix_estimated_element_size;
} camf_entry_t;
typedef struct camf_entry_table_s {
uint32_t size;
camf_entry_t *element;
} camf_entry_table_t;
typedef struct x3f_camf_typeN_s {
uint32_t val0;
uint32_t val1;
uint32_t val2;
uint32_t val3;
} x3f_camf_typeN_t;
typedef struct x3f_camf_type2_s {
uint32_t reserved;
uint32_t infotype;
uint32_t infotype_version;
uint32_t crypt_key;
} x3f_camf_type2_t;
typedef struct x3f_camf_type4_s {
uint32_t decoded_data_size;
uint32_t decode_bias;
uint32_t block_size;
uint32_t block_count;
} x3f_camf_type4_t;
typedef struct x3f_camf_type5_s {
uint32_t decoded_data_size;
uint32_t decode_bias;
uint32_t unknown2;
uint32_t unknown3;
} x3f_camf_type5_t;
typedef struct x3f_camf_s {
/* Header info */
uint32_t type;
union {
x3f_camf_typeN_t tN;
x3f_camf_type2_t t2;
x3f_camf_type4_t t4;
x3f_camf_type5_t t5;
};
/* The encrypted raw data */
void *data;
uint32_t data_size;
/* Help data for type 4 Huffman compression */
x3f_true_huffman_t table;
x3f_hufftree_t tree;
uint8_t *decoding_start;
uint32_t decoding_size;
/* The decrypted data */
void *decoded_data;
uint32_t decoded_data_size;
/* Pointers into the decrypted data */
camf_entry_table_t entry_table;
} x3f_camf_t;
typedef struct x3f_directory_entry_header_s {
uint32_t identifier; /* Should be ´SECp´, "SECi", ... */
uint32_t version; /* 0x00020001 is version 2.1 */
union {
x3f_property_list_t property_list;
x3f_image_data_t image_data;
x3f_camf_t camf;
} data_subsection;
} x3f_directory_entry_header_t;
typedef struct x3f_directory_entry_s {
struct {
uint32_t offset;
uint32_t size;
} input, output;
uint32_t type;
x3f_directory_entry_header_t header;
} x3f_directory_entry_t;
typedef struct x3f_directory_section_s {
uint32_t identifier; /* Should be ´SECd´ */
uint32_t version; /* 0x00020001 is version 2.1 */
/* 2.0 Fields */
uint32_t num_directory_entries;
x3f_directory_entry_t *directory_entry;
} x3f_directory_section_t;
typedef struct x3f_header_s {
/* 2.0 Fields */
uint32_t identifier; /* Should be ´FOVb´ */
uint32_t version; /* 0x00020001 means 2.1 */
uint8_t unique_identifier[SIZE_UNIQUE_IDENTIFIER];
uint32_t mark_bits;
uint32_t columns; /* Columns and rows ... */
uint32_t rows; /* ... before rotation */
uint32_t rotation; /* 0, 90, 180, 270 */
char white_balance[SIZE_WHITE_BALANCE]; /* Introduced in 2.1 */
char color_mode[SIZE_COLOR_MODE]; /* Introduced in 2.3 */
/* Introduced in 2.1 and extended from 32 to 64 in 3.0 */
uint8_t extended_types[NUM_EXT_DATA]; /* x3f_extended_types_t */
float extended_data[NUM_EXT_DATA]; /* 32 bits, but do type differ? */
} x3f_header_t;
typedef struct x3f_info_s {
char *error;
struct {
LibRaw_abstract_datastream *file; /* Use if more data is needed */
} input, output;
} x3f_info_t;
typedef struct x3f_s {
x3f_info_t info;
x3f_header_t header;
x3f_directory_section_t directory_section;
} x3f_t;
typedef enum x3f_return_e {
X3F_OK=0,
X3F_ARGUMENT_ERROR=1,
X3F_INFILE_ERROR=2,
X3F_OUTFILE_ERROR=3,
X3F_INTERNAL_ERROR=4
} x3f_return_t;
x3f_return_t x3f_delete(x3f_t *x3f);
/* Hacky external flags */
/* --------------------------------------------------------------------- */
/* extern */ int legacy_offset = 0;
/* extern */ bool_t auto_legacy_offset = 1;
/* --------------------------------------------------------------------- */
/* Huffman Decode Macros */
/* --------------------------------------------------------------------- */
#define HUF_TREE_MAX_LENGTH 27
#define HUF_TREE_MAX_NODES(_leaves) ((HUF_TREE_MAX_LENGTH+1)*(_leaves))
#define HUF_TREE_GET_LENGTH(_v) (((_v)>>27)&0x1f)
#define HUF_TREE_GET_CODE(_v) ((_v)&0x07ffffff)
/* --------------------------------------------------------------------- */
/* Reading and writing - assuming little endian in the file */
/* --------------------------------------------------------------------- */
static int x3f_get1(LibRaw_abstract_datastream *f)
{
/* Little endian file */
return f->get_char();
}
static int x3f_sget2 (uchar *s)
{
return s[0] | s[1] << 8;
}
static int x3f_get2(LibRaw_abstract_datastream *f)
{
uchar str[2] = { 0xff,0xff };
f->read (str, 1, 2);
return x3f_sget2(str);
}
unsigned x3f_sget4 (uchar *s)
{
return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
}
unsigned x3f_get4(LibRaw_abstract_datastream *f)
{
uchar str[4] = { 0xff,0xff,0xff,0xff };
f->read (str, 1, 4);
return x3f_sget4(str);
}
#define FREE(P) do { free(P); (P) = NULL; } while (0)
#define PUT_GET_N(_buffer,_size,_file,_func) \
do \
{ \
int _left = _size; \
while (_left != 0) { \
int _cur = _file->_func(_buffer,1,_left); \
if (_cur == 0) { \
throw LIBRAW_EXCEPTION_IO_CORRUPT; \
} \
_left -= _cur; \
} \
} while(0)
#define GET1(_v) do {(_v) = x3f_get1(I->input.file);} while (0)
#define GET2(_v) do {(_v) = x3f_get2(I->input.file);} while (0)
#define GET4(_v) do {(_v) = x3f_get4(I->input.file);} while (0)
#define GET4F(_v) \
do { \
union {int32_t i; float f;} _tmp; \
_tmp.i = x3f_get4(I->input.file); \
(_v) = _tmp.f; \
} while (0)
#define GETN(_v,_s) PUT_GET_N(_v,_s,I->input.file,read)
#define GET_TABLE(_T, _GETX, _NUM,_TYPE) \
do { \
int _i; \
(_T).size = (_NUM); \
(_T).element = (_TYPE *)realloc((_T).element, \
(_NUM)*sizeof((_T).element[0])); \
for (_i = 0; _i < (_T).size; _i++) \
_GETX((_T).element[_i]); \
} while (0)
#define GET_PROPERTY_TABLE(_T, _NUM) \
do { \
int _i; \
(_T).size = (_NUM); \
(_T).element = (x3f_property_t *)realloc((_T).element, \
(_NUM)*sizeof((_T).element[0])); \
for (_i = 0; _i < (_T).size; _i++) { \
GET4((_T).element[_i].name_offset); \
GET4((_T).element[_i].value_offset); \
} \
} while (0)
#define GET_TRUE_HUFF_TABLE(_T) \
do { \
int _i; \
(_T).element = NULL; \
for (_i = 0; ; _i++) { \
(_T).size = _i + 1; \
(_T).element = (x3f_true_huffman_element_t *)realloc((_T).element, \
(_i + 1)*sizeof((_T).element[0])); \
GET1((_T).element[_i].code_size); \
GET1((_T).element[_i].code); \
if ((_T).element[_i].code_size == 0) break; \
} \
} while (0)
/* --------------------------------------------------------------------- */
/* Allocating Huffman tree help data */
/* --------------------------------------------------------------------- */
static void cleanup_huffman_tree(x3f_hufftree_t *HTP)
{
free(HTP->nodes);
}
static void new_huffman_tree(x3f_hufftree_t *HTP, int bits)
{
int leaves = 1<<bits;
HTP->free_node_index = 0;
HTP->nodes = (x3f_huffnode_t *)
calloc(1, HUF_TREE_MAX_NODES(leaves)*sizeof(x3f_huffnode_t));
}
/* --------------------------------------------------------------------- */
/* Allocating TRUE engine RAW help data */
/* --------------------------------------------------------------------- */
static void cleanup_true(x3f_true_t **TRUP)
{
x3f_true_t *TRU = *TRUP;
if (TRU == NULL) return;
FREE(TRU->table.element);
FREE(TRU->plane_size.element);
cleanup_huffman_tree(&TRU->tree);
FREE(TRU->x3rgb16.buf);
FREE(TRU);
*TRUP = NULL;
}
static x3f_true_t *new_true(x3f_true_t **TRUP)
{
x3f_true_t *TRU = (x3f_true_t *)calloc(1, sizeof(x3f_true_t));
cleanup_true(TRUP);
TRU->table.size = 0;
TRU->table.element = NULL;
TRU->plane_size.size = 0;
TRU->plane_size.element = NULL;
TRU->tree.nodes = NULL;
TRU->x3rgb16.data = NULL;
TRU->x3rgb16.buf = NULL;
*TRUP = TRU;
return TRU;
}
static void cleanup_quattro(x3f_quattro_t **QP)
{
x3f_quattro_t *Q = *QP;
if (Q == NULL) return;
FREE(Q->top16.buf);
FREE(Q);
*QP = NULL;
}
static x3f_quattro_t *new_quattro(x3f_quattro_t **QP)
{
x3f_quattro_t *Q = (x3f_quattro_t *)calloc(1, sizeof(x3f_quattro_t));
int i;
cleanup_quattro(QP);
for (i=0; i<TRUE_PLANES; i++) {
Q->plane[i].columns = 0;
Q->plane[i].rows = 0;
}
Q->unknown = 0;
Q->top16.data = NULL;
Q->top16.buf = NULL;
*QP = Q;
return Q;
}
/* --------------------------------------------------------------------- */
/* Allocating Huffman engine help data */
/* --------------------------------------------------------------------- */
static void cleanup_huffman(x3f_huffman_t **HUFP)
{
x3f_huffman_t *HUF = *HUFP;
if (HUF == NULL) return;
FREE(HUF->mapping.element);
FREE(HUF->table.element);
cleanup_huffman_tree(&HUF->tree);
FREE(HUF->row_offsets.element);
FREE(HUF->rgb8.buf);
FREE(HUF->x3rgb16.buf);
FREE(HUF);
*HUFP = NULL;
}
static x3f_huffman_t *new_huffman(x3f_huffman_t **HUFP)
{
x3f_huffman_t *HUF = (x3f_huffman_t *)calloc(1, sizeof(x3f_huffman_t));
cleanup_huffman(HUFP);
/* Set all not read data block pointers to NULL */
HUF->mapping.size = 0;
HUF->mapping.element = NULL;
HUF->table.size = 0;
HUF->table.element = NULL;
HUF->tree.nodes = NULL;
HUF->row_offsets.size = 0;
HUF->row_offsets.element = NULL;
HUF->rgb8.data = NULL;
HUF->rgb8.buf = NULL;
HUF->x3rgb16.data = NULL;
HUF->x3rgb16.buf = NULL;
*HUFP = HUF;
return HUF;
}
/* --------------------------------------------------------------------- */
/* Creating a new x3f structure from file */
/* --------------------------------------------------------------------- */
/* extern */ x3f_t *x3f_new_from_file(LibRaw_abstract_datastream *infile)
{
if (!infile) return NULL;
INT64 fsize = infile->size();
x3f_t *x3f = (x3f_t *)calloc(1, sizeof(x3f_t));
if(!x3f)
throw LIBRAW_EXCEPTION_ALLOC;
try {
x3f_info_t *I = NULL;
x3f_header_t *H = NULL;
x3f_directory_section_t *DS = NULL;
int i, d;
I = &x3f->info;
I->error = NULL;
I->input.file = infile;
I->output.file = NULL;
/* Read file header */
H = &x3f->header;
infile->seek(0, SEEK_SET);
GET4(H->identifier);
if (H->identifier != X3F_FOVb) {
free(x3f);
return NULL;
}
GET4(H->version);
GETN(H->unique_identifier, SIZE_UNIQUE_IDENTIFIER);
/* TODO: the meaning of the rest of the header for version >= 4.0 (Quattro) is unknown */
if (H->version < X3F_VERSION_4_0) {
GET4(H->mark_bits);
GET4(H->columns);
GET4(H->rows);
GET4(H->rotation);
if (H->version >= X3F_VERSION_2_1) {
int num_ext_data =
H->version >= X3F_VERSION_3_0 ? NUM_EXT_DATA_3_0 : NUM_EXT_DATA_2_1;
GETN(H->white_balance, SIZE_WHITE_BALANCE);
if (H->version >= X3F_VERSION_2_3)
GETN(H->color_mode, SIZE_COLOR_MODE);
GETN(H->extended_types, num_ext_data);
for (i = 0; i < num_ext_data; i++)
GET4F(H->extended_data[i]);
}
}
/* Go to the beginning of the directory */
infile->seek(-4, SEEK_END);
infile->seek(x3f_get4(infile), SEEK_SET);
/* Read the directory header */
DS = &x3f->directory_section;
GET4(DS->identifier);
GET4(DS->version);
GET4(DS->num_directory_entries);
if (DS->num_directory_entries > 50)
goto _err; // too much direntries, most likely broken file
if (DS->num_directory_entries > 0) {
size_t size = DS->num_directory_entries * sizeof(x3f_directory_entry_t);
DS->directory_entry = (x3f_directory_entry_t *)calloc(1, size);
}
/* Traverse the directory */
for (d=0; d<DS->num_directory_entries; d++) {
x3f_directory_entry_t *DE = &DS->directory_entry[d];
x3f_directory_entry_header_t *DEH = &DE->header;
uint32_t save_dir_pos;
/* Read the directory entry info */
GET4(DE->input.offset);
GET4(DE->input.size);
if (DE->input.offset + DE->input.size > fsize * 2)
goto _err;
DE->output.offset = 0;
DE->output.size = 0;
GET4(DE->type);
/* Save current pos and go to the entry */
save_dir_pos = infile->tell();
infile->seek(DE->input.offset, SEEK_SET);
/* Read the type independent part of the entry header */
DEH = &DE->header;
GET4(DEH->identifier);
GET4(DEH->version);
/* NOTE - the tests below could be made on DE->type instead */
if (DEH->identifier == X3F_SECp) {
x3f_property_list_t *PL = &DEH->data_subsection.property_list;
if (!PL)
goto _err;
/* Read the property part of the header */
GET4(PL->num_properties);
GET4(PL->character_format);
GET4(PL->reserved);
GET4(PL->total_length);
/* Set all not read data block pointers to NULL */
PL->data = NULL;
PL->data_size = 0;
}
if (DEH->identifier == X3F_SECi) {
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
if (!ID)
goto _err;
/* Read the image part of the header */
GET4(ID->type);
GET4(ID->format);
ID->type_format = (ID->type << 16) + (ID->format);
GET4(ID->columns);
GET4(ID->rows);
GET4(ID->row_stride);
/* Set all not read data block pointers to NULL */
ID->huffman = NULL;
ID->data = NULL;
ID->data_size = 0;
}
if (DEH->identifier == X3F_SECc) {
x3f_camf_t *CAMF = &DEH->data_subsection.camf;
if (!CAMF)
goto _err;
/* Read the CAMF part of the header */
GET4(CAMF->type);
GET4(CAMF->tN.val0);
GET4(CAMF->tN.val1);
GET4(CAMF->tN.val2);
GET4(CAMF->tN.val3);
/* Set all not read data block pointers to NULL */
CAMF->data = NULL;
CAMF->data_size = 0;
/* Set all not allocated help pointers to NULL */
CAMF->table.element = NULL;
CAMF->table.size = 0;
CAMF->tree.nodes = NULL;
CAMF->decoded_data = NULL;
CAMF->decoded_data_size = 0;
CAMF->entry_table.element = NULL;
CAMF->entry_table.size = 0;
}
/* Reset the file pointer back to the directory */
infile->seek(save_dir_pos, SEEK_SET);
}
return x3f;
_err:
if (x3f)
{
DS = &x3f->directory_section;
if (DS && DS->directory_entry)
free(DS->directory_entry);
free(x3f);
}
return NULL;
} catch (...) {
x3f_directory_section_t *DS = &x3f->directory_section;
if (DS && DS->directory_entry)
free(DS->directory_entry);
free(x3f);
return NULL;
}
}
/* --------------------------------------------------------------------- */
/* Clean up an x3f structure */
/* --------------------------------------------------------------------- */
static void free_camf_entry(camf_entry_t *entry)
{
FREE(entry->property_name);
FREE(entry->property_value);
FREE(entry->matrix_decoded);
FREE(entry->matrix_dim_entry);
}
/* extern */ x3f_return_t x3f_delete(x3f_t *x3f)
{
x3f_directory_section_t *DS;
int d;
if (x3f == NULL)
return X3F_ARGUMENT_ERROR;
DS = &x3f->directory_section;
if (DS->num_directory_entries > 50)
return X3F_ARGUMENT_ERROR;
for (d=0; d<DS->num_directory_entries; d++) {
x3f_directory_entry_t *DE = &DS->directory_entry[d];
x3f_directory_entry_header_t *DEH = &DE->header;
if (DEH->identifier == X3F_SECp) {
x3f_property_list_t *PL = &DEH->data_subsection.property_list;
if (PL)
{
int i;
}
FREE(PL->property_table.element);
FREE(PL->data);
}
if (DEH->identifier == X3F_SECi) {
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
if (ID)
{
cleanup_huffman(&ID->huffman);
cleanup_true(&ID->tru);
cleanup_quattro(&ID->quattro);
FREE(ID->data);
}
}
if (DEH->identifier == X3F_SECc) {
x3f_camf_t *CAMF = &DEH->data_subsection.camf;
int i;
if (CAMF)
{
FREE(CAMF->data);
FREE(CAMF->table.element);
cleanup_huffman_tree(&CAMF->tree);
FREE(CAMF->decoded_data);
for (i = 0; i < CAMF->entry_table.size; i++) {
free_camf_entry(&CAMF->entry_table.element[i]);
}
}
FREE(CAMF->entry_table.element);
}
}
FREE(DS->directory_entry);
FREE(x3f);
return X3F_OK;
}
/* --------------------------------------------------------------------- */
/* Getting a reference to a directory entry */
/* --------------------------------------------------------------------- */
/* TODO: all those only get the first instance */
static x3f_directory_entry_t *x3f_get(x3f_t *x3f,
uint32_t type,
uint32_t image_type)
{
x3f_directory_section_t *DS;
int d;
if (x3f == NULL) return NULL;
DS = &x3f->directory_section;
for (d=0; d<DS->num_directory_entries; d++) {
x3f_directory_entry_t *DE = &DS->directory_entry[d];
x3f_directory_entry_header_t *DEH = &DE->header;
if (DEH->identifier == type) {
switch (DEH->identifier) {
case X3F_SECi:
{
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
if (ID->type_format == image_type)
return DE;
}
break;
default:
return DE;
}
}
}
return NULL;
}
/* extern */ x3f_directory_entry_t *x3f_get_raw(x3f_t *x3f)
{
x3f_directory_entry_t *DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_HUFFMAN_X530)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_HUFFMAN_10BIT)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_TRUE)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_MERRILL)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_QUATTRO)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_SDQ)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_SDQH)) != NULL)
return DE;
if ((DE = x3f_get(x3f, X3F_SECi, X3F_IMAGE_RAW_SDQH2)) != NULL)
return DE;
return NULL;
}
/* extern */ x3f_directory_entry_t *x3f_get_thumb_plain(x3f_t *x3f)
{
return x3f_get(x3f, X3F_SECi, X3F_IMAGE_THUMB_PLAIN);
}
/* extern */ x3f_directory_entry_t *x3f_get_thumb_huffman(x3f_t *x3f)
{
return x3f_get(x3f, X3F_SECi, X3F_IMAGE_THUMB_HUFFMAN);
}
/* extern */ x3f_directory_entry_t *x3f_get_thumb_jpeg(x3f_t *x3f)
{
return x3f_get(x3f, X3F_SECi, X3F_IMAGE_THUMB_JPEG);
}
/* extern */ x3f_directory_entry_t *x3f_get_camf(x3f_t *x3f)
{
return x3f_get(x3f, X3F_SECc, 0);
}
/* extern */ x3f_directory_entry_t *x3f_get_prop(x3f_t *x3f)
{
return x3f_get(x3f, X3F_SECp, 0);
}
/* For some obscure reason, the bit numbering is weird. It is
generally some kind of "big endian" style - e.g. the bit 7 is the
first in a byte and bit 31 first in a 4 byte int. For patterns in
the huffman pattern table, bit 27 is the first bit and bit 26 the
next one. */
#define PATTERN_BIT_POS(_len, _bit) ((_len) - (_bit) - 1)
#define MEMORY_BIT_POS(_bit) PATTERN_BIT_POS(8, _bit)
/* --------------------------------------------------------------------- */
/* Huffman Decode */
/* --------------------------------------------------------------------- */
/* Make the huffman tree */
#ifdef DBG_PRNT
static char *display_code(int length, uint32_t code, char *buffer)
{
int i;
for (i=0; i<length; i++) {
int pos = PATTERN_BIT_POS(length, i);
buffer[i] = ((code>>pos)&1) == 0 ? '0' : '1';
}
buffer[i] = 0;
return buffer;
}
#endif
static x3f_huffnode_t *new_node(x3f_hufftree_t *tree)
{
x3f_huffnode_t *t = &tree->nodes[tree->free_node_index];
t->branch[0] = NULL;
t->branch[1] = NULL;
t->leaf = UNDEFINED_LEAF;
tree->free_node_index++;
return t;
}
static void add_code_to_tree(x3f_hufftree_t *tree,
int length, uint32_t code, uint32_t value)
{
int i;
x3f_huffnode_t *t = tree->nodes;
for (i=0; i<length; i++) {
int pos = PATTERN_BIT_POS(length, i);
int bit = (code>>pos)&1;
x3f_huffnode_t *t_next = t->branch[bit];
if (t_next == NULL)
t_next = t->branch[bit] = new_node(tree);
t = t_next;
}
t->leaf = value;
}
static void populate_true_huffman_tree(x3f_hufftree_t *tree,
x3f_true_huffman_t *table)
{
int i;
new_node(tree);
for (i=0; i<table->size; i++) {
x3f_true_huffman_element_t *element = &table->element[i];
uint32_t length = element->code_size;
if (length != 0) {
/* add_code_to_tree wants the code right adjusted */
uint32_t code = ((element->code) >> (8 - length)) & 0xff;
uint32_t value = i;
add_code_to_tree(tree, length, code, value);
#ifdef DBG_PRNT
{
char buffer[100];
x3f_printf(DEBUG, "H %5d : %5x : %5d : %02x %08x (%08x) (%s)\n",
i, i, value, length, code, value,
display_code(length, code, buffer));
}
#endif
}
}
}
static void populate_huffman_tree(x3f_hufftree_t *tree,
x3f_table32_t *table,
x3f_table16_t *mapping)
{
int i;
new_node(tree);
for (i=0; i<table->size; i++) {
uint32_t element = table->element[i];
if (element != 0) {
uint32_t length = HUF_TREE_GET_LENGTH(element);
uint32_t code = HUF_TREE_GET_CODE(element);
uint32_t value;
/* If we have a valid mapping table - then the value from the
mapping table shall be used. Otherwise we use the current
index in the table as value. */
if (table->size == mapping->size)
value = mapping->element[i];
else
value = i;
add_code_to_tree(tree, length, code, value);
#ifdef DBG_PRNT
{
char buffer[100];
x3f_printf(DEBUG, "H %5d : %5x : %5d : %02x %08x (%08x) (%s)\n",
i, i, value, length, code, element,
display_code(length, code, buffer));
}
#endif
}
}
}
#ifdef DBG_PRNT
static void print_huffman_tree(x3f_huffnode_t *t, int length, uint32_t code)
{
char buf1[100];
char buf2[100];
x3f_printf(DEBUG, "%*s (%s,%s) %s (%s)\n",
length, length < 1 ? "-" : (code&1) ? "1" : "0",
t->branch[0]==NULL ? "-" : "0",
t->branch[1]==NULL ? "-" : "1",
t->leaf==UNDEFINED_LEAF ? "-" : (sprintf(buf1, "%x", t->leaf),buf1),
display_code(length, code, buf2));
code = code << 1;
if (t->branch[0]) print_huffman_tree(t->branch[0], length+1, code+0);
if (t->branch[1]) print_huffman_tree(t->branch[1], length+1, code+1);
}
#endif
/* Help machinery for reading bits in a memory */
typedef struct bit_state_s {
uint8_t *next_address;
uint8_t bit_offset;
uint8_t bits[8];
} bit_state_t;
static void set_bit_state(bit_state_t *BS, uint8_t *address)
{
BS->next_address = address;
BS->bit_offset = 8;
}
static uint8_t get_bit(bit_state_t *BS)
{
if (BS->bit_offset == 8) {
uint8_t byte = *BS->next_address;
int i;
for (i=7; i>= 0; i--) {
BS->bits[i] = byte&1;
byte = byte >> 1;
}
BS->next_address++;
BS->bit_offset = 0;
}
return BS->bits[BS->bit_offset++];
}
/* Decode use the TRUE algorithm */
static int32_t get_true_diff(bit_state_t *BS, x3f_hufftree_t *HTP)
{
int32_t diff;
x3f_huffnode_t *node = &HTP->nodes[0];
uint8_t bits;
while (node->branch[0] != NULL || node->branch[1] != NULL) {
uint8_t bit = get_bit(BS);
x3f_huffnode_t *new_node = node->branch[bit];
node = new_node;
if (node == NULL) {
/* TODO: Shouldn't this be treated as a fatal error? */
return 0;
}
}
bits = node->leaf;
if (bits == 0)
diff = 0;
else {
uint8_t first_bit = get_bit(BS);
int i;
diff = first_bit;
for (i=1; i<bits; i++)
diff = (diff << 1) + get_bit(BS);
if (first_bit == 0)
diff -= (1<<bits) - 1;
}
return diff;
}
/* This code (that decodes one of the X3F color planes, really is a
decoding of a compression algorithm suited for Bayer CFA data. In
Bayer CFA the data is divided into 2x2 squares that represents
(R,G1,G2,B) data. Those four positions are (in this compression)
treated as one data stream each, where you store the differences to
previous data in the stream. The reason for this is, of course,
that the date is more often than not near to the next data in a
stream that represents the same color. */
/* TODO: write more about the compression */
static void true_decode_one_color(x3f_image_data_t *ID, int color)
{
x3f_true_t *TRU = ID->tru;
x3f_quattro_t *Q = ID->quattro;
uint32_t seed = TRU->seed[color]; /* TODO : Is this correct ? */
int row;
x3f_hufftree_t *tree = &TRU->tree;
bit_state_t BS;
int32_t row_start_acc[2][2];
uint32_t rows = ID->rows;
uint32_t cols = ID->columns;
x3f_area16_t *area = &TRU->x3rgb16;
uint16_t *dst = area->data + color;
set_bit_state(&BS, TRU->plane_address[color]);
row_start_acc[0][0] = seed;
row_start_acc[0][1] = seed;
row_start_acc[1][0] = seed;
row_start_acc[1][1] = seed;
if (ID->type_format == X3F_IMAGE_RAW_QUATTRO
|| ID->type_format == X3F_IMAGE_RAW_SDQ
|| ID->type_format == X3F_IMAGE_RAW_SDQH
|| ID->type_format == X3F_IMAGE_RAW_SDQH2
) {
rows = Q->plane[color].rows;
cols = Q->plane[color].columns;
if (Q->quattro_layout && color == 2) {
area = &Q->top16;
dst = area->data;
}
} else {
}
if(rows != area->rows || cols < area->columns)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
for (row = 0; row < rows; row++) {
int col;
bool_t odd_row = row&1;
int32_t acc[2];
for (col = 0; col < cols; col++) {
bool_t odd_col = col&1;
int32_t diff = get_true_diff(&BS, tree);
int32_t prev = col < 2 ?
row_start_acc[odd_row][odd_col] :
acc[odd_col];
int32_t value = prev + diff;
acc[odd_col] = value;
if (col < 2)
row_start_acc[odd_row][odd_col] = value;
/* Discard additional data at the right for binned Quattro plane 2 */
if (col >= area->columns) continue;
*dst = value;
dst += area->channels;
}
}
}
static void true_decode(x3f_info_t *I,
x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
int color;
for (color = 0; color < 3; color++) {
true_decode_one_color(ID, color);
}
}
/* Decode use the huffman tree */
static int32_t get_huffman_diff(bit_state_t *BS, x3f_hufftree_t *HTP)
{
int32_t diff;
x3f_huffnode_t *node = &HTP->nodes[0];
while (node->branch[0] != NULL || node->branch[1] != NULL) {
uint8_t bit = get_bit(BS);
x3f_huffnode_t *new_node = node->branch[bit];
node = new_node;
if (node == NULL) {
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
return 0;
}
}
diff = node->leaf;
return diff;
}
static void huffman_decode_row(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits,
int row,
int offset,
int *minimum)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
x3f_huffman_t *HUF = ID->huffman;
int16_t c[3] = {(int16_t)offset,(int16_t)offset,(int16_t)offset};
int col;
bit_state_t BS;
set_bit_state(&BS, (uint8_t*)ID->data + HUF->row_offsets.element[row]);
for (col = 0; col < ID->columns; col++) {
int color;
for (color = 0; color < 3; color++) {
uint16_t c_fix;
c[color] += get_huffman_diff(&BS, &HUF->tree);
if (c[color] < 0) {
c_fix = 0;
if (c[color] < *minimum)
*minimum = c[color];
} else {
c_fix = c[color];
}
switch (ID->type_format) {
case X3F_IMAGE_RAW_HUFFMAN_X530:
case X3F_IMAGE_RAW_HUFFMAN_10BIT:
HUF->x3rgb16.data[3*(row*ID->columns + col) + color] = (uint16_t)c_fix;
break;
case X3F_IMAGE_THUMB_HUFFMAN:
HUF->rgb8.data[3*(row*ID->columns + col) + color] = (uint8_t)c_fix;
break;
default:
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
}
}
static void huffman_decode(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
int row;
int minimum = 0;
int offset = legacy_offset;
for (row = 0; row < ID->rows; row++)
huffman_decode_row(I, DE, bits, row, offset, &minimum);
if (auto_legacy_offset && minimum < 0) {
offset = -minimum;
for (row = 0; row < ID->rows; row++)
huffman_decode_row(I, DE, bits, row, offset, &minimum);
}
}
static int32_t get_simple_diff(x3f_huffman_t *HUF, uint16_t index)
{
if (HUF->mapping.size == 0)
return index;
else
return HUF->mapping.element[index];
}
static void simple_decode_row(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits,
int row,
int row_stride)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
x3f_huffman_t *HUF = ID->huffman;
uint32_t *data = (uint32_t *)((unsigned char*)ID->data + row*row_stride);
uint16_t c[3] = {0,0,0};
int col;
uint32_t mask = 0;
switch (bits) {
case 8:
mask = 0x0ff;
break;
case 9:
mask = 0x1ff;
break;
case 10:
mask = 0x3ff;
break;
case 11:
mask = 0x7ff;
break;
case 12:
mask = 0xfff;
break;
default:
mask = 0;
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
break;
}
for (col = 0; col < ID->columns; col++) {
int color;
uint32_t val = data[col];
for (color = 0; color < 3; color++) {
uint16_t c_fix;
c[color] += get_simple_diff(HUF, (val>>(color*bits))&mask);
switch (ID->type_format) {
case X3F_IMAGE_RAW_HUFFMAN_X530:
case X3F_IMAGE_RAW_HUFFMAN_10BIT:
c_fix = (int16_t)c[color] > 0 ? c[color] : 0;
HUF->x3rgb16.data[3*(row*ID->columns + col) + color] = c_fix;
break;
case X3F_IMAGE_THUMB_HUFFMAN:
c_fix = (int8_t)c[color] > 0 ? c[color] : 0;
HUF->rgb8.data[3*(row*ID->columns + col) + color] = c_fix;
break;
default:
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
}
}
static void simple_decode(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits,
int row_stride)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
int row;
for (row = 0; row < ID->rows; row++)
simple_decode_row(I, DE, bits, row, row_stride);
}
/* --------------------------------------------------------------------- */
/* Loading the data in a directory entry */
/* --------------------------------------------------------------------- */
/* First you set the offset to where to start reading the data ... */
static void read_data_set_offset(x3f_info_t *I,
x3f_directory_entry_t *DE,
uint32_t header_size)
{
uint32_t i_off = DE->input.offset + header_size;
I->input.file->seek(i_off, SEEK_SET);
}
/* ... then you read the data, block for block */
static uint32_t read_data_block(void **data,
x3f_info_t *I,
x3f_directory_entry_t *DE,
uint32_t footer)
{
INT64 fpos = I->input.file->tell();
uint32_t size =
DE->input.size + DE->input.offset - fpos - footer;
if (fpos + size > I->input.file->size())
throw LIBRAW_EXCEPTION_IO_CORRUPT;
*data = (void *)malloc(size);
GETN(*data, size);
return size;
}
static uint32_t data_block_size(void **data,
x3f_info_t *I,
x3f_directory_entry_t *DE,
uint32_t footer)
{
uint32_t size =
DE->input.size + DE->input.offset - I->input.file->tell() - footer;
return size;
}
static void x3f_load_image_verbatim(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
if (!ID->data_size)
ID->data_size = read_data_block(&ID->data, I, DE, 0);
}
static int32_t x3f_load_image_verbatim_size(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
return data_block_size(&ID->data, I, DE, 0);
}
static void x3f_load_property_list(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_property_list_t *PL = &DEH->data_subsection.property_list;
int i;
read_data_set_offset(I, DE, X3F_PROPERTY_LIST_HEADER_SIZE);
GET_PROPERTY_TABLE(PL->property_table, PL->num_properties);
if (!PL->data_size)
PL->data_size = read_data_block(&PL->data, I, DE, 0);
uint32_t maxoffset = PL->data_size/sizeof(utf16_t)-2; // at least 2 chars, value + terminating 0x0000
for (i=0; i<PL->num_properties; i++) {
x3f_property_t *P = &PL->property_table.element[i];
if(P->name_offset > maxoffset || P->value_offset > maxoffset)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
P->name = ((utf16_t *)PL->data + P->name_offset);
P->value = ((utf16_t *)PL->data + P->value_offset);
}
}
static void x3f_load_true(x3f_info_t *I,
x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
x3f_true_t *TRU = new_true(&ID->tru);
x3f_quattro_t *Q = NULL;
int i;
if (ID->type_format == X3F_IMAGE_RAW_QUATTRO
|| ID->type_format == X3F_IMAGE_RAW_SDQ
|| ID->type_format == X3F_IMAGE_RAW_SDQH
|| ID->type_format == X3F_IMAGE_RAW_SDQH2
) {
Q = new_quattro(&ID->quattro);
for (i=0; i<TRUE_PLANES; i++) {
GET2(Q->plane[i].columns);
GET2(Q->plane[i].rows);
}
if (Q->plane[0].rows == ID->rows/2) {
Q->quattro_layout = 1;
} else if (Q->plane[0].rows == ID->rows) {
Q->quattro_layout = 0;
} else {
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
/* Read TRUE header data */
GET2(TRU->seed[0]);
GET2(TRU->seed[1]);
GET2(TRU->seed[2]);
GET2(TRU->unknown);
GET_TRUE_HUFF_TABLE(TRU->table);
if (ID->type_format == X3F_IMAGE_RAW_QUATTRO
||ID->type_format == X3F_IMAGE_RAW_SDQ
||ID->type_format == X3F_IMAGE_RAW_SDQH
||ID->type_format == X3F_IMAGE_RAW_SDQH2
) {
GET4(Q->unknown);
}
GET_TABLE(TRU->plane_size, GET4, TRUE_PLANES,uint32_t);
/* Read image data */
if (!ID->data_size)
ID->data_size = read_data_block(&ID->data, I, DE, 0);
/* TODO: can it be fewer than 8 bits? Maybe taken from TRU->table? */
new_huffman_tree(&TRU->tree, 8);
populate_true_huffman_tree(&TRU->tree, &TRU->table);
#ifdef DBG_PRNT
print_huffman_tree(TRU->tree.nodes, 0, 0);
#endif
TRU->plane_address[0] = (uint8_t*)ID->data;
for (i=1; i<TRUE_PLANES; i++)
TRU->plane_address[i] =
TRU->plane_address[i-1] +
(((TRU->plane_size.element[i-1] + 15) / 16) * 16);
if ( (ID->type_format == X3F_IMAGE_RAW_QUATTRO
|| ID->type_format == X3F_IMAGE_RAW_SDQ
|| ID->type_format == X3F_IMAGE_RAW_SDQH
|| ID->type_format == X3F_IMAGE_RAW_SDQH2
) &&
Q->quattro_layout) {
uint32_t columns = Q->plane[0].columns;
uint32_t rows = Q->plane[0].rows;
uint32_t channels = 3;
uint32_t size = columns * rows * channels;
TRU->x3rgb16.columns = columns;
TRU->x3rgb16.rows = rows;
TRU->x3rgb16.channels = channels;
TRU->x3rgb16.row_stride = columns * channels;
TRU->x3rgb16.buf = malloc(sizeof(uint16_t)*size);
TRU->x3rgb16.data = (uint16_t *) TRU->x3rgb16.buf;
columns = Q->plane[2].columns;
rows = Q->plane[2].rows;
channels = 1;
size = columns * rows * channels;
Q->top16.columns = columns;
Q->top16.rows = rows;
Q->top16.channels = channels;
Q->top16.row_stride = columns * channels;
Q->top16.buf = malloc(sizeof(uint16_t)*size);
Q->top16.data = (uint16_t *)Q->top16.buf;
} else {
uint32_t size = ID->columns * ID->rows * 3;
TRU->x3rgb16.columns = ID->columns;
TRU->x3rgb16.rows = ID->rows;
TRU->x3rgb16.channels = 3;
TRU->x3rgb16.row_stride = ID->columns * 3;
TRU->x3rgb16.buf =malloc(sizeof(uint16_t)*size);
TRU->x3rgb16.data = (uint16_t *)TRU->x3rgb16.buf;
}
true_decode(I, DE);
}
static void x3f_load_huffman_compressed(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits,
int use_map_table)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
x3f_huffman_t *HUF = ID->huffman;
int table_size = 1<<bits;
int row_offsets_size = ID->rows * sizeof(HUF->row_offsets.element[0]);
GET_TABLE(HUF->table, GET4, table_size,uint32_t);
if (!ID->data_size)
ID->data_size = read_data_block(&ID->data, I, DE, row_offsets_size);
GET_TABLE(HUF->row_offsets, GET4, ID->rows,uint32_t);
new_huffman_tree(&HUF->tree, bits);
populate_huffman_tree(&HUF->tree, &HUF->table, &HUF->mapping);
huffman_decode(I, DE, bits);
}
static void x3f_load_huffman_not_compressed(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits,
int use_map_table,
int row_stride)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
if (!ID->data_size)
ID->data_size = read_data_block(&ID->data, I, DE, 0);
simple_decode(I, DE, bits, row_stride);
}
static void x3f_load_huffman(x3f_info_t *I,
x3f_directory_entry_t *DE,
int bits,
int use_map_table,
int row_stride)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
x3f_huffman_t *HUF = new_huffman(&ID->huffman);
uint32_t size;
if (use_map_table) {
int table_size = 1<<bits;
GET_TABLE(HUF->mapping, GET2, table_size,uint16_t);
}
switch (ID->type_format) {
case X3F_IMAGE_RAW_HUFFMAN_X530:
case X3F_IMAGE_RAW_HUFFMAN_10BIT:
size = ID->columns * ID->rows * 3;
HUF->x3rgb16.columns = ID->columns;
HUF->x3rgb16.rows = ID->rows;
HUF->x3rgb16.channels = 3;
HUF->x3rgb16.row_stride = ID->columns * 3;
HUF->x3rgb16.buf = malloc(sizeof(uint16_t)*size);
HUF->x3rgb16.data = (uint16_t *)HUF->x3rgb16.buf;
break;
case X3F_IMAGE_THUMB_HUFFMAN:
size = ID->columns * ID->rows * 3;
HUF->rgb8.columns = ID->columns;
HUF->rgb8.rows = ID->rows;
HUF->rgb8.channels = 3;
HUF->rgb8.row_stride = ID->columns * 3;
HUF->rgb8.buf = malloc(sizeof(uint8_t)*size);
HUF->rgb8.data = (uint8_t *)HUF->rgb8.buf;
break;
default:
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
if (row_stride == 0)
return x3f_load_huffman_compressed(I, DE, bits, use_map_table);
else
return x3f_load_huffman_not_compressed(I, DE, bits, use_map_table, row_stride);
}
static void x3f_load_pixmap(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_load_image_verbatim(I, DE);
}
static uint32_t x3f_load_pixmap_size(x3f_info_t *I, x3f_directory_entry_t *DE)
{
return x3f_load_image_verbatim_size(I, DE);
}
static void x3f_load_jpeg(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_load_image_verbatim(I, DE);
}
static uint32_t x3f_load_jpeg_size(x3f_info_t *I, x3f_directory_entry_t *DE)
{
return x3f_load_image_verbatim_size(I, DE);
}
static void x3f_load_image(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
read_data_set_offset(I, DE, X3F_IMAGE_HEADER_SIZE);
switch (ID->type_format) {
case X3F_IMAGE_RAW_TRUE:
case X3F_IMAGE_RAW_MERRILL:
case X3F_IMAGE_RAW_QUATTRO:
case X3F_IMAGE_RAW_SDQ:
case X3F_IMAGE_RAW_SDQH:
case X3F_IMAGE_RAW_SDQH2:
x3f_load_true(I, DE);
break;
case X3F_IMAGE_RAW_HUFFMAN_X530:
case X3F_IMAGE_RAW_HUFFMAN_10BIT:
x3f_load_huffman(I, DE, 10, 1, ID->row_stride);
break;
case X3F_IMAGE_THUMB_PLAIN:
x3f_load_pixmap(I, DE);
break;
case X3F_IMAGE_THUMB_HUFFMAN:
x3f_load_huffman(I, DE, 8, 0, ID->row_stride);
break;
case X3F_IMAGE_THUMB_JPEG:
x3f_load_jpeg(I, DE);
break;
default:
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
// Used only for thumbnail size estimation
static uint32_t x3f_load_image_size(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
read_data_set_offset(I, DE, X3F_IMAGE_HEADER_SIZE);
switch (ID->type_format) {
case X3F_IMAGE_THUMB_PLAIN:
return x3f_load_pixmap_size(I, DE);
case X3F_IMAGE_THUMB_JPEG:
return x3f_load_jpeg_size(I, DE);
break;
default:
return 0;
}
}
static void x3f_load_camf_decode_type2(x3f_camf_t *CAMF)
{
uint32_t key = CAMF->t2.crypt_key;
int i;
CAMF->decoded_data_size = CAMF->data_size;
CAMF->decoded_data = malloc(CAMF->decoded_data_size);
for (i=0; i<CAMF->data_size; i++) {
uint8_t old, _new;
uint32_t tmp;
old = ((uint8_t *)CAMF->data)[i];
key = (key * 1597 + 51749) % 244944;
tmp = (uint32_t)(key * ((int64_t)301593171) >> 24);
_new = (uint8_t)(old ^ (uint8_t)(((((key << 8) - tmp) >> 1) + tmp) >> 17));
((uint8_t *)CAMF->decoded_data)[i] = _new;
}
}
/* NOTE: the unpacking in this code is in big respects identical to
true_decode_one_color(). The difference is in the output you
build. It might be possible to make some parts shared. NOTE ALSO:
This means that the meta data is obfuscated using an image
compression algorithm. */
static void camf_decode_type4(x3f_camf_t *CAMF)
{
uint32_t seed = CAMF->t4.decode_bias;
int row;
uint8_t *dst;
uint32_t dst_size = CAMF->t4.decoded_data_size;
uint8_t *dst_end;
bool_t odd_dst = 0;
x3f_hufftree_t *tree = &CAMF->tree;
bit_state_t BS;
int32_t row_start_acc[2][2];
uint32_t rows = CAMF->t4.block_count;
uint32_t cols = CAMF->t4.block_size;
CAMF->decoded_data_size = dst_size;
CAMF->decoded_data = malloc(CAMF->decoded_data_size);
memset(CAMF->decoded_data, 0, CAMF->decoded_data_size);
dst = (uint8_t *)CAMF->decoded_data;
dst_end = dst + dst_size;
set_bit_state(&BS, CAMF->decoding_start);
row_start_acc[0][0] = seed;
row_start_acc[0][1] = seed;
row_start_acc[1][0] = seed;
row_start_acc[1][1] = seed;
for (row = 0; row < rows; row++) {
int col;
bool_t odd_row = row&1;
int32_t acc[2];
/* We loop through all the columns and the rows. But the actual
data is smaller than that, so we break the loop when reaching
the end. */
for (col = 0; col < cols; col++) {
bool_t odd_col = col&1;
int32_t diff = get_true_diff(&BS, tree);
int32_t prev = col < 2 ?
row_start_acc[odd_row][odd_col] :
acc[odd_col];
int32_t value = prev + diff;
acc[odd_col] = value;
if (col < 2)
row_start_acc[odd_row][odd_col] = value;
switch(odd_dst) {
case 0:
*dst++ = (uint8_t)((value>>4)&0xff);
if (dst >= dst_end) {
goto ready;
}
*dst = (uint8_t)((value<<4)&0xf0);
break;
case 1:
*dst++ |= (uint8_t)((value>>8)&0x0f);
if (dst >= dst_end) {
goto ready;
}
*dst++ = (uint8_t)((value<<0)&0xff);
if (dst >= dst_end) {
goto ready;
}
break;
}
odd_dst = !odd_dst;
} /* end col */
} /* end row */
ready:;
}
static void x3f_load_camf_decode_type4(x3f_camf_t *CAMF)
{
int i;
uint8_t *p;
x3f_true_huffman_element_t *element = NULL;
for (i=0, p = (uint8_t*)CAMF->data; *p != 0; i++) {
/* TODO: Is this too expensive ??*/
element =
(x3f_true_huffman_element_t *)realloc(element, (i+1)*sizeof(*element));
element[i].code_size = *p++;
element[i].code = *p++;
}
CAMF->table.size = i;
CAMF->table.element = element;
/* TODO: where does the values 28 and 32 come from? */
#define CAMF_T4_DATA_SIZE_OFFSET 28
#define CAMF_T4_DATA_OFFSET 32
CAMF->decoding_size = *(uint32_t *)((unsigned char*)CAMF->data + CAMF_T4_DATA_SIZE_OFFSET);
CAMF->decoding_start = (uint8_t *)CAMF->data + CAMF_T4_DATA_OFFSET;
/* TODO: can it be fewer than 8 bits? Maybe taken from TRU->table? */
new_huffman_tree(&CAMF->tree, 8);
populate_true_huffman_tree(&CAMF->tree, &CAMF->table);
#ifdef DBG_PRNT
print_huffman_tree(CAMF->tree.nodes, 0, 0);
#endif
camf_decode_type4(CAMF);
}
static void camf_decode_type5(x3f_camf_t *CAMF)
{
int32_t acc = CAMF->t5.decode_bias;
uint8_t *dst;
x3f_hufftree_t *tree = &CAMF->tree;
bit_state_t BS;
int32_t i;
CAMF->decoded_data_size = CAMF->t5.decoded_data_size;
CAMF->decoded_data = malloc(CAMF->decoded_data_size);
dst = (uint8_t *)CAMF->decoded_data;
set_bit_state(&BS, CAMF->decoding_start);
for (i = 0; i < CAMF->decoded_data_size; i++) {
int32_t diff = get_true_diff(&BS, tree);
acc = acc + diff;
*dst++ = (uint8_t)(acc & 0xff);
}
}
static void x3f_load_camf_decode_type5(x3f_camf_t *CAMF)
{
int i;
uint8_t *p;
x3f_true_huffman_element_t *element = NULL;
for (i=0, p = (uint8_t*)CAMF->data; *p != 0; i++) {
/* TODO: Is this too expensive ??*/
element =
(x3f_true_huffman_element_t *)realloc(element, (i+1)*sizeof(*element));
element[i].code_size = *p++;
element[i].code = *p++;
}
CAMF->table.size = i;
CAMF->table.element = element;
/* TODO: where does the values 28 and 32 come from? */
#define CAMF_T5_DATA_SIZE_OFFSET 28
#define CAMF_T5_DATA_OFFSET 32
CAMF->decoding_size = *(uint32_t *)((uint8_t*)CAMF->data + CAMF_T5_DATA_SIZE_OFFSET);
CAMF->decoding_start = (uint8_t *)CAMF->data + CAMF_T5_DATA_OFFSET;
/* TODO: can it be fewer than 8 bits? Maybe taken from TRU->table? */
new_huffman_tree(&CAMF->tree, 8);
populate_true_huffman_tree(&CAMF->tree, &CAMF->table);
#ifdef DBG_PRNT
print_huffman_tree(CAMF->tree.nodes, 0, 0);
#endif
camf_decode_type5(CAMF);
}
static void x3f_setup_camf_text_entry(camf_entry_t *entry)
{
entry->text_size = *(uint32_t *)entry->value_address;
entry->text = (char*)entry->value_address + 4;
}
static void x3f_setup_camf_property_entry(camf_entry_t *entry)
{
int i;
uint8_t *e =
(uint8_t*)entry->entry;
uint8_t *v =
(uint8_t*)entry->value_address;
uint32_t num =
entry->property_num = *(uint32_t *)v;
uint32_t off = *(uint32_t *)(v + 4);
entry->property_name = (char **)malloc(num*sizeof(uint8_t*));
entry->property_value = (uint8_t **)malloc(num*sizeof(uint8_t*));
for (i=0; i<num; i++) {
uint32_t name_off = off + *(uint32_t *)(v + 8 + 8*i);
uint32_t value_off = off + *(uint32_t *)(v + 8 + 8*i + 4);
entry->property_name[i] = (char *)(e + name_off);
entry->property_value[i] = e + value_off;
}
}
static void set_matrix_element_info(uint32_t type,
uint32_t *size,
matrix_type_t *decoded_type)
{
switch (type) {
case 0:
*size = 2;
*decoded_type = M_INT; /* known to be true */
break;
case 1:
*size = 4;
*decoded_type = M_UINT; /* TODO: unknown ???? */
break;
case 2:
*size = 4;
*decoded_type = M_UINT; /* TODO: unknown ???? */
break;
case 3:
*size = 4;
*decoded_type = M_FLOAT; /* known to be true */
break;
case 5:
*size = 1;
*decoded_type = M_UINT; /* TODO: unknown ???? */
break;
case 6:
*size = 2;
*decoded_type = M_UINT; /* TODO: unknown ???? */
break;
default:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
static void get_matrix_copy(camf_entry_t *entry)
{
uint32_t element_size = entry->matrix_element_size;
uint32_t elements = entry->matrix_elements;
int i, size = (entry->matrix_decoded_type==M_FLOAT ?
sizeof(double) :
sizeof(uint32_t)) * elements;
entry->matrix_decoded = malloc(size);
switch (element_size) {
case 4:
switch (entry->matrix_decoded_type) {
case M_INT:
case M_UINT:
memcpy(entry->matrix_decoded, entry->matrix_data, size);
break;
case M_FLOAT:
for (i=0; i<elements; i++)
((double *)entry->matrix_decoded)[i] =
(double)((float *)entry->matrix_data)[i];
break;
default:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
break;
case 2:
switch (entry->matrix_decoded_type) {
case M_INT:
for (i=0; i<elements; i++)
((int32_t *)entry->matrix_decoded)[i] =
(int32_t)((int16_t *)entry->matrix_data)[i];
break;
case M_UINT:
for (i=0; i<elements; i++)
((uint32_t *)entry->matrix_decoded)[i] =
(uint32_t)((uint16_t *)entry->matrix_data)[i];
break;
default:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
break;
case 1:
switch (entry->matrix_decoded_type) {
case M_INT:
for (i=0; i<elements; i++)
((int32_t *)entry->matrix_decoded)[i] =
(int32_t)((int8_t *)entry->matrix_data)[i];
break;
case M_UINT:
for (i=0; i<elements; i++)
((uint32_t *)entry->matrix_decoded)[i] =
(uint32_t)((uint8_t *)entry->matrix_data)[i];
break;
default:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
break;
default:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
static void x3f_setup_camf_matrix_entry(camf_entry_t *entry)
{
int i;
int totalsize = 1;
uint8_t *e =
(uint8_t *)entry->entry;
uint8_t *v =
(uint8_t *)entry->value_address;
uint32_t type =
entry->matrix_type = *(uint32_t *)(v + 0);
uint32_t dim =
entry->matrix_dim = *(uint32_t *)(v + 4);
uint32_t off =
entry->matrix_data_off = *(uint32_t *)(v + 8);
camf_dim_entry_t *dentry =
entry->matrix_dim_entry =
(camf_dim_entry_t*)malloc(dim*sizeof(camf_dim_entry_t));
for (i=0; i<dim; i++) {
uint32_t size =
dentry[i].size = *(uint32_t *)(v + 12 + 12*i + 0);
dentry[i].name_offset = *(uint32_t *)(v + 12 + 12*i + 4);
dentry[i].n = *(uint32_t *)(v + 12 + 12*i + 8);
dentry[i].name = (char *)(e + dentry[i].name_offset);
if (dentry[i].n != i) {
}
totalsize *= size;
}
set_matrix_element_info(type,
&entry->matrix_element_size,
&entry->matrix_decoded_type);
entry->matrix_data = (void *)(e + off);
entry->matrix_elements = totalsize;
entry->matrix_used_space = entry->entry_size - off;
/* This estimate only works for matrices above a certain size */
entry->matrix_estimated_element_size = entry->matrix_used_space / totalsize;
get_matrix_copy(entry);
}
static void x3f_setup_camf_entries(x3f_camf_t *CAMF)
{
uint8_t *p = (uint8_t *)CAMF->decoded_data;
uint8_t *end = p + CAMF->decoded_data_size;
camf_entry_t *entry = NULL;
int i;
for (i=0; p < end; i++) {
uint32_t *p4 = (uint32_t *)p;
switch (*p4) {
case X3F_CMbP:
case X3F_CMbT:
case X3F_CMbM:
break;
default:
goto stop;
}
/* TODO: lots of realloc - may be inefficient */
entry = (camf_entry_t *)realloc(entry, (i+1)*sizeof(camf_entry_t));
/* Pointer */
entry[i].entry = p;
/* Header */
entry[i].id = *p4++;
entry[i].version = *p4++;
entry[i].entry_size = *p4++;
entry[i].name_offset = *p4++;
entry[i].value_offset = *p4++;
- /* Compute adresses and sizes */
+ /* Compute addresses and sizes */
entry[i].name_address = (char *)(p + entry[i].name_offset);
entry[i].value_address = p + entry[i].value_offset;
entry[i].name_size = entry[i].value_offset - entry[i].name_offset;
entry[i].value_size = entry[i].entry_size - entry[i].value_offset;
entry[i].text_size = 0;
entry[i].text = NULL;
entry[i].property_num = 0;
entry[i].property_name = NULL;
entry[i].property_value = NULL;
entry[i].matrix_type = 0;
entry[i].matrix_dim = 0;
entry[i].matrix_data_off = 0;
entry[i].matrix_data = NULL;
entry[i].matrix_dim_entry = NULL;
entry[i].matrix_decoded = NULL;
switch (entry[i].id) {
case X3F_CMbP:
x3f_setup_camf_property_entry(&entry[i]);
break;
case X3F_CMbT:
x3f_setup_camf_text_entry(&entry[i]);
break;
case X3F_CMbM:
x3f_setup_camf_matrix_entry(&entry[i]);
break;
}
p += entry[i].entry_size;
}
stop:
CAMF->entry_table.size = i;
CAMF->entry_table.element = entry;
}
static void x3f_load_camf(x3f_info_t *I, x3f_directory_entry_t *DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_camf_t *CAMF = &DEH->data_subsection.camf;
read_data_set_offset(I, DE, X3F_CAMF_HEADER_SIZE);
if (!CAMF->data_size)
CAMF->data_size = read_data_block(&CAMF->data, I, DE, 0);
switch (CAMF->type) {
case 2: /* Older SD9-SD14 */
x3f_load_camf_decode_type2(CAMF);
break;
case 4: /* TRUE ... Merrill */
x3f_load_camf_decode_type4(CAMF);
break;
case 5: /* Quattro ... */
x3f_load_camf_decode_type5(CAMF);
break;
default:
/* TODO: Shouldn't this be treated as a fatal error? */
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
if (CAMF->decoded_data != NULL)
x3f_setup_camf_entries(CAMF);
else
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
/* extern */ x3f_return_t x3f_load_data(x3f_t *x3f, x3f_directory_entry_t *DE)
{
x3f_info_t *I = &x3f->info;
if (DE == NULL)
return X3F_ARGUMENT_ERROR;
switch (DE->header.identifier) {
case X3F_SECp:
x3f_load_property_list(I, DE);
break;
case X3F_SECi:
x3f_load_image(I, DE);
break;
case X3F_SECc:
x3f_load_camf(I, DE);
break;
default:
return X3F_INTERNAL_ERROR;
}
return X3F_OK;
}
/* extern */ int64_t x3f_load_data_size(x3f_t *x3f, x3f_directory_entry_t *DE)
{
x3f_info_t *I = &x3f->info;
if (DE == NULL)
return -1;
switch (DE->header.identifier)
{
case X3F_SECi:
return x3f_load_image_size(I, DE);
default:
return 0;
}
}
/* extern */ x3f_return_t x3f_load_image_block(x3f_t *x3f, x3f_directory_entry_t *DE)
{
x3f_info_t *I = &x3f->info;
if (DE == NULL)
return X3F_ARGUMENT_ERROR;
switch (DE->header.identifier) {
case X3F_SECi:
read_data_set_offset(I, DE, X3F_IMAGE_HEADER_SIZE);
x3f_load_image_verbatim(I, DE);
break;
default:
throw LIBRAW_EXCEPTION_IO_CORRUPT;
return X3F_INTERNAL_ERROR;
}
return X3F_OK;
}
/* --------------------------------------------------------------------- */
/* The End */
/* --------------------------------------------------------------------- */
diff --git a/core/libs/rawengine/libraw/internal/wf_filtering.cpp b/core/libs/rawengine/libraw/internal/wf_filtering.cpp
index 75f9533dbc..106d1e7132 100644
--- a/core/libs/rawengine/libraw/internal/wf_filtering.cpp
+++ b/core/libs/rawengine/libraw/internal/wf_filtering.cpp
@@ -1,1941 +1,1941 @@
/*
WF debanding code
Copyright 2011 by Yan Vladimirovich
Used in LibRaw with author permission.
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
*/
#define P1 imgdata.idata
#define S imgdata.sizes
#define O imgdata.params
#define C imgdata.color
#define T imgdata.thumbnail
#define IO libraw_internal_data.internal_output_params
#define ID libraw_internal_data.internal_data
int LibRaw::wf_remove_banding()
{
#define WF_IMGMODE_BAYER4PLANE 4
#define WF_IMGMODE_BAYER1PLANE 1
#define WF_GREENMODE_IND 0
#define WF_GREENMODE_GX_XG 1
#define WF_GREENMODE_XG_GX 2
#define WF_DEBANDING_OK 0
#define WF_DEBANDING_NOTBAYER2X2 1
#define WF_DEBANDING_TOOSMALL 2
#define WF_GAUSS_PIRAMID_SIZE 4
#define WF_MAXTRESHOLD 65536
#define WF_BAYERSRC(row, col, c) ((ushort(*)[4])imgdata.image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERGAU(l, row, col) (gauss_pyramid[l])[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
#define WF_BAYERDFG(l, row, col) (difwg_pyramid[l])[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define WF_i_1TO4 for(int i=0; i<4; i++)
// too small?
if (S.width<128 || S.height<128)
return WF_DEBANDING_TOOSMALL;
// is 2x2 bayer?
int bayer2x2flag=-1;
for(int row_shift=0; row_shift<=8; row_shift+=2)
{
for(int col_shift=0; col_shift<=8; col_shift+=2)
{
if ((FC(0,0)!=FC(row_shift, col_shift)) ||
(FC(1,0)!=FC(row_shift+1, col_shift)) ||
(FC(0,1)!=FC(row_shift, col_shift+1)) ||
(FC(1,1)!=FC(row_shift+1, col_shift+1)))
{
bayer2x2flag=0;
}
}
}
if (bayer2x2flag==0)
return WF_DEBANDING_NOTBAYER2X2;
int x_green_flag = -1;
int width_d2, height_d2;
int width_p1_d2, height_p1_d2;
width_d2 = S.width/2;
height_d2 = S.height/2;
width_p1_d2 = (S.width+1)/2;
height_p1_d2 = (S.height+1)/2;
ushort val_max_c[4]={0,0,0,0};
ushort val_max;
ushort dummy_pixel=0;
ushort *dummy_line;
dummy_line = (ushort*)calloc(S.width, sizeof(ushort)*4);
for(int i=0; i<S.width*4; i++)
dummy_line[i]=0;
// Searching max value for increasing bit-depth
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
{
int row, row_p1;
ushort *src[4];
ushort *src_first, *src_plast, *src_last;
row = row_d2*2;
row_p1 = row+1;
WF_i_1TO4 src[i] = &WF_BAYERSRC((i<2)?row:row_p1, i&1, FC((i<2)?row:row_p1, i&1));
if (row_p1==S.height)
src[2]=src[3]=dummy_line;
src_first = &WF_BAYERSRC(row, 0, FC(row, 0));
src_plast = &WF_BAYERSRC(row, width_d2*2-2, FC(row, 0));
src_last = &WF_BAYERSRC(row, width_p1_d2*2-2, FC(row, 0));
do
{
// Do
WF_i_1TO4 val_max_c[i]=MAX(val_max_c[i], *src[i]);
// Next 4 pixel or exit
if (src[0]<src_plast)
{
WF_i_1TO4 src[i]+=8;
}
else if(src[0]>src_first && src[0]<src_last)
{
WF_i_1TO4 src[i]=i&1?&dummy_pixel:src[i]+8;
}
else break;
}
while(1);
}
val_max=MAX(MAX(val_max_c[0], val_max_c[1]), MAX(val_max_c[2], val_max_c[3]));
// end of searching max value
if (val_max==0)
return WF_DEBANDING_OK;
int data_shift;
int data_mult;
int val_max_s;
data_shift = 15;
val_max_s = val_max;
if (val_max_s >= (1 << 8)) { val_max_s >>= 8; data_shift -= 8; }
if (val_max_s >= (1 << 4)) { val_max_s >>= 4; data_shift -= 4; }
if (val_max_s >= (1 << 2)) { val_max_s >>= 2; data_shift -= 2; }
if (val_max_s >= (1 << 1)) { data_shift -= 1; }
data_mult = 1<<data_shift;
val_max <<= data_shift;
// Bit shift
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
{
int row, row_p1;
ushort *src[4];
ushort *src_first, *src_plast, *src_last;
row = row_d2*2;
row_p1 = row+1;
WF_i_1TO4 src[i] = &WF_BAYERSRC((i<2)?row:row_p1, i&1, FC((i<2)?row:row_p1, i&1));
if (row_p1==S.height)
src[2]=src[3]=dummy_line;
src_first = &WF_BAYERSRC(row, 0, FC(row, 0));
src_plast = &WF_BAYERSRC(row, width_d2*2-2, FC(row, 0));
src_last = &WF_BAYERSRC(row, width_p1_d2*2-2, FC(row, 0));
do
{
// Do
WF_i_1TO4 (*src[i])<<=data_shift;
// Next 4 pixel or exit
if (src[0]<src_plast)
{
WF_i_1TO4 src[i]+=8;
}
else if(src[0]>src_first && src[0]<src_last)
{
WF_i_1TO4 src[i]=i&1?&dummy_pixel:src[i]+8;
}
else break;
}
while(1);
}
ushort *gauss_pyramid[WF_GAUSS_PIRAMID_SIZE];
ushort *difwg_pyramid[WF_GAUSS_PIRAMID_SIZE];
for(int i=0; i<WF_GAUSS_PIRAMID_SIZE; i++)
{
gauss_pyramid[i] = (ushort*)calloc(S.width*S.height, sizeof(ushort));
difwg_pyramid[i] = (ushort*)calloc(S.width*S.height, sizeof(ushort));
}
int radius3x3 [4]={3, 3, 3, 0}; // as gau r=24
int radius3x14[4]={14, 14, 14, 0}; // as gau r=420
int radius3x45[4]={45, 45, 45, 0}; // as gau r=4140
// Making 4-level gaussian pyramid
if (x_green_flag)
{
wf_bayer4_green_blur (0, imgdata.image, WF_IMGMODE_BAYER4PLANE, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE);
wf_bayer4_igauss_filter(1, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE);
}
else
{
wf_bayer4_igauss_filter(1, imgdata.image, WF_IMGMODE_BAYER4PLANE, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE);
}
wf_bayer4_block_filter (radius3x3, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE, gauss_pyramid[1], WF_IMGMODE_BAYER1PLANE); // as gau r=24
wf_bayer4_block_filter (radius3x14, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE, gauss_pyramid[2], WF_IMGMODE_BAYER1PLANE); // as gau r=420
wf_bayer4_block_filter (radius3x45, gauss_pyramid[0], WF_IMGMODE_BAYER1PLANE, gauss_pyramid[3], WF_IMGMODE_BAYER1PLANE); // as gau r=4140
// Energy multiplyers for laplasyan pyramid
float dfg_mult[WF_GAUSS_PIRAMID_SIZE]={1.560976, 8.196011, 180.413773, 3601.427246/3.0};
/* dif_mult[0]=1.0/wf_filter_energy(0, 0, 0, 1);
dif_mult[1]=1.0/wf_filter_energy(0, 1, 0, 24);
dif_mult[2]=1.0/wf_filter_energy(0, 24, 0, 420);
dif_mult[3]=1.0/wf_filter_energy(0, 420, 0, 4140);*/
float dfg_mulg[WF_GAUSS_PIRAMID_SIZE]={1.235223, 19.813868, 365.148407, 7208.362793/3.0};
/* dif_mulg[0]=1.0/wf_filter_energy(0, 0, 1, 1);
dif_mulg[1]=1.0/wf_filter_energy(1, 1, 1, 24);
dif_mulg[2]=1.0/wf_filter_energy(1, 24, 1, 420);
dif_mulg[3]=1.0/wf_filter_energy(1, 420, 1, 4140);*/
float dfg_mlcc[WF_GAUSS_PIRAMID_SIZE][4];
long int dfg_dmax[WF_GAUSS_PIRAMID_SIZE][4];
int green_mode;
if ( x_green_flag && (imgdata.idata.cdesc[FC(0, 0)] == imgdata.idata.cdesc[FC(1, 1)]) )
green_mode = WF_GREENMODE_GX_XG;
else if ( x_green_flag && (imgdata.idata.cdesc[FC(0, 1)] == imgdata.idata.cdesc[FC(1, 0)]) )
green_mode = WF_GREENMODE_XG_GX;
else
green_mode = WF_GREENMODE_IND;
for(int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
{
switch (green_mode)
{
case WF_GREENMODE_GX_XG:
dfg_mlcc[l][0]=dfg_mlcc[l][3]=dfg_mulg[l];
dfg_dmax[l][0]=dfg_dmax[l][3]=65535/dfg_mulg[l];
dfg_mlcc[l][1]=dfg_mlcc[l][2]=dfg_mult[l];
dfg_dmax[l][1]=dfg_dmax[l][2]=65535/dfg_mult[l];
break;
case WF_GREENMODE_XG_GX:
dfg_mlcc[l][1]=dfg_mlcc[l][2]=dfg_mulg[l];
dfg_dmax[l][1]=dfg_dmax[l][2]=65535/dfg_mulg[l];
dfg_mlcc[l][0]=dfg_mlcc[l][3]=dfg_mult[l];
dfg_dmax[l][0]=dfg_dmax[l][3]=65535/dfg_mult[l];
break;
case WF_GREENMODE_IND:
dfg_mlcc[l][0]=dfg_mlcc[l][1]=dfg_mlcc[l][2]=dfg_mlcc[l][3]=dfg_mult[l];
dfg_dmax[l][0]=dfg_dmax[l][1]=dfg_dmax[l][2]=dfg_dmax[l][3]=65535/dfg_mult[l];
break;
}
}
// laplasyan energy
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
{
int row, row_p1;
ushort *src[4];
ushort *gau[WF_GAUSS_PIRAMID_SIZE][4];
ushort *dfg[WF_GAUSS_PIRAMID_SIZE][4];
row = row_d2*2;
row_p1 = row+1;
WF_i_1TO4 src[i] = &WF_BAYERSRC((i<2)?row:row_p1, i&1, FC((i<2)?row:row_p1, i&1));
if (row_p1==S.height)
src[2]=src[3]=dummy_line;
for(int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
{
WF_i_1TO4 gau[l][i] = &WF_BAYERGAU(l, (i<2)?row:row_p1, i&1);
WF_i_1TO4 dfg[l][i] = &WF_BAYERDFG(l, (i<2)?row:row_p1, i&1);
if ((row+1)==S.height)
dfg[l][2]=dfg[l][3]=gau[l][2]=gau[l][3]=dummy_line;
}
ushort *src_first, *src_last, *src_last2;
src_first = &WF_BAYERSRC(row, 0, FC(row, 0));
src_last = &WF_BAYERSRC(row, width_d2*2-2, FC(row, 0));
src_last2 = &WF_BAYERSRC(row, width_p1_d2*2-2, FC(row, 0));
do
{
long int val_gau[4];
long int val_dif[4];
long int val_src[4];
WF_i_1TO4 val_src[i]=*src[i];
for(int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
{
WF_i_1TO4 val_gau[i]=*gau[l][i];
WF_i_1TO4 val_dif[i]=val_src[i]-val_gau[i];
WF_i_1TO4 val_src[i]=val_gau[i];
WF_i_1TO4 val_dif[i]*=val_dif[i];
WF_i_1TO4
if(val_dif[i]<dfg_dmax[l][i])
{
val_dif[i]*=dfg_mlcc[l][i];
*dfg[l][i] =val_dif[i];
}
else
{
*dfg[l][i]=65535;
}
}
// Next 4 pixel or exit
if (src[0]<src_last)
{
WF_i_1TO4 src[i]+=8;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 gau[l][i]+=2;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 dfg[l][i]+=2;
}
else if(src[0]>src_first && src[0]<src_last2)
{
WF_i_1TO4 src[i]=i&1?&dummy_pixel:src[i]+8;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 gau[l][i]=i&1?&dummy_pixel:gau[l][i]+2;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 dfg[l][i]=i&1?&dummy_pixel:dfg[l][i]+2;
}
else break;
}
while(1);
}
int radius2x32 [3]={32, 32, 0};
int radius2x56 [3]={56, 56, 0};
int radius2x90 [3]={90, 90, 0};
int radius2x104[3]={104, 104, 0};
if (x_green_flag)
{
for(int i=0;i<4;i++)
wf_bayer4_green_blur (0, difwg_pyramid[i], WF_IMGMODE_BAYER1PLANE, difwg_pyramid[i], WF_IMGMODE_BAYER1PLANE);
}
wf_bayer4_block_filter (radius2x32, difwg_pyramid[0], WF_IMGMODE_BAYER1PLANE, difwg_pyramid[0], WF_IMGMODE_BAYER1PLANE);
wf_bayer4_block_filter (radius2x56, difwg_pyramid[1], WF_IMGMODE_BAYER1PLANE, difwg_pyramid[1], WF_IMGMODE_BAYER1PLANE);
wf_bayer4_block_filter (radius2x90, difwg_pyramid[2], WF_IMGMODE_BAYER1PLANE, difwg_pyramid[2], WF_IMGMODE_BAYER1PLANE);
wf_bayer4_block_filter (radius2x104, difwg_pyramid[3], WF_IMGMODE_BAYER1PLANE, difwg_pyramid[3], WF_IMGMODE_BAYER1PLANE);
float (*banding_col)[4];
float (*banding_row)[4];
float (*banding_col_count)[4];
float (*banding_row_count)[4];
banding_col = (float(*)[4])calloc(height_p1_d2, sizeof(float)*4);
banding_col_count = (float(*)[4])calloc(height_p1_d2, sizeof(float)*4);
banding_row = (float(*)[4])calloc(width_p1_d2, sizeof(float)*4);
banding_row_count = (float(*)[4])calloc(width_p1_d2, sizeof(float)*4);
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
WF_i_1TO4 banding_col[row_d2][i]=banding_col_count[row_d2][i]=0;
for(int col_d2=0; col_d2<width_p1_d2; col_d2++)
WF_i_1TO4 banding_row[col_d2][i]=banding_row_count[col_d2][i]=0;
long int val_accepted;
float treshold[4];
WF_i_1TO4 treshold[i]=imgdata.params.wf_deband_treshold[FC(i>>1,i&1)];
val_accepted = val_max-3*MAX(MAX(treshold[0],treshold[1]),MAX(treshold[2],treshold[3]));
float (*tr_weight)[4];
tr_weight=(float(*)[4])calloc(WF_MAXTRESHOLD*4, sizeof(float));
WF_i_1TO4 treshold[i]*=data_mult;
for(int v=0; v<WF_MAXTRESHOLD; v++)
{
for(int i=0; i<4; i++)
{
if (v<treshold[i]*treshold[i])
tr_weight[v][i] = 1.0;
else if (v*5<6*treshold[i]*treshold[i])
tr_weight[v][i] = 6.0-5.0*float(v)/(treshold[i]*treshold[i]);
else
tr_weight[v][i] = 0.0;
}
}
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
{
int row, row_p1;
ushort *src[4];
ushort *gau[WF_GAUSS_PIRAMID_SIZE][4];
ushort *dfg[WF_GAUSS_PIRAMID_SIZE][4];
row = row_d2*2;
row_p1 = row+1;
WF_i_1TO4 src[i] = &WF_BAYERSRC((i<2)?row:row_p1, i&1, FC((i<2)?row:row_p1, i&1));
if (row_p1==S.height)
src[2]=src[3]=dummy_line;
for(int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
{
WF_i_1TO4 gau[l][i] = &WF_BAYERGAU(l, (i<2)?row:row_p1, i&1);
WF_i_1TO4 dfg[l][i] = &WF_BAYERDFG(l, (i<2)?row:row_p1, i&1);
if (row_p1==S.height)
dfg[l][2]=dfg[l][3]=gau[l][2]=gau[l][3]=dummy_line;
}
ushort *src_first, *src_last, *src_last2;
src_first = &WF_BAYERSRC(row, 0, FC(row, 0));
src_last = &WF_BAYERSRC(row, width_d2*2-2, FC(row, 0));
src_last2 = &WF_BAYERSRC(row, width_p1_d2*2-2, FC(row, 0));
int col_d2 = 0;
do
{
float val_src[4];
float bsum[4]={0,0,0,0};
float wsum[4]={0,0,0,0};
WF_i_1TO4 val_src[i]=*src[i];
for(int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
{
float val_dif[4];
float val_gau[4];
float wght[4];
WF_i_1TO4 val_gau[i] = *gau[l][i];
WF_i_1TO4 val_dif[i] = val_src[i]-val_gau[i];
WF_i_1TO4 val_src[i] = val_gau[i];
WF_i_1TO4 wght[i] = tr_weight[*dfg[l][i]][i];
WF_i_1TO4 wsum[i] += wght[i];
WF_i_1TO4 bsum[i] += wght[i]*val_dif[i];
}
//WF_i_1TO4 *src[i]=bsum[i];
WF_i_1TO4 wsum[i]*=wsum[i];
WF_i_1TO4 banding_col [row_d2][i] += bsum[i]*wsum[i];
WF_i_1TO4 banding_col_count[row_d2][i] += wsum[i];
WF_i_1TO4 banding_row [col_d2][i] += bsum[i]*wsum[i];
WF_i_1TO4 banding_row_count[col_d2][i] += wsum[i];
// Next 4 pixel or exit
if (src[0]<src_last)
{
WF_i_1TO4 src[i]+=8;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 gau[l][i]+=2;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 dfg[l][i]+=2;
}
else if(src[0]>src_first && src[0]<src_last2)
{
WF_i_1TO4 src[i]=i&1?&dummy_pixel:src[i]+8;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 gau[l][i]=i&1?&dummy_pixel:gau[l][i]+2;
for (int l=0; l<WF_GAUSS_PIRAMID_SIZE; l++)
WF_i_1TO4 dfg[l][i]=i&1?&dummy_pixel:dfg[l][i]+2;
}
else break;
col_d2++;
}
while(1);
}
float bsum[4], bmean[4];
int (*banding_col_i)[4];
int (*banding_row_i)[4];
banding_col_i = (int(*)[4])calloc(height_p1_d2, sizeof(int)*4);
banding_row_i = (int(*)[4])calloc(width_p1_d2, sizeof(int)*4);
// cols
WF_i_1TO4 bsum[i]=bmean[i]=0;
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
for(int i=0; i<4; i++)
{
if (banding_col_count[row_d2][i]>0)
{
banding_col[row_d2][i]=banding_col[row_d2][i]/banding_col_count[row_d2][i];
bsum[i]+=banding_col[row_d2][i];
}
}
WF_i_1TO4 bmean[i]=bsum[i]/(i<2?height_d2:height_p1_d2);
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
for(int i=0; i<4; i++)
banding_col_i[row_d2][i]=int(banding_col[row_d2][i]-bmean[i]);
// rows
WF_i_1TO4 bsum[i]=bmean[i]=0;
for(int col_d2=0; col_d2<width_p1_d2; col_d2++)
for(int i=0; i<4; i++)
{
if (banding_row_count[col_d2][i]>0)
{
banding_row[col_d2][i]=(banding_row[col_d2][i]/banding_row_count[col_d2][i]);
bsum[i]+=banding_row[col_d2][i];
}
}
WF_i_1TO4 bmean[i]=bsum[i]/(i<2?width_d2:width_p1_d2);
for(int col_d2=0; col_d2<width_p1_d2; col_d2++)
for(int i=0; i<4; i++)
if (banding_row_count[col_d2][i]>0)
banding_row_i[col_d2][i]=int(banding_row[col_d2][i]-bmean[i]);
for(int row_d2=0; row_d2<height_p1_d2; row_d2++)
{
int row, row_p1;
ushort *src[4];
ushort *src_first, *src_plast, *src_last;
row = row_d2*2;
row_p1 = row+1;
WF_i_1TO4 src[i] = &WF_BAYERSRC((i<2)?row:row_p1, i&1, FC((i<2)?row:row_p1, i&1));
if (row_p1==S.height)
src[2]=src[3]=dummy_line;
src_first = &WF_BAYERSRC(row, 0, FC(row, 0));
src_plast = &WF_BAYERSRC(row, width_d2*2-2, FC(row, 0));
src_last = &WF_BAYERSRC(row, width_p1_d2*2-2, FC(row, 0));
int col_d2=0;
do
{
// Do
int val_new[4];
WF_i_1TO4 val_new[i]=*src[i];
WF_i_1TO4 val_new[i]-=banding_col_i[row_d2][i];
WF_i_1TO4 val_new[i]-=banding_row_i[col_d2][i];
for(int i=0; i<4; i++)
{
if (*src[i]>=val_accepted)
{
val_new[i]=*src[i]>>data_shift;
}
else
{
if (val_new[i]>val_max)
val_new[i]=val_max;
else if (val_new[i]<0)
val_new[i]=0;
val_new[i]>>=data_shift;
}
}
WF_i_1TO4 *src[i]=val_new[i];
// Next 4 pixel or exit
if (src[0]<src_plast)
{
WF_i_1TO4 src[i]+=8;
}
else if(src[0]>src_first && src[0]<src_last)
{
WF_i_1TO4 src[i]=i&1?&dummy_pixel:src[i]+8;
}
else break;
col_d2++;
}
while(1);
}
free(banding_col_i);
free(banding_row_i);
free(tr_weight);
free(banding_col);
free(banding_col_count);
free(banding_row);
free(banding_row_count);
for(int i=0; i<WF_GAUSS_PIRAMID_SIZE; i++)
{
free(gauss_pyramid[i]);
free(difwg_pyramid[i]);
}
free(dummy_line);
return WF_DEBANDING_OK;
}
double LibRaw::wf_filter_energy(int r1_greenmode, int r1, int r2_greenmode, int r2)
{
/*
- This function caclulates energy of laplasyan piramid level.
- Laplasyan level is difference between two 2D gaussian (exactly, binominal) convolutions with radius r1 and r2
+ This function calculates energy of laplasian pyramid level.
+ Laplasian level is difference between two 2D gaussian (exactly, binominal) convolutions with radius r1 and r2
Convolution is done on bayer data, 4 channels, and if (greenmode), additive on green channel.
Not optimized, because now it's used only for precalculations.
*/
#define WF_MAXFILTERSIZE 10000
int rmin, rmax;
int rmin_greenmode, rmax_greenmode;
if (r1>r2)
{
rmax=r1;
rmin=r2;
rmax_greenmode=r1_greenmode;
rmin_greenmode=r2_greenmode;
}
else
{
rmax=r2;
rmin=r1;
rmax_greenmode=r2_greenmode;
rmin_greenmode=r1_greenmode;
}
int rmin_x2_p1, rmax_x2_p1;
rmin_x2_p1=rmin*2+1;
rmax_x2_p1=rmax*2+1;
double gau_kernel_rmin[WF_MAXFILTERSIZE];
double gau_kernel_rmax[WF_MAXFILTERSIZE];
for(int i=0; i<rmax_x2_p1; i++)
gau_kernel_rmin[i]=0;
gau_kernel_rmin[1]=1.0;
for(int i=2; i<=rmin_x2_p1; i++)
{
for(int j=i; j>0; j--)
gau_kernel_rmin[j]=0.5*(gau_kernel_rmin[j]+gau_kernel_rmin[j-1]);
}
for(int i=0; i<=rmax_x2_p1; i++)
gau_kernel_rmax[i]=gau_kernel_rmin[i];
for(int i=rmin_x2_p1+1; i<=rmax_x2_p1; i++)
{
for(int j=i; j>0; j--)
gau_kernel_rmax[j]=0.5*(gau_kernel_rmax[j]+gau_kernel_rmax[j-1]);
}
double wmin_sum, wmax_sum, energy_sum;
wmin_sum=0;
wmax_sum=0;
energy_sum=0;
for(int row=-rmax*2-1; row<=rmax*2+1; row++)
{
for(int col=-rmax*2-1; col<=rmax*2+1; col++)
{
double wght_rmax=0;
double wght_rmin=0;
#define WF_WMAX(row, col) (((abs(row)<=rmax*2)&&(abs(col)<=rmax*2))?gau_kernel_rmax[abs(row)/2+rmax+1]*gau_kernel_rmax[abs(col)/2+rmax+1]:0)
#define WF_WMIN(row, col) (((abs(row)<=rmin*2)&&(abs(col)<=rmin*2))?gau_kernel_rmin[abs(row)/2+rmin+1]*gau_kernel_rmin[abs(col)/2+rmin+1]:0)
if ( ((row&1)==0) && ((col&1)==0))
{
wght_rmax = WF_WMAX(row, col);
wght_rmin = WF_WMIN(row, col);
}
if (rmax_greenmode)
{
if ( ((row&1)==0) && ((col&1)==0))
wght_rmax = 0.5*wght_rmax;
else if ( ((row&1)==1) && ((col&1)==1))
{
wght_rmax = 0.125*(WF_WMAX(row-1, col-1)+WF_WMAX(row-1, col+1)+WF_WMAX(row+1, col-1)+WF_WMAX(row+1, col+1));
}
}
if (rmin_greenmode)
{
if ( ((row&1)==0) && ((col&1)==0))
wght_rmin = 0.5*wght_rmin;
else if ( ((row&1)==1) && ((col&1)==1))
{
wght_rmin = 0.125*(WF_WMIN(row-1, col-1)+WF_WMIN(row-1, col+1)+WF_WMIN(row+1, col-1)+WF_WMIN(row+1, col+1));
}
}
wmin_sum+=wght_rmin;
wmax_sum+=wght_rmax;
energy_sum+=(wght_rmax-wght_rmin)*(wght_rmax-wght_rmin);
}
}
return energy_sum;
}
void LibRaw::wf_bayer4_green_blur(int mode, void* src_image, int src_imgmode, void* dst_image, int dst_imgmode)
{
/*
This function filters green (or any "diagonal") channel of bayer4 data with "X" kernel,
1 1
4
1 1
*/
#define WF_BAYERSRC4(row, col, c) ((ushort(*)[4])src_image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERSRC1(row, col) ((ushort*)src_image) [((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
#define WF_BAYERDST4(row, col, c) ((ushort(*)[4])dst_image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERDST1(row, col) ((ushort*)dst_image) [((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
int green_mode;
if ( imgdata.idata.cdesc[FC(0, 0)] == imgdata.idata.cdesc[FC(1, 1)] )
green_mode = WF_GREENMODE_GX_XG;
else if ( imgdata.idata.cdesc[FC(0, 1)] == imgdata.idata.cdesc[FC(1, 0)] )
green_mode = WF_GREENMODE_XG_GX;
else
green_mode = WF_GREENMODE_IND;
int src_h_shift, dst_h_shift, src_h_shift_x2;
if (src_imgmode == WF_IMGMODE_BAYER1PLANE)
src_h_shift = 2 >> IO.shrink;
else if (src_imgmode == WF_IMGMODE_BAYER4PLANE)
src_h_shift = 8 >> IO.shrink;
src_h_shift_x2 = src_h_shift*2;
if (dst_imgmode == WF_IMGMODE_BAYER1PLANE)
dst_h_shift = 2 >> IO.shrink;
else if (dst_imgmode == WF_IMGMODE_BAYER4PLANE)
dst_h_shift = 8 >> IO.shrink;
int row, col;
long int *line_filtered;
line_filtered = (long int*) calloc(S.width, sizeof(*line_filtered));
ushort *src, *src_c, *src_u1, *src_u2, *src_d1, *src_d2, *dst_c, *src_ca, *dst_ca, *dst_rb;
int start_col, start_col_left, row_up, row_dn;
if ( green_mode != WF_GREENMODE_IND)
{
for(row=0; row<S.height; row++)
{
if (row == 0)
row_up = 1;
else
row_up = row-1;
if (row == S.height-1)
row_dn = S.height-2;
else
row_dn = row+1;
if ( green_mode == WF_GREENMODE_GX_XG )
start_col = row & 1;
else
start_col = ( row+1 ) & 1;
if ( start_col == 0 )
start_col_left = 1;
else
start_col_left = 0;
switch (src_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src_c = &WF_BAYERSRC1(row, start_col);
src_u1 = &WF_BAYERSRC1(row_up, start_col_left);
src_d1 = &WF_BAYERSRC1(row_dn, start_col_left);
src_u2 = &WF_BAYERSRC1(row_up, start_col+1);
src_d2 = &WF_BAYERSRC1(row_dn, start_col+1);
break;
case WF_IMGMODE_BAYER4PLANE:
src_c = &WF_BAYERSRC4(row, start_col, FC(row, start_col));
src_u1 = &WF_BAYERSRC4(row_up, start_col_left, FC(row_up, start_col_left));
src_d1 = &WF_BAYERSRC4(row_dn, start_col_left, FC(row_dn, start_col_left));
src_u2 = &WF_BAYERSRC4(row_up, start_col+1, FC(row_up, start_col+1));
src_d2 = &WF_BAYERSRC4(row_dn, start_col+1, FC(row_dn, start_col+1));
break;
}
long int sum_l1, sum_l2;
sum_l1 = *src_u1 + *src_d1;
sum_l2 = *src_u2 + *src_d2;
if (start_col == 0)
{
// Edges
line_filtered[start_col] = sum_l1 + sum_l2 + (*src_c)*4;
src_u2 += src_h_shift;
src_d2 += src_h_shift;
sum_l2 = *src_u2 + *src_d2;
src_c += src_h_shift;
start_col=2;
}
int width_m_3 = S.width-3;
// Main
for (col=start_col; col<width_m_3; col+=2)
{
line_filtered[col] = sum_l1 + sum_l2 + 4*(*src_c);
src_u1 += src_h_shift_x2;
src_d1 += src_h_shift_x2;
sum_l1 = *src_u1 + *src_d1;
src_c += src_h_shift;
col+=2;
line_filtered[col] = sum_l1 + sum_l2 + 4*(*src_c);
src_u2 += src_h_shift_x2;
src_d2 += src_h_shift_x2;
sum_l2 = *src_u2 + *src_d2;
src_c += src_h_shift;
}
// Right edge
if (col == S.width-1)
{
line_filtered[col] = 2*sum_l1 + 4*(*src_c);
}
else if (col == S.width-2)
{
line_filtered[col] = sum_l1 + sum_l2 + 4*(*src_c);
}
else if (col == S.width-3)
{
line_filtered[col] = sum_l1 + sum_l2 + 4*(*src_c);
src_c += src_h_shift;
col+=2;
line_filtered[col] = 2*sum_l2 + 4*(*src_c);
}
if (row>0)
{
if ( green_mode == WF_GREENMODE_GX_XG )
start_col = ( row+1 ) & 1;
else
start_col = row & 1;
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst_c = &WF_BAYERDST1(row-1, start_col);
break;
case WF_IMGMODE_BAYER4PLANE:
dst_c = &WF_BAYERDST4(row-1, start_col, FC(row-1, start_col));
break;
}
for (col=start_col; col<S.width; col+=2)
{
*dst_c=(line_filtered[col])>>3;
dst_c+=dst_h_shift;
}
if (src_image != dst_image)
{
// copy red or blue channel
if ( green_mode == WF_GREENMODE_GX_XG )
start_col = row & 1;
else
start_col = (row+1) & 1;
switch (src_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src = &WF_BAYERSRC1(row-1, start_col);
break;
case WF_IMGMODE_BAYER4PLANE:
src = &WF_BAYERSRC4(row-1, start_col, FC(row-1, start_col));
break;
}
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst_rb = &WF_BAYERDST1(row-1, start_col);
break;
case WF_IMGMODE_BAYER4PLANE:
dst_rb = &WF_BAYERDST4(row-1, start_col, FC(row-1, start_col));
break;
}
for (col=start_col; col<S.width; col+=2)
{
*dst_rb=*src;
src +=src_h_shift;
dst_rb+=dst_h_shift;
}
}
}
}
if ( green_mode == WF_GREENMODE_GX_XG )
start_col = ( row+1 ) & 1;
else
start_col = row & 1;
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst_c = &WF_BAYERDST1(row-1, start_col);
break;
case WF_IMGMODE_BAYER4PLANE:
dst_c = &WF_BAYERDST4(row-1, start_col, FC(row-1, start_col));
break;
}
for (col=start_col; col<S.width; col+=2)
{
*dst_c=(line_filtered[col])>>3;
dst_c+=dst_h_shift;
}
if (src_image != dst_image)
{
// copy red or blue channel
if ( green_mode == WF_GREENMODE_GX_XG )
start_col = row & 1;
else
start_col = (row+1) & 1;
switch (src_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src = &WF_BAYERSRC1(row-1, start_col);
break;
case WF_IMGMODE_BAYER4PLANE:
src = &WF_BAYERSRC4(row-1, start_col, FC(row-1, start_col));
break;
}
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst_rb = &WF_BAYERDST1(row-1, start_col);
break;
case WF_IMGMODE_BAYER4PLANE:
dst_rb = &WF_BAYERDST4(row-1, start_col, FC(row-1, start_col));
break;
}
for (col=start_col; col<S.width; col+=2)
{
*dst_rb=*src;
src +=src_h_shift;
dst_rb+=dst_h_shift;
}
}
}
free(line_filtered);
}
void LibRaw::wf_bayer4_igauss_filter(int radius, void* src_image, int src_imgmode, void* dst_image, int dst_imgmode)
{
/*
This function filter source bayer4 data with gauss (binominal), 4 channels independently.
*/
#define WF_BAYERSRC4(row, col, c) ((ushort(*)[4])src_image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERSRC1(row, col) ((ushort*)src_image) [((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
#define WF_BAYERDST4(row, col, c) ((ushort(*)[4])dst_image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERDST1(row, col) ((ushort*)dst_image) [((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
if (radius <= 0 || radius > 8)
return;
long int (*line_filtered)[4];
long int gauss_conv_kernel[9][4];
long int gauss_conv_kernel_c[8][9] =
{
{32768, 16384},
{24576, 16384, 4096},
{20480, 15360, 6144, 1024},
{17920, 14336, 7168, 2048, 256},
{16128, 13440, 7680, 2880, 640, 64},
{14784, 12672, 7920, 3520, 1056, 192, 16},
{13728, 12012, 8008, 4004, 1456, 364, 56, 4},
{12870, 11440, 8008, 4368, 1820, 560, 120, 16, 1},
};
int line_memory_len = (MAX(S.height, S.width)+1)/2+radius*2+1;
line_filtered = (long int(*)[4]) calloc(line_memory_len, sizeof(long int[4]));
int src_h_shift, src_v_shift;
int dst_h_shift, dst_v_shift;
if (src_imgmode == WF_IMGMODE_BAYER1PLANE)
src_h_shift = 2 >> IO.shrink;
else if (src_imgmode == WF_IMGMODE_BAYER4PLANE)
src_h_shift = 8 >> IO.shrink;
src_v_shift = S.width*src_h_shift;
if (dst_imgmode == WF_IMGMODE_BAYER1PLANE)
dst_h_shift = 2 >> IO.shrink;
else if (dst_imgmode == WF_IMGMODE_BAYER4PLANE)
dst_h_shift = 8 >> IO.shrink;
dst_v_shift = S.width*dst_h_shift;
int width_d2 = S.width / 2;
int height_d2 = S.height / 2;
int i, j;
for (j=0; j<=radius; j++)
{
for (i=0; i<4; i++)
{
gauss_conv_kernel[j][i] = gauss_conv_kernel_c[radius-1][j];
}
}
int row, col;
int rowf, colf;
ushort *src [4], *dst[4];
long int src_c[4];
// Horizontal
int right_edge[4];
for (i=0; i<4; i++)
{
int padding = i<2 && (S.width & 1) ? 1 : 0;
right_edge[i]=width_d2 + radius + padding;
}
for(row=0; row<S.height; row+=2)
{
int row_p1=MIN(row+1, S.height-1);
switch (src_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src[0] = &WF_BAYERSRC1(row, 0);
src[1] = &WF_BAYERSRC1(row_p1, 0);
src[2] = &WF_BAYERSRC1(row, 1);
src[3] = &WF_BAYERSRC1(row_p1, 1);
break;
case WF_IMGMODE_BAYER4PLANE:
src[0] = &WF_BAYERSRC4(row, 0, FC(0, 0));
src[1] = &WF_BAYERSRC4(row_p1, 0, FC(row_p1, 0));
src[2] = &WF_BAYERSRC4(row, 1, FC(0, 1));
src[3] = &WF_BAYERSRC4(row_p1, 1, FC(row_p1, 1));
break;
}
colf = radius;
for (int j=0; j<line_memory_len; j++)
{
for(i=0;i<4;i++)
line_filtered[j][i]=0;
}
for(col=0; col<S.width-1; col+=2)
{
int col1, col2;
col1=col2=colf;
for (i=0; i<4; i++)
src_c[i]=*src[i];
for (i=0; i<4; i++)
line_filtered[colf][i]+=gauss_conv_kernel[0][i]*(src_c[i]);
for(int j=1; j<=radius; j++)
{
col1++;
col2--;
for (i=0; i<4; i++)
{
long int g;
g = gauss_conv_kernel[j][i]*src_c[i];
line_filtered[col1][i]+=g;
line_filtered[col2][i]+=g;
}
}
colf++;
for (i=0; i<4; i++)
src[i]+=src_h_shift;
}
// width is odd number
if (col == S.width-1)
{
int col1, col2;
col1=col2=colf;
for (i=0; i<2; i++)
src_c[i]=*src[i];
for (i=0; i<2; i++)
line_filtered[colf][i]+=gauss_conv_kernel[0][i]*(src_c[i]);
for(int j=1; j<=radius; j++)
{
col1++;
col2--;
for (i=0; i<2; i++)
{
long int g;
g = gauss_conv_kernel[j][i]*src_c[i];
line_filtered[col1][i]+=g;
line_filtered[col2][i]+=g;
}
}
colf++;
for (i=0; i<2; i++)
src[i]+=src_h_shift;
}
// Edges mirroring
for(j=0; j<radius; j++)
{
for (i=0; i<4; i++)
{
line_filtered[radius+j ][i]+=line_filtered[radius-j-1 ][i];
line_filtered[right_edge[i]-1-j][i]+=line_filtered[right_edge[i]+j][i];
}
}
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst[0] = &WF_BAYERDST1(row, 0);
dst[1] = &WF_BAYERDST1(row_p1, 0);
dst[2] = &WF_BAYERDST1(row, 1);
dst[3] = &WF_BAYERDST1(row_p1, 1);
break;
case WF_IMGMODE_BAYER4PLANE:
dst[0] = &WF_BAYERDST4(row, 0, FC(0, 0));
dst[1] = &WF_BAYERDST4(row_p1, 0, FC(row_p1, 0));
dst[2] = &WF_BAYERDST4(row, 1, FC(0, 1));
dst[3] = &WF_BAYERDST4(row_p1, 1, FC(row_p1, 1));
break;
}
colf = radius;
for(col=0; col<S.width-1; col+=2)
{
for(i=0; i<4; i++)
{
*dst[i]=line_filtered[colf][i]>>16;
dst[i]+=dst_h_shift;
}
colf++;
}
if (col == S.width-1)
{
for(i=0; i<2; i++)
*dst[i]=line_filtered[colf][i]>>16;
}
}
// Vertical
int lower_edge[4];
for (i=0; i<4; i++)
{
int padding = i<2 && (S.height & 1 ) ? 1 : 0;
lower_edge[i]=height_d2 + radius + padding;
}
for(col=0; col<S.width; col+=2)
{
int col_p1=MIN(col+1, S.width-1);
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src[0] = &WF_BAYERDST1(0, col);
src[1] = &WF_BAYERDST1(0, col_p1);
src[2] = &WF_BAYERDST1(1, col);
src[3] = &WF_BAYERDST1(1, col_p1);
break;
case WF_IMGMODE_BAYER4PLANE:
src[0] = &WF_BAYERDST4(0, col, FC(0, 0));
src[1] = &WF_BAYERDST4(0, col_p1, FC(0, col_p1));
src[2] = &WF_BAYERDST4(1, col, FC(1, 0));
src[3] = &WF_BAYERDST4(1, col_p1, FC(1, col_p1));
break;
}
rowf = radius;
for (int j=0; j<line_memory_len; j++)
{
for(i=0;i<4;i++)
line_filtered[j][i]=0;
}
for(row=0; row<S.height-1; row+=2)
{
int row1, row2;
row1=row2=rowf;
for (i=0; i<4; i++)
src_c[i]=*src[i];
for (i=0; i<4; i++)
line_filtered[rowf][i]+=gauss_conv_kernel[0][i]*(src_c[i]);
for(int j=1; j<=radius; j++)
{
row1++;
row2--;
long int g[4];
for (i=0; i<4; i++)
{
g[i] = gauss_conv_kernel[j][i]*src_c[i];
line_filtered[row1][i]+=g[i];
line_filtered[row2][i]+=g[i];
}
}
rowf++;
for (i=0; i<4; i++)
src[i]+=dst_v_shift;
}
// height is odd number
if (row == S.height-1)
{
int row1, row2;
row1=row2=rowf;
for (i=0; i<2; i++)
src_c[i]=*src[i];
for (i=0; i<2; i++)
line_filtered[rowf][i]+=gauss_conv_kernel[0][i]*(src_c[i]);
for(int j=1; j<=radius; j++)
{
row1++;
row2--;
long int g[4];
for (i=0; i<2; i++)
{
g[i] = gauss_conv_kernel[j][i]*src_c[i];
line_filtered[row1][i]+=g[i];
line_filtered[row2][i]+=g[i];
}
}
rowf++;
for (i=0; i<2; i++)
src[i]+=dst_v_shift;
}
// Edge mirroring
for(int j=0; j<radius; j++)
{
for (int i=0; i<4; i++)
{
line_filtered[radius+j][i] +=line_filtered[radius-j-1][i];
line_filtered[lower_edge[i]-1-j][i]+=line_filtered[lower_edge[i]+j][i];
}
}
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst[0] = &WF_BAYERDST1(0, col);
dst[1] = &WF_BAYERDST1(0, col_p1);
dst[2] = &WF_BAYERDST1(1, col);
dst[3] = &WF_BAYERDST1(1, col_p1);
break;
case WF_IMGMODE_BAYER4PLANE:
dst[0] = &WF_BAYERDST4(0, col, FC(0, 0));
dst[1] = &WF_BAYERDST4(0, col_p1, FC(0, col_p1));
dst[2] = &WF_BAYERDST4(1, col, FC(1, 0));
dst[3] = &WF_BAYERDST4(1, col_p1, FC(1, col_p1));
break;
}
rowf = radius;
for(row=0; row<S.height-1; row+=2)
{
for(i=0; i<4; i++)
{
*dst[i]=line_filtered[rowf][i]>>16;
dst[i]+=dst_v_shift;
}
rowf++;
}
if (row == S.height-1)
{
for(i=0; i<2; i++)
*dst[i]=line_filtered[rowf][i]>>16;
}
}
free(line_filtered);
}
void LibRaw::wf_bayer4_block_filter(int* radius_list, void* src_image, int src_imgmode, void* dst_image, int dst_imgmode)
{
#define WF_BLOCKFILTER_MAXF 8
#define WF_BAYERSRC4(row,col,c) ((ushort(*)[4])src_image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERSRC1(row,col) ((ushort*)src_image) [((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
#define WF_BAYERDST4(row,col,c) ((ushort(*)[4])dst_image)[((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)][c]
#define WF_BAYERDST1(row,col) ((ushort*)dst_image) [((row) >> IO.shrink)*S.iwidth + ((col) >> IO.shrink)]
int filter_itrtns_num = 0;
int block_radius [WF_BLOCKFILTER_MAXF];
int block_radius_x2 [WF_BLOCKFILTER_MAXF];
int block_radius_x2_p1[WF_BLOCKFILTER_MAXF];
int block_radius_max = 0;
int block_radius_max_x2;
int block_radius_max_x2_p1;
for(int i=0; (i<WF_BLOCKFILTER_MAXF) && (radius_list[i]!=0); i++)
{
block_radius [i]=radius_list[i];
block_radius_x2 [i]=block_radius[i]*2;
block_radius_x2_p1[i]=block_radius_x2[i]+1;
if(block_radius_max<block_radius[i])
block_radius_max=block_radius[i];
filter_itrtns_num++;
}
long int divider[WF_BLOCKFILTER_MAXF];
long int div_multiplication;
div_multiplication=block_radius_x2_p1[0];
for(int i=1; i<filter_itrtns_num; i++)
{
if (div_multiplication*((long int)block_radius_x2_p1[i])<65535)
{
div_multiplication*=block_radius_x2_p1[i];
divider[i-1]=1;
}
else
{
divider[i-1]=block_radius_x2_p1[i];
}
}
divider[filter_itrtns_num-1]=div_multiplication;
block_radius_max_x2 = block_radius_max*2;
block_radius_max_x2_p1 = block_radius_max_x2+1;
int line_memory_len;
long int (*source_line)[4];
long int (*line_block_filtered)[4];
line_memory_len = (MAX(S.height, S.width)+1)/2+block_radius_max_x2_p1*2;
line_block_filtered=(long int(*)[4]) calloc(line_memory_len, sizeof(long int[4]));
source_line =(long int(*)[4]) calloc(line_memory_len, sizeof(long int[4]));
int src_h_shift, dst_h_shift, src_v_shift, dst_v_shift;
if (src_imgmode == WF_IMGMODE_BAYER1PLANE)
src_h_shift = 2 >> IO.shrink;
else if (src_imgmode == WF_IMGMODE_BAYER4PLANE)
src_h_shift = 8 >> IO.shrink;
src_v_shift = S.width*src_h_shift;
if (dst_imgmode == WF_IMGMODE_BAYER1PLANE)
dst_h_shift = 2 >> IO.shrink;
else if (dst_imgmode == WF_IMGMODE_BAYER4PLANE)
dst_h_shift = 8 >> IO.shrink;
dst_v_shift = S.width*dst_h_shift;
int width_d2 = S.width / 2;
int height_d2 = S.height / 2;
int width_p1_d2 = (S.width+1) / 2;
int height_p1_d2 = (S.height+1) / 2;
ushort *src[4], *dst[4];
long int (*src_plus)[4], (*src_minus)[4];
long int block_sum[4];
int row, col;
int right_edge[4], lower_edge[4];
for(row=0; row<S.height; row+=2)
{
int row_p1=MIN(row+1, S.height-1);
switch (src_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src[0] = &WF_BAYERSRC1(row, 0);
src[1] = &WF_BAYERSRC1(row_p1, 0);
src[2] = &WF_BAYERSRC1(row, 1);
src[3] = &WF_BAYERSRC1(row_p1, 1);
break;
case WF_IMGMODE_BAYER4PLANE:
src[0] = &WF_BAYERSRC4(row, 0, FC(0, 0));
src[1] = &WF_BAYERSRC4(row_p1, 0, FC(row_p1, 0));
src[2] = &WF_BAYERSRC4(row, 1, FC(0, 1));
src[3] = &WF_BAYERSRC4(row_p1, 1, FC(row_p1, 1));
break;
}
for(col=0; col<width_d2; col++)
{
for (int i=0; i<4; i++)
{
source_line[col][i]=*src[i];
src[i] += src_h_shift;
}
}
if ((S.width & 1) == 1)
{
for (int i=0; i<2; i++)
{
source_line[width_d2][i]=*src[i];
}
for (int i=2; i<4; i++)
{
source_line[width_d2][i]=0;
}
}
for(int f=0; f<filter_itrtns_num; f++)
{
src_minus=src_plus=source_line;
for (int i=0; i<4; i++)
block_sum[i]=0;
for(col=0; col<block_radius_x2_p1[f]; col++)
{
for (int i=0; i<4; i++)
{
block_sum[i]+=(*src_plus)[i];
line_block_filtered[col][i]=block_sum[i];
}
src_plus++;
}
for(col=block_radius_x2_p1[f]; col<width_p1_d2; col++)
{
for (int i=0; i<4; i++)
{
block_sum[i]+=(*src_plus)[i];
block_sum[i]-=(*src_minus)[i];
line_block_filtered[col][i]=block_sum[i];
}
src_plus++;
src_minus++;
}
for(col=width_p1_d2; col<width_p1_d2+block_radius_x2_p1[f]; col++)
{
for (int i=0; i<4; i++)
{
block_sum[i]-=(*src_minus)[i];
line_block_filtered[col][i]=block_sum[i];
}
src_minus++;
}
// Edge mirroring
for (int i=0; i<4; i++)
{
int padding = i<2 && ((S.width & 1) == 1) ? 1 : 0;
right_edge[i]=width_d2 + block_radius[f] + padding;
}
for(int j=0; j<block_radius[f]; j++)
{
for (int i=0; i<4; i++)
{
line_block_filtered[block_radius[f]+j][i]+=line_block_filtered[block_radius[f]-j-1][i];
line_block_filtered[right_edge[i]-1-j][i]+=line_block_filtered[right_edge[i]+j][i];
}
}
if (divider[f]==1)
{
for(col=0; col<width_d2; col++)
{
for (int i=0; i<4; i++)
source_line[col][i]=line_block_filtered[col+block_radius[f]][i];
}
if ((S.width & 1) == 1)
{
for (int i=0; i<2; i++)
source_line[width_d2][i]=line_block_filtered[width_d2+block_radius[f]][i];
for (int i=2; i<4; i++)
source_line[width_d2][i]=0;
}
}
else
{
for(col=0; col<width_d2; col++)
{
for (int i=0; i<4; i++)
source_line[col][i]=line_block_filtered[col+block_radius[f]][i]/divider[f];
}
if ((S.width & 1) == 1)
{
for (int i=0; i<2; i++)
source_line[width_d2][i]=line_block_filtered[width_d2+block_radius[f]][i]/divider[f];
for (int i=2; i<4; i++)
source_line[width_d2][i]=0;
}
}
}
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst[0] = &WF_BAYERDST1(row, 0);
dst[1] = &WF_BAYERDST1(row_p1, 0);
dst[2] = &WF_BAYERDST1(row, 1);
dst[3] = &WF_BAYERDST1(row_p1, 1);
break;
case WF_IMGMODE_BAYER4PLANE:
dst[0] = &WF_BAYERDST4(row, 0, FC(0, 0));
dst[1] = &WF_BAYERDST4(row_p1, 0, FC(row_p1, 0));
dst[2] = &WF_BAYERDST4(row, 1, FC(0, 1));
dst[3] = &WF_BAYERDST4(row_p1, 1, FC(row_p1, 1));
break;
}
for(col=0; col<width_d2; col++)
{
for (int i=0; i<4; i++)
{
*dst[i]=source_line[col][i];
dst[i]+=dst_h_shift;
}
}
if ((S.width & 1) == 1)
{
for (int i=0; i<2; i++)
*dst[i]=source_line[col][i];
}
}
for(col=0; col<S.width; col+=2)
{
int col_p1=MIN(col+1, S.width-1);
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
src[0] = &WF_BAYERDST1(0, col);
src[1] = &WF_BAYERDST1(0, col_p1);
src[2] = &WF_BAYERDST1(1, col);
src[3] = &WF_BAYERDST1(1, col_p1);
break;
case WF_IMGMODE_BAYER4PLANE:
src[0] = &WF_BAYERDST4(0, col, FC(0, 0));
src[1] = &WF_BAYERDST4(0, col_p1, FC(0, col_p1));
src[2] = &WF_BAYERDST4(1, col, FC(1, 0));
src[3] = &WF_BAYERDST4(1, col_p1, FC(1, col_p1));
break;
}
for(row=0; row<height_d2; row++)
{
for (int i=0; i<4; i++)
{
source_line[row][i]=*src[i];
src[i] += dst_v_shift;
}
}
if ((S.height & 1) == 1)
{
for (int i=0; i<2; i++)
{
source_line[height_d2][i]=*src[i];
}
for (int i=2; i<4; i++)
{
source_line[height_d2][i]=0;
}
}
for(int f=0; f<filter_itrtns_num; f++)
{
src_minus=src_plus=source_line;
for (int i=0; i<4; i++)
block_sum[i]=0;
for(row=0; row<block_radius_x2_p1[f]; row++)
{
for (int i=0; i<4; i++)
{
block_sum[i]+=(*src_plus)[i];
line_block_filtered[row][i]=block_sum[i];
}
src_plus++;
}
for(row=block_radius_x2_p1[f]; row<height_p1_d2; row++)
{
for (int i=0; i<4; i++)
{
block_sum[i]+=(*src_plus)[i];
block_sum[i]-=(*src_minus)[i];
line_block_filtered[row][i]=block_sum[i];
}
src_plus++;
src_minus++;
}
for(row=height_p1_d2; row<height_p1_d2+block_radius_x2_p1[f]; row++)
{
for (int i=0; i<4; i++)
{
block_sum[i]-=(*src_minus)[i];
line_block_filtered[row][i]=block_sum[i];
}
src_minus++;
}
// Edge mirroring
for (int i=0; i<4; i++)
{
int padding = (i<2) && ((S.height & 1) == 1) ? 1 : 0;
lower_edge[i]=height_d2 + block_radius[f] + padding;
}
for(int j=0; j<block_radius[f]; j++)
{
for (int i=0; i<4; i++)
{
line_block_filtered[block_radius[f]+j][i]+=line_block_filtered[block_radius[f]-j-1][i];
line_block_filtered[lower_edge[i]-1-j][i]+=line_block_filtered[lower_edge[i]+j][i];
}
}
if (divider[f]==1)
{
for(row=0; row<height_d2; row++)
{
for (int i=0; i<4; i++)
source_line[row][i]=line_block_filtered[row+block_radius[f]][i];
}
if ((S.height & 1) == 1)
{
for (int i=0; i<2; i++)
source_line[height_d2][i]=line_block_filtered[height_d2+block_radius[f]][i];
for (int i=2; i<4; i++)
source_line[height_d2][i]=0;
}
}
else
{
for(row=0; row<height_d2; row++)
{
for (int i=0; i<4; i++)
source_line[row][i]=line_block_filtered[row+block_radius[f]][i]/divider[f];
}
if ((S.height & 1) == 1)
{
for (int i=0; i<2; i++)
source_line[height_d2][i]=line_block_filtered[height_d2+block_radius[f]][i]/divider[f];
for (int i=2; i<4; i++)
source_line[height_d2][i]=0;
}
}
}
switch (dst_imgmode)
{
case WF_IMGMODE_BAYER1PLANE:
dst[0] = &WF_BAYERDST1(0, col);
dst[1] = &WF_BAYERDST1(0, col_p1);
dst[2] = &WF_BAYERDST1(1, col);
dst[3] = &WF_BAYERDST1(1, col_p1);
break;
case WF_IMGMODE_BAYER4PLANE:
dst[0] = &WF_BAYERDST4(0, col, FC(0, 0));
dst[1] = &WF_BAYERDST4(0, col_p1, FC(0, col_p1));
dst[2] = &WF_BAYERDST4(1, col, FC(1, 0));
dst[3] = &WF_BAYERDST4(1, col_p1, FC(1, col_p1));
break;
}
for(row=0; row<height_d2; row++)
{
for (int i=0; i<4; i++)
{
*dst[i]=source_line[row][i];
dst[i]+=dst_v_shift;
}
}
if ((S.height & 1) == 1)
{
for (int i=0; i<2; i++)
*dst[i]=source_line[height_d2][i];
}
}
free(line_block_filtered);
free(source_line);
}
#undef P1
#undef S
#undef O
#undef C
#undef T
#undef IO
#undef ID
diff --git a/core/libs/rawengine/libraw/libraw/libraw_types.h b/core/libs/rawengine/libraw/libraw/libraw_types.h
index 1b46664989..1865c4c968 100644
--- a/core/libs/rawengine/libraw/libraw/libraw_types.h
+++ b/core/libs/rawengine/libraw/libraw/libraw_types.h
@@ -1,790 +1,790 @@
/* -*- C++ -*-
* File: libraw_types.h
* Copyright 2008-2018 LibRaw LLC (info@libraw.org)
* Created: Sat Mar 8 , 2008
*
* LibRaw C data structures
*
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
*/
#ifndef _LIBRAW_TYPES_H
#define _LIBRAW_TYPES_H
#include <sys/types.h>
#ifndef WIN32
#include <sys/time.h>
#endif
#include <stdio.h>
#if defined(_WIN32)
#if defined(_MSC_VER) && (_MSC_VER <= 1500)
typedef signed __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef signed __int16 int16_t;
typedef unsigned __int16 uint16_t;
typedef signed __int32 int32_t;
typedef unsigned __int32 uint32_t;
typedef signed __int64 int64_t;
typedef unsigned __int64 uint64_t;
#else
#include <stdint.h>
#endif /* _WIN32 */
#include <sys/types.h>
#else
#include <inttypes.h>
#endif
#if defined(_OPENMP)
#if defined(WIN32)
#if defined(_MSC_VER) && (_MSC_VER >= 1600 || (_MSC_VER == 1500 && _MSC_FULL_VER >= 150030729))
/* VS2010+ : OpenMP works OK, VS2008: have tested by cgilles */
#define LIBRAW_USE_OPENMP
#elif defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 910)
/* Have not tested on 9.x and 10.x, but Intel documentation claims OpenMP 2.5 support in 9.1 */
#define LIBRAW_USE_OPENMP
#else
#undef LIBRAW_USE_OPENMP
#endif
/* Not Win32 */
#elif (defined(__APPLE__) || defined(__MACOSX__)) && defined(_REENTRANT)
#undef LIBRAW_USE_OPENMP
#else
#define LIBRAW_USE_OPENMP
#endif
#endif
#ifdef LIBRAW_USE_OPENMP
#include <omp.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
#if defined(USE_LCMS)
#include <lcms.h>
#elif defined(USE_LCMS2)
#include <lcms2.h>
#else
#define NO_LCMS
#endif
#include "libraw_const.h"
#include "libraw_version.h"
#ifdef WIN32
typedef __int64 INT64;
typedef unsigned __int64 UINT64;
#else
typedef long long INT64;
typedef unsigned long long UINT64;
#endif
typedef unsigned char uchar;
typedef unsigned short ushort;
#ifdef WIN32
# ifdef LIBRAW_NODLL
# define DllDef
# else
# ifdef LIBRAW_BUILDLIB
# define DllDef __declspec(dllexport)
# else
# define DllDef __declspec(dllimport)
# endif
# endif
#else
# define DllDef __attribute__((visibility("default")))
#endif
typedef struct
{
const char *decoder_name;
unsigned decoder_flags;
} libraw_decoder_info_t;
typedef struct
{
unsigned mix_green;
unsigned raw_color;
unsigned zero_is_bad;
ushort shrink;
ushort fuji_width;
} libraw_internal_output_params_t;
typedef void (*memory_callback)(void *data, const char *file, const char *where);
typedef void (*exif_parser_callback)(void *context, int tag, int type, int len, unsigned int ord, void *ifp);
DllDef void default_memory_callback(void *data, const char *file, const char *where);
typedef void (*data_callback)(void *data, const char *file, const int offset);
DllDef void default_data_callback(void *data, const char *file, const int offset);
typedef int (*progress_callback)(void *data, enum LibRaw_progress stage, int iteration, int expected);
typedef int (*pre_identify_callback)(void *ctx);
typedef void (*post_identify_callback)(void *ctx);
typedef void (*process_step_callback)(void *ctx);
typedef struct
{
memory_callback mem_cb;
void *memcb_data;
data_callback data_cb;
void *datacb_data;
progress_callback progress_cb;
void *progresscb_data;
exif_parser_callback exif_cb;
void *exifparser_data;
pre_identify_callback pre_identify_cb;
post_identify_callback post_identify_cb;
process_step_callback pre_subtractblack_cb, pre_scalecolors_cb, pre_preinterpolate_cb, pre_interpolate_cb,
interpolate_bayer_cb, interpolate_xtrans_cb,
post_interpolate_cb, pre_converttorgb_cb, post_converttorgb_cb;
} libraw_callbacks_t;
typedef struct
{
enum LibRaw_image_formats type;
ushort height, width, colors, bits;
unsigned int data_size;
unsigned char data[1];
} libraw_processed_image_t;
typedef struct
{
char guard[4];
char make[64];
char model[64];
char software[64];
unsigned raw_count;
unsigned dng_version;
unsigned is_foveon;
int colors;
unsigned filters;
char xtrans[6][6];
char xtrans_abs[6][6];
char cdesc[5];
unsigned xmplen;
char *xmpdata;
} libraw_iparams_t;
typedef struct
{
ushort cleft, ctop, cwidth, cheight;
} libraw_raw_crop_t;
typedef struct
{
ushort raw_height, raw_width, height, width, top_margin, left_margin;
ushort iheight, iwidth;
unsigned raw_pitch;
double pixel_aspect;
int flip;
int mask[8][4];
libraw_raw_crop_t raw_crop;
} libraw_image_sizes_t;
struct ph1_t
{
int format, key_off, tag_21a;
int t_black, split_col, black_col, split_row, black_row;
float tag_210;
};
typedef struct
{
unsigned parsedfields;
ushort illuminant;
float calibration[4][4];
float colormatrix[4][3];
float forwardmatrix[3][4];
} libraw_dng_color_t;
typedef struct
{
unsigned parsedfields;
unsigned dng_cblack[4102];
unsigned dng_black;
unsigned dng_whitelevel[4];
unsigned default_crop[4]; /* Origin and size */
unsigned preview_colorspace;
float analogbalance[4];
} libraw_dng_levels_t;
typedef struct
{
float romm_cam[9];
} libraw_P1_color_t;
typedef struct
{
int CanonColorDataVer;
int CanonColorDataSubVer;
int SpecularWhiteLevel;
int NormalWhiteLevel;
int ChannelBlackLevel[4];
int AverageBlackLevel;
/* multishot */
unsigned int multishot[4];
/* metering */
short MeteringMode;
short SpotMeteringMode;
uchar FlashMeteringMode;
short FlashExposureLock;
short ExposureMode;
short AESetting;
uchar HighlightTonePriority;
/* stabilization */
short ImageStabilization;
/* focus */
short FocusMode;
short AFPoint;
short FocusContinuous;
short AFPointsInFocus30D;
uchar AFPointsInFocus1D[8];
ushort AFPointsInFocus5D; /* bytes in reverse*/
/* AFInfo */
ushort AFAreaMode;
ushort NumAFPoints;
ushort ValidAFPoints;
ushort AFImageWidth;
ushort AFImageHeight;
short AFAreaWidths[61]; /* cycle to NumAFPoints */
short AFAreaHeights[61]; /* --''-- */
short AFAreaXPositions[61]; /* --''-- */
short AFAreaYPositions[61]; /* --''-- */
short AFPointsInFocus[4]; /* cycle to floor((NumAFPoints+15)/16) */
short AFPointsSelected[4]; /* --''-- */
ushort PrimaryAFPoint;
/* flash */
short FlashMode;
short FlashActivity;
short FlashBits;
short ManualFlashOutput;
short FlashOutput;
short FlashGuideNumber;
/* drive */
short ContinuousDrive;
/* sensor */
short SensorWidth;
short SensorHeight;
short SensorLeftBorder;
short SensorTopBorder;
short SensorRightBorder;
short SensorBottomBorder;
short BlackMaskLeftBorder;
short BlackMaskTopBorder;
short BlackMaskRightBorder;
short BlackMaskBottomBorder;
int AFMicroAdjMode;
float AFMicroAdjValue;
} libraw_canon_makernotes_t;
typedef struct
{
int BaseISO;
double Gain;
} libraw_hasselblad_makernotes_t;
typedef struct
{
float FujiExpoMidPointShift;
ushort FujiDynamicRange;
ushort FujiFilmMode;
ushort FujiDynamicRangeSetting;
ushort FujiDevelopmentDynamicRange;
ushort FujiAutoDynamicRange;
ushort FocusMode;
ushort AFMode;
ushort FocusPixel[2];
ushort ImageStabilization[3];
ushort FlashMode;
ushort WB_Preset;
ushort ShutterType;
ushort ExrMode;
ushort Macro;
unsigned Rating;
ushort FrameRate;
ushort FrameWidth;
ushort FrameHeight;
} libraw_fuji_info_t;
typedef struct
{
double ExposureBracketValue;
ushort ActiveDLighting;
ushort ShootingMode;
/* stabilization */
uchar ImageStabilization[7];
uchar VibrationReduction;
uchar VRMode;
/* focus */
char FocusMode[7];
uchar AFPoint;
ushort AFPointsInFocus;
uchar ContrastDetectAF;
uchar AFAreaMode;
uchar PhaseDetectAF;
uchar PrimaryAFPoint;
uchar AFPointsUsed[29];
ushort AFImageWidth;
ushort AFImageHeight;
ushort AFAreaXPposition;
ushort AFAreaYPosition;
ushort AFAreaWidth;
ushort AFAreaHeight;
uchar ContrastDetectAFInFocus;
/* flash */
char FlashSetting[13];
char FlashType[20];
uchar FlashExposureCompensation[4];
uchar ExternalFlashExposureComp[4];
uchar FlashExposureBracketValue[4];
uchar FlashMode;
signed char FlashExposureCompensation2;
signed char FlashExposureCompensation3;
signed char FlashExposureCompensation4;
uchar FlashSource;
uchar FlashFirmware[2];
uchar ExternalFlashFlags;
uchar FlashControlCommanderMode;
uchar FlashOutputAndCompensation;
uchar FlashFocalLength;
uchar FlashGNDistance;
uchar FlashGroupControlMode[4];
uchar FlashGroupOutputAndCompensation[4];
uchar FlashColorFilter;
ushort NEFCompression;
int ExposureMode;
int nMEshots;
int MEgainOn;
double ME_WB[4];
uchar AFFineTune;
uchar AFFineTuneIndex;
int8_t AFFineTuneAdj;
} libraw_nikon_makernotes_t;
typedef struct
{
int OlympusCropID;
ushort OlympusFrame[4]; /* upper left XY, lower right XY */
int OlympusSensorCalibration[2];
ushort FocusMode[2];
ushort AutoFocus;
ushort AFPoint;
unsigned AFAreas[64];
double AFPointSelected[5];
ushort AFResult;
unsigned ImageStabilization;
ushort ColorSpace;
uchar AFFineTune;
short AFFineTuneAdj[3];
} libraw_olympus_makernotes_t;
typedef struct
{
/* Compression:
34826 (Panasonic RAW 2): LEICA DIGILUX 2;
34828 (Panasonic RAW 3): LEICA D-LUX 3; LEICA V-LUX 1; Panasonic DMC-LX1; Panasonic DMC-LX2; Panasonic DMC-FZ30; Panasonic DMC-FZ50;
34830 (not in exiftool): LEICA DIGILUX 3; Panasonic DMC-L1;
34316 (Panasonic RAW 1): others (LEICA, Panasonic, YUNEEC);
*/
ushort Compression;
ushort BlackLevelDim;
float BlackLevel[8];
} libraw_panasonic_makernotes_t;
typedef struct
{
ushort FocusMode;
ushort AFPointSelected;
unsigned AFPointsInFocus;
ushort FocusPosition;
uchar DriveMode[4];
short AFAdjustment;
/* uchar AFPointMode; */
/* uchar SRResult; */
/* uchar ShakeReduction; */
} libraw_pentax_makernotes_t;
typedef struct
{
ushort BlackLevelTop;
ushort BlackLevelBottom;
short offset_left, offset_top; /* KDC files, negative values or zeros */
ushort clipBlack, clipWhite; /* valid for P712, P850, P880 */
float romm_camDaylight[3][3];
float romm_camTungsten[3][3];
float romm_camFluorescent[3][3];
float romm_camFlash[3][3];
float romm_camCustom[3][3];
float romm_camAuto[3][3];
} libraw_kodak_makernotes_t;
typedef struct
{
ushort SonyCameraType;
uchar Sony0x9400_version; /* 0 if not found/deciphered, 0xa, 0xb, 0xc following exiftool convention */
uchar Sony0x9400_ReleaseMode2;
unsigned Sony0x9400_SequenceImageNumber;
uchar Sony0x9400_SequenceLength1;
unsigned Sony0x9400_SequenceFileNumber;
uchar Sony0x9400_SequenceLength2;
libraw_raw_crop_t raw_crop;
int8_t AFMicroAdjValue;
int8_t AFMicroAdjOn;
uchar AFMicroAdjRegisteredLenses;
ushort group2010;
ushort real_iso_offset;
float firmware;
ushort ImageCount3_offset;
unsigned ImageCount3;
unsigned ElectronicFrontCurtainShutter;
ushort MeteringMode2;
char SonyDateTime[20];
uchar TimeStamp[6];
unsigned ShotNumberSincePowerUp;
} libraw_sony_info_t;
typedef struct
{
ushort curve[0x10000];
unsigned cblack[4102];
unsigned black;
unsigned data_maximum;
unsigned maximum;
long linear_max[4];
float fmaximum;
float fnorm;
ushort white[8][8];
float cam_mul[4];
float pre_mul[4];
float cmatrix[3][4];
float ccm[3][4];
float rgb_cam[3][4];
float cam_xyz[4][3];
struct ph1_t phase_one_data;
float flash_used;
float canon_ev;
char model2[64];
char UniqueCameraModel[64];
char LocalizedCameraModel[64];
void *profile;
unsigned profile_length;
unsigned black_stat[8];
libraw_dng_color_t dng_color[2];
libraw_dng_levels_t dng_levels;
float baseline_exposure;
int WB_Coeffs[256][4]; /* R, G1, B, G2 coeffs */
float WBCT_Coeffs[64][5]; /* CCT, than R, G1, B, G2 coeffs */
libraw_P1_color_t P1_color[2];
} libraw_colordata_t;
typedef struct
{
enum LibRaw_thumbnail_formats tformat;
ushort twidth, theight;
unsigned tlength;
int tcolors;
char *thumb;
} libraw_thumbnail_t;
typedef struct
{
float latitude[3]; /* Deg,min,sec */
float longtitude[3]; /* Deg,min,sec */
float gpstimestamp[3]; /* Deg,min,sec */
float altitude;
char altref, latref, longref, gpsstatus;
char gpsparsed;
} libraw_gps_info_t;
typedef struct
{
float iso_speed;
float shutter;
float aperture;
float focal_len;
time_t timestamp;
unsigned shot_order;
unsigned gpsdata[32];
libraw_gps_info_t parsed_gps;
char desc[512], artist[64];
float FlashEC;
float FlashGN;
float CameraTemperature;
float SensorTemperature;
float SensorTemperature2;
float LensTemperature;
float AmbientTemperature;
float BatteryTemperature;
float exifAmbientTemperature;
float exifHumidity;
float exifPressure;
float exifWaterDepth;
float exifAcceleration;
float exifCameraElevationAngle;
float real_ISO;
} libraw_imgother_t;
typedef struct
{
unsigned greybox[4]; /* -A x1 y1 x2 y2 */
unsigned cropbox[4]; /* -B x1 y1 x2 y2 */
double aber[4]; /* -C */
double gamm[6]; /* -g */
float user_mul[4]; /* -r mul0 mul1 mul2 mul3 */
unsigned shot_select; /* -s */
float bright; /* -b */
float threshold; /* -n */
int half_size; /* -h */
int four_color_rgb; /* -f */
int highlight; /* -H */
int use_auto_wb; /* -a */
int use_camera_wb; /* -w */
int use_camera_matrix; /* +M/-M */
int output_color; /* -o */
char *output_profile; /* -o */
char *camera_profile; /* -p */
char *bad_pixels; /* -P */
char *dark_frame; /* -K */
int output_bps; /* -4 */
int output_tiff; /* -T */
int user_flip; /* -t */
int user_qual; /* -q */
int user_black; /* -k */
int user_cblack[4];
int user_sat; /* -S */
int med_passes; /* -m */
float auto_bright_thr;
float adjust_maximum_thr;
int no_auto_bright; /* -W */
int use_fuji_rotate; /* -j */
int green_matching;
/* DCB parameters */
int dcb_iterations;
int dcb_enhance_fl;
int fbdd_noiserd;
int exp_correc;
float exp_shift;
float exp_preser;
/* Raw speed */
int use_rawspeed;
/* DNG SDK */
int use_dngsdk;
/* Disable Auto-scale */
int no_auto_scale;
- /* Disable intepolation */
+ /* Disable interpolation */
int no_interpolation;
/* int x3f_flags; */
/* Sony ARW2 digging mode */
/* int sony_arw2_options; */
unsigned raw_processing_options;
int sony_arw2_posterization_thr;
/* Nikon Coolscan */
float coolscan_nef_gamma;
char p4shot_order[5];
/* Custom camera list */
char **custom_camera_strings;
} libraw_output_params_t;
typedef struct
{
/* really allocated bitmap */
void *raw_alloc;
/* alias to single_channel variant */
ushort *raw_image;
/* alias to 4-channel variant */
ushort (*color4_image)[4];
/* alias to 3-color variand decoded by RawSpeed */
ushort (*color3_image)[3];
/* float bayer */
float *float_image;
/* float 3-component */
float (*float3_image)[3];
/* float 4-component */
float (*float4_image)[4];
/* Phase One black level data; */
short (*ph1_cblack)[2];
short (*ph1_rblack)[2];
/* save color and sizes here, too.... */
libraw_iparams_t iparams;
libraw_image_sizes_t sizes;
libraw_internal_output_params_t ioparams;
libraw_colordata_t color;
} libraw_rawdata_t;
typedef struct
{
unsigned long long LensID;
char Lens[128];
ushort LensFormat; /* to characterize the image circle the lens covers */
ushort LensMount; /* 'male', lens itself */
unsigned long long CamID;
ushort CameraFormat; /* some of the sensor formats */
ushort CameraMount; /* 'female', body throat */
char body[64];
short FocalType; /* -1/0 is unknown; 1 is fixed focal; 2 is zoom */
char LensFeatures_pre[16], LensFeatures_suf[16];
float MinFocal, MaxFocal;
float MaxAp4MinFocal, MaxAp4MaxFocal, MinAp4MinFocal, MinAp4MaxFocal;
float MaxAp, MinAp;
float CurFocal, CurAp;
float MaxAp4CurFocal, MinAp4CurFocal;
float MinFocusDistance;
float FocusRangeIndex;
float LensFStops;
unsigned long long TeleconverterID;
char Teleconverter[128];
unsigned long long AdapterID;
char Adapter[128];
unsigned long long AttachmentID;
char Attachment[128];
ushort CanonFocalUnits;
float FocalLengthIn35mmFormat;
} libraw_makernotes_lens_t;
typedef struct
{
float NikonEffectiveMaxAp;
uchar NikonLensIDNumber, NikonLensFStops, NikonMCUVersion, NikonLensType;
} libraw_nikonlens_t;
typedef struct
{
float MinFocal, MaxFocal, MaxAp4MinFocal, MaxAp4MaxFocal;
} libraw_dnglens_t;
typedef struct
{
float MinFocal, MaxFocal, MaxAp4MinFocal, MaxAp4MaxFocal, EXIF_MaxAp;
char LensMake[128], Lens[128], LensSerial[128], InternalLensSerial[128];
ushort FocalLengthIn35mmFormat;
libraw_nikonlens_t nikon;
libraw_dnglens_t dng;
libraw_makernotes_lens_t makernotes;
} libraw_lensinfo_t;
typedef struct
{
libraw_canon_makernotes_t canon;
libraw_nikon_makernotes_t nikon;
libraw_hasselblad_makernotes_t hasselblad;
libraw_fuji_info_t fuji;
libraw_olympus_makernotes_t olympus;
libraw_sony_info_t sony;
libraw_kodak_makernotes_t kodak;
libraw_panasonic_makernotes_t panasonic;
libraw_pentax_makernotes_t pentax;
} libraw_makernotes_t;
typedef struct
{
short DriveMode;
short FocusMode;
short MeteringMode;
short AFPoint;
short ExposureMode;
short ImageStabilization;
char BodySerial[64];
char InternalBodySerial[64]; /* this may be PCB or sensor serial, depends on make/model*/
} libraw_shootinginfo_t;
typedef struct
{
unsigned fsize;
ushort rw, rh;
uchar lm, tm, rm, bm, lf, cf, max, flags;
char t_make[10], t_model[20];
ushort offset;
} libraw_custom_camera_t;
typedef struct
{
ushort (*image)[4];
libraw_image_sizes_t sizes;
libraw_iparams_t idata;
libraw_lensinfo_t lens;
libraw_makernotes_t makernotes;
libraw_shootinginfo_t shootinginfo;
libraw_output_params_t params;
unsigned int progress_flags;
unsigned int process_warnings;
libraw_colordata_t color;
libraw_imgother_t other;
libraw_thumbnail_t thumbnail;
libraw_rawdata_t rawdata;
void *parent_class;
} libraw_data_t;
struct fuji_compressed_params
{
int8_t *q_table; /* quantization table */
int q_point[5]; /* quantization points */
int max_bits;
int min_value;
int raw_bits;
int total_values;
int maxDiff;
ushort line_width;
};
#ifdef __cplusplus
}
#endif
/* Byte order */
#if defined(__POWERPC__)
#define LibRawBigEndian 1
#elif defined(__INTEL__)
#define LibRawBigEndian 0
#elif defined(_M_IX86) || defined(__i386__)
#define LibRawBigEndian 0
#elif defined(_M_X64) || defined(__amd64__) || defined(__x86_64__)
#define LibRawBigEndian 0
#elif defined(__LITTLE_ENDIAN__)
#define LibRawBigEndian 0
#elif defined(__BIG_ENDIAN__)
#define LibRawBigEndian 1
#elif defined(_ARM_)
#define LibRawBigEndian 0
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define LibRawBigEndian 0
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define LibRawBigEndian 1
#else
#ifndef qXCodeRez
#error Unable to figure out byte order.
#endif
#endif
#endif
diff --git a/core/libs/rawengine/libraw/samples/mem_image.cpp b/core/libs/rawengine/libraw/samples/mem_image.cpp
index 9640a53389..2f8fd5d3a9 100644
--- a/core/libs/rawengine/libraw/samples/mem_image.cpp
+++ b/core/libs/rawengine/libraw/samples/mem_image.cpp
@@ -1,198 +1,198 @@
/* -*- C++ -*-
* File: mem_image.cpp
* Copyright 2008-2018 LibRaw LLC (info@libraw.org)
*
* LibRaw mem_image/mem_thumb API test. Results should be same (bitwise) to dcraw [-4] [-6] [-e]
* Testing note: for ppm-thumbnails you should use dcraw -w -e for thumbnail extraction
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "libraw/libraw.h"
#ifdef WIN32
#define snprintf _snprintf
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <netinet/in.h>
#endif
// no error reporting, only params check
void write_ppm(libraw_processed_image_t *img, const char *basename)
{
if (!img)
return;
// type SHOULD be LIBRAW_IMAGE_BITMAP, but we'll check
if (img->type != LIBRAW_IMAGE_BITMAP)
return;
// only 3-color images supported...
if (img->colors != 3)
return;
char fn[1024];
snprintf(fn, 1024, "%s.ppm", basename);
FILE *f = fopen(fn, "wb");
if (!f)
return;
fprintf(f, "P6\n%d %d\n%d\n", img->width, img->height, (1 << img->bits) - 1);
/*
NOTE:
data in img->data is not converted to network byte order.
So, we should swap values on some architectures for dcraw compatibility
(unfortunately, xv cannot display 16-bit PPMs with network byte order data
*/
#define SWAP(a, b) \
{ \
a ^= b; \
a ^= (b ^= a); \
}
if (img->bits == 16 && htons(0x55aa) != 0x55aa)
for (unsigned i = 0; i < img->data_size; i += 2)
SWAP(img->data[i], img->data[i + 1]);
#undef SWAP
fwrite(img->data, img->data_size, 1, f);
fclose(f);
}
void write_thumb(libraw_processed_image_t *img, const char *basename)
{
if (!img)
return;
if (img->type == LIBRAW_IMAGE_BITMAP)
{
char fnt[1024];
snprintf(fnt, 1024, "%s.thumb", basename);
write_ppm(img, fnt);
}
else if (img->type == LIBRAW_IMAGE_JPEG)
{
char fn[1024];
snprintf(fn, 1024, "%s.thumb.jpg", basename);
FILE *f = fopen(fn, "wb");
if (!f)
return;
fwrite(img->data, img->data_size, 1, f);
fclose(f);
}
}
int main(int ac, char *av[])
{
int i, ret, output_thumbs = 0;
// don't use fixed size buffers in real apps!
LibRaw RawProcessor;
if (ac < 2)
{
printf("mem_image - LibRaw sample, to illustrate work for memory buffers. Emulates dcraw [-4] [-1] [-e] [-h]\n"
"Usage: %s [-D] [-T] [-v] [-e] raw-files....\n"
"\t-6 - output 16-bit PPM\n"
"\t-4 - linear 16-bit data\n"
"\t-e - extract thumbnails (same as dcraw -e in separate run)\n",
"\t-h - use half_size\n");
return 0;
}
putenv((char *)"TZ=UTC"); // dcraw compatibility, affects TIFF datestamp field
#define P1 RawProcessor.imgdata.idata
#define S RawProcessor.imgdata.sizes
#define C RawProcessor.imgdata.color
#define T RawProcessor.imgdata.thumbnail
#define P2 RawProcessor.imgdata.other
#define OUT RawProcessor.imgdata.params
for (i = 1; i < ac; i++)
{
if (av[i][0] == '-')
{
if (av[i][1] == '6' && av[i][2] == 0)
OUT.output_bps = 16;
if (av[i][1] == '4' && av[i][2] == 0)
{
OUT.output_bps = 16;
OUT.gamm[0] = OUT.gamm[1] = OUT.no_auto_bright = 1;
}
if (av[i][1] == 'e' && av[i][2] == 0)
output_thumbs++;
if (av[i][1] == 'h' && av[i][2] == 0)
OUT.half_size = 1;
continue;
}
printf("Processing %s\n", av[i]);
if ((ret = RawProcessor.open_file(av[i])) != LIBRAW_SUCCESS)
{
fprintf(stderr, "Cannot open %s: %s\n", av[i], libraw_strerror(ret));
continue; // no recycle b/c open file will recycle itself
}
if ((ret = RawProcessor.unpack()) != LIBRAW_SUCCESS)
{
fprintf(stderr, "Cannot unpack %s: %s\n", av[i], libraw_strerror(ret));
continue;
}
// we should call dcraw_process before thumbnail extraction because for
- // some cameras (i.e. Kodak ones) white balance for thumbnal should be set
+ // some cameras (i.e. Kodak ones) white balance for thumbnail should be set
// from main image settings
ret = RawProcessor.dcraw_process();
if (LIBRAW_SUCCESS != ret)
{
fprintf(stderr, "Cannot do postpocessing on %s: %s\n", av[i], libraw_strerror(ret));
if (LIBRAW_FATAL_ERROR(ret))
continue;
}
libraw_processed_image_t *image = RawProcessor.dcraw_make_mem_image(&ret);
if (image)
{
write_ppm(image, av[i]);
LibRaw::dcraw_clear_mem(image);
}
else
fprintf(stderr, "Cannot unpack %s to memory buffer: %s\n", av[i], libraw_strerror(ret));
if (output_thumbs)
{
if ((ret = RawProcessor.unpack_thumb()) != LIBRAW_SUCCESS)
{
fprintf(stderr, "Cannot unpack_thumb %s: %s\n", av[i], libraw_strerror(ret));
if (LIBRAW_FATAL_ERROR(ret))
continue; // skip to next file
}
else
{
libraw_processed_image_t *thumb = RawProcessor.dcraw_make_mem_thumb(&ret);
if (thumb)
{
write_thumb(thumb, av[i]);
LibRaw::dcraw_clear_mem(thumb);
}
else
fprintf(stderr, "Cannot unpack thumbnail of %s to memory buffer: %s\n", av[i], libraw_strerror(ret));
}
}
RawProcessor.recycle(); // just for show this call
}
return 0;
}
diff --git a/core/libs/rawengine/libraw/src/libraw_cxx.cpp b/core/libs/rawengine/libraw/src/libraw_cxx.cpp
index d79d34bf03..d7c423ca4a 100644
--- a/core/libs/rawengine/libraw/src/libraw_cxx.cpp
+++ b/core/libs/rawengine/libraw/src/libraw_cxx.cpp
@@ -1,6674 +1,6674 @@
/* -*- C++ -*-
* File: libraw_cxx.cpp
* Copyright 2008-2018 LibRaw LLC (info@libraw.org)
* Created: Sat Mar 8 , 2008
*
* LibRaw C++ interface (implementation)
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
*/
#include <math.h>
#include <errno.h>
#include <float.h>
#include <new>
#include <exception>
#include <sys/types.h>
#include <sys/stat.h>
#if !defined(_WIN32) && !defined(__MINGW32__)
#include <netinet/in.h>
#else
#include <winsock2.h>
#endif
#define LIBRAW_LIBRARY_BUILD
#include "libraw/libraw.h"
#include "internal/defines.h"
#ifdef USE_ZLIB
#include <zlib.h>
#endif
#ifdef USE_RAWSPEED
#include "../RawSpeed/rawspeed_xmldata.cpp"
#include <RawSpeed/StdAfx.h>
#include <RawSpeed/FileMap.h>
#include <RawSpeed/RawParser.h>
#include <RawSpeed/RawDecoder.h>
#include <RawSpeed/CameraMetaData.h>
#include <RawSpeed/ColorFilterArray.h>
#endif
#ifdef USE_DNGSDK
#include "dng_host.h"
#include "dng_negative.h"
#include "dng_simple_image.h"
#include "dng_info.h"
#endif
#include "libraw_fuji_compressed.cpp"
#ifdef __cplusplus
extern "C"
{
#endif
void default_memory_callback(void *, const char *file, const char *where)
{
fprintf(stderr, "%s: Out of memory in %s\n", file ? file : "unknown file", where);
}
void default_data_callback(void *, const char *file, const int offset)
{
if (offset < 0)
fprintf(stderr, "%s: Unexpected end of file\n", file ? file : "unknown file");
else
fprintf(stderr, "%s: data corrupted at %d\n", file ? file : "unknown file", offset);
}
const char *libraw_strerror(int e)
{
enum LibRaw_errors errorcode = (LibRaw_errors)e;
switch (errorcode)
{
case LIBRAW_SUCCESS:
return "No error";
case LIBRAW_UNSPECIFIED_ERROR:
return "Unspecified error";
case LIBRAW_FILE_UNSUPPORTED:
return "Unsupported file format or not RAW file";
case LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE:
return "Request for nonexisting image number";
case LIBRAW_OUT_OF_ORDER_CALL:
return "Out of order call of libraw function";
case LIBRAW_NO_THUMBNAIL:
return "No thumbnail in file";
case LIBRAW_UNSUPPORTED_THUMBNAIL:
return "Unsupported thumbnail format";
case LIBRAW_INPUT_CLOSED:
return "No input stream, or input stream closed";
case LIBRAW_UNSUFFICIENT_MEMORY:
- return "Unsufficient memory";
+ return "Insufficient memory";
case LIBRAW_DATA_ERROR:
return "Corrupted data or unexpected EOF";
case LIBRAW_IO_ERROR:
return "Input/output error";
case LIBRAW_CANCELLED_BY_CALLBACK:
return "Cancelled by user callback";
case LIBRAW_BAD_CROP:
return "Bad crop box";
case LIBRAW_TOO_BIG:
return "Image too big for processing";
default:
return "Unknown error code";
}
}
#ifdef __cplusplus
}
#endif
#define Sigma_X3F 22
const double LibRaw_constants::xyz_rgb[3][3] = {
{0.4124564, 0.3575761, 0.1804375}, {0.2126729, 0.7151522, 0.0721750}, {0.0193339, 0.1191920, 0.9503041}};
const float LibRaw_constants::d65_white[3] = {0.95047f, 1.0f, 1.08883f};
#define P1 imgdata.idata
#define S imgdata.sizes
#define O imgdata.params
#define C imgdata.color
#define T imgdata.thumbnail
#define IO libraw_internal_data.internal_output_params
#define ID libraw_internal_data.internal_data
#define EXCEPTION_HANDLER(e) \
do \
{ \
/* fprintf(stderr,"Exception %d caught\n",e);*/ \
switch (e) \
{ \
case LIBRAW_EXCEPTION_ALLOC: \
recycle(); \
return LIBRAW_UNSUFFICIENT_MEMORY; \
case LIBRAW_EXCEPTION_TOOBIG: \
recycle(); \
return LIBRAW_TOO_BIG; \
case LIBRAW_EXCEPTION_DECODE_RAW: \
case LIBRAW_EXCEPTION_DECODE_JPEG: \
recycle(); \
return LIBRAW_DATA_ERROR; \
case LIBRAW_EXCEPTION_DECODE_JPEG2000: \
recycle(); \
return LIBRAW_DATA_ERROR; \
case LIBRAW_EXCEPTION_IO_EOF: \
case LIBRAW_EXCEPTION_IO_CORRUPT: \
recycle(); \
return LIBRAW_IO_ERROR; \
case LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK: \
recycle(); \
return LIBRAW_CANCELLED_BY_CALLBACK; \
case LIBRAW_EXCEPTION_BAD_CROP: \
recycle(); \
return LIBRAW_BAD_CROP; \
default: \
return LIBRAW_UNSPECIFIED_ERROR; \
} \
} while (0)
const char *LibRaw::version() { return LIBRAW_VERSION_STR; }
int LibRaw::versionNumber() { return LIBRAW_VERSION; }
const char *LibRaw::strerror(int p) { return libraw_strerror(p); }
unsigned LibRaw::capabilities()
{
unsigned ret = 0;
#ifdef USE_RAWSPEED
ret |= LIBRAW_CAPS_RAWSPEED;
#endif
#ifdef USE_DNGSDK
ret |= LIBRAW_CAPS_DNGSDK;
#endif
return ret;
}
unsigned LibRaw::parse_custom_cameras(unsigned limit, libraw_custom_camera_t table[], char **list)
{
if (!list)
return 0;
unsigned index = 0;
for (int i = 0; i < limit; i++)
{
if (!list[i])
break;
if (strlen(list[i]) < 10)
continue;
char *string = (char *)malloc(strlen(list[i]) + 1);
strcpy(string, list[i]);
char *start = string;
memset(&table[index], 0, sizeof(table[0]));
for (int j = 0; start && j < 14; j++)
{
char *end = strchr(start, ',');
if (end)
{
*end = 0;
end++;
} // move to next char
while (isspace(*start) && *start)
start++; // skip leading spaces?
unsigned val = strtol(start, 0, 10);
switch (j)
{
case 0:
table[index].fsize = val;
break;
case 1:
table[index].rw = val;
break;
case 2:
table[index].rh = val;
break;
case 3:
table[index].lm = val;
break;
case 4:
table[index].tm = val;
break;
case 5:
table[index].rm = val;
break;
case 6:
table[index].bm = val;
break;
case 7:
table[index].lf = val;
break;
case 8:
table[index].cf = val;
break;
case 9:
table[index].max = val;
break;
case 10:
table[index].flags = val;
break;
case 11:
strncpy(table[index].t_make, start, sizeof(table[index].t_make) - 1);
break;
case 12:
strncpy(table[index].t_model, start, sizeof(table[index].t_model) - 1);
break;
case 13:
table[index].offset = val;
break;
default:
break;
}
start = end;
}
free(string);
if (table[index].t_make[0])
index++;
}
return index;
}
void LibRaw::derror()
{
if (!libraw_internal_data.unpacker_data.data_error && libraw_internal_data.internal_data.input)
{
if (libraw_internal_data.internal_data.input->eof())
{
if (callbacks.data_cb)
(*callbacks.data_cb)(callbacks.datacb_data, libraw_internal_data.internal_data.input->fname(), -1);
throw LIBRAW_EXCEPTION_IO_EOF;
}
else
{
if (callbacks.data_cb)
(*callbacks.data_cb)(callbacks.datacb_data, libraw_internal_data.internal_data.input->fname(),
libraw_internal_data.internal_data.input->tell());
// throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
}
libraw_internal_data.unpacker_data.data_error++;
}
void LibRaw::dcraw_clear_mem(libraw_processed_image_t *p)
{
if (p)
::free(p);
}
int LibRaw::is_sraw() { return load_raw == &LibRaw::canon_sraw_load_raw || load_raw == &LibRaw::nikon_load_sraw; }
int LibRaw::is_coolscan_nef() { return load_raw == &LibRaw::nikon_coolscan_load_raw; }
int LibRaw::is_jpeg_thumb() { return thumb_load_raw == 0 && write_thumb == &LibRaw::jpeg_thumb; }
int LibRaw::is_nikon_sraw() { return load_raw == &LibRaw::nikon_load_sraw; }
int LibRaw::sraw_midpoint()
{
if (load_raw == &LibRaw::canon_sraw_load_raw)
return 8192;
else if (load_raw == &LibRaw::nikon_load_sraw)
return 2048;
else
return 0;
}
#ifdef USE_RAWSPEED
using namespace RawSpeed;
class CameraMetaDataLR : public CameraMetaData
{
public:
CameraMetaDataLR() : CameraMetaData() {}
CameraMetaDataLR(char *filename) : CameraMetaData(filename) {}
CameraMetaDataLR(char *data, int sz);
};
CameraMetaDataLR::CameraMetaDataLR(char *data, int sz) : CameraMetaData()
{
ctxt = xmlNewParserCtxt();
if (ctxt == NULL)
{
ThrowCME("CameraMetaData:Could not initialize context.");
}
xmlResetLastError();
doc = xmlCtxtReadMemory(ctxt, data, sz, "", NULL, XML_PARSE_DTDVALID);
if (doc == NULL)
{
ThrowCME("CameraMetaData: XML Document could not be parsed successfully. Error was: %s", ctxt->lastError.message);
}
if (ctxt->valid == 0)
{
if (ctxt->lastError.code == 0x5e)
{
// printf("CameraMetaData: Unable to locate DTD, attempting to ignore.");
}
else
{
ThrowCME("CameraMetaData: XML file does not validate. DTD Error was: %s", ctxt->lastError.message);
}
}
xmlNodePtr cur;
cur = xmlDocGetRootElement(doc);
if (xmlStrcmp(cur->name, (const xmlChar *)"Cameras"))
{
ThrowCME("CameraMetaData: XML document of the wrong type, root node is not cameras.");
return;
}
cur = cur->xmlChildrenNode;
while (cur != NULL)
{
if ((!xmlStrcmp(cur->name, (const xmlChar *)"Camera")))
{
Camera *camera = new Camera(doc, cur);
addCamera(camera);
// Create cameras for aliases.
for (unsigned int i = 0; i < camera->aliases.size(); i++)
{
addCamera(new Camera(camera, i));
}
}
cur = cur->next;
}
if (doc)
xmlFreeDoc(doc);
doc = 0;
if (ctxt)
xmlFreeParserCtxt(ctxt);
ctxt = 0;
}
#define RAWSPEED_DATA_COUNT (sizeof(_rawspeed_data_xml) / sizeof(_rawspeed_data_xml[0]))
static CameraMetaDataLR *make_camera_metadata()
{
int len = 0, i;
for (i = 0; i < RAWSPEED_DATA_COUNT; i++)
if (_rawspeed_data_xml[i])
{
len += strlen(_rawspeed_data_xml[i]);
}
char *rawspeed_xml = (char *)calloc(len + 1, sizeof(_rawspeed_data_xml[0][0]));
if (!rawspeed_xml)
return NULL;
int offt = 0;
for (i = 0; i < RAWSPEED_DATA_COUNT; i++)
if (_rawspeed_data_xml[i])
{
int ll = strlen(_rawspeed_data_xml[i]);
if (offt + ll > len)
break;
memmove(rawspeed_xml + offt, _rawspeed_data_xml[i], ll);
offt += ll;
}
rawspeed_xml[offt] = 0;
CameraMetaDataLR *ret = NULL;
try
{
ret = new CameraMetaDataLR(rawspeed_xml, offt);
}
catch (...)
{
// Mask all exceptions
}
free(rawspeed_xml);
return ret;
}
#endif
#define ZERO(a) memset(&a, 0, sizeof(a))
static void cleargps(libraw_gps_info_t *q)
{
for (int i = 0; i < 3; i++)
q->latitude[i] = q->longtitude[i] = q->gpstimestamp[i] = 0.f;
q->altitude = 0.f;
q->altref = q->latref = q->longref = q->gpsstatus = q->gpsparsed = 0;
}
LibRaw::LibRaw(unsigned int flags) : memmgr(1024)
{
double aber[4] = {1, 1, 1, 1};
double gamm[6] = {0.45, 4.5, 0, 0, 0, 0};
unsigned greybox[4] = {0, 0, UINT_MAX, UINT_MAX};
unsigned cropbox[4] = {0, 0, UINT_MAX, UINT_MAX};
#ifdef DCRAW_VERBOSE
verbose = 1;
#else
verbose = 0;
#endif
ZERO(imgdata);
cleargps(&imgdata.other.parsed_gps);
ZERO(libraw_internal_data);
ZERO(callbacks);
_rawspeed_camerameta = _rawspeed_decoder = NULL;
dnghost = NULL;
_x3f_data = NULL;
#ifdef USE_RAWSPEED
CameraMetaDataLR *camerameta = make_camera_metadata(); // May be NULL in case of exception in make_camera_metadata()
_rawspeed_camerameta = static_cast<void *>(camerameta);
#endif
callbacks.mem_cb = (flags & LIBRAW_OPIONS_NO_MEMERR_CALLBACK) ? NULL : &default_memory_callback;
callbacks.data_cb = (flags & LIBRAW_OPIONS_NO_DATAERR_CALLBACK) ? NULL : &default_data_callback;
callbacks.exif_cb = NULL; // no default callback
callbacks.pre_identify_cb = NULL;
callbacks.post_identify_cb = NULL;
callbacks.pre_subtractblack_cb = callbacks.pre_scalecolors_cb = callbacks.pre_preinterpolate_cb
= callbacks.pre_interpolate_cb = callbacks.interpolate_bayer_cb = callbacks.interpolate_xtrans_cb
= callbacks.post_interpolate_cb = callbacks.pre_converttorgb_cb = callbacks.post_converttorgb_cb
= NULL;
memmove(&imgdata.params.aber, &aber, sizeof(aber));
memmove(&imgdata.params.gamm, &gamm, sizeof(gamm));
memmove(&imgdata.params.greybox, &greybox, sizeof(greybox));
memmove(&imgdata.params.cropbox, &cropbox, sizeof(cropbox));
imgdata.params.bright = 1;
imgdata.params.use_camera_matrix = 1;
imgdata.params.user_flip = -1;
imgdata.params.user_black = -1;
imgdata.params.user_cblack[0] = imgdata.params.user_cblack[1] = imgdata.params.user_cblack[2] =
imgdata.params.user_cblack[3] = -1000001;
imgdata.params.user_sat = -1;
imgdata.params.user_qual = -1;
imgdata.params.output_color = 1;
imgdata.params.output_bps = 8;
imgdata.params.use_fuji_rotate = 1;
imgdata.params.exp_shift = 1.0;
imgdata.params.auto_bright_thr = LIBRAW_DEFAULT_AUTO_BRIGHTNESS_THRESHOLD;
imgdata.params.adjust_maximum_thr = LIBRAW_DEFAULT_ADJUST_MAXIMUM_THRESHOLD;
imgdata.params.use_rawspeed = 1;
imgdata.params.use_dngsdk = LIBRAW_DNG_DEFAULT;
imgdata.params.no_auto_scale = 0;
imgdata.params.no_interpolation = 0;
imgdata.params.raw_processing_options = LIBRAW_PROCESSING_DP2Q_INTERPOLATERG | LIBRAW_PROCESSING_DP2Q_INTERPOLATEAF |
LIBRAW_PROCESSING_CONVERTFLOAT_TO_INT;
imgdata.params.sony_arw2_posterization_thr = 0;
imgdata.params.green_matching = 0;
imgdata.params.custom_camera_strings = 0;
imgdata.params.coolscan_nef_gamma = 1.0f;
imgdata.parent_class = this;
imgdata.progress_flags = 0;
imgdata.color.baseline_exposure = -999.f;
_exitflag = 0;
tls = new LibRaw_TLS;
tls->init();
}
int LibRaw::set_rawspeed_camerafile(char *filename)
{
#ifdef USE_RAWSPEED
try
{
CameraMetaDataLR *camerameta = new CameraMetaDataLR(filename);
if (_rawspeed_camerameta)
{
CameraMetaDataLR *d = static_cast<CameraMetaDataLR *>(_rawspeed_camerameta);
delete d;
}
_rawspeed_camerameta = static_cast<void *>(camerameta);
}
catch (...)
{
// just return error code
return -1;
}
#endif
return 0;
}
LibRaw::~LibRaw()
{
recycle();
delete tls;
#ifdef USE_RAWSPEED
if (_rawspeed_camerameta)
{
CameraMetaDataLR *cmeta = static_cast<CameraMetaDataLR *>(_rawspeed_camerameta);
delete cmeta;
_rawspeed_camerameta = NULL;
}
#endif
}
void *LibRaw::malloc(size_t t)
{
void *p = memmgr.malloc(t);
if (!p)
throw LIBRAW_EXCEPTION_ALLOC;
return p;
}
void *LibRaw::realloc(void *q, size_t t)
{
void *p = memmgr.realloc(q, t);
if (!p)
throw LIBRAW_EXCEPTION_ALLOC;
return p;
}
void *LibRaw::calloc(size_t n, size_t t)
{
void *p = memmgr.calloc(n, t);
if (!p)
throw LIBRAW_EXCEPTION_ALLOC;
return p;
}
void LibRaw::free(void *p) { memmgr.free(p); }
void LibRaw::recycle_datastream()
{
if (libraw_internal_data.internal_data.input && libraw_internal_data.internal_data.input_internal)
{
delete libraw_internal_data.internal_data.input;
libraw_internal_data.internal_data.input = NULL;
}
libraw_internal_data.internal_data.input_internal = 0;
}
void x3f_clear(void *);
void LibRaw::recycle()
{
recycle_datastream();
#define FREE(a) \
do \
{ \
if (a) \
{ \
free(a); \
a = NULL; \
} \
} while (0)
FREE(imgdata.image);
FREE(imgdata.thumbnail.thumb);
FREE(libraw_internal_data.internal_data.meta_data);
FREE(libraw_internal_data.output_data.histogram);
FREE(libraw_internal_data.output_data.oprof);
FREE(imgdata.color.profile);
FREE(imgdata.rawdata.ph1_cblack);
FREE(imgdata.rawdata.ph1_rblack);
FREE(imgdata.rawdata.raw_alloc);
FREE(imgdata.idata.xmpdata);
#undef FREE
ZERO(imgdata.sizes);
imgdata.sizes.raw_crop.cleft = 0xffff;
imgdata.sizes.raw_crop.ctop = 0xffff;
ZERO(imgdata.idata);
ZERO(imgdata.makernotes);
ZERO(imgdata.color);
ZERO(imgdata.other);
ZERO(imgdata.thumbnail);
ZERO(imgdata.rawdata);
imgdata.makernotes.olympus.OlympusCropID = -1;
imgdata.makernotes.sony.raw_crop.cleft = 0xffff;
imgdata.makernotes.sony.raw_crop.ctop = 0xffff;
cleargps(&imgdata.other.parsed_gps);
imgdata.color.baseline_exposure = -999.f;
imgdata.makernotes.fuji.FujiExpoMidPointShift = -999.f;
imgdata.makernotes.fuji.FujiDynamicRange = 0xffff;
imgdata.makernotes.fuji.FujiFilmMode = 0xffff;
imgdata.makernotes.fuji.FujiDynamicRangeSetting = 0xffff;
imgdata.makernotes.fuji.FujiDevelopmentDynamicRange = 0xffff;
imgdata.makernotes.fuji.FujiAutoDynamicRange = 0xffff;
imgdata.makernotes.fuji.FocusMode = 0xffff;
imgdata.makernotes.fuji.AFMode = 0xffff;
imgdata.makernotes.fuji.FocusPixel[0] = imgdata.makernotes.fuji.FocusPixel[1] = 0xffff;
imgdata.makernotes.fuji.ImageStabilization[0] = imgdata.makernotes.fuji.ImageStabilization[1] =
imgdata.makernotes.fuji.ImageStabilization[2] = 0xffff;
imgdata.makernotes.sony.SonyCameraType = 0xffff;
imgdata.makernotes.sony.real_iso_offset = 0xffff;
imgdata.makernotes.sony.ImageCount3_offset = 0xffff;
imgdata.makernotes.sony.ElectronicFrontCurtainShutter = 0xffff;
imgdata.makernotes.kodak.BlackLevelTop = 0xffff;
imgdata.makernotes.kodak.BlackLevelBottom = 0xffff;
imgdata.color.dng_color[0].illuminant = imgdata.color.dng_color[1].illuminant = 0xffff;
for (int i = 0; i < 4; i++)
imgdata.color.dng_levels.analogbalance[i] = 1.0f;
ZERO(libraw_internal_data);
ZERO(imgdata.lens);
imgdata.lens.makernotes.CanonFocalUnits = 1;
imgdata.lens.makernotes.LensID = 0xffffffffffffffffULL;
ZERO(imgdata.shootinginfo);
imgdata.shootinginfo.DriveMode = -1;
imgdata.shootinginfo.FocusMode = -1;
imgdata.shootinginfo.MeteringMode = -1;
imgdata.shootinginfo.AFPoint = -1;
imgdata.shootinginfo.ExposureMode = -1;
imgdata.shootinginfo.ImageStabilization = -1;
_exitflag = 0;
#ifdef USE_RAWSPEED
if (_rawspeed_decoder)
{
RawDecoder *d = static_cast<RawDecoder *>(_rawspeed_decoder);
delete d;
}
_rawspeed_decoder = 0;
#endif
if (_x3f_data)
{
x3f_clear(_x3f_data);
_x3f_data = 0;
}
memmgr.cleanup();
imgdata.thumbnail.tformat = LIBRAW_THUMBNAIL_UNKNOWN;
imgdata.progress_flags = 0;
load_raw = thumb_load_raw = 0;
tls->init();
}
const char *LibRaw::unpack_function_name()
{
libraw_decoder_info_t decoder_info;
get_decoder_info(&decoder_info);
return decoder_info.decoder_name;
}
int LibRaw::get_decoder_info(libraw_decoder_info_t *d_info)
{
if (!d_info)
return LIBRAW_UNSPECIFIED_ERROR;
d_info->decoder_name = 0;
d_info->decoder_flags = 0;
if (!load_raw)
return LIBRAW_OUT_OF_ORDER_CALL;
int rawdata = (imgdata.idata.filters || P1.colors == 1);
// dcraw.c names order
if (load_raw == &LibRaw::android_tight_load_raw)
{
d_info->decoder_name = "android_tight_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::android_loose_load_raw)
{
d_info->decoder_name = "android_loose_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::canon_600_load_raw)
{
d_info->decoder_name = "canon_600_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::fuji_compressed_load_raw)
{
d_info->decoder_name = "fuji_compressed_load_raw()";
}
else if (load_raw == &LibRaw::fuji_14bit_load_raw)
{
d_info->decoder_name = "fuji_14bit_load_raw()";
}
else if (load_raw == &LibRaw::canon_load_raw)
{
d_info->decoder_name = "canon_load_raw()";
}
else if (load_raw == &LibRaw::lossless_jpeg_load_raw)
{
d_info->decoder_name = "lossless_jpeg_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::canon_sraw_load_raw)
{
d_info->decoder_name = "canon_sraw_load_raw()";
// d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::lossless_dng_load_raw)
{
d_info->decoder_name = "lossless_dng_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_TRYRAWSPEED | LIBRAW_DECODER_ADOBECOPYPIXEL;
}
else if (load_raw == &LibRaw::packed_dng_load_raw)
{
d_info->decoder_name = "packed_dng_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_TRYRAWSPEED | LIBRAW_DECODER_ADOBECOPYPIXEL;
}
else if (load_raw == &LibRaw::pentax_load_raw)
{
d_info->decoder_name = "pentax_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::nikon_load_raw)
{
d_info->decoder_name = "nikon_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::nikon_coolscan_load_raw)
{
d_info->decoder_name = "nikon_coolscan_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::nikon_load_sraw)
{
d_info->decoder_name = "nikon_load_sraw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::nikon_yuv_load_raw)
{
d_info->decoder_name = "nikon_load_yuv_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::rollei_load_raw)
{
// UNTESTED
d_info->decoder_name = "rollei_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::phase_one_load_raw)
{
d_info->decoder_name = "phase_one_load_raw()";
}
else if (load_raw == &LibRaw::phase_one_load_raw_c)
{
d_info->decoder_name = "phase_one_load_raw_c()";
}
else if (load_raw == &LibRaw::hasselblad_load_raw)
{
d_info->decoder_name = "hasselblad_load_raw()";
}
else if (load_raw == &LibRaw::leaf_hdr_load_raw)
{
d_info->decoder_name = "leaf_hdr_load_raw()";
}
else if (load_raw == &LibRaw::unpacked_load_raw)
{
d_info->decoder_name = "unpacked_load_raw()";
}
else if (load_raw == &LibRaw::unpacked_load_raw_reversed)
{
d_info->decoder_name = "unpacked_load_raw_reversed()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::sinar_4shot_load_raw)
{
// UNTESTED
d_info->decoder_name = "sinar_4shot_load_raw()";
}
else if (load_raw == &LibRaw::imacon_full_load_raw)
{
d_info->decoder_name = "imacon_full_load_raw()";
}
else if (load_raw == &LibRaw::hasselblad_full_load_raw)
{
d_info->decoder_name = "hasselblad_full_load_raw()";
}
else if (load_raw == &LibRaw::packed_load_raw)
{
d_info->decoder_name = "packed_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::broadcom_load_raw)
{
// UNTESTED
d_info->decoder_name = "broadcom_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::nokia_load_raw)
{
// UNTESTED
d_info->decoder_name = "nokia_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::canon_rmf_load_raw)
{
// UNTESTED
d_info->decoder_name = "canon_rmf_load_raw()";
}
else if (load_raw == &LibRaw::panasonic_load_raw)
{
d_info->decoder_name = "panasonic_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::olympus_load_raw)
{
d_info->decoder_name = "olympus_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::minolta_rd175_load_raw)
{
// UNTESTED
d_info->decoder_name = "minolta_rd175_load_raw()";
}
else if (load_raw == &LibRaw::quicktake_100_load_raw)
{
// UNTESTED
d_info->decoder_name = "quicktake_100_load_raw()";
}
else if (load_raw == &LibRaw::kodak_radc_load_raw)
{
d_info->decoder_name = "kodak_radc_load_raw()";
}
else if (load_raw == &LibRaw::kodak_jpeg_load_raw)
{
// UNTESTED + RBAYER
d_info->decoder_name = "kodak_jpeg_load_raw()";
}
else if (load_raw == &LibRaw::lossy_dng_load_raw)
{
// Check rbayer
d_info->decoder_name = "lossy_dng_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED | LIBRAW_DECODER_HASCURVE;
}
else if (load_raw == &LibRaw::kodak_dc120_load_raw)
{
d_info->decoder_name = "kodak_dc120_load_raw()";
}
else if (load_raw == &LibRaw::eight_bit_load_raw)
{
d_info->decoder_name = "eight_bit_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::kodak_c330_load_raw)
{
d_info->decoder_name = "kodak_yrgb_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::kodak_c603_load_raw)
{
d_info->decoder_name = "kodak_yrgb_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::kodak_262_load_raw)
{
d_info->decoder_name = "kodak_262_load_raw()"; // UNTESTED!
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::kodak_65000_load_raw)
{
d_info->decoder_name = "kodak_65000_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE;
}
else if (load_raw == &LibRaw::kodak_ycbcr_load_raw)
{
// UNTESTED
d_info->decoder_name = "kodak_ycbcr_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::kodak_rgb_load_raw)
{
// UNTESTED
d_info->decoder_name = "kodak_rgb_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::sony_load_raw)
{
d_info->decoder_name = "sony_load_raw()";
}
else if (load_raw == &LibRaw::sony_arw_load_raw)
{
d_info->decoder_name = "sony_arw_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::sony_arw2_load_raw)
{
d_info->decoder_name = "sony_arw2_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE | LIBRAW_DECODER_TRYRAWSPEED | LIBRAW_DECODER_SONYARW2;
}
else if (load_raw == &LibRaw::sony_arq_load_raw)
{
d_info->decoder_name = "sony_arq_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_LEGACY_WITH_MARGINS;
}
else if (load_raw == &LibRaw::samsung_load_raw)
{
d_info->decoder_name = "samsung_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_TRYRAWSPEED;
}
else if (load_raw == &LibRaw::samsung2_load_raw)
{
d_info->decoder_name = "samsung2_load_raw()";
}
else if (load_raw == &LibRaw::samsung3_load_raw)
{
d_info->decoder_name = "samsung3_load_raw()";
}
else if (load_raw == &LibRaw::smal_v6_load_raw)
{
// UNTESTED
d_info->decoder_name = "smal_v6_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::smal_v9_load_raw)
{
// UNTESTED
d_info->decoder_name = "smal_v9_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_FIXEDMAXC;
}
else if (load_raw == &LibRaw::redcine_load_raw)
{
d_info->decoder_name = "redcine_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_HASCURVE;
}
else if (load_raw == &LibRaw::x3f_load_raw)
{
d_info->decoder_name = "x3f_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_OWNALLOC | LIBRAW_DECODER_FIXEDMAXC | LIBRAW_DECODER_LEGACY_WITH_MARGINS;
}
else if (load_raw == &LibRaw::pentax_4shot_load_raw)
{
d_info->decoder_name = "pentax_4shot_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_OWNALLOC;
}
else if (load_raw == &LibRaw::deflate_dng_load_raw)
{
d_info->decoder_name = "deflate_dng_load_raw()";
d_info->decoder_flags = LIBRAW_DECODER_OWNALLOC;
}
else if (load_raw == &LibRaw::nikon_load_striped_packed_raw)
{
d_info->decoder_name = "nikon_load_striped_packed_raw()";
}
else
{
d_info->decoder_name = "Unknown unpack function";
d_info->decoder_flags = LIBRAW_DECODER_NOTSET;
}
return LIBRAW_SUCCESS;
}
int LibRaw::adjust_maximum()
{
ushort real_max;
float auto_threshold;
if (O.adjust_maximum_thr < 0.00001)
return LIBRAW_SUCCESS;
else if (O.adjust_maximum_thr > 0.99999)
auto_threshold = LIBRAW_DEFAULT_ADJUST_MAXIMUM_THRESHOLD;
else
auto_threshold = O.adjust_maximum_thr;
real_max = C.data_maximum;
if (real_max > 0 && real_max < C.maximum && real_max > C.maximum * auto_threshold)
{
C.maximum = real_max;
}
return LIBRAW_SUCCESS;
}
void LibRaw::merror(void *ptr, const char *where)
{
if (ptr)
return;
if (callbacks.mem_cb)
(*callbacks.mem_cb)(
callbacks.memcb_data,
libraw_internal_data.internal_data.input ? libraw_internal_data.internal_data.input->fname() : NULL, where);
throw LIBRAW_EXCEPTION_ALLOC;
}
int LibRaw::open_file(const char *fname, INT64 max_buf_size)
{
#ifndef WIN32
struct stat st;
if (stat(fname, &st))
return LIBRAW_IO_ERROR;
int big = (st.st_size > max_buf_size) ? 1 : 0;
#else
struct _stati64 st;
if (_stati64(fname, &st))
return LIBRAW_IO_ERROR;
int big = (st.st_size > max_buf_size) ? 1 : 0;
#endif
LibRaw_abstract_datastream *stream;
try
{
if (big)
stream = new LibRaw_bigfile_datastream(fname);
else
stream = new LibRaw_file_datastream(fname);
}
catch (std::bad_alloc)
{
recycle();
return LIBRAW_UNSUFFICIENT_MEMORY;
}
if (!stream->valid())
{
delete stream;
return LIBRAW_IO_ERROR;
}
ID.input_internal = 0; // preserve from deletion on error
int ret = open_datastream(stream);
if (ret == LIBRAW_SUCCESS)
{
ID.input_internal = 1; // flag to delete datastream on recycle
}
else
{
delete stream;
ID.input_internal = 0;
}
return ret;
}
#if defined(_WIN32) && !defined(__MINGW32__) && defined(_MSC_VER) && (_MSC_VER > 1310)
int LibRaw::open_file(const wchar_t *fname, INT64 max_buf_size)
{
struct _stati64 st;
if (_wstati64(fname, &st))
return LIBRAW_IO_ERROR;
int big = (st.st_size > max_buf_size) ? 1 : 0;
LibRaw_abstract_datastream *stream;
try
{
if (big)
stream = new LibRaw_bigfile_datastream(fname);
else
stream = new LibRaw_file_datastream(fname);
}
catch (std::bad_alloc)
{
recycle();
return LIBRAW_UNSUFFICIENT_MEMORY;
}
if (!stream->valid())
{
delete stream;
return LIBRAW_IO_ERROR;
}
ID.input_internal = 0; // preserve from deletion on error
int ret = open_datastream(stream);
if (ret == LIBRAW_SUCCESS)
{
ID.input_internal = 1; // flag to delete datastream on recycle
}
else
{
delete stream;
ID.input_internal = 0;
}
return ret;
}
#endif
int LibRaw::open_buffer(void *buffer, size_t size)
{
// this stream will close on recycle()
if (!buffer || buffer == (void *)-1)
return LIBRAW_IO_ERROR;
LibRaw_buffer_datastream *stream;
try
{
stream = new LibRaw_buffer_datastream(buffer, size);
}
catch (std::bad_alloc)
{
recycle();
return LIBRAW_UNSUFFICIENT_MEMORY;
}
if (!stream->valid())
{
delete stream;
return LIBRAW_IO_ERROR;
}
ID.input_internal = 0; // preserve from deletion on error
int ret = open_datastream(stream);
if (ret == LIBRAW_SUCCESS)
{
ID.input_internal = 1; // flag to delete datastream on recycle
}
else
{
delete stream;
ID.input_internal = 0;
}
return ret;
}
int LibRaw::open_bayer(unsigned char *buffer, unsigned datalen, ushort _raw_width, ushort _raw_height,
ushort _left_margin, ushort _top_margin, ushort _right_margin, ushort _bottom_margin,
unsigned char procflags, unsigned char bayer_pattern, unsigned unused_bits, unsigned otherflags,
unsigned black_level)
{
// this stream will close on recycle()
if (!buffer || buffer == (void *)-1)
return LIBRAW_IO_ERROR;
LibRaw_buffer_datastream *stream;
try
{
stream = new LibRaw_buffer_datastream(buffer, datalen);
}
catch (std::bad_alloc)
{
recycle();
return LIBRAW_UNSUFFICIENT_MEMORY;
}
if (!stream->valid())
{
delete stream;
return LIBRAW_IO_ERROR;
}
ID.input = stream;
SET_PROC_FLAG(LIBRAW_PROGRESS_OPEN);
// From identify
initdata();
strcpy(imgdata.idata.make, "BayerDump");
snprintf(imgdata.idata.model, sizeof(imgdata.idata.model) - 1, "%u x %u pixels", _raw_width, _raw_height);
S.flip = procflags >> 2;
libraw_internal_data.internal_output_params.zero_is_bad = procflags & 2;
libraw_internal_data.unpacker_data.data_offset = 0;
S.raw_width = _raw_width;
S.raw_height = _raw_height;
S.left_margin = _left_margin;
S.top_margin = _top_margin;
S.width = S.raw_width - S.left_margin - _right_margin;
S.height = S.raw_height - S.top_margin - _bottom_margin;
imgdata.idata.filters = 0x1010101 * bayer_pattern;
imgdata.idata.colors = 4 - !((imgdata.idata.filters & imgdata.idata.filters >> 1) & 0x5555);
libraw_internal_data.unpacker_data.load_flags = otherflags;
switch (libraw_internal_data.unpacker_data.tiff_bps = (datalen)*8 / (S.raw_width * S.raw_height))
{
case 8:
load_raw = &CLASS eight_bit_load_raw;
break;
case 10:
if ((datalen) / S.raw_height * 3 >= S.raw_width * 4)
{
load_raw = &CLASS android_loose_load_raw;
break;
}
else if (libraw_internal_data.unpacker_data.load_flags & 1)
{
load_raw = &CLASS android_tight_load_raw;
break;
}
case 12:
libraw_internal_data.unpacker_data.load_flags |= 128;
load_raw = &CLASS packed_load_raw;
break;
case 16:
libraw_internal_data.unpacker_data.order = 0x4949 | 0x404 * (libraw_internal_data.unpacker_data.load_flags & 1);
libraw_internal_data.unpacker_data.tiff_bps -= libraw_internal_data.unpacker_data.load_flags >> 4;
libraw_internal_data.unpacker_data.tiff_bps -= libraw_internal_data.unpacker_data.load_flags =
libraw_internal_data.unpacker_data.load_flags >> 1 & 7;
load_raw = &CLASS unpacked_load_raw;
}
C.maximum = (1 << libraw_internal_data.unpacker_data.tiff_bps) - (1 << unused_bits);
C.black = black_level;
S.iwidth = S.width;
S.iheight = S.height;
imgdata.idata.colors = 3;
imgdata.idata.filters |= ((imgdata.idata.filters >> 2 & 0x22222222) | (imgdata.idata.filters << 2 & 0x88888888)) &
imgdata.idata.filters << 1;
imgdata.idata.raw_count = 1;
for (int i = 0; i < 4; i++)
imgdata.color.pre_mul[i] = 1.0;
strcpy(imgdata.idata.cdesc, "RGBG");
ID.input_internal = 1;
SET_PROC_FLAG(LIBRAW_PROGRESS_IDENTIFY);
return LIBRAW_SUCCESS;
}
#ifdef USE_ZLIB
inline unsigned int __DNG_HalfToFloat(ushort halfValue)
{
int sign = (halfValue >> 15) & 0x00000001;
int exponent = (halfValue >> 10) & 0x0000001f;
int mantissa = halfValue & 0x000003ff;
if (exponent == 0)
{
if (mantissa == 0)
{
return (unsigned int)(sign << 31);
}
else
{
while (!(mantissa & 0x00000400))
{
mantissa <<= 1;
exponent -= 1;
}
exponent += 1;
mantissa &= ~0x00000400;
}
}
else if (exponent == 31)
{
if (mantissa == 0)
{
return (unsigned int)((sign << 31) | ((0x1eL + 127 - 15) << 23) | (0x3ffL << 13));
}
else
{
return 0;
}
}
exponent += (127 - 15);
mantissa <<= 13;
return (unsigned int)((sign << 31) | (exponent << 23) | mantissa);
}
inline unsigned int __DNG_FP24ToFloat(const unsigned char *input)
{
int sign = (input[0] >> 7) & 0x01;
int exponent = (input[0]) & 0x7F;
int mantissa = (((int)input[1]) << 8) | input[2];
if (exponent == 0)
{
if (mantissa == 0)
{
return (unsigned int)(sign << 31);
}
else
{
while (!(mantissa & 0x00010000))
{
mantissa <<= 1;
exponent -= 1;
}
exponent += 1;
mantissa &= ~0x00010000;
}
}
else if (exponent == 127)
{
if (mantissa == 0)
{
return (unsigned int)((sign << 31) | ((0x7eL + 128 - 64) << 23) | (0xffffL << 7));
}
else
{
// Nan -- Just set to zero.
return 0;
}
}
exponent += (128 - 64);
mantissa <<= 7;
return (uint32_t)((sign << 31) | (exponent << 23) | mantissa);
}
inline void DecodeDeltaBytes(unsigned char *bytePtr, int cols, int channels)
{
if (channels == 1)
{
unsigned char b0 = bytePtr[0];
bytePtr += 1;
for (uint32_t col = 1; col < cols; ++col)
{
b0 += bytePtr[0];
bytePtr[0] = b0;
bytePtr += 1;
}
}
else if (channels == 3)
{
unsigned char b0 = bytePtr[0];
unsigned char b1 = bytePtr[1];
unsigned char b2 = bytePtr[2];
bytePtr += 3;
for (int col = 1; col < cols; ++col)
{
b0 += bytePtr[0];
b1 += bytePtr[1];
b2 += bytePtr[2];
bytePtr[0] = b0;
bytePtr[1] = b1;
bytePtr[2] = b2;
bytePtr += 3;
}
}
else if (channels == 4)
{
unsigned char b0 = bytePtr[0];
unsigned char b1 = bytePtr[1];
unsigned char b2 = bytePtr[2];
unsigned char b3 = bytePtr[3];
bytePtr += 4;
for (uint32_t col = 1; col < cols; ++col)
{
b0 += bytePtr[0];
b1 += bytePtr[1];
b2 += bytePtr[2];
b3 += bytePtr[3];
bytePtr[0] = b0;
bytePtr[1] = b1;
bytePtr[2] = b2;
bytePtr[3] = b3;
bytePtr += 4;
}
}
else
{
for (int col = 1; col < cols; ++col)
{
for (int chan = 0; chan < channels; ++chan)
{
bytePtr[chan + channels] += bytePtr[chan];
}
bytePtr += channels;
}
}
}
static void DecodeFPDelta(unsigned char *input, unsigned char *output, int cols, int channels, int bytesPerSample)
{
DecodeDeltaBytes(input, cols * bytesPerSample, channels);
int32_t rowIncrement = cols * channels;
if (bytesPerSample == 2)
{
#if LibRawBigEndian
const unsigned char *input0 = input;
const unsigned char *input1 = input + rowIncrement;
#else
const unsigned char *input1 = input;
const unsigned char *input0 = input + rowIncrement;
#endif
for (int col = 0; col < rowIncrement; ++col)
{
output[0] = input0[col];
output[1] = input1[col];
output += 2;
}
}
else if (bytesPerSample == 3)
{
const unsigned char *input0 = input;
const unsigned char *input1 = input + rowIncrement;
const unsigned char *input2 = input + rowIncrement * 2;
for (int col = 0; col < rowIncrement; ++col)
{
output[0] = input0[col];
output[1] = input1[col];
output[2] = input2[col];
output += 3;
}
}
else
{
#if LibRawBigEndian
const unsigned char *input0 = input;
const unsigned char *input1 = input + rowIncrement;
const unsigned char *input2 = input + rowIncrement * 2;
const unsigned char *input3 = input + rowIncrement * 3;
#else
const unsigned char *input3 = input;
const unsigned char *input2 = input + rowIncrement;
const unsigned char *input1 = input + rowIncrement * 2;
const unsigned char *input0 = input + rowIncrement * 3;
#endif
for (int col = 0; col < rowIncrement; ++col)
{
output[0] = input0[col];
output[1] = input1[col];
output[2] = input2[col];
output[3] = input3[col];
output += 4;
}
}
}
static float expandFloats(unsigned char *dst, int tileWidth, int bytesps)
{
float max = 0.f;
if (bytesps == 2)
{
uint16_t *dst16 = (ushort *)dst;
uint32_t *dst32 = (unsigned int *)dst;
float *f32 = (float *)dst;
for (int index = tileWidth - 1; index >= 0; --index)
{
dst32[index] = __DNG_HalfToFloat(dst16[index]);
max = MAX(max, f32[index]);
}
}
else if (bytesps == 3)
{
uint8_t *dst8 = ((unsigned char *)dst) + (tileWidth - 1) * 3;
uint32_t *dst32 = (unsigned int *)dst;
float *f32 = (float *)dst;
for (int index = tileWidth - 1; index >= 0; --index, dst8 -= 3)
{
dst32[index] = __DNG_FP24ToFloat(dst8);
max = MAX(max, f32[index]);
}
}
else if (bytesps == 4)
{
float *f32 = (float *)dst;
for (int index = 0; index < tileWidth; index++)
max = MAX(max, f32[index]);
}
return max;
}
void LibRaw::deflate_dng_load_raw()
{
struct tiff_ifd_t *ifd = &tiff_ifd[0];
while (ifd < &tiff_ifd[libraw_internal_data.identify_data.tiff_nifds] &&
ifd->offset != libraw_internal_data.unpacker_data.data_offset)
++ifd;
if (ifd == &tiff_ifd[libraw_internal_data.identify_data.tiff_nifds])
{
throw LIBRAW_EXCEPTION_DECODE_RAW;
}
float *float_raw_image = 0;
float max = 0.f;
if (ifd->samples != 1 && ifd->samples != 3 && ifd->samples != 4)
throw LIBRAW_EXCEPTION_DECODE_RAW; // Only float deflated supported
if (libraw_internal_data.unpacker_data.tiff_samples != ifd->samples)
throw LIBRAW_EXCEPTION_DECODE_RAW; // Wrong IFD
size_t tilesH = (imgdata.sizes.raw_width + libraw_internal_data.unpacker_data.tile_width - 1) /
libraw_internal_data.unpacker_data.tile_width;
size_t tilesV = (imgdata.sizes.raw_height + libraw_internal_data.unpacker_data.tile_length - 1) /
libraw_internal_data.unpacker_data.tile_length;
size_t tileCnt = tilesH * tilesV;
if (ifd->sample_format == 3)
{ // Floating point data
float_raw_image = (float *)calloc(tileCnt * libraw_internal_data.unpacker_data.tile_length *
libraw_internal_data.unpacker_data.tile_width * ifd->samples,
sizeof(float));
// imgdata.color.maximum = 65535;
// imgdata.color.black = 0;
// memset(imgdata.color.cblack,0,sizeof(imgdata.color.cblack));
}
else
throw LIBRAW_EXCEPTION_DECODE_RAW; // Only float deflated supported
int xFactor;
switch (ifd->predictor)
{
case 3:
default:
xFactor = 1;
break;
case 34894:
xFactor = 2;
break;
case 34895:
xFactor = 4;
break;
}
if (libraw_internal_data.unpacker_data.tile_length < INT_MAX)
{
if (tileCnt < 1 || tileCnt > 1000000)
throw LIBRAW_EXCEPTION_DECODE_RAW;
size_t *tOffsets = (size_t *)malloc(tileCnt * sizeof(size_t));
for (int t = 0; t < tileCnt; ++t)
tOffsets[t] = get4();
size_t *tBytes = (size_t *)malloc(tileCnt * sizeof(size_t));
unsigned long maxBytesInTile = 0;
if (tileCnt == 1)
tBytes[0] = maxBytesInTile = ifd->bytes;
else
{
libraw_internal_data.internal_data.input->seek(ifd->bytes, SEEK_SET);
for (size_t t = 0; t < tileCnt; ++t)
{
tBytes[t] = get4();
maxBytesInTile = MAX(maxBytesInTile, tBytes[t]);
}
}
unsigned tilePixels =
libraw_internal_data.unpacker_data.tile_width * libraw_internal_data.unpacker_data.tile_length;
unsigned pixelSize = sizeof(float) * ifd->samples;
unsigned tileBytes = tilePixels * pixelSize;
unsigned tileRowBytes = libraw_internal_data.unpacker_data.tile_width * pixelSize;
unsigned char *cBuffer = (unsigned char *)malloc(maxBytesInTile);
unsigned char *uBuffer = (unsigned char *)malloc(tileBytes + tileRowBytes); // extra row for decoding
for (size_t y = 0, t = 0; y < imgdata.sizes.raw_height; y += libraw_internal_data.unpacker_data.tile_length)
{
for (size_t x = 0; x < imgdata.sizes.raw_width; x += libraw_internal_data.unpacker_data.tile_width, ++t)
{
libraw_internal_data.internal_data.input->seek(tOffsets[t], SEEK_SET);
libraw_internal_data.internal_data.input->read(cBuffer, 1, tBytes[t]);
unsigned long dstLen = tileBytes;
int err = uncompress(uBuffer + tileRowBytes, &dstLen, cBuffer, tBytes[t]);
if (err != Z_OK)
{
free(tOffsets);
free(tBytes);
free(cBuffer);
free(uBuffer);
throw LIBRAW_EXCEPTION_DECODE_RAW;
return;
}
else
{
int bytesps = ifd->bps >> 3;
size_t rowsInTile = y + libraw_internal_data.unpacker_data.tile_length > imgdata.sizes.raw_height
? imgdata.sizes.raw_height - y
: libraw_internal_data.unpacker_data.tile_length;
size_t colsInTile = x + libraw_internal_data.unpacker_data.tile_width > imgdata.sizes.raw_width
? imgdata.sizes.raw_width - x
: libraw_internal_data.unpacker_data.tile_width;
for (size_t row = 0; row < rowsInTile; ++row) // do not process full tile if not needed
{
unsigned char *dst = uBuffer + row * libraw_internal_data.unpacker_data.tile_width * bytesps * ifd->samples;
unsigned char *src = dst + tileRowBytes;
DecodeFPDelta(src, dst, libraw_internal_data.unpacker_data.tile_width / xFactor, ifd->samples * xFactor,
bytesps);
float lmax = expandFloats(dst, libraw_internal_data.unpacker_data.tile_width * ifd->samples, bytesps);
max = MAX(max, lmax);
unsigned char *dst2 =
(unsigned char *)&float_raw_image[((y + row) * imgdata.sizes.raw_width + x) * ifd->samples];
memmove(dst2, dst, colsInTile * ifd->samples * sizeof(float));
}
}
}
}
free(tOffsets);
free(tBytes);
free(cBuffer);
free(uBuffer);
}
imgdata.color.fmaximum = max;
// Set fields according to data format
imgdata.rawdata.raw_alloc = float_raw_image;
if (ifd->samples == 1)
{
imgdata.rawdata.float_image = float_raw_image;
imgdata.rawdata.sizes.raw_pitch = imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 4;
}
else if (ifd->samples == 3)
{
imgdata.rawdata.float3_image = (float(*)[3])float_raw_image;
imgdata.rawdata.sizes.raw_pitch = imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 12;
}
else if (ifd->samples == 4)
{
imgdata.rawdata.float4_image = (float(*)[4])float_raw_image;
imgdata.rawdata.sizes.raw_pitch = imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 16;
}
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_CONVERTFLOAT_TO_INT)
convertFloatToInt(); // with default settings
}
#else
void LibRaw::deflate_dng_load_raw() { throw LIBRAW_EXCEPTION_DECODE_RAW; }
#endif
int LibRaw::is_floating_point()
{
struct tiff_ifd_t *ifd = &tiff_ifd[0];
while (ifd < &tiff_ifd[libraw_internal_data.identify_data.tiff_nifds] &&
ifd->offset != libraw_internal_data.unpacker_data.data_offset)
++ifd;
if (ifd == &tiff_ifd[libraw_internal_data.identify_data.tiff_nifds])
return 0;
return ifd->sample_format == 3;
}
int LibRaw::have_fpdata()
{
return imgdata.rawdata.float_image || imgdata.rawdata.float3_image || imgdata.rawdata.float4_image;
}
void LibRaw::convertFloatToInt(float dmin /* =4096.f */, float dmax /* =32767.f */, float dtarget /*= 16383.f */)
{
int samples = 0;
float *data = 0;
if (imgdata.rawdata.float_image)
{
samples = 1;
data = imgdata.rawdata.float_image;
}
else if (imgdata.rawdata.float3_image)
{
samples = 3;
data = (float *)imgdata.rawdata.float3_image;
}
else if (imgdata.rawdata.float4_image)
{
samples = 4;
data = (float *)imgdata.rawdata.float4_image;
}
else
return;
ushort *raw_alloc = (ushort *)malloc(imgdata.sizes.raw_height * imgdata.sizes.raw_width *
libraw_internal_data.unpacker_data.tiff_samples * sizeof(ushort));
float tmax = MAX(imgdata.color.maximum, 1);
float datamax = imgdata.color.fmaximum;
tmax = MAX(tmax, datamax);
tmax = MAX(tmax, 1.f);
float multip = 1.f;
if (tmax < dmin || tmax > dmax)
{
imgdata.rawdata.color.fnorm = imgdata.color.fnorm = multip = dtarget / tmax;
imgdata.rawdata.color.maximum = imgdata.color.maximum = dtarget;
imgdata.rawdata.color.black = imgdata.color.black = (float)imgdata.color.black * multip;
for (int i = 0; i < sizeof(imgdata.color.cblack) / sizeof(imgdata.color.cblack[0]); i++)
if (i != 4 && i != 5)
imgdata.rawdata.color.cblack[i] = imgdata.color.cblack[i] = (float)imgdata.color.cblack[i] * multip;
}
else
imgdata.rawdata.color.fnorm = imgdata.color.fnorm = 0.f;
for (size_t i = 0;
i < imgdata.sizes.raw_height * imgdata.sizes.raw_width * libraw_internal_data.unpacker_data.tiff_samples; ++i)
{
float val = MAX(data[i], 0.f);
raw_alloc[i] = (ushort)(val * multip);
}
if (samples == 1)
{
imgdata.rawdata.raw_alloc = imgdata.rawdata.raw_image = raw_alloc;
imgdata.rawdata.sizes.raw_pitch = imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 2;
}
else if (samples == 3)
{
imgdata.rawdata.raw_alloc = imgdata.rawdata.color3_image = (ushort(*)[3])raw_alloc;
imgdata.rawdata.sizes.raw_pitch = imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 6;
}
else if (samples == 4)
{
imgdata.rawdata.raw_alloc = imgdata.rawdata.color4_image = (ushort(*)[4])raw_alloc;
imgdata.rawdata.sizes.raw_pitch = imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 8;
}
free(data); // remove old allocation
imgdata.rawdata.float_image = 0;
imgdata.rawdata.float3_image = 0;
imgdata.rawdata.float4_image = 0;
}
void LibRaw::sony_arq_load_raw()
{
int row, col;
read_shorts(imgdata.rawdata.raw_image, imgdata.sizes.raw_width * imgdata.sizes.raw_height * 4);
libraw_internal_data.internal_data.input->seek(-2,SEEK_CUR); // avoid wrong eof error
for (row = 0; row < imgdata.sizes.raw_height; row++)
{
unsigned short(*rowp)[4] = (unsigned short(*)[4]) & imgdata.rawdata.raw_image[row * imgdata.sizes.raw_width * 4];
for (col = 0; col < imgdata.sizes.raw_width; col++)
{
unsigned short g2 = rowp[col][2];
rowp[col][2] = rowp[col][3];
rowp[col][3] = g2;
if (((unsigned)(row - imgdata.sizes.top_margin) < imgdata.sizes.height) &&
((unsigned)(col - imgdata.sizes.left_margin) < imgdata.sizes.width) &&
(MAX(MAX(rowp[col][0], rowp[col][1]), MAX(rowp[col][2], rowp[col][3])) > imgdata.color.maximum))
derror();
}
}
}
void LibRaw::pentax_4shot_load_raw()
{
ushort *plane = (ushort *)malloc(imgdata.sizes.raw_width * imgdata.sizes.raw_height * sizeof(ushort));
int alloc_sz = imgdata.sizes.raw_width * (imgdata.sizes.raw_height + 16) * 4 * sizeof(ushort);
ushort(*result)[4] = (ushort(*)[4])malloc(alloc_sz);
struct movement_t
{
int row, col;
} _move[4] = {
{1, 1},
{0, 1},
{0, 0},
{1, 0},
};
int tidx = 0;
for (int i = 0; i < 4; i++)
{
int move_row, move_col;
if (imgdata.params.p4shot_order[i] >= '0' && imgdata.params.p4shot_order[i] <= '3')
{
move_row = (imgdata.params.p4shot_order[i] - '0' & 2) ? 1 : 0;
move_col = (imgdata.params.p4shot_order[i] - '0' & 1) ? 1 : 0;
}
else
{
move_row = _move[i].row;
move_col = _move[i].col;
}
for (; tidx < 16; tidx++)
if (tiff_ifd[tidx].t_width == imgdata.sizes.raw_width && tiff_ifd[tidx].t_height == imgdata.sizes.raw_height &&
tiff_ifd[tidx].bps > 8 && tiff_ifd[tidx].samples == 1)
break;
if (tidx >= 16)
break;
imgdata.rawdata.raw_image = plane;
ID.input->seek(tiff_ifd[tidx].offset, SEEK_SET);
imgdata.idata.filters = 0xb4b4b4b4;
libraw_internal_data.unpacker_data.data_offset = tiff_ifd[tidx].offset;
(this->*pentax_component_load_raw)();
for (int row = 0; row < imgdata.sizes.raw_height - move_row; row++)
{
int colors[2];
for (int c = 0; c < 2; c++)
colors[c] = COLOR(row, c);
ushort *srcrow = &plane[imgdata.sizes.raw_width * row];
ushort(*dstrow)[4] = &result[(imgdata.sizes.raw_width) * (row + move_row) + move_col];
for (int col = 0; col < imgdata.sizes.raw_width - move_col; col++)
dstrow[col][colors[col % 2]] = srcrow[col];
}
tidx++;
}
// assign things back:
imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 8;
imgdata.idata.filters = 0;
imgdata.rawdata.raw_alloc = imgdata.rawdata.color4_image = result;
free(plane);
imgdata.rawdata.raw_image = 0;
}
void LibRaw::hasselblad_full_load_raw()
{
int row, col;
for (row = 0; row < S.height; row++)
for (col = 0; col < S.width; col++)
{
read_shorts(&imgdata.image[row * S.width + col][2], 1); // B
read_shorts(&imgdata.image[row * S.width + col][1], 1); // G
read_shorts(&imgdata.image[row * S.width + col][0], 1); // R
}
}
static inline void unpack7bytesto4x16(unsigned char *src, unsigned short *dest)
{
dest[0] = (src[0] << 6) | (src[1] >> 2);
dest[1] = ((src[1] & 0x3) << 12) | (src[2] << 4) | (src[3] >> 4);
dest[2] = (src[3] & 0xf) << 10 | (src[4] << 2) | (src[5] >> 6);
dest[3] = ((src[5] & 0x3f) << 8) | src[6];
}
static inline void unpack28bytesto16x16ns(unsigned char *src, unsigned short *dest)
{
dest[0] = (src[3] << 6) | (src[2] >> 2);
dest[1] = ((src[2] & 0x3) << 12) | (src[1] << 4) | (src[0] >> 4);
dest[2] = (src[0] & 0xf) << 10 | (src[7] << 2) | (src[6] >> 6);
dest[3] = ((src[6] & 0x3f) << 8) | src[5];
dest[4] = (src[4] << 6) | (src[11] >> 2);
dest[5] = ((src[11] & 0x3) << 12) | (src[10] << 4) | (src[9] >> 4);
dest[6] = (src[9] & 0xf) << 10 | (src[8] << 2) | (src[15] >> 6);
dest[7] = ((src[15] & 0x3f) << 8) | src[14];
dest[8] = (src[13] << 6) | (src[12] >> 2);
dest[9] = ((src[12] & 0x3) << 12) | (src[19] << 4) | (src[18] >> 4);
dest[10] = (src[18] & 0xf) << 10 | (src[17] << 2) | (src[16] >> 6);
dest[11] = ((src[16] & 0x3f) << 8) | src[23];
dest[12] = (src[22] << 6) | (src[21] >> 2);
dest[13] = ((src[21] & 0x3) << 12) | (src[20] << 4) | (src[27] >> 4);
dest[14] = (src[27] & 0xf) << 10 | (src[26] << 2) | (src[25] >> 6);
dest[15] = ((src[25] & 0x3f) << 8) | src[24];
}
#define swab32(x) \
((unsigned int)((((unsigned int)(x) & (unsigned int)0x000000ffUL) << 24) | \
(((unsigned int)(x) & (unsigned int)0x0000ff00UL) << 8) | \
(((unsigned int)(x) & (unsigned int)0x00ff0000UL) >> 8) | \
(((unsigned int)(x) & (unsigned int)0xff000000UL) >> 24)))
static inline void swab32arr(unsigned *arr, unsigned len)
{
for (unsigned i = 0; i < len; i++)
arr[i] = swab32(arr[i]);
}
#undef swab32
void LibRaw::fuji_14bit_load_raw()
{
const unsigned linelen = S.raw_width * 7 / 4;
const unsigned pitch = S.raw_pitch ? S.raw_pitch / 2 : S.raw_width;
unsigned char *buf = (unsigned char *)malloc(linelen);
merror(buf, "fuji_14bit_load_raw()");
for (int row = 0; row < S.raw_height; row++)
{
unsigned bytesread = libraw_internal_data.internal_data.input->read(buf, 1, linelen);
unsigned short *dest = &imgdata.rawdata.raw_image[pitch * row];
if (bytesread % 28)
{
swab32arr((unsigned *)buf, bytesread / 4);
for (int sp = 0, dp = 0; dp < pitch - 3 && sp < linelen - 6 && sp < bytesread - 6; sp += 7, dp += 4)
unpack7bytesto4x16(buf + sp, dest + dp);
}
else
for (int sp = 0, dp = 0; dp < pitch - 15 && sp < linelen - 27 && sp < bytesread - 27; sp += 28, dp += 16)
unpack28bytesto16x16ns(buf + sp, dest + dp);
}
free(buf);
}
void LibRaw::nikon_load_striped_packed_raw()
{
int vbits = 0, bwide, rbits, bite, row, col, val, i;
UINT64 bitbuf = 0;
unsigned load_flags = 24; // libraw_internal_data.unpacker_data.load_flags;
unsigned tiff_bps = libraw_internal_data.unpacker_data.tiff_bps;
int tiff_compress = libraw_internal_data.unpacker_data.tiff_compress;
struct tiff_ifd_t *ifd = &tiff_ifd[0];
while (ifd < &tiff_ifd[libraw_internal_data.identify_data.tiff_nifds] &&
ifd->offset != libraw_internal_data.unpacker_data.data_offset)
++ifd;
if (ifd == &tiff_ifd[libraw_internal_data.identify_data.tiff_nifds])
throw LIBRAW_EXCEPTION_DECODE_RAW;
if (!ifd->rows_per_strip || !ifd->strip_offsets_count)
return; // not unpacked
int stripcnt = 0;
bwide = S.raw_width * tiff_bps / 8;
bwide += bwide & load_flags >> 7;
rbits = bwide * 8 - S.raw_width * tiff_bps;
if (load_flags & 1)
bwide = bwide * 16 / 15;
bite = 8 + (load_flags & 24);
for (row = 0; row < S.raw_height; row++)
{
checkCancel();
if (!(row % ifd->rows_per_strip))
{
if (stripcnt >= ifd->strip_offsets_count)
return; // run out of data
libraw_internal_data.internal_data.input->seek(ifd->strip_offsets[stripcnt], SEEK_SET);
stripcnt++;
}
for (col = 0; col < S.raw_width; col++)
{
for (vbits -= tiff_bps; vbits < 0; vbits += bite)
{
bitbuf <<= bite;
for (i = 0; i < bite; i += 8)
bitbuf |= (unsigned)(libraw_internal_data.internal_data.input->get_char() << i);
}
imgdata.rawdata.raw_image[(row)*S.raw_width + (col)] = bitbuf << (64 - tiff_bps - vbits) >> (64 - tiff_bps);
}
vbits -= rbits;
}
}
struct foveon_data_t
{
const char *make;
const char *model;
const int raw_width, raw_height;
const int white;
const int left_margin, top_margin;
const int width, height;
} foveon_data[] = {
{"Sigma", "SD9", 2304, 1531, 12000, 20, 8, 2266, 1510},
{"Sigma", "SD9", 1152, 763, 12000, 10, 2, 1132, 755},
{"Sigma", "SD10", 2304, 1531, 12000, 20, 8, 2266, 1510},
{"Sigma", "SD10", 1152, 763, 12000, 10, 2, 1132, 755},
{"Sigma", "SD14", 2688, 1792, 14000, 18, 12, 2651, 1767},
{"Sigma", "SD14", 2688, 896, 14000, 18, 6, 2651, 883}, // 2/3
{"Sigma", "SD14", 1344, 896, 14000, 9, 6, 1326, 883}, // 1/2
{"Sigma", "SD15", 2688, 1792, 2900, 18, 12, 2651, 1767},
{"Sigma", "SD15", 2688, 896, 2900, 18, 6, 2651, 883}, // 2/3 ?
{"Sigma", "SD15", 1344, 896, 2900, 9, 6, 1326, 883}, // 1/2 ?
{"Sigma", "DP1", 2688, 1792, 2100, 18, 12, 2651, 1767},
{"Sigma", "DP1", 2688, 896, 2100, 18, 6, 2651, 883}, // 2/3 ?
{"Sigma", "DP1", 1344, 896, 2100, 9, 6, 1326, 883}, // 1/2 ?
{"Sigma", "DP1S", 2688, 1792, 2200, 18, 12, 2651, 1767},
{"Sigma", "DP1S", 2688, 896, 2200, 18, 6, 2651, 883}, // 2/3
{"Sigma", "DP1S", 1344, 896, 2200, 9, 6, 1326, 883}, // 1/2
{"Sigma", "DP1X", 2688, 1792, 3560, 18, 12, 2651, 1767},
{"Sigma", "DP1X", 2688, 896, 3560, 18, 6, 2651, 883}, // 2/3
{"Sigma", "DP1X", 1344, 896, 3560, 9, 6, 1326, 883}, // 1/2
{"Sigma", "DP2", 2688, 1792, 2326, 13, 16, 2651, 1767},
{"Sigma", "DP2", 2688, 896, 2326, 13, 8, 2651, 883}, // 2/3 ??
{"Sigma", "DP2", 1344, 896, 2326, 7, 8, 1325, 883}, // 1/2 ??
{"Sigma", "DP2S", 2688, 1792, 2300, 18, 12, 2651, 1767},
{"Sigma", "DP2S", 2688, 896, 2300, 18, 6, 2651, 883}, // 2/3
{"Sigma", "DP2S", 1344, 896, 2300, 9, 6, 1326, 883}, // 1/2
{"Sigma", "DP2X", 2688, 1792, 2300, 18, 12, 2651, 1767},
{"Sigma", "DP2X", 2688, 896, 2300, 18, 6, 2651, 883}, // 2/3
{"Sigma", "DP2X", 1344, 896, 2300, 9, 6, 1325, 883}, // 1/2
{"Sigma", "SD1", 4928, 3264, 3900, 12, 52, 4807, 3205}, // Full size
{"Sigma", "SD1", 4928, 1632, 3900, 12, 26, 4807, 1603}, // 2/3 size
{"Sigma", "SD1", 2464, 1632, 3900, 6, 26, 2403, 1603}, // 1/2 size
{"Sigma", "SD1 Merrill", 4928, 3264, 3900, 12, 52, 4807, 3205}, // Full size
{"Sigma", "SD1 Merrill", 4928, 1632, 3900, 12, 26, 4807, 1603}, // 2/3 size
{"Sigma", "SD1 Merrill", 2464, 1632, 3900, 6, 26, 2403, 1603}, // 1/2 size
{"Sigma", "DP1 Merrill", 4928, 3264, 3900, 12, 0, 4807, 3205},
{"Sigma", "DP1 Merrill", 2464, 1632, 3900, 12, 0, 2403, 1603}, // 1/2 size
{"Sigma", "DP1 Merrill", 4928, 1632, 3900, 12, 0, 4807, 1603}, // 2/3 size
{"Sigma", "DP2 Merrill", 4928, 3264, 3900, 12, 0, 4807, 3205},
{"Sigma", "DP2 Merrill", 2464, 1632, 3900, 12, 0, 2403, 1603}, // 1/2 size
{"Sigma", "DP2 Merrill", 4928, 1632, 3900, 12, 0, 4807, 1603}, // 2/3 size
{"Sigma", "DP3 Merrill", 4928, 3264, 3900, 12, 0, 4807, 3205},
{"Sigma", "DP3 Merrill", 2464, 1632, 3900, 12, 0, 2403, 1603}, // 1/2 size
{"Sigma", "DP3 Merrill", 4928, 1632, 3900, 12, 0, 4807, 1603}, // 2/3 size
{"Polaroid", "x530", 1440, 1088, 2700, 10, 13, 1419, 1059},
// dp2 Q
{"Sigma", "dp3 Quattro", 5888, 3672, 16383, 204, 24, 5446, 3624}, // full size
{"Sigma", "dp3 Quattro", 2944, 1836, 16383, 102, 12, 2723, 1812}, // half size
{"Sigma", "dp2 Quattro", 5888, 3672, 16383, 204, 24, 5446, 3624}, // full size
{"Sigma", "dp2 Quattro", 2944, 1836, 16383, 102, 12, 2723, 1812}, // half size
{"Sigma", "dp1 Quattro", 5888, 3672, 16383, 204, 24, 5446, 3624}, // full size
{"Sigma", "dp1 Quattro", 2944, 1836, 16383, 102, 12, 2723, 1812}, // half size
{"Sigma", "dp0 Quattro", 5888, 3672, 16383, 204, 24, 5446, 3624}, // full size
{"Sigma", "dp0 Quattro", 2944, 1836, 16383, 102, 12, 2723, 1812}, // half size
// Sigma sd Quattro
{"Sigma", "sd Quattro", 5888, 3776, 16383, 204, 76, 5446, 3624}, // full size
{"Sigma", "sd Quattro", 2944, 1888, 16383, 102, 38, 2723, 1812}, // half size
// Sd Quattro H
{"Sigma", "sd Quattro H", 6656, 4480, 16383, 224, 160, 6208, 4160}, // full size
{"Sigma", "sd Quattro H", 3328, 2240, 16383, 112, 80, 3104, 2080}, // half size
{"Sigma", "sd Quattro H", 5504, 3680, 16383, 0, 4, 5496, 3668}, // full size
{"Sigma", "sd Quattro H", 2752, 1840, 16383, 0, 2, 2748, 1834}, // half size
};
const int foveon_count = sizeof(foveon_data) / sizeof(foveon_data[0]);
int LibRaw::open_datastream(LibRaw_abstract_datastream *stream)
{
if (!stream)
return ENOENT;
if (!stream->valid())
return LIBRAW_IO_ERROR;
recycle();
if(callbacks.pre_identify_cb)
{
int r = (callbacks.pre_identify_cb)(this);
if(r == 1) goto final;
}
try
{
ID.input = stream;
SET_PROC_FLAG(LIBRAW_PROGRESS_OPEN);
identify();
if(callbacks.post_identify_cb)
(callbacks.post_identify_cb)(this);
// Linear max from 14-bit camera, but on 12-bit data?
if(( !strcasecmp(imgdata.idata.make, "Sony") /* || !strcasecmp(imgdata.idata.make, "Nikon") */)
&& imgdata.color.maximum > 0 && imgdata.color.linear_max[0] > imgdata.color.maximum
&& imgdata.color.linear_max[0] <= imgdata.color.maximum*4)
for(int c = 0; c<4; c++)
imgdata.color.linear_max[c] /= 4;
if (!strcasecmp(imgdata.idata.make, "Canon") && (load_raw == &LibRaw::canon_sraw_load_raw) &&
imgdata.sizes.raw_width > 0)
{
float ratio = float(imgdata.sizes.raw_height) / float(imgdata.sizes.raw_width);
if ((ratio < 0.57 || ratio > 0.75) && imgdata.makernotes.canon.SensorHeight > 1 &&
imgdata.makernotes.canon.SensorWidth > 1)
{
imgdata.sizes.raw_width = imgdata.makernotes.canon.SensorWidth;
imgdata.sizes.left_margin = imgdata.makernotes.canon.SensorLeftBorder;
imgdata.sizes.iwidth = imgdata.sizes.width =
imgdata.makernotes.canon.SensorRightBorder - imgdata.makernotes.canon.SensorLeftBorder + 1;
imgdata.sizes.raw_height = imgdata.makernotes.canon.SensorHeight;
imgdata.sizes.top_margin = imgdata.makernotes.canon.SensorTopBorder;
imgdata.sizes.iheight = imgdata.sizes.height =
imgdata.makernotes.canon.SensorBottomBorder - imgdata.makernotes.canon.SensorTopBorder + 1;
libraw_internal_data.unpacker_data.load_flags |= 256; // reset width/height in canon_sraw_load_raw()
imgdata.sizes.raw_pitch = 8 * imgdata.sizes.raw_width;
}
else if (imgdata.sizes.raw_width == 4032 && imgdata.sizes.raw_height == 3402 &&
!strcasecmp(imgdata.idata.model, "EOS 80D")) // 80D hardcoded
{
imgdata.sizes.raw_width = 4536;
imgdata.sizes.left_margin = 28;
imgdata.sizes.iwidth = imgdata.sizes.width = imgdata.sizes.raw_width - imgdata.sizes.left_margin;
imgdata.sizes.raw_height = 3024;
imgdata.sizes.top_margin = 8;
imgdata.sizes.iheight = imgdata.sizes.height = imgdata.sizes.raw_height - imgdata.sizes.top_margin;
libraw_internal_data.unpacker_data.load_flags |= 256;
imgdata.sizes.raw_pitch = 8 * imgdata.sizes.raw_width;
}
}
// XTrans Compressed?
if (!imgdata.idata.dng_version && !strcasecmp(imgdata.idata.make, "Fujifilm") &&
(load_raw == &LibRaw::unpacked_load_raw))
{
if (imgdata.sizes.raw_width * imgdata.sizes.raw_height * 2 != libraw_internal_data.unpacker_data.data_size)
{
if (imgdata.sizes.raw_width * imgdata.sizes.raw_height * 7 / 4 == libraw_internal_data.unpacker_data.data_size)
load_raw = &LibRaw::fuji_14bit_load_raw;
else
parse_fuji_compressed_header();
}
if (imgdata.idata.filters == 9)
{
// Adjust top/left margins for X-Trans
int newtm = imgdata.sizes.top_margin % 6 ? (imgdata.sizes.top_margin / 6 + 1) * 6 : imgdata.sizes.top_margin;
int newlm = imgdata.sizes.left_margin % 6 ? (imgdata.sizes.left_margin / 6 + 1) * 6 : imgdata.sizes.left_margin;
if (newtm != imgdata.sizes.top_margin || newlm != imgdata.sizes.left_margin)
{
imgdata.sizes.height -= (newtm - imgdata.sizes.top_margin);
imgdata.sizes.top_margin = newtm;
imgdata.sizes.width -= (newlm - imgdata.sizes.left_margin);
imgdata.sizes.left_margin = newlm;
for (int c1 = 0; c1 < 6; c1++)
for (int c2 = 0; c2 < 6; c2++)
imgdata.idata.xtrans[c1][c2] = imgdata.idata.xtrans_abs[c1][c2];
}
}
}
// Fix DNG white balance if needed
if (imgdata.idata.dng_version && (imgdata.idata.filters == 0) && imgdata.idata.colors > 1 &&
imgdata.idata.colors < 5)
{
float delta[4] = {0.f, 0.f, 0.f, 0.f};
int black[4];
for (int c = 0; c < 4; c++)
black[c] = imgdata.color.dng_levels.dng_black + imgdata.color.dng_levels.dng_cblack[c];
for (int c = 0; c < imgdata.idata.colors; c++)
delta[c] = imgdata.color.dng_levels.dng_whitelevel[c] - black[c];
float mindelta = delta[0], maxdelta = delta[0];
for (int c = 1; c < imgdata.idata.colors; c++)
{
if (mindelta > delta[c])
mindelta = delta[c];
if (maxdelta < delta[c])
maxdelta = delta[c];
}
if (mindelta > 1 && maxdelta < (mindelta * 20)) // safety
{
for (int c = 0; c < imgdata.idata.colors; c++)
{
imgdata.color.cam_mul[c] /= (delta[c] / maxdelta);
imgdata.color.pre_mul[c] /= (delta[c] / maxdelta);
}
imgdata.color.maximum = imgdata.color.cblack[0] + maxdelta;
}
}
if (imgdata.idata.dng_version &&
((!strcasecmp(imgdata.idata.make, "Leica") && !strcasecmp(imgdata.idata.model, "D-LUX (Typ 109)")) ||
(!strcasecmp(imgdata.idata.make, "Panasonic") && !strcasecmp(imgdata.idata.model, "LX100"))))
imgdata.sizes.width = 4288;
if (!strncasecmp(imgdata.idata.make, "Sony", 4) && imgdata.idata.dng_version &&
!(imgdata.params.raw_processing_options & LIBRAW_PROCESSING_USE_DNG_DEFAULT_CROP))
{
if (S.raw_width == 3984)
S.width = 3925;
else if (S.raw_width == 4288)
S.width = S.raw_width - 32;
else if (S.raw_width == 4928 && S.height < 3280)
S.width = S.raw_width - 8;
else if (S.raw_width == 5504)
S.width = S.raw_width - (S.height > 3664 ? 8 : 32);
}
if (!strcasecmp(imgdata.idata.make, "Pentax") &&
/*!strcasecmp(imgdata.idata.model,"K-3 II") &&*/ imgdata.idata.raw_count == 4 &&
(imgdata.params.raw_processing_options & LIBRAW_PROCESSING_PENTAX_PS_ALLFRAMES))
{
imgdata.idata.raw_count = 1;
imgdata.idata.filters = 0;
imgdata.idata.colors = 4;
IO.mix_green = 1;
pentax_component_load_raw = load_raw;
load_raw = &LibRaw::pentax_4shot_load_raw;
}
if (!imgdata.idata.dng_version && !strcmp(imgdata.idata.make, "Leaf") && !strcmp(imgdata.idata.model, "Credo 50"))
{
imgdata.color.pre_mul[0] = 1.f / 0.3984f;
imgdata.color.pre_mul[2] = 1.f / 0.7666f;
imgdata.color.pre_mul[1] = imgdata.color.pre_mul[3] = 1.0;
}
// S3Pro DNG patch
if (imgdata.idata.dng_version && !strcmp(imgdata.idata.make, "Fujifilm") && !strcmp(imgdata.idata.model, "S3Pro") &&
imgdata.sizes.raw_width == 4288)
{
imgdata.sizes.left_margin++;
imgdata.sizes.width--;
}
if (imgdata.idata.dng_version && !strcmp(imgdata.idata.make, "Fujifilm") && !strcmp(imgdata.idata.model, "S5Pro") &&
imgdata.sizes.raw_width == 4288)
{
imgdata.sizes.left_margin++;
imgdata.sizes.width--;
}
if (!imgdata.idata.dng_version && !strcmp(imgdata.idata.make, "Fujifilm") &&
(!strncmp(imgdata.idata.model, "S20Pro", 6) || !strncmp(imgdata.idata.model, "F700", 4)))
{
imgdata.sizes.raw_width /= 2;
load_raw = &LibRaw::unpacked_load_raw_fuji_f700s20;
}
if (load_raw == &LibRaw::packed_load_raw && !strcasecmp(imgdata.idata.make, "Nikon") &&
!libraw_internal_data.unpacker_data.load_flags &&
(!strncasecmp(imgdata.idata.model, "D810", 4) || !strcasecmp(imgdata.idata.model, "D4S")) &&
libraw_internal_data.unpacker_data.data_size * 2 == imgdata.sizes.raw_height * imgdata.sizes.raw_width * 3)
{
libraw_internal_data.unpacker_data.load_flags = 80;
}
// Adjust BL for Sony A900/A850
if (load_raw == &LibRaw::packed_load_raw &&
!strcasecmp(imgdata.idata.make, "Sony")) // 12 bit sony, but metadata may be for 14-bit range
{
if (C.maximum > 4095)
C.maximum = 4095;
if (C.black > 256 || C.cblack[0] > 256)
{
C.black /= 4;
for (int c = 0; c < 4; c++)
C.cblack[c] /= 4;
for (int c = 0; c < C.cblack[4] * C.cblack[5]; c++)
C.cblack[6 + c] /= 4;
}
}
if (load_raw == &LibRaw::nikon_yuv_load_raw) // Is it Nikon sRAW?
{
load_raw = &LibRaw::nikon_load_sraw;
C.black = 0;
memset(C.cblack, 0, sizeof(C.cblack));
imgdata.idata.filters = 0;
libraw_internal_data.unpacker_data.tiff_samples = 3;
imgdata.idata.colors = 3;
double beta_1 = -5.79342238397656E-02;
double beta_2 = 3.28163551282665;
double beta_3 = -8.43136004842678;
double beta_4 = 1.03533181861023E+01;
for (int i = 0; i <= 3072; i++)
{
double x = (double)i / 3072.;
double y = (1. - exp(-beta_1 * x - beta_2 * x * x - beta_3 * x * x * x - beta_4 * x * x * x * x));
if (y < 0.)
y = 0.;
imgdata.color.curve[i] = (y * 16383.);
}
for (int i = 0; i < 3; i++)
for (int j = 0; j < 4; j++)
imgdata.color.rgb_cam[i][j] = float(i == j);
}
// Adjust BL for Nikon 12bit
if ((load_raw == &LibRaw::nikon_load_raw || load_raw == &LibRaw::packed_load_raw) &&
!strcasecmp(imgdata.idata.make, "Nikon") &&
strncmp(imgdata.idata.model, "COOLPIX", 7)
// && strncmp(imgdata.idata.model,"1 ",2)
&& libraw_internal_data.unpacker_data.tiff_bps == 12)
{
C.maximum = 4095;
C.black /= 4;
for (int c = 0; c < 4; c++)
C.cblack[c] /= 4;
for (int c = 0; c < C.cblack[4] * C.cblack[5]; c++)
C.cblack[6 + c] /= 4;
}
// Adjust Highlight Linearity limit
if (C.linear_max[0] < 0)
{
if (imgdata.idata.dng_version)
{
for (int c = 0; c < 4; c++)
C.linear_max[c] = -1 * C.linear_max[c] + imgdata.color.cblack[c + 6];
}
else
{
for (int c = 0; c < 4; c++)
C.linear_max[c] = -1 * C.linear_max[c] + imgdata.color.cblack[c];
}
}
if (!strcasecmp(imgdata.idata.make, "Nikon") && (!C.linear_max[0]) && (C.maximum > 1024) &&
(load_raw != &LibRaw::nikon_load_sraw))
{
C.linear_max[0] = C.linear_max[1] = C.linear_max[2] = C.linear_max[3] = (long)((float)(C.maximum) / 1.07f);
}
// Correct WB for Samsung GX20
if (!strcasecmp(imgdata.idata.make, "Samsung") && !strcasecmp(imgdata.idata.model, "GX20"))
{
C.WB_Coeffs[LIBRAW_WBI_Daylight][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_Daylight][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_Shade][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_Shade][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_Cloudy][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_Cloudy][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_Tungsten][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_Tungsten][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_FL_D][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_FL_D][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_FL_N][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_FL_N][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_FL_W][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_FL_W][2]) * 2.56f);
C.WB_Coeffs[LIBRAW_WBI_Flash][2] = (int)((float)(C.WB_Coeffs[LIBRAW_WBI_Flash][2]) * 2.56f);
for (int c = 0; c < 64; c++)
{
if (imgdata.color.WBCT_Coeffs[c][0] > 0.0f)
{
imgdata.color.WBCT_Coeffs[c][3] *= 2.56f;
}
}
}
// Adjust BL for Panasonic
if (load_raw == &LibRaw::panasonic_load_raw &&
(!strcasecmp(imgdata.idata.make, "Panasonic") || !strcasecmp(imgdata.idata.make, "Leica") ||
!strcasecmp(imgdata.idata.make, "YUNEEC")) &&
ID.pana_black[0] && ID.pana_black[1] && ID.pana_black[2])
{
if(libraw_internal_data.unpacker_data.pana_encoding == 5)
libraw_internal_data.internal_output_params.zero_is_bad = 0;
C.black = 0;
int add = libraw_internal_data.unpacker_data.pana_encoding == 4?15:0;
C.cblack[0] = ID.pana_black[0]+add;
C.cblack[1] = C.cblack[3] = ID.pana_black[1]+add;
C.cblack[2] = ID.pana_black[2]+add;
int i = C.cblack[3];
for (int c = 0; c < 3; c++)
if (i > C.cblack[c])
i = C.cblack[c];
for (int c = 0; c < 4; c++)
C.cblack[c] -= i;
C.black = i;
}
// Adjust sizes for X3F processing
if (load_raw == &LibRaw::x3f_load_raw)
{
for (int i = 0; i < foveon_count; i++)
if (!strcasecmp(imgdata.idata.make, foveon_data[i].make) &&
!strcasecmp(imgdata.idata.model, foveon_data[i].model) &&
imgdata.sizes.raw_width == foveon_data[i].raw_width &&
imgdata.sizes.raw_height == foveon_data[i].raw_height)
{
imgdata.sizes.top_margin = foveon_data[i].top_margin;
imgdata.sizes.left_margin = foveon_data[i].left_margin;
imgdata.sizes.width = imgdata.sizes.iwidth = foveon_data[i].width;
imgdata.sizes.height = imgdata.sizes.iheight = foveon_data[i].height;
C.maximum = foveon_data[i].white;
break;
}
}
#if 0
size_t bytes = ID.input->size()-libraw_internal_data.unpacker_data.data_offset;
float bpp = float(bytes)/float(S.raw_width)/float(S.raw_height);
float bpp2 = float(bytes)/float(S.width)/float(S.height);
printf("RawSize: %dx%d data offset: %d data size:%d bpp: %g bpp2: %g\n",S.raw_width,S.raw_height,libraw_internal_data.unpacker_data.data_offset,bytes,bpp,bpp2);
if(!strcasecmp(imgdata.idata.make,"Hasselblad") && bpp == 6.0f)
{
load_raw = &LibRaw::hasselblad_full_load_raw;
S.width = S.raw_width;
S.height = S.raw_height;
P1.filters = 0;
P1.colors=3;
P1.raw_count=1;
C.maximum=0xffff;
printf("3 channel hassy found\n");
}
#endif
if (C.profile_length)
{
if (C.profile)
free(C.profile);
C.profile = malloc(C.profile_length);
merror(C.profile, "LibRaw::open_file()");
ID.input->seek(ID.profile_offset, SEEK_SET);
ID.input->read(C.profile, C.profile_length, 1);
}
SET_PROC_FLAG(LIBRAW_PROGRESS_IDENTIFY);
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
catch (std::exception ee)
{
EXCEPTION_HANDLER(LIBRAW_EXCEPTION_IO_CORRUPT);
}
final:;
if (P1.raw_count < 1)
return LIBRAW_FILE_UNSUPPORTED;
write_fun = &LibRaw::write_ppm_tiff;
if (load_raw == &LibRaw::kodak_ycbcr_load_raw)
{
S.height += S.height & 1;
S.width += S.width & 1;
}
IO.shrink = P1.filters && (O.half_size || ((O.threshold || O.aber[0] != 1 || O.aber[2] != 1)));
if (IO.shrink && P1.filters >= 1000)
{
S.width &= 65534;
S.height &= 65534;
}
S.iheight = (S.height + IO.shrink) >> IO.shrink;
S.iwidth = (S.width + IO.shrink) >> IO.shrink;
// Save color,sizes and internal data into raw_image fields
memmove(&imgdata.rawdata.color, &imgdata.color, sizeof(imgdata.color));
memmove(&imgdata.rawdata.sizes, &imgdata.sizes, sizeof(imgdata.sizes));
memmove(&imgdata.rawdata.iparams, &imgdata.idata, sizeof(imgdata.idata));
memmove(&imgdata.rawdata.ioparams, &libraw_internal_data.internal_output_params,
sizeof(libraw_internal_data.internal_output_params));
SET_PROC_FLAG(LIBRAW_PROGRESS_SIZE_ADJUST);
return LIBRAW_SUCCESS;
}
#ifdef USE_RAWSPEED
void LibRaw::fix_after_rawspeed(int bl)
{
if (load_raw == &LibRaw::lossy_dng_load_raw)
C.maximum = 0xffff;
else if (load_raw == &LibRaw::sony_load_raw)
C.maximum = 0x3ff0;
}
#else
void LibRaw::fix_after_rawspeed(int) {}
#endif
void LibRaw::clearCancelFlag()
{
#ifdef WIN32
InterlockedExchange(&_exitflag, 0);
#else
__sync_fetch_and_and(&_exitflag, 0);
#endif
#ifdef RAWSPEED_FASTEXIT
if (_rawspeed_decoder)
{
RawDecoder *d = static_cast<RawDecoder *>(_rawspeed_decoder);
d->resumeProcessing();
}
#endif
}
void LibRaw::setCancelFlag()
{
#ifdef WIN32
InterlockedExchange(&_exitflag, 1);
#else
__sync_fetch_and_add(&_exitflag, 1);
#endif
#ifdef RAWSPEED_FASTEXIT
if (_rawspeed_decoder)
{
RawDecoder *d = static_cast<RawDecoder *>(_rawspeed_decoder);
d->cancelProcessing();
}
#endif
}
void LibRaw::checkCancel()
{
#ifdef WIN32
if (InterlockedExchange(&_exitflag, 0))
throw LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK;
#else
if (__sync_fetch_and_and(&_exitflag, 0))
throw LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK;
#endif
}
int LibRaw::try_rawspeed()
{
#ifdef USE_RAWSPEED
int ret = LIBRAW_SUCCESS;
int rawspeed_ignore_errors = 0;
if (imgdata.idata.dng_version && imgdata.idata.colors == 3 &&
!strcasecmp(imgdata.idata.software, "Adobe Photoshop Lightroom 6.1.1 (Windows)"))
rawspeed_ignore_errors = 1;
// RawSpeed Supported,
INT64 spos = ID.input->tell();
void *_rawspeed_buffer = 0;
try
{
// printf("Using rawspeed\n");
ID.input->seek(0, SEEK_SET);
INT64 _rawspeed_buffer_sz = ID.input->size() + 32;
_rawspeed_buffer = malloc(_rawspeed_buffer_sz);
if (!_rawspeed_buffer)
throw LIBRAW_EXCEPTION_ALLOC;
ID.input->read(_rawspeed_buffer, _rawspeed_buffer_sz, 1);
FileMap map((uchar8 *)_rawspeed_buffer, _rawspeed_buffer_sz);
RawParser t(&map);
RawDecoder *d = 0;
CameraMetaDataLR *meta = static_cast<CameraMetaDataLR *>(_rawspeed_camerameta);
d = t.getDecoder();
if (!d)
throw "Unable to find decoder";
try
{
d->checkSupport(meta);
}
catch (const RawDecoderException &e)
{
imgdata.process_warnings |= LIBRAW_WARN_RAWSPEED_UNSUPPORTED;
throw e;
}
d->interpolateBadPixels = FALSE;
d->applyStage1DngOpcodes = FALSE;
_rawspeed_decoder = static_cast<void *>(d);
d->decodeRaw();
d->decodeMetaData(meta);
RawImage r = d->mRaw;
if (r->errors.size() > 0 && !rawspeed_ignore_errors)
{
delete d;
_rawspeed_decoder = 0;
throw 1;
}
if (r->isCFA)
{
imgdata.rawdata.raw_image = (ushort *)r->getDataUncropped(0, 0);
}
else if (r->getCpp() == 4)
{
imgdata.rawdata.color4_image = (ushort(*)[4])r->getDataUncropped(0, 0);
if (r->whitePoint > 0 && r->whitePoint < 65536)
C.maximum = r->whitePoint;
}
else if (r->getCpp() == 3)
{
imgdata.rawdata.color3_image = (ushort(*)[3])r->getDataUncropped(0, 0);
if (r->whitePoint > 0 && r->whitePoint < 65536)
C.maximum = r->whitePoint;
}
else
{
delete d;
_rawspeed_decoder = 0;
ret = LIBRAW_UNSPECIFIED_ERROR;
}
if (_rawspeed_decoder)
{
// set sizes
iPoint2D rsdim = r->getUncroppedDim();
S.raw_pitch = r->pitch;
S.raw_width = rsdim.x;
S.raw_height = rsdim.y;
// C.maximum = r->whitePoint;
fix_after_rawspeed(r->blackLevel);
}
free(_rawspeed_buffer);
_rawspeed_buffer = 0;
imgdata.process_warnings |= LIBRAW_WARN_RAWSPEED_PROCESSED;
}
catch (const RawDecoderException &RDE)
{
imgdata.process_warnings |= LIBRAW_WARN_RAWSPEED_PROBLEM;
if (_rawspeed_buffer)
{
free(_rawspeed_buffer);
_rawspeed_buffer = 0;
}
const char *p = RDE.what();
if (!strncmp(RDE.what(), "Decoder canceled", strlen("Decoder canceled")))
throw LIBRAW_EXCEPTION_CANCELLED_BY_CALLBACK;
ret = LIBRAW_UNSPECIFIED_ERROR;
}
catch (...)
{
// We may get here due to cancellation flag
imgdata.process_warnings |= LIBRAW_WARN_RAWSPEED_PROBLEM;
if (_rawspeed_buffer)
{
free(_rawspeed_buffer);
_rawspeed_buffer = 0;
}
ret = LIBRAW_UNSPECIFIED_ERROR;
}
ID.input->seek(spos, SEEK_SET);
return ret;
#else
return LIBRAW_NOT_IMPLEMENTED;
#endif
}
int LibRaw::valid_for_dngsdk()
{
#ifndef USE_DNGSDK
return 0;
#else
if (!imgdata.idata.dng_version)
return 0;
if (!imgdata.params.use_dngsdk)
return 0;
if (load_raw == &LibRaw::lossy_dng_load_raw)
return 0;
if (is_floating_point() && (imgdata.params.use_dngsdk & LIBRAW_DNG_FLOAT))
return 1;
if (!imgdata.idata.filters && (imgdata.params.use_dngsdk & LIBRAW_DNG_LINEAR))
return 1;
if (libraw_internal_data.unpacker_data.tiff_bps == 8 && (imgdata.params.use_dngsdk & LIBRAW_DNG_8BIT))
return 1;
if (libraw_internal_data.unpacker_data.tiff_compress == 8 && (imgdata.params.use_dngsdk & LIBRAW_DNG_DEFLATE))
return 1;
if (libraw_internal_data.unpacker_data.tiff_samples == 2)
return 0; // Always deny 2-samples (old fuji superccd)
if (imgdata.idata.filters == 9 && (imgdata.params.use_dngsdk & LIBRAW_DNG_XTRANS))
return 1;
if (is_fuji_rotated())
return 0; // refuse
if (imgdata.params.use_dngsdk & LIBRAW_DNG_OTHER)
return 1;
return 0;
#endif
}
int LibRaw::is_curve_linear()
{
for (int i = 0; i < 0x10000; i++)
if (imgdata.color.curve[i] != i)
return 0;
return 1;
}
int LibRaw::try_dngsdk()
{
#ifdef USE_DNGSDK
if (!dnghost)
return LIBRAW_UNSPECIFIED_ERROR;
dng_host *host = static_cast<dng_host *>(dnghost);
try
{
libraw_dng_stream stream(libraw_internal_data.internal_data.input);
AutoPtr<dng_negative> negative;
negative.Reset(host->Make_dng_negative());
dng_info info;
info.Parse(*host, stream);
info.PostParse(*host);
if (!info.IsValidDNG())
{
return LIBRAW_DATA_ERROR;
}
negative->Parse(*host, stream, info);
negative->PostParse(*host, stream, info);
negative->ReadStage1Image(*host, stream, info);
dng_simple_image *stage2 = (dng_simple_image *)negative->Stage1Image();
if (stage2->Bounds().W() != S.raw_width || stage2->Bounds().H() != S.raw_height)
{
return LIBRAW_DATA_ERROR;
}
int pplanes = stage2->Planes();
int ptype = stage2->PixelType();
dng_pixel_buffer buffer;
stage2->GetPixelBuffer(buffer);
int pixels = stage2->Bounds().H() * stage2->Bounds().W() * pplanes;
if (ptype == ttByte)
imgdata.rawdata.raw_alloc = malloc(pixels * TagTypeSize(ttShort));
else
imgdata.rawdata.raw_alloc = malloc(pixels * TagTypeSize(ptype));
if (ptype == ttShort && !is_curve_linear())
{
ushort *src = (ushort *)buffer.fData;
ushort *dst = (ushort *)imgdata.rawdata.raw_alloc;
for (int i = 0; i < pixels; i++)
dst[i] = imgdata.color.curve[src[i]];
S.raw_pitch = S.raw_width * pplanes * TagTypeSize(ptype);
}
else if (ptype == ttByte)
{
unsigned char *src = (unsigned char *)buffer.fData;
ushort *dst = (ushort *)imgdata.rawdata.raw_alloc;
if (is_curve_linear())
{
for (int i = 0; i < pixels; i++)
dst[i] = src[i];
}
else
{
for (int i = 0; i < pixels; i++)
dst[i] = imgdata.color.curve[src[i]];
}
S.raw_pitch = S.raw_width * pplanes * TagTypeSize(ttShort);
}
else
{
memmove(imgdata.rawdata.raw_alloc, buffer.fData, pixels * TagTypeSize(ptype));
S.raw_pitch = S.raw_width * pplanes * TagTypeSize(ptype);
}
switch (ptype)
{
case ttFloat:
if (pplanes == 1)
imgdata.rawdata.float_image = (float *)imgdata.rawdata.raw_alloc;
else if (pplanes == 3)
imgdata.rawdata.float3_image = (float(*)[3])imgdata.rawdata.raw_alloc;
else if (pplanes == 4)
imgdata.rawdata.float4_image = (float(*)[4])imgdata.rawdata.raw_alloc;
break;
case ttByte:
case ttShort:
if (pplanes == 1)
imgdata.rawdata.raw_image = (ushort *)imgdata.rawdata.raw_alloc;
else if (pplanes == 3)
imgdata.rawdata.color3_image = (ushort(*)[3])imgdata.rawdata.raw_alloc;
else if (pplanes == 4)
imgdata.rawdata.color4_image = (ushort(*)[4])imgdata.rawdata.raw_alloc;
break;
default:
/* do nothing */
break;
}
}
catch (...)
{
return LIBRAW_UNSPECIFIED_ERROR;
}
return imgdata.rawdata.raw_alloc ? LIBRAW_SUCCESS : LIBRAW_UNSPECIFIED_ERROR;
#else
return LIBRAW_UNSPECIFIED_ERROR;
#endif
}
void LibRaw::set_dng_host(void *p)
{
#ifdef USE_DNGSDK
dnghost = p;
#endif
}
int LibRaw::unpack(void)
{
CHECK_ORDER_HIGH(LIBRAW_PROGRESS_LOAD_RAW);
CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY);
try
{
if (!libraw_internal_data.internal_data.input)
return LIBRAW_INPUT_CLOSED;
RUN_CALLBACK(LIBRAW_PROGRESS_LOAD_RAW, 0, 2);
if (O.shot_select >= P1.raw_count)
return LIBRAW_REQUEST_FOR_NONEXISTENT_IMAGE;
if (!load_raw)
return LIBRAW_UNSPECIFIED_ERROR;
// already allocated ?
if (imgdata.image)
{
free(imgdata.image);
imgdata.image = 0;
}
if (imgdata.rawdata.raw_alloc)
{
free(imgdata.rawdata.raw_alloc);
imgdata.rawdata.raw_alloc = 0;
}
if (libraw_internal_data.unpacker_data.meta_length)
{
libraw_internal_data.internal_data.meta_data = (char *)malloc(libraw_internal_data.unpacker_data.meta_length);
merror(libraw_internal_data.internal_data.meta_data, "LibRaw::unpack()");
}
libraw_decoder_info_t decoder_info;
get_decoder_info(&decoder_info);
int save_iwidth = S.iwidth, save_iheight = S.iheight, save_shrink = IO.shrink;
int rwidth = S.raw_width, rheight = S.raw_height;
if (!IO.fuji_width)
{
// adjust non-Fuji allocation
if (rwidth < S.width + S.left_margin)
rwidth = S.width + S.left_margin;
if (rheight < S.height + S.top_margin)
rheight = S.height + S.top_margin;
}
if (rwidth > 65535 || rheight > 65535) // No way to make image larger than 64k pix
throw LIBRAW_EXCEPTION_IO_CORRUPT;
imgdata.rawdata.raw_image = 0;
imgdata.rawdata.color4_image = 0;
imgdata.rawdata.color3_image = 0;
imgdata.rawdata.float_image = 0;
imgdata.rawdata.float3_image = 0;
#ifdef USE_DNGSDK
if (imgdata.idata.dng_version && dnghost && imgdata.idata.raw_count == 1 && valid_for_dngsdk() &&
load_raw != &LibRaw::pentax_4shot_load_raw)
{
// Data size check
INT64 pixcount = INT64(MAX(S.width, S.raw_width)) * INT64(MAX(S.height, S.raw_height));
INT64 planecount = (imgdata.idata.filters || P1.colors == 1)?1: LIM(P1.colors,3,4);
INT64 samplesize = is_floating_point()?4:2;
INT64 bytes = pixcount * planecount * samplesize;
if(bytes > LIBRAW_MAX_ALLOC_MB * INT64(1024 * 1024)) throw LIBRAW_EXCEPTION_TOOBIG;
// find ifd to check sample
int rr = try_dngsdk();
}
#endif
#ifdef USE_RAWSPEED
if (!raw_was_read())
{
int rawspeed_enabled = 1;
if (imgdata.idata.dng_version && libraw_internal_data.unpacker_data.tiff_samples == 2)
rawspeed_enabled = 0;
if(libraw_internal_data.unpacker_data.pana_encoding == 5)
rawspeed_enabled = 0;
if (imgdata.idata.raw_count > 1)
rawspeed_enabled = 0;
if (!strncasecmp(imgdata.idata.software, "Magic", 5))
rawspeed_enabled = 0;
// Disable rawspeed for double-sized Oly files
if (!strncasecmp(imgdata.idata.make, "Olympus", 7) &&
((imgdata.sizes.raw_width > 6000) || !strncasecmp(imgdata.idata.model, "SH-2", 4) ||
!strncasecmp(imgdata.idata.model, "SH-3", 4) || !strncasecmp(imgdata.idata.model, "TG-4", 4) ||
!strncasecmp(imgdata.idata.model, "TG-5", 4)))
rawspeed_enabled = 0;
if (!strncasecmp(imgdata.idata.make, "Canon", 5) && !strcasecmp(imgdata.idata.model, "EOS 6D Mark II"))
rawspeed_enabled = 0;
if (imgdata.idata.dng_version && imgdata.idata.filters == 0 &&
libraw_internal_data.unpacker_data.tiff_bps == 8) // Disable for 8 bit
rawspeed_enabled = 0;
if (load_raw == &LibRaw::packed_load_raw && !strncasecmp(imgdata.idata.make, "Nikon", 5) &&
(!strncasecmp(imgdata.idata.model, "E", 1) || !strncasecmp(imgdata.idata.model, "COOLPIX B", 9)))
rawspeed_enabled = 0;
// RawSpeed Supported,
if (O.use_rawspeed && rawspeed_enabled &&
!(is_sraw() &&
(O.raw_processing_options & (LIBRAW_PROCESSING_SRAW_NO_RGB | LIBRAW_PROCESSING_SRAW_NO_INTERPOLATE))) &&
(decoder_info.decoder_flags & LIBRAW_DECODER_TRYRAWSPEED) && _rawspeed_camerameta)
{
INT64 pixcount = INT64(MAX(S.width, S.raw_width)) * INT64(MAX(S.height, S.raw_height));
INT64 planecount = (imgdata.idata.filters || P1.colors == 1)?1: LIM(P1.colors,3,4);
INT64 bytes = pixcount * planecount * 2; // sample size is always 2 for rawspeed
if(bytes > LIBRAW_MAX_ALLOC_MB * INT64(1024 * 1024)) throw LIBRAW_EXCEPTION_TOOBIG;
int rr = try_rawspeed();
}
}
#endif
if (!raw_was_read()) // RawSpeed failed or not run
{
// Not allocated on RawSpeed call, try call LibRaow
int zero_rawimage = 0;
if (decoder_info.decoder_flags & LIBRAW_DECODER_OWNALLOC)
{
// x3f foveon decoder and DNG float
// Do nothing! Decoder will allocate data internally
}
if (decoder_info.decoder_flags & LIBRAW_DECODER_3CHANNEL)
{
if (INT64(rwidth) * INT64(rheight + 8) * sizeof(imgdata.rawdata.raw_image[0]) * 3 >
LIBRAW_MAX_ALLOC_MB * INT64(1024 * 1024))
throw LIBRAW_EXCEPTION_TOOBIG;
imgdata.rawdata.raw_alloc = malloc(rwidth * (rheight + 8) * sizeof(imgdata.rawdata.raw_image[0]) * 3);
imgdata.rawdata.color3_image = (ushort(*)[3])imgdata.rawdata.raw_alloc;
if (!S.raw_pitch)
S.raw_pitch = S.raw_width * 6;
}
else if (imgdata.idata.filters || P1.colors == 1) // Bayer image or single color -> decode to raw_image
{
if (INT64(rwidth) * INT64(rheight + 8) * sizeof(imgdata.rawdata.raw_image[0]) >
LIBRAW_MAX_ALLOC_MB * INT64(1024 * 1024))
throw LIBRAW_EXCEPTION_TOOBIG;
imgdata.rawdata.raw_alloc = malloc(rwidth * (rheight + 8) * sizeof(imgdata.rawdata.raw_image[0]));
imgdata.rawdata.raw_image = (ushort *)imgdata.rawdata.raw_alloc;
if (!S.raw_pitch)
S.raw_pitch = S.raw_width * 2; // Bayer case, not set before
}
else // NO LEGACY FLAG if (decoder_info.decoder_flags & LIBRAW_DECODER_LEGACY)
{
if (decoder_info.decoder_flags & LIBRAW_DECODER_ADOBECOPYPIXEL)
{
S.raw_pitch = S.raw_width * 8;
}
else
{
S.iwidth = S.width;
S.iheight = S.height;
IO.shrink = 0;
if (!S.raw_pitch)
S.raw_pitch =
(decoder_info.decoder_flags & LIBRAW_DECODER_LEGACY_WITH_MARGINS) ? S.raw_width * 8 : S.width * 8;
}
// sRAW and old Foveon decoders only, so extra buffer size is just 1/4
// allocate image as temporary buffer, size
if (INT64(MAX(S.width, S.raw_width)) * INT64(MAX(S.height, S.raw_height)+8) * sizeof(*imgdata.image) >
LIBRAW_MAX_ALLOC_MB * INT64(1024 * 1024))
throw LIBRAW_EXCEPTION_TOOBIG;
imgdata.rawdata.raw_alloc = 0;
imgdata.image = (ushort(*)[4])calloc(
unsigned(MAX(S.width, S.raw_width)) * unsigned(MAX(S.height, S.raw_height)+8), sizeof(*imgdata.image));
if (!(decoder_info.decoder_flags & LIBRAW_DECODER_ADOBECOPYPIXEL))
{
imgdata.rawdata.raw_image = (ushort *)imgdata.image;
zero_rawimage = 1;
}
}
ID.input->seek(libraw_internal_data.unpacker_data.data_offset, SEEK_SET);
unsigned m_save = C.maximum;
if (load_raw == &LibRaw::unpacked_load_raw && !strcasecmp(imgdata.idata.make, "Nikon"))
C.maximum = 65535;
(this->*load_raw)();
if (zero_rawimage)
imgdata.rawdata.raw_image = 0;
if (load_raw == &LibRaw::unpacked_load_raw && !strcasecmp(imgdata.idata.make, "Nikon"))
C.maximum = m_save;
if (decoder_info.decoder_flags & LIBRAW_DECODER_OWNALLOC)
{
// x3f foveon decoder only: do nothing
}
else if (!(imgdata.idata.filters || P1.colors == 1)) // legacy decoder, ownalloc handled above
{
// successfully decoded legacy image, attach image to raw_alloc
imgdata.rawdata.raw_alloc = imgdata.image;
imgdata.rawdata.color4_image = (ushort(*)[4])imgdata.rawdata.raw_alloc;
imgdata.image = 0;
// Restore saved values. Note: Foveon have masked frame
// Other 4-color legacy data: no borders
if (!(libraw_internal_data.unpacker_data.load_flags & 256) &&
!(decoder_info.decoder_flags & LIBRAW_DECODER_ADOBECOPYPIXEL) &&
!(decoder_info.decoder_flags & LIBRAW_DECODER_LEGACY_WITH_MARGINS))
{
S.raw_width = S.width;
S.left_margin = 0;
S.raw_height = S.height;
S.top_margin = 0;
}
}
}
if (imgdata.rawdata.raw_image)
crop_masked_pixels(); // calculate black levels
// recover image sizes
S.iwidth = save_iwidth;
S.iheight = save_iheight;
IO.shrink = save_shrink;
// adjust black to possible maximum
unsigned int i = C.cblack[3];
unsigned int c;
for (c = 0; c < 3; c++)
if (i > C.cblack[c])
i = C.cblack[c];
for (c = 0; c < 4; c++)
C.cblack[c] -= i;
C.black += i;
// Save color,sizes and internal data into raw_image fields
memmove(&imgdata.rawdata.color, &imgdata.color, sizeof(imgdata.color));
memmove(&imgdata.rawdata.sizes, &imgdata.sizes, sizeof(imgdata.sizes));
memmove(&imgdata.rawdata.iparams, &imgdata.idata, sizeof(imgdata.idata));
memmove(&imgdata.rawdata.ioparams, &libraw_internal_data.internal_output_params,
sizeof(libraw_internal_data.internal_output_params));
SET_PROC_FLAG(LIBRAW_PROGRESS_LOAD_RAW);
RUN_CALLBACK(LIBRAW_PROGRESS_LOAD_RAW, 1, 2);
return 0;
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
catch (std::exception ee)
{
EXCEPTION_HANDLER(LIBRAW_EXCEPTION_IO_CORRUPT);
}
}
void LibRaw::unpacked_load_raw_fuji_f700s20()
{
int base_offset = 0;
int row_size = imgdata.sizes.raw_width * 2; // in bytes
if (imgdata.idata.raw_count == 2 && imgdata.params.shot_select)
{
libraw_internal_data.internal_data.input->seek(-row_size, SEEK_CUR);
base_offset = row_size; // in bytes
}
unsigned char *buffer = (unsigned char *)malloc(row_size * 2);
for (int row = 0; row < imgdata.sizes.raw_height; row++)
{
read_shorts((ushort *)buffer, imgdata.sizes.raw_width * 2);
memmove(&imgdata.rawdata.raw_image[row * imgdata.sizes.raw_pitch / 2], buffer + base_offset, row_size);
}
free(buffer);
}
void LibRaw::nikon_load_sraw()
{
// We're already seeked to data!
unsigned char *rd = (unsigned char *)malloc(3 * (imgdata.sizes.raw_width + 2));
if (!rd)
throw LIBRAW_EXCEPTION_ALLOC;
try
{
int row, col;
for (row = 0; row < imgdata.sizes.raw_height; row++)
{
checkCancel();
libraw_internal_data.internal_data.input->read(rd, 3, imgdata.sizes.raw_width);
for (col = 0; col < imgdata.sizes.raw_width - 1; col += 2)
{
int bi = col * 3;
ushort bits1 = (rd[bi + 1] & 0xf) << 8 | rd[bi]; // 3,0,1
ushort bits2 = rd[bi + 2] << 4 | ((rd[bi + 1] >> 4) & 0xf); // 452
ushort bits3 = ((rd[bi + 4] & 0xf) << 8) | rd[bi + 3]; // 967
ushort bits4 = rd[bi + 5] << 4 | ((rd[bi + 4] >> 4) & 0xf); // ab8
imgdata.image[row * imgdata.sizes.raw_width + col][0] = bits1;
imgdata.image[row * imgdata.sizes.raw_width + col][1] = bits3;
imgdata.image[row * imgdata.sizes.raw_width + col][2] = bits4;
imgdata.image[row * imgdata.sizes.raw_width + col + 1][0] = bits2;
imgdata.image[row * imgdata.sizes.raw_width + col + 1][1] = 2048;
imgdata.image[row * imgdata.sizes.raw_width + col + 1][2] = 2048;
}
}
}
catch (...)
{
free(rd);
throw;
}
free(rd);
C.maximum = 0xfff; // 12 bit?
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SRAW_NO_INTERPOLATE)
{
return; // no CbCr interpolation
}
// Interpolate CC channels
int row, col;
for (row = 0; row < imgdata.sizes.raw_height; row++)
{
checkCancel(); // will throw out
for (col = 0; col < imgdata.sizes.raw_width; col += 2)
{
int col2 = col < imgdata.sizes.raw_width - 2 ? col + 2 : col;
imgdata.image[row * imgdata.sizes.raw_width + col + 1][1] =
(unsigned short)(int(imgdata.image[row * imgdata.sizes.raw_width + col][1] +
imgdata.image[row * imgdata.sizes.raw_width + col2][1]) /
2);
imgdata.image[row * imgdata.sizes.raw_width + col + 1][2] =
(unsigned short)(int(imgdata.image[row * imgdata.sizes.raw_width + col][2] +
imgdata.image[row * imgdata.sizes.raw_width + col2][2]) /
2);
}
}
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_SRAW_NO_RGB)
return;
for (row = 0; row < imgdata.sizes.raw_height; row++)
{
checkCancel(); // will throw out
for (col = 0; col < imgdata.sizes.raw_width; col++)
{
float Y = float(imgdata.image[row * imgdata.sizes.raw_width + col][0]) / 2549.f;
float Ch2 = float(imgdata.image[row * imgdata.sizes.raw_width + col][1] - 1280) / 1536.f;
float Ch3 = float(imgdata.image[row * imgdata.sizes.raw_width + col][2] - 1280) / 1536.f;
if (Y > 1.f)
Y = 1.f;
if (Y > 0.803f)
Ch2 = Ch3 = 0.5f;
float r = Y + 1.40200f * (Ch3 - 0.5f);
if (r < 0.f)
r = 0.f;
if (r > 1.f)
r = 1.f;
float g = Y - 0.34414f * (Ch2 - 0.5f) - 0.71414 * (Ch3 - 0.5f);
if (g > 1.f)
g = 1.f;
if (g < 0.f)
g = 0.f;
float b = Y + 1.77200 * (Ch2 - 0.5f);
if (b > 1.f)
b = 1.f;
if (b < 0.f)
b = 0.f;
imgdata.image[row * imgdata.sizes.raw_width + col][0] = imgdata.color.curve[int(r * 3072.f)];
imgdata.image[row * imgdata.sizes.raw_width + col][1] = imgdata.color.curve[int(g * 3072.f)];
imgdata.image[row * imgdata.sizes.raw_width + col][2] = imgdata.color.curve[int(b * 3072.f)];
}
}
C.maximum = 16383;
}
void LibRaw::free_image(void)
{
if (imgdata.image)
{
free(imgdata.image);
imgdata.image = 0;
imgdata.progress_flags = LIBRAW_PROGRESS_START | LIBRAW_PROGRESS_OPEN | LIBRAW_PROGRESS_IDENTIFY |
LIBRAW_PROGRESS_SIZE_ADJUST | LIBRAW_PROGRESS_LOAD_RAW;
}
}
void LibRaw::raw2image_start()
{
// restore color,sizes and internal data into raw_image fields
memmove(&imgdata.color, &imgdata.rawdata.color, sizeof(imgdata.color));
memmove(&imgdata.sizes, &imgdata.rawdata.sizes, sizeof(imgdata.sizes));
memmove(&imgdata.idata, &imgdata.rawdata.iparams, sizeof(imgdata.idata));
memmove(&libraw_internal_data.internal_output_params, &imgdata.rawdata.ioparams,
sizeof(libraw_internal_data.internal_output_params));
if (O.user_flip >= 0)
S.flip = O.user_flip;
switch ((S.flip + 3600) % 360)
{
case 270:
S.flip = 5;
break;
case 180:
S.flip = 3;
break;
case 90:
S.flip = 6;
break;
}
// adjust for half mode!
IO.shrink = P1.filters && (O.half_size || ((O.threshold || O.aber[0] != 1 || O.aber[2] != 1)));
S.iheight = (S.height + IO.shrink) >> IO.shrink;
S.iwidth = (S.width + IO.shrink) >> IO.shrink;
}
int LibRaw::is_phaseone_compressed()
{
return (load_raw == &LibRaw::phase_one_load_raw_c || load_raw == &LibRaw::phase_one_load_raw);
}
int LibRaw::is_canon_600() { return load_raw == &LibRaw::canon_600_load_raw; }
int LibRaw::raw2image(void)
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW);
try
{
raw2image_start();
if (is_phaseone_compressed())
{
phase_one_allocate_tempbuffer();
int rc = phase_one_subtract_black((ushort *)imgdata.rawdata.raw_alloc, imgdata.rawdata.raw_image);
if (rc == 0)
rc = phase_one_correct();
if (rc != 0)
{
phase_one_free_tempbuffer();
return rc;
}
}
// free and re-allocate image bitmap
if (imgdata.image)
{
imgdata.image = (ushort(*)[4])realloc(imgdata.image, S.iheight * S.iwidth * sizeof(*imgdata.image));
memset(imgdata.image, 0, S.iheight * S.iwidth * sizeof(*imgdata.image));
}
else
imgdata.image = (ushort(*)[4])calloc(S.iheight * S.iwidth, sizeof(*imgdata.image));
merror(imgdata.image, "raw2image()");
libraw_decoder_info_t decoder_info;
get_decoder_info(&decoder_info);
// Move saved bitmap to imgdata.image
if (imgdata.idata.filters || P1.colors == 1)
{
if (IO.fuji_width)
{
unsigned r, c;
int row, col;
for (row = 0; row < S.raw_height - S.top_margin * 2; row++)
{
for (col = 0; col < IO.fuji_width << !libraw_internal_data.unpacker_data.fuji_layout; col++)
{
if (libraw_internal_data.unpacker_data.fuji_layout)
{
r = IO.fuji_width - 1 - col + (row >> 1);
c = col + ((row + 1) >> 1);
}
else
{
r = IO.fuji_width - 1 + row - (col >> 1);
c = row + ((col + 1) >> 1);
}
if (r < S.height && c < S.width)
imgdata.image[((r) >> IO.shrink) * S.iwidth + ((c) >> IO.shrink)][FC(r, c)] =
imgdata.rawdata.raw_image[(row + S.top_margin) * S.raw_pitch / 2 + (col + S.left_margin)];
}
}
}
else
{
int row, col;
for (row = 0; row < S.height; row++)
for (col = 0; col < S.width; col++)
imgdata.image[((row) >> IO.shrink) * S.iwidth + ((col) >> IO.shrink)][fcol(row, col)] =
imgdata.rawdata.raw_image[(row + S.top_margin) * S.raw_pitch / 2 + (col + S.left_margin)];
}
}
else // if(decoder_info.decoder_flags & LIBRAW_DECODER_LEGACY)
{
if (imgdata.rawdata.color4_image)
{
if (S.width * 8 == S.raw_pitch)
memmove(imgdata.image, imgdata.rawdata.color4_image, S.width * S.height * sizeof(*imgdata.image));
else
{
for (int row = 0; row < S.height; row++)
memmove(&imgdata.image[row * S.width],
&imgdata.rawdata.color4_image[(row + S.top_margin) * S.raw_pitch / 8 + S.left_margin],
S.width * sizeof(*imgdata.image));
}
}
else if (imgdata.rawdata.color3_image)
{
unsigned char *c3image = (unsigned char *)imgdata.rawdata.color3_image;
for (int row = 0; row < S.height; row++)
{
ushort(*srcrow)[3] = (ushort(*)[3]) & c3image[(row + S.top_margin) * S.raw_pitch];
ushort(*dstrow)[4] = (ushort(*)[4]) & imgdata.image[row * S.width];
for (int col = 0; col < S.width; col++)
{
for (int c = 0; c < 3; c++)
dstrow[col][c] = srcrow[S.left_margin + col][c];
dstrow[col][3] = 0;
}
}
}
else
{
// legacy decoder, but no data?
throw LIBRAW_EXCEPTION_DECODE_RAW;
}
}
// Free PhaseOne separate copy allocated at function start
if (is_phaseone_compressed())
{
phase_one_free_tempbuffer();
}
// hack - clear later flags!
if (load_raw == &CLASS canon_600_load_raw && S.width < S.raw_width)
{
canon_600_correct();
}
imgdata.progress_flags = LIBRAW_PROGRESS_START | LIBRAW_PROGRESS_OPEN | LIBRAW_PROGRESS_RAW2_IMAGE |
LIBRAW_PROGRESS_IDENTIFY | LIBRAW_PROGRESS_SIZE_ADJUST | LIBRAW_PROGRESS_LOAD_RAW;
return 0;
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
}
void LibRaw::phase_one_allocate_tempbuffer()
{
// Allocate temp raw_image buffer
imgdata.rawdata.raw_image = (ushort *)malloc(S.raw_pitch * S.raw_height);
merror(imgdata.rawdata.raw_image, "phase_one_prepare_to_correct()");
}
void LibRaw::phase_one_free_tempbuffer()
{
free(imgdata.rawdata.raw_image);
imgdata.rawdata.raw_image = (ushort *)imgdata.rawdata.raw_alloc;
}
int LibRaw::phase_one_subtract_black(ushort *src, ushort *dest)
{
try
{
if (O.user_black < 0 && O.user_cblack[0] <= -1000000 && O.user_cblack[1] <= -1000000 &&
O.user_cblack[2] <= -1000000 && O.user_cblack[3] <= -1000000)
{
if (!imgdata.rawdata.ph1_cblack || !imgdata.rawdata.ph1_rblack)
{
register int bl = imgdata.color.phase_one_data.t_black;
for (int row = 0; row < S.raw_height; row++)
{
checkCancel();
for (int col = 0; col < S.raw_width; col++)
{
int idx = row * S.raw_width + col;
int val = int(src[idx]) - bl;
dest[idx] = val > 0 ? val : 0;
}
}
}
else
{
register int bl = imgdata.color.phase_one_data.t_black;
for (int row = 0; row < S.raw_height; row++)
{
checkCancel();
for (int col = 0; col < S.raw_width; col++)
{
int idx = row * S.raw_width + col;
int val = int(src[idx]) - bl +
imgdata.rawdata.ph1_cblack[row][col >= imgdata.rawdata.color.phase_one_data.split_col] +
imgdata.rawdata.ph1_rblack[col][row >= imgdata.rawdata.color.phase_one_data.split_row];
dest[idx] = val > 0 ? val : 0;
}
}
}
}
else // black set by user interaction
{
// Black level in cblack!
for (int row = 0; row < S.raw_height; row++)
{
checkCancel();
unsigned short cblk[16];
for (int cc = 0; cc < 16; cc++)
cblk[cc] = C.cblack[fcol(row, cc)];
for (int col = 0; col < S.raw_width; col++)
{
int idx = row * S.raw_width + col;
ushort val = src[idx];
ushort bl = cblk[col & 0xf];
dest[idx] = val > bl ? val - bl : 0;
}
}
}
return 0;
}
catch (LibRaw_exceptions err)
{
return LIBRAW_CANCELLED_BY_CALLBACK;
}
}
void LibRaw::copy_fuji_uncropped(unsigned short cblack[4], unsigned short *dmaxp)
{
int row;
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for default(shared)
#endif
for (row = 0; row < S.raw_height - S.top_margin * 2; row++)
{
int col;
unsigned short ldmax = 0;
for (col = 0; col < IO.fuji_width << !libraw_internal_data.unpacker_data.fuji_layout; col++)
{
unsigned r, c;
if (libraw_internal_data.unpacker_data.fuji_layout)
{
r = IO.fuji_width - 1 - col + (row >> 1);
c = col + ((row + 1) >> 1);
}
else
{
r = IO.fuji_width - 1 + row - (col >> 1);
c = row + ((col + 1) >> 1);
}
if (r < S.height && c < S.width)
{
unsigned short val = imgdata.rawdata.raw_image[(row + S.top_margin) * S.raw_pitch / 2 + (col + S.left_margin)];
int cc = FC(r, c);
if (val > cblack[cc])
{
val -= cblack[cc];
if (val > ldmax)
ldmax = val;
}
else
val = 0;
imgdata.image[((r) >> IO.shrink) * S.iwidth + ((c) >> IO.shrink)][cc] = val;
}
}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp critical(dataupdate)
#endif
{
if (*dmaxp < ldmax)
*dmaxp = ldmax;
}
}
}
void LibRaw::copy_bayer(unsigned short cblack[4], unsigned short *dmaxp)
{
// Both cropped and uncropped
int row;
#if defined(LIBRAW_USE_OPENMP)
#pragma omp parallel for default(shared)
#endif
for (row = 0; row < S.height; row++)
{
int col;
unsigned short ldmax = 0;
for (col = 0; col < S.width; col++)
{
unsigned short val = imgdata.rawdata.raw_image[(row + S.top_margin) * S.raw_pitch / 2 + (col + S.left_margin)];
int cc = fcol(row, col);
if (val > cblack[cc])
{
val -= cblack[cc];
if (val > ldmax)
ldmax = val;
}
else
val = 0;
imgdata.image[((row) >> IO.shrink) * S.iwidth + ((col) >> IO.shrink)][cc] = val;
}
#if defined(LIBRAW_USE_OPENMP)
#pragma omp critical(dataupdate)
#endif
{
if (*dmaxp < ldmax)
*dmaxp = ldmax;
}
}
}
int LibRaw::raw2image_ex(int do_subtract_black)
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW);
try
{
raw2image_start();
// Compressed P1 files with bl data!
if (is_phaseone_compressed())
{
phase_one_allocate_tempbuffer();
int rc = phase_one_subtract_black((ushort *)imgdata.rawdata.raw_alloc, imgdata.rawdata.raw_image);
if (rc == 0)
rc = phase_one_correct();
if (rc != 0)
{
phase_one_free_tempbuffer();
return rc;
}
}
// process cropping
int do_crop = 0;
unsigned save_width = S.width;
if (~O.cropbox[2] && ~O.cropbox[3])
{
int crop[4], c, filt;
for (int c = 0; c < 4; c++)
{
crop[c] = O.cropbox[c];
if (crop[c] < 0)
crop[c] = 0;
}
if (IO.fuji_width && imgdata.idata.filters >= 1000)
{
crop[0] = (crop[0] / 4) * 4;
crop[1] = (crop[1] / 4) * 4;
if (!libraw_internal_data.unpacker_data.fuji_layout)
{
crop[2] *= sqrt(2.0);
crop[3] /= sqrt(2.0);
}
crop[2] = (crop[2] / 4 + 1) * 4;
crop[3] = (crop[3] / 4 + 1) * 4;
}
else if (imgdata.idata.filters == 1)
{
crop[0] = (crop[0] / 16) * 16;
crop[1] = (crop[1] / 16) * 16;
}
else if (imgdata.idata.filters == LIBRAW_XTRANS)
{
crop[0] = (crop[0] / 6) * 6;
crop[1] = (crop[1] / 6) * 6;
}
do_crop = 1;
crop[2] = MIN(crop[2], (signed)S.width - crop[0]);
crop[3] = MIN(crop[3], (signed)S.height - crop[1]);
if (crop[2] <= 0 || crop[3] <= 0)
throw LIBRAW_EXCEPTION_BAD_CROP;
// adjust sizes!
S.left_margin += crop[0];
S.top_margin += crop[1];
S.width = crop[2];
S.height = crop[3];
S.iheight = (S.height + IO.shrink) >> IO.shrink;
S.iwidth = (S.width + IO.shrink) >> IO.shrink;
if (!IO.fuji_width && imgdata.idata.filters && imgdata.idata.filters >= 1000)
{
for (filt = c = 0; c < 16; c++)
filt |= FC((c >> 1) + (crop[1]), (c & 1) + (crop[0])) << c * 2;
imgdata.idata.filters = filt;
}
}
int alloc_width = S.iwidth;
int alloc_height = S.iheight;
if (IO.fuji_width && do_crop)
{
int IO_fw = S.width >> !libraw_internal_data.unpacker_data.fuji_layout;
int t_alloc_width = (S.height >> libraw_internal_data.unpacker_data.fuji_layout) + IO_fw;
int t_alloc_height = t_alloc_width - 1;
alloc_height = (t_alloc_height + IO.shrink) >> IO.shrink;
alloc_width = (t_alloc_width + IO.shrink) >> IO.shrink;
}
int alloc_sz = alloc_width * alloc_height;
if (imgdata.image)
{
imgdata.image = (ushort(*)[4])realloc(imgdata.image, alloc_sz * sizeof(*imgdata.image));
memset(imgdata.image, 0, alloc_sz * sizeof(*imgdata.image));
}
else
imgdata.image = (ushort(*)[4])calloc(alloc_sz, sizeof(*imgdata.image));
merror(imgdata.image, "raw2image_ex()");
libraw_decoder_info_t decoder_info;
get_decoder_info(&decoder_info);
// Adjust black levels
unsigned short cblack[4] = {0, 0, 0, 0};
unsigned short dmax = 0;
if (do_subtract_black)
{
adjust_bl();
for (int i = 0; i < 4; i++)
cblack[i] = (unsigned short)C.cblack[i];
}
// Move saved bitmap to imgdata.image
if (imgdata.idata.filters || P1.colors == 1)
{
if (IO.fuji_width)
{
if (do_crop)
{
IO.fuji_width = S.width >> !libraw_internal_data.unpacker_data.fuji_layout;
int IO_fwidth = (S.height >> libraw_internal_data.unpacker_data.fuji_layout) + IO.fuji_width;
int IO_fheight = IO_fwidth - 1;
int row, col;
for (row = 0; row < S.height; row++)
{
for (col = 0; col < S.width; col++)
{
int r, c;
if (libraw_internal_data.unpacker_data.fuji_layout)
{
r = IO.fuji_width - 1 - col + (row >> 1);
c = col + ((row + 1) >> 1);
}
else
{
r = IO.fuji_width - 1 + row - (col >> 1);
c = row + ((col + 1) >> 1);
}
unsigned short val =
imgdata.rawdata.raw_image[(row + S.top_margin) * S.raw_pitch / 2 + (col + S.left_margin)];
int cc = FCF(row, col);
if (val > cblack[cc])
{
val -= cblack[cc];
if (dmax < val)
dmax = val;
}
else
val = 0;
imgdata.image[((r) >> IO.shrink) * alloc_width + ((c) >> IO.shrink)][cc] = val;
}
}
S.height = IO_fheight;
S.width = IO_fwidth;
S.iheight = (S.height + IO.shrink) >> IO.shrink;
S.iwidth = (S.width + IO.shrink) >> IO.shrink;
S.raw_height -= 2 * S.top_margin;
}
else
{
copy_fuji_uncropped(cblack, &dmax);
}
} // end Fuji
else
{
copy_bayer(cblack, &dmax);
}
}
else // if(decoder_info.decoder_flags & LIBRAW_DECODER_LEGACY)
{
if (imgdata.rawdata.color4_image)
{
if (S.raw_pitch != S.width * 8)
{
for (int row = 0; row < S.height; row++)
memmove(&imgdata.image[row * S.width],
&imgdata.rawdata.color4_image[(row + S.top_margin) * S.raw_pitch / 8 + S.left_margin],
S.width * sizeof(*imgdata.image));
}
else
{
// legacy is always 4channel and not shrinked!
memmove(imgdata.image, imgdata.rawdata.color4_image, S.width * S.height * sizeof(*imgdata.image));
}
}
else if (imgdata.rawdata.color3_image)
{
unsigned char *c3image = (unsigned char *)imgdata.rawdata.color3_image;
for (int row = 0; row < S.height; row++)
{
ushort(*srcrow)[3] = (ushort(*)[3]) & c3image[(row + S.top_margin) * S.raw_pitch];
ushort(*dstrow)[4] = (ushort(*)[4]) & imgdata.image[row * S.width];
for (int col = 0; col < S.width; col++)
{
for (int c = 0; c < 3; c++)
dstrow[col][c] = srcrow[S.left_margin + col][c];
dstrow[col][3] = 0;
}
}
}
else
{
// legacy decoder, but no data?
throw LIBRAW_EXCEPTION_DECODE_RAW;
}
}
// Free PhaseOne separate copy allocated at function start
if (is_phaseone_compressed())
{
phase_one_free_tempbuffer();
}
if (load_raw == &CLASS canon_600_load_raw && S.width < S.raw_width)
{
canon_600_correct();
}
if (do_subtract_black)
{
C.data_maximum = (int)dmax;
C.maximum -= C.black;
// ZERO(C.cblack);
C.cblack[0] = C.cblack[1] = C.cblack[2] = C.cblack[3] = 0;
C.black = 0;
}
// hack - clear later flags!
imgdata.progress_flags = LIBRAW_PROGRESS_START | LIBRAW_PROGRESS_OPEN | LIBRAW_PROGRESS_RAW2_IMAGE |
LIBRAW_PROGRESS_IDENTIFY | LIBRAW_PROGRESS_SIZE_ADJUST | LIBRAW_PROGRESS_LOAD_RAW;
return 0;
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
}
#if 1
libraw_processed_image_t *LibRaw::dcraw_make_mem_thumb(int *errcode)
{
if (!T.thumb)
{
if (!ID.toffset && !(imgdata.thumbnail.tlength > 0 && load_raw == &LibRaw::broadcom_load_raw) // RPi
)
{
if (errcode)
*errcode = LIBRAW_NO_THUMBNAIL;
}
else
{
if (errcode)
*errcode = LIBRAW_OUT_OF_ORDER_CALL;
}
return NULL;
}
if (T.tformat == LIBRAW_THUMBNAIL_BITMAP)
{
libraw_processed_image_t *ret = (libraw_processed_image_t *)::malloc(sizeof(libraw_processed_image_t) + T.tlength);
if (!ret)
{
if (errcode)
*errcode = ENOMEM;
return NULL;
}
memset(ret, 0, sizeof(libraw_processed_image_t));
ret->type = LIBRAW_IMAGE_BITMAP;
ret->height = T.theight;
ret->width = T.twidth;
ret->colors = 3;
ret->bits = 8;
ret->data_size = T.tlength;
memmove(ret->data, T.thumb, T.tlength);
if (errcode)
*errcode = 0;
return ret;
}
else if (T.tformat == LIBRAW_THUMBNAIL_JPEG)
{
ushort exif[5];
int mk_exif = 0;
if (strcmp(T.thumb + 6, "Exif"))
mk_exif = 1;
int dsize = T.tlength + mk_exif * (sizeof(exif) + sizeof(tiff_hdr));
libraw_processed_image_t *ret = (libraw_processed_image_t *)::malloc(sizeof(libraw_processed_image_t) + dsize);
if (!ret)
{
if (errcode)
*errcode = ENOMEM;
return NULL;
}
memset(ret, 0, sizeof(libraw_processed_image_t));
ret->type = LIBRAW_IMAGE_JPEG;
ret->data_size = dsize;
ret->data[0] = 0xff;
ret->data[1] = 0xd8;
if (mk_exif)
{
struct tiff_hdr th;
memcpy(exif, "\xff\xe1 Exif\0\0", 10);
exif[1] = htons(8 + sizeof th);
memmove(ret->data + 2, exif, sizeof(exif));
tiff_head(&th, 0);
memmove(ret->data + (2 + sizeof(exif)), &th, sizeof(th));
memmove(ret->data + (2 + sizeof(exif) + sizeof(th)), T.thumb + 2, T.tlength - 2);
}
else
{
memmove(ret->data + 2, T.thumb + 2, T.tlength - 2);
}
if (errcode)
*errcode = 0;
return ret;
}
else
{
if (errcode)
*errcode = LIBRAW_UNSUPPORTED_THUMBNAIL;
return NULL;
}
}
// jlb
// macros for copying pixels to either BGR or RGB formats
#define FORBGR for (c = P1.colors - 1; c >= 0; c--)
#define FORRGB for (c = 0; c < P1.colors; c++)
void LibRaw::get_mem_image_format(int *width, int *height, int *colors, int *bps) const
{
if (S.flip & 4)
{
*width = S.height;
*height = S.width;
}
else
{
*width = S.width;
*height = S.height;
}
*colors = P1.colors;
*bps = O.output_bps;
}
int LibRaw::copy_mem_image(void *scan0, int stride, int bgr)
{
// the image memory pointed to by scan0 is assumed to be in the format returned by get_mem_image_format
if ((imgdata.progress_flags & LIBRAW_PROGRESS_THUMB_MASK) < LIBRAW_PROGRESS_PRE_INTERPOLATE)
return LIBRAW_OUT_OF_ORDER_CALL;
if (libraw_internal_data.output_data.histogram)
{
int perc, val, total, t_white = 0x2000, c;
perc = S.width * S.height * O.auto_bright_thr;
if (IO.fuji_width)
perc /= 2;
if (!((O.highlight & ~2) || O.no_auto_bright))
for (t_white = c = 0; c < P1.colors; c++)
{
for (val = 0x2000, total = 0; --val > 32;)
if ((total += libraw_internal_data.output_data.histogram[c][val]) > perc)
break;
if (t_white < val)
t_white = val;
}
gamma_curve(O.gamm[0], O.gamm[1], 2, (t_white << 3) / O.bright);
}
int s_iheight = S.iheight;
int s_iwidth = S.iwidth;
int s_width = S.width;
int s_hwight = S.height;
S.iheight = S.height;
S.iwidth = S.width;
if (S.flip & 4)
SWAP(S.height, S.width);
uchar *ppm;
ushort *ppm2;
int c, row, col, soff, rstep, cstep;
soff = flip_index(0, 0);
cstep = flip_index(0, 1) - soff;
rstep = flip_index(1, 0) - flip_index(0, S.width);
for (row = 0; row < S.height; row++, soff += rstep)
{
uchar *bufp = ((uchar *)scan0) + row * stride;
ppm2 = (ushort *)(ppm = bufp);
// keep trivial decisions in the outer loop for speed
if (bgr)
{
if (O.output_bps == 8)
{
for (col = 0; col < S.width; col++, soff += cstep)
FORBGR *ppm++ = imgdata.color.curve[imgdata.image[soff][c]] >> 8;
}
else
{
for (col = 0; col < S.width; col++, soff += cstep)
FORBGR *ppm2++ = imgdata.color.curve[imgdata.image[soff][c]];
}
}
else
{
if (O.output_bps == 8)
{
for (col = 0; col < S.width; col++, soff += cstep)
FORRGB *ppm++ = imgdata.color.curve[imgdata.image[soff][c]] >> 8;
}
else
{
for (col = 0; col < S.width; col++, soff += cstep)
FORRGB *ppm2++ = imgdata.color.curve[imgdata.image[soff][c]];
}
}
// bufp += stride; // go to the next line
}
S.iheight = s_iheight;
S.iwidth = s_iwidth;
S.width = s_width;
S.height = s_hwight;
return 0;
}
#undef FORBGR
#undef FORRGB
libraw_processed_image_t *LibRaw::dcraw_make_mem_image(int *errcode)
{
int width, height, colors, bps;
get_mem_image_format(&width, &height, &colors, &bps);
int stride = width * (bps / 8) * colors;
unsigned ds = height * stride;
libraw_processed_image_t *ret = (libraw_processed_image_t *)::malloc(sizeof(libraw_processed_image_t) + ds);
if (!ret)
{
if (errcode)
*errcode = ENOMEM;
return NULL;
}
memset(ret, 0, sizeof(libraw_processed_image_t));
// metadata init
ret->type = LIBRAW_IMAGE_BITMAP;
ret->height = height;
ret->width = width;
ret->colors = colors;
ret->bits = bps;
ret->data_size = ds;
copy_mem_image(ret->data, stride, 0);
return ret;
}
#undef FORC
#undef FORCC
#undef SWAP
#endif
int LibRaw::dcraw_ppm_tiff_writer(const char *filename)
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW);
if (!imgdata.image)
return LIBRAW_OUT_OF_ORDER_CALL;
if (!filename)
return ENOENT;
FILE *f = fopen(filename, "wb");
if (!f)
return errno;
try
{
if (!libraw_internal_data.output_data.histogram)
{
libraw_internal_data.output_data.histogram =
(int(*)[LIBRAW_HISTOGRAM_SIZE])malloc(sizeof(*libraw_internal_data.output_data.histogram) * 4);
merror(libraw_internal_data.output_data.histogram, "LibRaw::dcraw_ppm_tiff_writer()");
}
libraw_internal_data.internal_data.output = f;
write_ppm_tiff();
SET_PROC_FLAG(LIBRAW_PROGRESS_FLIP);
libraw_internal_data.internal_data.output = NULL;
fclose(f);
return 0;
}
catch (LibRaw_exceptions err)
{
fclose(f);
EXCEPTION_HANDLER(err);
}
}
#define THUMB_READ_BEYOND 16384
void LibRaw::kodak_thumb_loader()
{
INT64 est_datasize = T.theight * T.twidth / 3; // is 0.3 bytes per pixel good estimate?
if (ID.toffset < 0)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
if (ID.toffset + est_datasize > ID.input->size() + THUMB_READ_BEYOND)
throw LIBRAW_EXCEPTION_IO_EOF;
// some kodak cameras
ushort s_height = S.height, s_width = S.width, s_iwidth = S.iwidth, s_iheight = S.iheight;
ushort s_flags = libraw_internal_data.unpacker_data.load_flags;
libraw_internal_data.unpacker_data.load_flags = 12;
int s_colors = P1.colors;
unsigned s_filters = P1.filters;
ushort(*s_image)[4] = imgdata.image;
S.height = T.theight;
S.width = T.twidth;
P1.filters = 0;
if (thumb_load_raw == &CLASS kodak_ycbcr_load_raw)
{
S.height += S.height & 1;
S.width += S.width & 1;
}
imgdata.image = (ushort(*)[4])calloc(S.iheight * S.iwidth, sizeof(*imgdata.image));
merror(imgdata.image, "LibRaw::kodak_thumb_loader()");
ID.input->seek(ID.toffset, SEEK_SET);
// read kodak thumbnail into T.image[]
try
{
(this->*thumb_load_raw)();
}
catch (...)
{
free(imgdata.image);
imgdata.image = s_image;
T.twidth = 0;
S.width = s_width;
S.iwidth = s_iwidth;
S.iheight = s_iheight;
T.theight = 0;
S.height = s_height;
T.tcolors = 0;
P1.colors = s_colors;
P1.filters = s_filters;
T.tlength = 0;
libraw_internal_data.unpacker_data.load_flags = s_flags;
return;
}
// copy-n-paste from image pipe
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define LIM(x, min, max) MAX(min, MIN(x, max))
#ifndef CLIP
#define CLIP(x) LIM(x, 0, 65535)
#endif
#define SWAP(a, b) \
{ \
a ^= b; \
a ^= (b ^= a); \
}
// from scale_colors
{
double dmax;
float scale_mul[4];
int c, val;
for (dmax = DBL_MAX, c = 0; c < 3; c++)
if (dmax > C.pre_mul[c])
dmax = C.pre_mul[c];
for (c = 0; c < 3; c++)
scale_mul[c] = (C.pre_mul[c] / dmax) * 65535.0 / C.maximum;
scale_mul[3] = scale_mul[1];
size_t size = S.height * S.width;
for (unsigned i = 0; i < size * 4; i++)
{
val = imgdata.image[0][i];
if (!val)
continue;
val *= scale_mul[i & 3];
imgdata.image[0][i] = CLIP(val);
}
}
// from convert_to_rgb
ushort *img;
int row, col;
int(*t_hist)[LIBRAW_HISTOGRAM_SIZE] = (int(*)[LIBRAW_HISTOGRAM_SIZE])calloc(sizeof(*t_hist), 4);
merror(t_hist, "LibRaw::kodak_thumb_loader()");
float out[3], out_cam[3][4] = {{2.81761312, -1.98369181, 0.166078627, 0},
{-0.111855984, 1.73688626, -0.625030339, 0},
{-0.0379119813, -0.891268849, 1.92918086, 0}};
for (img = imgdata.image[0], row = 0; row < S.height; row++)
for (col = 0; col < S.width; col++, img += 4)
{
out[0] = out[1] = out[2] = 0;
int c;
for (c = 0; c < 3; c++)
{
out[0] += out_cam[0][c] * img[c];
out[1] += out_cam[1][c] * img[c];
out[2] += out_cam[2][c] * img[c];
}
for (c = 0; c < 3; c++)
img[c] = CLIP((int)out[c]);
for (c = 0; c < P1.colors; c++)
t_hist[c][img[c] >> 3]++;
}
// from gamma_lut
int(*save_hist)[LIBRAW_HISTOGRAM_SIZE] = libraw_internal_data.output_data.histogram;
libraw_internal_data.output_data.histogram = t_hist;
// make curve output curve!
ushort(*t_curve) = (ushort *)calloc(sizeof(C.curve), 1);
merror(t_curve, "LibRaw::kodak_thumb_loader()");
memmove(t_curve, C.curve, sizeof(C.curve));
memset(C.curve, 0, sizeof(C.curve));
{
int perc, val, total, t_white = 0x2000, c;
perc = S.width * S.height * 0.01; /* 99th percentile white level */
if (IO.fuji_width)
perc /= 2;
if (!((O.highlight & ~2) || O.no_auto_bright))
for (t_white = c = 0; c < P1.colors; c++)
{
for (val = 0x2000, total = 0; --val > 32;)
if ((total += libraw_internal_data.output_data.histogram[c][val]) > perc)
break;
if (t_white < val)
t_white = val;
}
gamma_curve(O.gamm[0], O.gamm[1], 2, (t_white << 3) / O.bright);
}
libraw_internal_data.output_data.histogram = save_hist;
free(t_hist);
// from write_ppm_tiff - copy pixels into bitmap
int s_flip = imgdata.sizes.flip;
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_NO_ROTATE_FOR_KODAK_THUMBNAILS)
imgdata.sizes.flip = 0;
S.iheight = S.height;
S.iwidth = S.width;
if (S.flip & 4)
SWAP(S.height, S.width);
if (T.thumb)
free(T.thumb);
T.thumb = (char *)calloc(S.width * S.height, P1.colors);
merror(T.thumb, "LibRaw::kodak_thumb_loader()");
T.tlength = S.width * S.height * P1.colors;
// from write_tiff_ppm
{
int soff = flip_index(0, 0);
int cstep = flip_index(0, 1) - soff;
int rstep = flip_index(1, 0) - flip_index(0, S.width);
for (int row = 0; row < S.height; row++, soff += rstep)
{
char *ppm = T.thumb + row * S.width * P1.colors;
for (int col = 0; col < S.width; col++, soff += cstep)
for (int c = 0; c < P1.colors; c++)
ppm[col * P1.colors + c] = imgdata.color.curve[imgdata.image[soff][c]] >> 8;
}
}
memmove(C.curve, t_curve, sizeof(C.curve));
free(t_curve);
// restore variables
free(imgdata.image);
imgdata.image = s_image;
if (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_NO_ROTATE_FOR_KODAK_THUMBNAILS)
imgdata.sizes.flip = s_flip;
T.twidth = S.width;
S.width = s_width;
S.iwidth = s_iwidth;
S.iheight = s_iheight;
T.theight = S.height;
S.height = s_height;
T.tcolors = P1.colors;
P1.colors = s_colors;
P1.filters = s_filters;
libraw_internal_data.unpacker_data.load_flags = s_flags;
}
#undef MIN
#undef MAX
#undef LIM
#undef CLIP
#undef SWAP
// ������� thumbnail �� �����, ������ thumb_format � ������������ � ��������
int LibRaw::thumbOK(INT64 maxsz)
{
if (!ID.input)
return 0;
if (!ID.toffset && !(imgdata.thumbnail.tlength > 0 && load_raw == &LibRaw::broadcom_load_raw) // RPi
)
return 0;
INT64 fsize = ID.input->size();
if (fsize > 0x7fffffffU)
return 0; // No thumb for raw > 2Gb
int tsize = 0;
int tcol = (T.tcolors > 0 && T.tcolors < 4) ? T.tcolors : 3;
if (write_thumb == &LibRaw::jpeg_thumb)
tsize = T.tlength;
else if (write_thumb == &LibRaw::ppm_thumb)
tsize = tcol * T.twidth * T.theight;
else if (write_thumb == &LibRaw::ppm16_thumb)
tsize = tcol * T.twidth * T.theight *
((imgdata.params.raw_processing_options & LIBRAW_PROCESSING_USE_PPM16_THUMBS) ? 2 : 1);
else if (write_thumb == &LibRaw::x3f_thumb_loader)
{
tsize = x3f_thumb_size();
}
else // Kodak => no check
tsize = 1;
if (tsize < 0)
return 0;
if (maxsz > 0 && tsize > maxsz)
return 0;
return (tsize + ID.toffset <= fsize) ? 1 : 0;
}
#ifndef NO_JPEG
struct jpegErrorManager
{
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
static void jpegErrorExit(j_common_ptr cinfo)
{
jpegErrorManager *myerr = (jpegErrorManager *)cinfo->err;
longjmp(myerr->setjmp_buffer, 1);
}
#endif
int LibRaw::unpack_thumb(void)
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY);
CHECK_ORDER_BIT(LIBRAW_PROGRESS_THUMB_LOAD);
try
{
if (!libraw_internal_data.internal_data.input)
return LIBRAW_INPUT_CLOSED;
int t_colors = libraw_internal_data.unpacker_data.thumb_misc >> 5 & 7;
int t_bytesps = (libraw_internal_data.unpacker_data.thumb_misc & 31) / 8;
if (!ID.toffset && !(imgdata.thumbnail.tlength > 0 && load_raw == &LibRaw::broadcom_load_raw) // RPi
)
{
return LIBRAW_NO_THUMBNAIL;
}
else if (thumb_load_raw)
{
kodak_thumb_loader();
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else
{
if (write_thumb == &LibRaw::x3f_thumb_loader)
{
INT64 tsize = x3f_thumb_size();
if (tsize < 2048 || INT64(ID.toffset) + tsize < 1)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
if (INT64(ID.toffset) + tsize > ID.input->size() + THUMB_READ_BEYOND)
throw LIBRAW_EXCEPTION_IO_EOF;
}
else
{
if (INT64(ID.toffset) + INT64(T.tlength) < 1)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
if (INT64(ID.toffset) + INT64(T.tlength) > ID.input->size() + THUMB_READ_BEYOND)
throw LIBRAW_EXCEPTION_IO_EOF;
}
ID.input->seek(ID.toffset, SEEK_SET);
if (write_thumb == &LibRaw::jpeg_thumb)
{
if (T.thumb)
free(T.thumb);
T.thumb = (char *)malloc(T.tlength);
merror(T.thumb, "jpeg_thumb()");
ID.input->read(T.thumb, 1, T.tlength);
unsigned char *tthumb = (unsigned char *)T.thumb;
tthumb[0] = 0xff;
tthumb[1] = 0xd8;
#ifdef NO_JPEG
T.tcolors = 3;
#else
{
jpegErrorManager jerr;
struct jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = jpegErrorExit;
if (setjmp(jerr.setjmp_buffer))
{
err2:
// Error in original JPEG thumb, read it again because
// original bytes 0-1 was damaged above
jpeg_destroy_decompress(&cinfo);
T.tcolors = 3;
T.tformat = LIBRAW_THUMBNAIL_UNKNOWN;
ID.input->seek(ID.toffset, SEEK_SET);
ID.input->read(T.thumb, 1, T.tlength);
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, (unsigned char *)T.thumb, T.tlength);
int rc = jpeg_read_header(&cinfo, TRUE);
if (rc != 1)
goto err2;
T.tcolors = (cinfo.num_components > 0 && cinfo.num_components <= 3) ? cinfo.num_components : 3;
jpeg_destroy_decompress(&cinfo);
}
#endif
T.tformat = LIBRAW_THUMBNAIL_JPEG;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else if (write_thumb == &LibRaw::ppm_thumb)
{
if (t_bytesps > 1)
throw LIBRAW_EXCEPTION_IO_CORRUPT; // 8-bit thumb, but parsed for more bits
int t_length = T.twidth * T.theight * t_colors;
if (T.tlength && T.tlength < t_length) // try to find tiff ifd with needed offset
{
int pifd = -1;
for (int ii = 0; ii < libraw_internal_data.identify_data.tiff_nifds && ii < LIBRAW_IFD_MAXCOUNT; ii++)
if (tiff_ifd[ii].offset == libraw_internal_data.internal_data.toffset) // found
{
pifd = ii;
break;
}
if (pifd >= 0 && tiff_ifd[pifd].strip_offsets_count && tiff_ifd[pifd].strip_byte_counts_count)
{
// We found it, calculate final size
unsigned total_size = 0;
for (int i = 0; i < tiff_ifd[pifd].strip_byte_counts_count; i++)
total_size += tiff_ifd[pifd].strip_byte_counts[i];
if (total_size != t_length) // recalculate colors
{
if (total_size == T.twidth * T.tlength * 3)
T.tcolors = 3;
else if (total_size == T.twidth * T.tlength)
T.tcolors = 1;
}
T.tlength = total_size;
if (T.thumb)
free(T.thumb);
T.thumb = (char *)malloc(T.tlength);
merror(T.thumb, "ppm_thumb()");
char *dest = T.thumb;
INT64 pos = ID.input->tell();
for (int i = 0; i < tiff_ifd[pifd].strip_byte_counts_count && i < tiff_ifd[pifd].strip_offsets_count; i++)
{
int remain = T.tlength;
int sz = tiff_ifd[pifd].strip_byte_counts[i];
int off = tiff_ifd[pifd].strip_offsets[i];
if (off >= 0 && off + sz <= ID.input->size() && sz <= remain)
{
ID.input->seek(off, SEEK_SET);
ID.input->read(dest, sz, 1);
remain -= sz;
dest += sz;
}
}
ID.input->seek(pos, SEEK_SET);
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
}
if (!T.tlength)
T.tlength = t_length;
if (T.thumb)
free(T.thumb);
T.thumb = (char *)malloc(T.tlength);
if (!T.tcolors)
T.tcolors = t_colors;
merror(T.thumb, "ppm_thumb()");
ID.input->read(T.thumb, 1, T.tlength);
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else if (write_thumb == &LibRaw::ppm16_thumb)
{
if (t_bytesps > 2)
throw LIBRAW_EXCEPTION_IO_CORRUPT; // 16-bit thumb, but parsed for more bits
int o_bps = (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_USE_PPM16_THUMBS) ? 2 : 1;
int o_length = T.twidth * T.theight * t_colors * o_bps;
int i_length = T.twidth * T.theight * t_colors * 2;
if (!T.tlength)
T.tlength = o_length;
ushort *t_thumb = (ushort *)calloc(i_length, 1);
ID.input->read(t_thumb, 1, i_length);
if ((libraw_internal_data.unpacker_data.order == 0x4949) == (ntohs(0x1234) == 0x1234))
swab((char *)t_thumb, (char *)t_thumb, i_length);
if (T.thumb)
free(T.thumb);
if ((imgdata.params.raw_processing_options & LIBRAW_PROCESSING_USE_PPM16_THUMBS))
{
T.thumb = (char *)t_thumb;
T.tformat = LIBRAW_THUMBNAIL_BITMAP16;
}
else
{
T.thumb = (char *)malloc(o_length);
merror(T.thumb, "ppm_thumb()");
for (int i = 0; i < o_length; i++)
T.thumb[i] = t_thumb[i] >> 8;
free(t_thumb);
T.tformat = LIBRAW_THUMBNAIL_BITMAP;
}
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else if (write_thumb == &LibRaw::x3f_thumb_loader)
{
x3f_thumb_loader();
SET_PROC_FLAG(LIBRAW_PROGRESS_THUMB_LOAD);
return 0;
}
else
{
return LIBRAW_UNSUPPORTED_THUMBNAIL;
}
}
// last resort
return LIBRAW_UNSUPPORTED_THUMBNAIL;
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
}
int LibRaw::dcraw_thumb_writer(const char *fname)
{
// CHECK_ORDER_LOW(LIBRAW_PROGRESS_THUMB_LOAD);
if (!fname)
return ENOENT;
FILE *tfp = fopen(fname, "wb");
if (!tfp)
return errno;
if (!T.thumb)
{
fclose(tfp);
return LIBRAW_OUT_OF_ORDER_CALL;
}
try
{
switch (T.tformat)
{
case LIBRAW_THUMBNAIL_JPEG:
jpeg_thumb_writer(tfp, T.thumb, T.tlength);
break;
case LIBRAW_THUMBNAIL_BITMAP:
fprintf(tfp, "P6\n%d %d\n255\n", T.twidth, T.theight);
fwrite(T.thumb, 1, T.tlength, tfp);
break;
default:
fclose(tfp);
return LIBRAW_UNSUPPORTED_THUMBNAIL;
}
fclose(tfp);
return 0;
}
catch (LibRaw_exceptions err)
{
fclose(tfp);
EXCEPTION_HANDLER(err);
}
}
int LibRaw::adjust_sizes_info_only(void)
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY);
raw2image_start();
if (O.use_fuji_rotate)
{
if (IO.fuji_width)
{
IO.fuji_width = (IO.fuji_width - 1 + IO.shrink) >> IO.shrink;
S.iwidth = (ushort)(IO.fuji_width / sqrt(0.5));
S.iheight = (ushort)((S.iheight - IO.fuji_width) / sqrt(0.5));
}
else
{
if (S.pixel_aspect < 0.995)
S.iheight = (ushort)(S.iheight / S.pixel_aspect + 0.5);
if (S.pixel_aspect > 1.005)
S.iwidth = (ushort)(S.iwidth * S.pixel_aspect + 0.5);
}
}
SET_PROC_FLAG(LIBRAW_PROGRESS_FUJI_ROTATE);
if (S.flip & 4)
{
unsigned short t = S.iheight;
S.iheight = S.iwidth;
S.iwidth = t;
SET_PROC_FLAG(LIBRAW_PROGRESS_FLIP);
}
return 0;
}
int LibRaw::subtract_black()
{
adjust_bl();
return subtract_black_internal();
}
int LibRaw::subtract_black_internal()
{
CHECK_ORDER_LOW(LIBRAW_PROGRESS_RAW2_IMAGE);
try
{
if (!is_phaseone_compressed() &&
(C.cblack[0] || C.cblack[1] || C.cblack[2] || C.cblack[3] || (C.cblack[4] && C.cblack[5])))
{
#define BAYERC(row, col, c) imgdata.image[((row) >> IO.shrink) * S.iwidth + ((col) >> IO.shrink)][c]
int cblk[4], i;
for (i = 0; i < 4; i++)
cblk[i] = C.cblack[i];
int size = S.iheight * S.iwidth;
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define LIM(x, min, max) MAX(min, MIN(x, max))
#define CLIP(x) LIM(x, 0, 65535)
int dmax = 0;
if (C.cblack[4] && C.cblack[5])
{
for (i = 0; i < size * 4; i++)
{
int val = imgdata.image[0][i];
val -= C.cblack[6 + i / 4 / S.iwidth % C.cblack[4] * C.cblack[5] + i / 4 % S.iwidth % C.cblack[5]];
val -= cblk[i & 3];
imgdata.image[0][i] = CLIP(val);
if (dmax < val)
dmax = val;
}
}
else
{
for (i = 0; i < size * 4; i++)
{
int val = imgdata.image[0][i];
val -= cblk[i & 3];
imgdata.image[0][i] = CLIP(val);
if (dmax < val)
dmax = val;
}
}
C.data_maximum = dmax & 0xffff;
#undef MIN
#undef MAX
#undef LIM
#undef CLIP
C.maximum -= C.black;
ZERO(C.cblack); // Yeah, we used cblack[6+] values too!
C.black = 0;
#undef BAYERC
}
else
{
// Nothing to Do, maximum is already calculated, black level is 0, so no change
// only calculate channel maximum;
int idx;
ushort *p = (ushort *)imgdata.image;
int dmax = 0;
for (idx = 0; idx < S.iheight * S.iwidth * 4; idx++)
if (dmax < p[idx])
dmax = p[idx];
C.data_maximum = dmax;
}
return 0;
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
}
#define TBLN 65535
void LibRaw::exp_bef(float shift, float smooth)
{
// params limits
if (shift > 8)
shift = 8;
if (shift < 0.25)
shift = 0.25;
if (smooth < 0.0)
smooth = 0.0;
if (smooth > 1.0)
smooth = 1.0;
unsigned short *lut = (ushort *)malloc((TBLN + 1) * sizeof(unsigned short));
if (shift <= 1.0)
{
for (int i = 0; i <= TBLN; i++)
lut[i] = (unsigned short)((float)i * shift);
}
else
{
float x1, x2, y1, y2;
float cstops = log(shift) / log(2.0f);
float room = cstops * 2;
float roomlin = powf(2.0f, room);
x2 = (float)TBLN;
x1 = (x2 + 1) / roomlin - 1;
y1 = x1 * shift;
y2 = x2 * (1 + (1 - smooth) * (shift - 1));
float sq3x = powf(x1 * x1 * x2, 1.0f / 3.0f);
float B = (y2 - y1 + shift * (3 * x1 - 3.0f * sq3x)) / (x2 + 2.0f * x1 - 3.0f * sq3x);
float A = (shift - B) * 3.0f * powf(x1 * x1, 1.0f / 3.0f);
float CC = y2 - A * powf(x2, 1.0f / 3.0f) - B * x2;
for (int i = 0; i <= TBLN; i++)
{
float X = (float)i;
float Y = A * powf(X, 1.0f / 3.0f) + B * X + CC;
if (i < x1)
lut[i] = (unsigned short)((float)i * shift);
else
lut[i] = Y < 0 ? 0 : (Y > TBLN ? TBLN : (unsigned short)(Y));
}
}
for (int i = 0; i < S.height * S.width; i++)
{
imgdata.image[i][0] = lut[imgdata.image[i][0]];
imgdata.image[i][1] = lut[imgdata.image[i][1]];
imgdata.image[i][2] = lut[imgdata.image[i][2]];
imgdata.image[i][3] = lut[imgdata.image[i][3]];
}
if (C.data_maximum <= TBLN)
C.data_maximum = lut[C.data_maximum];
if (C.maximum <= TBLN)
C.maximum = lut[C.maximum];
free(lut);
}
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define LIM(x, min, max) MAX(min, MIN(x, max))
#define ULIM(x, y, z) ((y) < (z) ? LIM(x, y, z) : LIM(x, z, y))
#define CLIP(x) LIM(x, 0, 65535)
void LibRaw::convert_to_rgb_loop(float out_cam[3][4])
{
int row, col, c;
float out[3];
ushort *img;
memset(libraw_internal_data.output_data.histogram, 0, sizeof(int) * LIBRAW_HISTOGRAM_SIZE * 4);
for (img = imgdata.image[0], row = 0; row < S.height; row++)
for (col = 0; col < S.width; col++, img += 4)
{
if (!libraw_internal_data.internal_output_params.raw_color)
{
out[0] = out[1] = out[2] = 0;
for (c = 0; c < imgdata.idata.colors; c++)
{
out[0] += out_cam[0][c] * img[c];
out[1] += out_cam[1][c] * img[c];
out[2] += out_cam[2][c] * img[c];
}
for (c = 0; c < 3; c++)
img[c] = CLIP((int)out[c]);
}
for (c = 0; c < imgdata.idata.colors; c++)
libraw_internal_data.output_data.histogram[c][img[c] >> 3]++;
}
}
void LibRaw::scale_colors_loop(float scale_mul[4])
{
unsigned size = S.iheight * S.iwidth;
if (C.cblack[4] && C.cblack[5])
{
int val;
for (unsigned i = 0; i < size * 4; i++)
{
if (!(val = imgdata.image[0][i]))
continue;
val -= C.cblack[6 + i / 4 / S.iwidth % C.cblack[4] * C.cblack[5] + i / 4 % S.iwidth % C.cblack[5]];
val -= C.cblack[i & 3];
val *= scale_mul[i & 3];
imgdata.image[0][i] = CLIP(val);
}
}
else if (C.cblack[0] || C.cblack[1] || C.cblack[2] || C.cblack[3])
{
for (unsigned i = 0; i < size * 4; i++)
{
int val = imgdata.image[0][i];
if (!val)
continue;
val -= C.cblack[i & 3];
val *= scale_mul[i & 3];
imgdata.image[0][i] = CLIP(val);
}
}
else // BL is zero
{
for (unsigned i = 0; i < size * 4; i++)
{
int val = imgdata.image[0][i];
val *= scale_mul[i & 3];
imgdata.image[0][i] = CLIP(val);
}
}
}
void LibRaw::adjust_bl()
{
int clear_repeat = 0;
if (O.user_black >= 0)
{
C.black = O.user_black;
clear_repeat = 1;
}
for (int i = 0; i < 4; i++)
if (O.user_cblack[i] > -1000000)
{
C.cblack[i] = O.user_cblack[i];
clear_repeat = 1;
}
if (clear_repeat)
C.cblack[4] = C.cblack[5] = 0;
// Add common part to cblack[] early
if (imgdata.idata.filters > 1000 && (C.cblack[4] + 1) / 2 == 1 && (C.cblack[5] + 1) / 2 == 1)
{
int clrs[4];
int lastg = -1, gcnt = 0;
for (int c = 0; c < 4; c++)
{
clrs[c] = FC(c / 2, c % 2);
if (clrs[c] == 1)
{
gcnt++;
lastg = c;
}
}
if (gcnt > 1 && lastg >= 0)
clrs[lastg] = 3;
for (int c = 0; c < 4; c++)
C.cblack[clrs[c]] += C.cblack[6 + c / 2 % C.cblack[4] * C.cblack[5] + c % 2 % C.cblack[5]];
C.cblack[4] = C.cblack[5] = 0;
// imgdata.idata.filters = sfilters;
}
else if (imgdata.idata.filters <= 1000 && C.cblack[4] == 1 && C.cblack[5] == 1) // Fuji RAF dng
{
for (int c = 0; c < 4; c++)
C.cblack[c] += C.cblack[6];
C.cblack[4] = C.cblack[5] = 0;
}
// remove common part from C.cblack[]
int i = C.cblack[3];
int c;
for (c = 0; c < 3; c++)
if (i > C.cblack[c])
i = C.cblack[c];
for (c = 0; c < 4; c++)
C.cblack[c] -= i; // remove common part
C.black += i;
// Now calculate common part for cblack[6+] part and move it to C.black
if (C.cblack[4] && C.cblack[5])
{
i = C.cblack[6];
for (c = 1; c < C.cblack[4] * C.cblack[5]; c++)
if (i > C.cblack[6 + c])
i = C.cblack[6 + c];
// Remove i from cblack[6+]
int nonz = 0;
for (c = 0; c < C.cblack[4] * C.cblack[5]; c++)
{
C.cblack[6 + c] -= i;
if (C.cblack[6 + c])
nonz++;
}
C.black += i;
if (!nonz)
C.cblack[4] = C.cblack[5] = 0;
}
for (c = 0; c < 4; c++)
C.cblack[c] += C.black;
}
int LibRaw::dcraw_process(void)
{
int quality, i;
int iterations = -1, dcb_enhance = 1, noiserd = 0;
float preser = 0;
float expos = 1.0;
CHECK_ORDER_LOW(LIBRAW_PROGRESS_LOAD_RAW);
// CHECK_ORDER_HIGH(LIBRAW_PROGRESS_PRE_INTERPOLATE);
try
{
int no_crop = 1;
if (~O.cropbox[2] && ~O.cropbox[3])
no_crop = 0;
libraw_decoder_info_t di;
get_decoder_info(&di);
bool is_bayer = (imgdata.idata.filters || P1.colors == 1);
int subtract_inline = !O.bad_pixels && !O.dark_frame && is_bayer && !IO.zero_is_bad;
raw2image_ex(subtract_inline); // allocate imgdata.image and copy data!
// Adjust sizes
int save_4color = O.four_color_rgb;
if (IO.zero_is_bad)
{
remove_zeroes();
SET_PROC_FLAG(LIBRAW_PROGRESS_REMOVE_ZEROES);
}
if (O.bad_pixels && no_crop)
{
bad_pixels(O.bad_pixels);
SET_PROC_FLAG(LIBRAW_PROGRESS_BAD_PIXELS);
}
if (O.dark_frame && no_crop)
{
subtract(O.dark_frame);
SET_PROC_FLAG(LIBRAW_PROGRESS_DARK_FRAME);
}
/* pre subtract black callback: check for it above to disable subtract inline */
if(callbacks.pre_subtractblack_cb)
(callbacks.pre_subtractblack_cb)(this);
quality = 2 + !IO.fuji_width;
if (O.user_qual >= 0)
quality = O.user_qual;
if (!subtract_inline || !C.data_maximum)
{
adjust_bl();
subtract_black_internal();
}
if (!(di.decoder_flags & LIBRAW_DECODER_FIXEDMAXC))
adjust_maximum();
if (O.user_sat > 0)
C.maximum = O.user_sat;
if (P1.is_foveon)
{
if (load_raw == &LibRaw::x3f_load_raw)
{
// Filter out zeroes
for (int i = 0; i < S.height * S.width * 4; i++)
if ((short)imgdata.image[0][i] < 0)
imgdata.image[0][i] = 0;
}
SET_PROC_FLAG(LIBRAW_PROGRESS_FOVEON_INTERPOLATE);
}
if (O.green_matching && !O.half_size)
{
green_matching();
}
if(callbacks.pre_scalecolors_cb)
(callbacks.pre_scalecolors_cb)(this);
if (!O.no_auto_scale)
{
scale_colors();
SET_PROC_FLAG(LIBRAW_PROGRESS_SCALE_COLORS);
}
if(callbacks.pre_preinterpolate_cb)
(callbacks.pre_preinterpolate_cb)(this);
pre_interpolate();
SET_PROC_FLAG(LIBRAW_PROGRESS_PRE_INTERPOLATE);
if (O.dcb_iterations >= 0)
iterations = O.dcb_iterations;
if (O.dcb_enhance_fl >= 0)
dcb_enhance = O.dcb_enhance_fl;
if (O.fbdd_noiserd >= 0)
noiserd = O.fbdd_noiserd;
/* pre-exposure correction callback */
if (O.exp_correc > 0)
{
expos = O.exp_shift;
preser = O.exp_preser;
exp_bef(expos, preser);
}
if(callbacks.pre_interpolate_cb)
(callbacks.pre_interpolate_cb)(this);
/* post-exposure correction fallback */
if (P1.filters && !O.no_interpolation)
{
if (noiserd > 0 && P1.colors == 3 && P1.filters)
fbdd(noiserd);
if (P1.filters > 1000 && callbacks.interpolate_bayer_cb)
(callbacks.interpolate_bayer_cb)(this);
else if (P1.filters == 9 && callbacks.interpolate_xtrans_cb)
(callbacks.interpolate_xtrans_cb)(this);
else if (quality == 0)
lin_interpolate();
else if (quality == 1 || P1.colors > 3)
vng_interpolate();
else if (quality == 2 && P1.filters > 1000)
ppg_interpolate();
else if (P1.filters == LIBRAW_XTRANS)
{
// Fuji X-Trans
xtrans_interpolate(quality > 2 ? 3 : 1);
}
else if (quality == 3)
ahd_interpolate(); // really don't need it here due to fallback op
else if (quality == 4)
dcb(iterations, dcb_enhance);
else if (quality == 11)
dht_interpolate();
else if (quality == 12)
aahd_interpolate();
// fallback to AHD
else
{
ahd_interpolate();
imgdata.process_warnings |= LIBRAW_WARN_FALLBACK_TO_AHD;
}
SET_PROC_FLAG(LIBRAW_PROGRESS_INTERPOLATE);
}
if (IO.mix_green)
{
for (P1.colors = 3, i = 0; i < S.height * S.width; i++)
imgdata.image[i][1] = (imgdata.image[i][1] + imgdata.image[i][3]) >> 1;
SET_PROC_FLAG(LIBRAW_PROGRESS_MIX_GREEN);
}
if(callbacks.post_interpolate_cb)
(callbacks.post_interpolate_cb)(this);
else if (!P1.is_foveon && P1.colors == 3 && O.med_passes > 0)
{
median_filter();
SET_PROC_FLAG(LIBRAW_PROGRESS_MEDIAN_FILTER);
}
if (O.highlight == 2)
{
blend_highlights();
SET_PROC_FLAG(LIBRAW_PROGRESS_HIGHLIGHTS);
}
if (O.highlight > 2)
{
recover_highlights();
SET_PROC_FLAG(LIBRAW_PROGRESS_HIGHLIGHTS);
}
if (O.use_fuji_rotate)
{
fuji_rotate();
SET_PROC_FLAG(LIBRAW_PROGRESS_FUJI_ROTATE);
}
if (!libraw_internal_data.output_data.histogram)
{
libraw_internal_data.output_data.histogram =
(int(*)[LIBRAW_HISTOGRAM_SIZE])malloc(sizeof(*libraw_internal_data.output_data.histogram) * 4);
merror(libraw_internal_data.output_data.histogram, "LibRaw::dcraw_process()");
}
#ifndef NO_LCMS
if (O.camera_profile)
{
apply_profile(O.camera_profile, O.output_profile);
SET_PROC_FLAG(LIBRAW_PROGRESS_APPLY_PROFILE);
}
#endif
if(callbacks.pre_converttorgb_cb)
(callbacks.pre_converttorgb_cb)(this);
convert_to_rgb();
SET_PROC_FLAG(LIBRAW_PROGRESS_CONVERT_RGB);
if(callbacks.post_converttorgb_cb)
(callbacks.post_converttorgb_cb)(this);
if (O.use_fuji_rotate)
{
stretch();
SET_PROC_FLAG(LIBRAW_PROGRESS_STRETCH);
}
O.four_color_rgb = save_4color; // also, restore
return 0;
}
catch (LibRaw_exceptions err)
{
EXCEPTION_HANDLER(err);
}
}
// clang-format off
// Supported cameras:
static const char *static_camera_list[] = {
"Adobe Digital Negative (DNG)",
"AgfaPhoto DC-833m",
"Alcatel 5035D",
"Apple iPad Pro",
"Apple iPhone SE",
"Apple iPhone 6s",
"Apple iPhone 6 plus",
"Apple iPhone 7",
"Apple iPhone 7 plus",
"Apple iPhone 8",
"Apple iPhone 8 plus",
"Apple iPhone X",
"Apple QuickTake 100",
"Apple QuickTake 150",
"Apple QuickTake 200",
"ARRIRAW format",
"AVT F-080C",
"AVT F-145C",
"AVT F-201C",
"AVT F-510C",
"AVT F-810C",
"Baumer TXG14",
"BlackMagic Cinema Camera",
"BlackMagic Micro Cinema Camera",
"BlackMagic Pocket Cinema Camera",
"BlackMagic Production Camera 4k",
"BlackMagic URSA",
"BlackMagic URSA Mini 4k",
"BlackMagic URSA Mini 4.6k",
"BlackMagic URSA Mini Pro 4.6k",
"Canon PowerShot 600",
"Canon PowerShot A5",
"Canon PowerShot A5 Zoom",
"Canon PowerShot A50",
"Canon PowerShot A410 (CHDK hack)",
"Canon PowerShot A460 (CHDK hack)",
"Canon PowerShot A470 (CHDK hack)",
"Canon PowerShot A530 (CHDK hack)",
"Canon PowerShot A540 (CHDK hack)",
"Canon PowerShot A550 (CHDK hack)",
"Canon PowerShot A570 (CHDK hack)",
"Canon PowerShot A590 (CHDK hack)",
"Canon PowerShot A610 (CHDK hack)",
"Canon PowerShot A620 (CHDK hack)",
"Canon PowerShot A630 (CHDK hack)",
"Canon PowerShot A640 (CHDK hack)",
"Canon PowerShot A650 (CHDK hack)",
"Canon PowerShot A710 IS (CHDK hack)",
"Canon PowerShot A720 IS (CHDK hack)",
"Canon PowerShot A3300 IS (CHDK hack)",
"Canon PowerShot D10 (CHDK hack)",
"Canon PowerShot ELPH 130 IS (CHDK hack)",
"Canon PowerShot ELPH 160 IS (CHDK hack)",
"Canon PowerShot Pro70",
"Canon PowerShot Pro90 IS",
"Canon PowerShot Pro1",
"Canon PowerShot G1",
"Canon PowerShot G1 X",
"Canon PowerShot G1 X Mark II",
"Canon PowerShot G1 X Mark III",
"Canon PowerShot G2",
"Canon PowerShot G3",
"Canon PowerShot G3 X",
"Canon PowerShot G5",
"Canon PowerShot G5 X",
"Canon PowerShot G6",
"Canon PowerShot G7 (CHDK hack)",
"Canon PowerShot G7 X",
"Canon PowerShot G7 X Mark II",
"Canon PowerShot G9",
"Canon PowerShot G9 X",
"Canon PowerShot G9 X Mark II",
"Canon PowerShot G10",
"Canon PowerShot G11",
"Canon PowerShot G12",
"Canon PowerShot G15",
"Canon PowerShot G16",
"Canon PowerShot S2 IS (CHDK hack)",
"Canon PowerShot S3 IS (CHDK hack)",
"Canon PowerShot S5 IS (CHDK hack)",
"Canon PowerShot SD300 (CHDK hack)",
"Canon PowerShot SD750 (CHDK hack)",
"Canon PowerShot SD950 (CHDK hack)",
"Canon PowerShot S30",
"Canon PowerShot S40",
"Canon PowerShot S45",
"Canon PowerShot S50",
"Canon PowerShot S60",
"Canon PowerShot S70",
"Canon PowerShot S90",
"Canon PowerShot S95",
"Canon PowerShot S100",
"Canon PowerShot S110",
"Canon PowerShot S120",
"Canon PowerShot SX1 IS",
"Canon PowerShot SX50 HS",
"Canon PowerShot SX60 HS",
"Canon PowerShot SX100 IS (CHDK hack)",
"Canon PowerShot SX110 IS (CHDK hack)",
"Canon PowerShot SX120 IS (CHDK hack)",
"Canon PowerShot SX130 IS (CHDK hack)",
"Canon PowerShot SX160 IS (CHDK hack)",
"Canon PowerShot SX220 HS (CHDK hack)",
"Canon PowerShot SX510 HS (CHDK hack)",
"Canon PowerShot SX10 IS (CHDK hack)",
"Canon PowerShot SX20 IS (CHDK hack)",
"Canon PowerShot SX30 IS (CHDK hack)",
"Canon PowerShot IXUS 160 (CHDK hack)",
"Canon PowerShot IXUS 900Ti (CHDK hack)",
"Canon EOS D30",
"Canon EOS D60",
"Canon EOS 5D",
"Canon EOS 5DS",
"Canon EOS 5DS R",
"Canon EOS 5D Mark II",
"Canon EOS 5D Mark III",
"Canon EOS 5D Mark IV",
"Canon EOS 6D",
"Canon EOS 6D Mark II",
"Canon EOS 7D",
"Canon EOS 7D Mark II",
"Canon EOS 10D",
"Canon EOS 20D",
"Canon EOS 20Da",
"Canon EOS 30D",
"Canon EOS 40D",
"Canon EOS 50D",
"Canon EOS 60D",
"Canon EOS 60Da",
"Canon EOS 70D",
"Canon EOS 77D",
"Canon EOS 80D",
"Canon EOS 200D",
"Canon EOS 300D / Digital Rebel / Kiss Digital",
"Canon EOS 350D / Digital Rebel XT / Kiss Digital N",
"Canon EOS 400D / Digital Rebel XTi / Kiss Digital X",
"Canon EOS 450D / Digital Rebel XSi / Kiss Digital X2",
"Canon EOS 500D / Digital Rebel T1i / Kiss Digital X3",
"Canon EOS 550D / Digital Rebel T2i / Kiss Digital X4",
"Canon EOS 600D / Digital Rebel T3i / Kiss Digital X5",
"Canon EOS 650D / Digital Rebel T4i / Kiss Digital X6i",
"Canon EOS 700D / Digital Rebel T5i",
"Canon EOS 750D / Digital Rebel T6i",
"Canon EOS 760D / Digital Rebel T6S",
"Canon EOS 800D",
"Canon EOS 100D / Digital Rebel SL1",
"Canon EOS 1000D / Digital Rebel XS / Kiss Digital F",
"Canon EOS 1100D / Digital Rebel T3 / Kiss Digital X50",
"Canon EOS 1200D",
"Canon EOS 1300D",
"Canon EOS C500",
"Canon EOS D2000C",
"Canon EOS M",
"Canon EOS M2",
"Canon EOS M3",
"Canon EOS M5",
"Canon EOS M6",
"Canon EOS M10",
"Canon EOS M100",
"Canon EOS-1D",
"Canon EOS-1DS",
"Canon EOS-1D C",
"Canon EOS-1D X",
"Canon EOS-1D Mark II",
"Canon EOS-1D Mark II N",
"Canon EOS-1D Mark III",
"Canon EOS-1D Mark IV",
"Canon EOS-1Ds Mark II",
"Canon EOS-1Ds Mark III",
"Canon EOS-1D X Mark II",
"Casio QV-2000UX",
"Casio QV-3000EX",
"Casio QV-3500EX",
"Casio QV-4000",
"Casio QV-5700",
"Casio QV-R41",
"Casio QV-R51",
"Casio QV-R61",
"Casio EX-F1",
"Casio EX-FC300S",
"Casio EX-FC400S",
"Casio EX-FH20",
"Casio EX-FH25",
"Casio EX-FH100",
"Casio EX-P600",
"Casio EX-S20",
"Casio EX-S100",
"Casio EX-Z4",
"Casio EX-Z50",
"Casio EX-Z500",
"Casio EX-Z55",
"Casio EX-Z60",
"Casio EX-Z75",
"Casio EX-Z750",
"Casio EX-Z8",
"Casio EX-Z850",
"Casio EX-Z1050",
"Casio EX-ZR100",
"Casio EX-Z1080",
"Casio EX-ZR700",
"Casio EX-ZR710",
"Casio EX-ZR750",
"Casio EX-ZR800",
"Casio EX-ZR850",
"Casio EX-ZR1000",
"Casio EX-ZR1100",
"Casio EX-ZR1200",
"Casio EX-ZR1300",
"Casio EX-ZR1500",
"Casio EX-ZR3000",
"Casio EX-ZR4000/5000",
"Casio EX-ZR4100/5100",
"Casio EX-100",
"Casio EX-100F",
"Casio EX-10",
"Casio Exlim Pro 505",
"Casio Exlim Pro 600",
"Casio Exlim Pro 700",
"Contax N Digital",
"Creative PC-CAM 600",
"Digital Bolex D16",
"Digital Bolex D16M",
"DJI 4384x3288",
"DJI Phantom4 Pro/Pro+",
"DJI Zenmuse X5",
"DJI Zenmuse X5R",
"DXO One",
"Epson R-D1",
"Epson R-D1s",
"Epson R-D1x",
"Foculus 531C",
"FujiFilm E505",
"FujiFilm E550",
"FujiFilm E900",
"FujiFilm F700",
"FujiFilm F710",
"FujiFilm F800",
"FujiFilm F810",
"FujiFilm S2Pro",
"FujiFilm S3Pro",
"FujiFilm S5Pro",
"FujiFilm S20Pro",
"FujiFilm S1",
"FujiFilm S100FS",
"FujiFilm S5000",
"FujiFilm S5100/S5500",
"FujiFilm S5200/S5600",
"FujiFilm S6000fd",
"FujiFilm S6500fd",
"FujiFilm S7000",
"FujiFilm S9000/S9500",
"FujiFilm S9100/S9600",
"FujiFilm S200EXR",
"FujiFilm S205EXR",
"FujiFilm SL1000",
"FujiFilm HS10/HS11",
"FujiFilm HS20EXR",
"FujiFilm HS22EXR",
"FujiFilm HS30EXR",
"FujiFilm HS33EXR",
"FujiFilm HS35EXR",
"FujiFilm HS50EXR",
"FujiFilm F505EXR",
"FujiFilm F550EXR",
"FujiFilm F600EXR",
"FujiFilm F605EXR",
"FujiFilm F770EXR",
"FujiFilm F775EXR",
"FujiFilm F800EXR",
"FujiFilm F900EXR",
"FujiFilm GFX 50S",
"FujiFilm X-Pro1",
"FujiFilm X-Pro2",
"FujiFilm X-S1",
"FujiFilm XQ1",
"FujiFilm XQ2",
"FujiFilm X100",
"FujiFilm X100f",
"FujiFilm X100S",
"FujiFilm X100T",
"FujiFilm X10",
"FujiFilm X20",
"FujiFilm X30",
"FujiFilm X70",
"FujiFilm X-A1",
"FujiFilm X-A2",
"FujiFilm X-A3",
"FujiFilm X-A5",
"FujiFilm X-A10",
"FujiFilm X-A20",
"FujiFilm X-E1",
"FujiFilm X-E2",
"FujiFilm X-E2S",
"FujiFilm X-E3",
"FujiFilm X-M1",
"FujiFilm XF1",
"FujiFilm X-H1",
"FujiFilm X-T1",
"FujiFilm X-T1 Graphite Silver",
"FujiFilm X-T2",
"FujiFilm X-T10",
"FujiFilm X-T20",
"FujiFilm IS-1",
"Gione E7",
"GITUP GIT2",
"GITUP GIT2P",
"Google Pixel",
"Google Pixel XL",
"Hasselblad H2D-22",
"Hasselblad H2D-39",
"Hasselblad H3DII-22",
"Hasselblad H3DII-31",
"Hasselblad H3DII-39",
"Hasselblad H3DII-50",
"Hasselblad H3D-22",
"Hasselblad H3D-31",
"Hasselblad H3D-39",
"Hasselblad H4D-60",
"Hasselblad H4D-50",
"Hasselblad H4D-40",
"Hasselblad H4D-31",
"Hasselblad H5D-60",
"Hasselblad H5D-50",
"Hasselblad H5D-50c",
"Hasselblad H5D-40",
"Hasselblad H6D-100c",
"Hasselblad A6D-100c", // Aerial camera
"Hasselblad CFV",
"Hasselblad CFV-50",
"Hasselblad CFH",
"Hasselblad CF-22",
"Hasselblad CF-31",
"Hasselblad CF-39",
"Hasselblad V96C",
"Hasselblad Lusso",
"Hasselblad Lunar",
"Hasselblad True Zoom",
"Hasselblad Stellar",
"Hasselblad Stellar II",
"Hasselblad HV",
"Hasselblad X1D",
"HTC UltraPixel",
"HTC MyTouch 4G",
"HTC One (A9)",
"HTC One (M9)",
"HTC 10",
"Huawei P9 (EVA-L09/AL00)",
"Huawei Honor6a",
"Huawei Honor9",
"Huawei Mate10 (BLA-L29)",
"Imacon Ixpress 96, 96C",
"Imacon Ixpress 384, 384C (single shot only)",
"Imacon Ixpress 132C",
"Imacon Ixpress 528C (single shot only)",
"ISG 2020x1520",
"Ikonoskop A-Cam dII Panchromatic",
"Ikonoskop A-Cam dII",
"Kinefinity KineMINI",
"Kinefinity KineRAW Mini",
"Kinefinity KineRAW S35",
"Kodak DC20",
"Kodak DC25",
"Kodak DC40",
"Kodak DC50",
"Kodak DC120",
"Kodak DCS200",
"Kodak DCS315C",
"Kodak DCS330C",
"Kodak DCS420",
"Kodak DCS460",
"Kodak DCS460A",
"Kodak DCS460D",
"Kodak DCS520C",
"Kodak DCS560C",
"Kodak DCS620C",
"Kodak DCS620X",
"Kodak DCS660C",
"Kodak DCS660M",
"Kodak DCS720X",
"Kodak DCS760C",
"Kodak DCS760M",
"Kodak EOSDCS1",
"Kodak EOSDCS3B",
"Kodak NC2000F",
"Kodak ProBack",
"Kodak PB645C",
"Kodak PB645H",
"Kodak PB645M",
"Kodak DCS Pro 14n",
"Kodak DCS Pro 14nx",
"Kodak DCS Pro SLR/c",
"Kodak DCS Pro SLR/n",
"Kodak C330",
"Kodak C603",
"Kodak P850",
"Kodak P880",
"Kodak S-1",
"Kodak Z980",
"Kodak Z981",
"Kodak Z990",
"Kodak Z1015",
"Kodak KAI-0340",
"Konica KD-400Z",
"Konica KD-510Z",
"Leaf AFi 5",
"Leaf AFi 6",
"Leaf AFi 7",
"Leaf AFi-II 6",
"Leaf AFi-II 7",
"Leaf AFi-II 10",
"Leaf AFi-II 10R",
"Leaf Aptus-II 5",
"Leaf Aptus-II 6",
"Leaf Aptus-II 7",
"Leaf Aptus-II 8",
"Leaf Aptus-II 10",
"Leaf Aptus-II 12",
"Leaf Aptus-II 12R",
"Leaf Aptus 17",
"Leaf Aptus 22",
"Leaf Aptus 54S",
"Leaf Aptus 65",
"Leaf Aptus 65S",
"Leaf Aptus 75",
"Leaf Aptus 75S",
"Leaf Cantare",
"Leaf Cantare XY",
"Leaf CatchLight",
"Leaf CMost",
"Leaf Credo 40",
"Leaf Credo 50",
"Leaf Credo 60",
"Leaf Credo 80 (low compression mode only)",
"Leaf DCB-II",
"Leaf Valeo 6",
"Leaf Valeo 11",
"Leaf Valeo 17",
"Leaf Valeo 17wi",
"Leaf Valeo 22",
"Leaf Valeo 22wi",
"Leaf Volare",
"Lenovo a820",
"Leica C (Typ 112)",
"Leica CL",
"Leica Digilux 2",
"Leica Digilux 3",
"Leica Digital-Modul-R",
"Leica D-LUX2",
"Leica D-LUX3",
"Leica D-LUX4",
"Leica D-LUX5",
"Leica D-LUX6",
"Leica D-Lux (Typ 109)",
"Leica M8",
"Leica M8.2",
"Leica M9",
"Leica M10",
"Leica M (Typ 240)",
"Leica M (Typ 262)",
"Leica Monochrom (Typ 240)",
"Leica Monochrom (Typ 246)",
"Leica M-D (Typ 262)",
"Leica M-E",
"Leica M-P",
"Leica R8",
"Leica Q (Typ 116)",
"Leica S",
"Leica S2",
"Leica S (Typ 007)",
"Leica SL (Typ 601)",
"Leica T (Typ 701)",
"Leica TL",
"Leica TL2",
"Leica X1",
"Leica X (Typ 113)",
"Leica X2",
"Leica X-E (Typ 102)",
"Leica X-U (Typ 113)",
"Leica V-LUX1",
"Leica V-LUX2",
"Leica V-LUX3",
"Leica V-LUX4",
"Leica V-Lux (Typ 114)",
"Leica X VARIO (Typ 107)",
"LG G3",
"LG G4",
"LG V20 (F800K)",
"LG VS995",
"Logitech Fotoman Pixtura",
"Mamiya ZD",
"Matrix 4608x3288",
"Meizy MX4",
"Micron 2010",
"Minolta RD175",
"Minolta DiMAGE 5",
"Minolta DiMAGE 7",
"Minolta DiMAGE 7i",
"Minolta DiMAGE 7Hi",
"Minolta DiMAGE A1",
"Minolta DiMAGE A2",
"Minolta DiMAGE A200",
"Minolta DiMAGE G400",
"Minolta DiMAGE G500",
"Minolta DiMAGE G530",
"Minolta DiMAGE G600",
"Minolta DiMAGE Z2",
"Minolta Alpha/Dynax/Maxxum 5D",
"Minolta Alpha/Dynax/Maxxum 7D",
"Motorola PIXL",
"Nikon D1",
"Nikon D1H",
"Nikon D1X",
"Nikon D2H",
"Nikon D2Hs",
"Nikon D2X",
"Nikon D2Xs",
"Nikon D3",
"Nikon D3s",
"Nikon D3X",
"Nikon D4",
"Nikon D4s",
"Nikon D40",
"Nikon D40X",
"Nikon D5",
"Nikon D50",
"Nikon D60",
"Nikon D70",
"Nikon D70s",
"Nikon D80",
"Nikon D90",
"Nikon D100",
"Nikon D200",
"Nikon D300",
"Nikon D300s",
"Nikon D500",
"Nikon D600",
"Nikon D610",
"Nikon D700",
"Nikon D750",
"Nikon D800",
"Nikon D800E",
"Nikon D810",
"Nikon D810A",
"Nikon D850",
"Nikon D3000",
"Nikon D3100",
"Nikon D3200",
"Nikon D3300",
"Nikon D3400",
"Nikon D5000",
"Nikon D5100",
"Nikon D5200",
"Nikon D5300",
"Nikon D5500",
"Nikon D5600",
"Nikon D7000",
"Nikon D7100",
"Nikon D7200",
"Nikon D7500",
"Nikon Df",
"Nikon 1 AW1",
"Nikon 1 J1",
"Nikon 1 J2",
"Nikon 1 J3",
"Nikon 1 J4",
"Nikon 1 J5",
"Nikon 1 S1",
"Nikon 1 S2",
"Nikon 1 V1",
"Nikon 1 V2",
"Nikon 1 V3",
"Nikon E700 (\"DIAG RAW\" hack)",
"Nikon E800 (\"DIAG RAW\" hack)",
"Nikon E880 (\"DIAG RAW\" hack)",
"Nikon E900 (\"DIAG RAW\" hack)",
"Nikon E950 (\"DIAG RAW\" hack)",
"Nikon E990 (\"DIAG RAW\" hack)",
"Nikon E995 (\"DIAG RAW\" hack)",
"Nikon E2100 (\"DIAG RAW\" hack)",
"Nikon E2500 (\"DIAG RAW\" hack)",
"Nikon E3200 (\"DIAG RAW\" hack)",
"Nikon E3700 (\"DIAG RAW\" hack)",
"Nikon E4300 (\"DIAG RAW\" hack)",
"Nikon E4500 (\"DIAG RAW\" hack)",
"Nikon E5000",
"Nikon E5400",
"Nikon E5700",
"Nikon E8400",
"Nikon E8700",
"Nikon E8800",
"Nikon Coolpix A",
"Nikon Coolpix B700",
"Nikon Coolpix P330",
"Nikon Coolpix P340",
"Nikon Coolpix P6000",
"Nikon Coolpix P7000",
"Nikon Coolpix P7100",
"Nikon Coolpix P7700",
"Nikon Coolpix P7800",
"Nikon Coolpix S6 (\"DIAG RAW\" hack)",
"Nikon Coolscan NEF",
"Nokia N95",
"Nokia X2",
"Nokia 1200x1600",
"Nokia Lumia 950 XL",
"Nokia Lumia 1020",
"Nokia Lumia 1520",
"Olympus AIR A01",
"Olympus C3030Z",
"Olympus C5050Z",
"Olympus C5060Z",
"Olympus C7070WZ",
"Olympus C70Z,C7000Z",
"Olympus C740UZ",
"Olympus C770UZ",
"Olympus C8080WZ",
"Olympus X200,D560Z,C350Z",
"Olympus E-1",
"Olympus E-3",
"Olympus E-5",
"Olympus E-10",
"Olympus E-20",
"Olympus E-30",
"Olympus E-300",
"Olympus E-330",
"Olympus E-400",
"Olympus E-410",
"Olympus E-420",
"Olympus E-450",
"Olympus E-500",
"Olympus E-510",
"Olympus E-520",
"Olympus E-600",
"Olympus E-620",
"Olympus E-P1",
"Olympus E-P2",
"Olympus E-P3",
"Olympus E-P5",
"Olympus E-PL1",
"Olympus E-PL1s",
"Olympus E-PL2",
"Olympus E-PL3",
"Olympus E-PL5",
"Olympus E-PL6",
"Olympus E-PL7",
"Olympus E-PL8",
"Olympus E-PL9",
"Olympus E-PM1",
"Olympus E-PM2",
"Olympus E-M1",
"Olympus E-M1 Mark II",
"Olympus E-M10",
"Olympus E-M10 Mark II",
"Olympus E-M10 Mark III",
"Olympus E-M5",
"Olympus E-M5 Mark II",
"Olympus Pen F",
"Olympus SP310",
"Olympus SP320",
"Olympus SP350",
"Olympus SP500UZ",
"Olympus SP510UZ",
"Olympus SP550UZ",
"Olympus SP560UZ",
"Olympus SP565UZ",
"Olympus SP570UZ",
"Olympus STYLUS1",
"Olympus STYLUS1s",
"Olympus SH-2",
"Olympus SH-3",
"Olympus TG-4",
"Olympus TG-5",
"Olympus XZ-1",
"Olympus XZ-2",
"Olympus XZ-10",
"OmniVision 4688",
"OmniVision OV5647",
"OmniVision OV5648",
"OmniVision OV8850",
"OmniVision 13860",
"OnePlus One",
"OnePlus A3303",
"OnePlus A5000",
"Panasonic DMC-CM1",
"Panasonic DMC-FZ8",
"Panasonic DMC-FZ18",
"Panasonic DMC-FZ28",
"Panasonic DMC-FZ30",
"Panasonic DMC-FZ35/FZ38",
"Panasonic DMC-FZ40",
"Panasonic DMC-FZ45",
"Panasonic DMC-FZ50",
"Panasonic DMC-FZ7",
"Panasonic DMC-FZ70",
"Panasonic DMC-FZ72",
"Panasonic DC-FZ80/82",
"Panasonic DMC-FZ100",
"Panasonic DMC-FZ150",
"Panasonic DMC-FZ200",
"Panasonic DMC-FZ300/330",
"Panasonic DMC-FZ1000",
"Panasonic DMC-FZ2000/2500/FZH1",
"Panasonic DMC-FX150",
"Panasonic DMC-G1",
"Panasonic DMC-G10",
"Panasonic DMC-G2",
"Panasonic DMC-G3",
"Panasonic DMC-G5",
"Panasonic DMC-G6",
"Panasonic DMC-G7/G70",
"Panasonic DMC-G8/80/81/85",
"Panasonic DC-G9",
"Panasonic DMC-GF1",
"Panasonic DMC-GF2",
"Panasonic DMC-GF3",
"Panasonic DMC-GF5",
"Panasonic DMC-GF6",
"Panasonic DMC-GF7",
"Panasonic DC-GF10/GF90",
"Panasonic DMC-GH1",
"Panasonic DMC-GH2",
"Panasonic DMC-GH3",
"Panasonic DMC-GH4",
"Panasonic AG-GH4",
"Panasonic DC-GH5",
"Panasonic DC-GH5S",
"Panasonic DMC-GM1",
"Panasonic DMC-GM1s",
"Panasonic DMC-GM5",
"Panasonic DMC-GX1",
"Panasonic DMC-GX7",
"Panasonic DMC-GX8",
"Panasonic DC-GX9",
"Panasonic DMC-GX80/85",
"Panasonic DC-GX800/850/GF9",
"Panasonic DMC-L1",
"Panasonic DMC-L10",
"Panasonic DMC-LC1",
"Panasonic DMC-LF1",
"Panasonic DMC-LX1",
"Panasonic DMC-LX2",
"Panasonic DMC-LX3",
"Panasonic DMC-LX5",
"Panasonic DMC-LX7",
"Panasonic DMC-LX9/10/15",
"Panasonic DMC-LX100",
"Panasonic DMC-TZ60/61/SZ40",
"Panasonic DMC-TZ70/71/ZS50",
"Panasonic DMC-TZ80/81/85/ZS60",
"Panasonic DC-ZS70 (DC-TZ90/91/92, DC-T93)",
"Panasonic DC-TZ100/101/ZS100",
"Panasonic DC-TZ200/ZS200",
"PARROT Bebop 2",
"PARROT Bebop Drone",
"Pentax *ist D",
"Pentax *ist DL",
"Pentax *ist DL2",
"Pentax *ist DS",
"Pentax *ist DS2",
"Pentax GR",
"Pentax K10D",
"Pentax K20D",
"Pentax K100D",
"Pentax K100D Super",
"Pentax K110D",
"Pentax K200D",
"Pentax K2000/K-m",
"Pentax KP",
"Pentax K-x",
"Pentax K-r",
"Pentax K-01",
"Pentax K-1",
"Pentax K-3",
"Pentax K-3 II",
"Pentax K-30",
"Pentax K-5",
"Pentax K-5 II",
"Pentax K-5 IIs",
"Pentax K-50",
"Pentax K-500",
"Pentax K-7",
"Pentax K-70",
"Pentax K-S1",
"Pentax K-S2",
"Pentax MX-1",
"Pentax Q",
"Pentax Q7",
"Pentax Q10",
"Pentax QS-1",
"Pentax Optio S",
"Pentax Optio S4",
"Pentax Optio 33WR",
"Pentax Optio 750Z",
"Pentax 645D",
"Pentax 645Z",
"PhaseOne IQ140",
"PhaseOne IQ150",
"PhaseOne IQ160",
"PhaseOne IQ180",
"PhaseOne IQ180 IR",
"PhaseOne IQ250",
"PhaseOne IQ260",
"PhaseOne IQ260 Achromatic",
"PhaseOne IQ280",
"PhaseOne IQ3 50MP",
"PhaseOne IQ3 60MP",
"PhaseOne IQ3 80MP",
"PhaseOne IQ3 100MP",
"PhaseOne IQ3 100MP Trichromatic",
"PhaseOne LightPhase",
"PhaseOne Achromatic+",
"PhaseOne H 10",
"PhaseOne H 20",
"PhaseOne H 25",
"PhaseOne P 20",
"PhaseOne P 20+",
"PhaseOne P 21",
"PhaseOne P 25",
"PhaseOne P 25+",
"PhaseOne P 30",
"PhaseOne P 30+",
"PhaseOne P 40+",
"PhaseOne P 45",
"PhaseOne P 45+",
"PhaseOne P 65",
"PhaseOne P 65+",
"Photron BC2-HD",
"Pixelink A782",
"Polaroid x530",
"RaspberryPi Camera",
"RaspberryPi Camera V2",
"Ricoh GR",
"Ricoh GR Digital",
"Ricoh GR Digital II",
"Ricoh GR Digital III",
"Ricoh GR Digital IV",
"Ricoh GR II",
"Ricoh GX100",
"Ricoh GX200",
"Ricoh GXR MOUNT A12",
"Ricoh GXR MOUNT A16 24-85mm F3.5-5.5",
"Ricoh GXR, S10 24-72mm F2.5-4.4 VC",
"Ricoh GXR, GR A12 50mm F2.5 MACRO",
"Ricoh GXR, GR LENS A12 28mm F2.5",
"Ricoh GXR, GXR P10",
#ifndef NO_JASPER
"Redcode R3D format",
#endif
"Rollei d530flex",
"RoverShot 3320af",
"Samsung EX1",
"Samsung EX2F",
"Samsung GX-1L",
"Samsung GX-1S",
"Samsung GX10",
"Samsung GX20",
"Samsung Galaxy Nexus",
"Samsung Galaxy NX (EK-GN120)",
"Samsung Galaxy S3",
"Samsung Galaxy S6 (SM-G920F)",
"Samsung Galaxy S7",
"Samsung Galaxy S7 Edge",
"Samsung Galaxy S8 (SM-G950U)",
"Samsung NX1",
"Samsung NX5",
"Samsung NX10",
"Samsung NX11",
"Samsung NX100",
"Samsung NX1000",
"Samsung NX1100",
"Samsung NX20",
"Samsung NX200",
"Samsung NX210",
"Samsung NX2000",
"Samsung NX30",
"Samsung NX300",
"Samsung NX300M",
"Samsung NX3000",
"Samsung NX500",
"Samsung NX mini",
"Samsung Pro815",
"Samsung WB550",
"Samsung WB2000",
"Samsung S85 (hacked)",
"Samsung S850 (hacked)",
"Sarnoff 4096x5440",
"Seitz 6x17",
"Seitz Roundshot D3",
"Seitz Roundshot D2X",
"Seitz Roundshot D2Xs",
"Sigma SD9 (raw decode only)",
"Sigma SD10 (raw decode only)",
"Sigma SD14 (raw decode only)",
"Sigma SD15 (raw decode only)",
"Sigma SD1",
"Sigma SD1 Merill",
"Sigma DP1",
"Sigma DP1 Merill",
"Sigma DP1S",
"Sigma DP1X",
"Sigma DP2",
"Sigma DP2 Merill",
"Sigma DP2S",
"Sigma DP2X",
"Sigma DP3 Merill",
"Sigma dp0 Quattro",
"Sigma dp1 Quattro",
"Sigma dp2 Quattro",
"Sigma dp3 Quattro",
"Sigma sd Quattro",
"Sigma sd Quattro H",
"Sinar eMotion 22",
"Sinar eMotion 54",
"Sinar eSpirit 65",
"Sinar eMotion 75",
"Sinar eVolution 75",
"Sinar 3072x2048",
"Sinar 4080x4080",
"Sinar 4080x5440",
"Sinar STI format",
"Sinar Sinarback 54",
"SMaL Ultra-Pocket 3",
"SMaL Ultra-Pocket 4",
"SMaL Ultra-Pocket 5",
"Sony A7",
"Sony A7 II",
"Sony A7R",
"Sony A7R II",
"Sony A7R III",
"Sony A7S",
"Sony A7S II",
"Sony A9",
"Sony ILCA-68 (A68)",
"Sony ILCA-77M2 (A77-II)",
"Sony ILCA-99M2 (A99-II)",
"Sony ILCE-3000",
"Sony ILCE-5000",
"Sony ILCE-5100",
"Sony ILCE-6000",
"Sony ILCE-6300",
"Sony ILCE-6500",
"Sony ILCE-QX1",
"Sony DSC-F828",
"Sony DSC-R1",
"Sony DSC-RX0",
"Sony DSC-RX1",
"Sony DSC-RX1R",
"Sony DSC-RX1R II",
"Sony DSC-RX10",
"Sony DSC-RX10II",
"Sony DSC-RX10III",
"Sony DSC-RX10IV",
"Sony DSC-RX100",
"Sony DSC-RX100II",
"Sony DSC-RX100III",
"Sony DSC-RX100IV",
"Sony DSC-RX100V",
"Sony DSC-V3",
"Sony DSLR-A100",
"Sony DSLR-A200",
"Sony DSLR-A230",
"Sony DSLR-A290",
"Sony DSLR-A300",
"Sony DSLR-A330",
"Sony DSLR-A350",
"Sony DSLR-A380",
"Sony DSLR-A390",
"Sony DSLR-A450",
"Sony DSLR-A500",
"Sony DSLR-A550",
"Sony DSLR-A560",
"Sony DSLR-A580",
"Sony DSLR-A700",
"Sony DSLR-A850",
"Sony DSLR-A900",
"Sony NEX-3",
"Sony NEX-3N",
"Sony NEX-5",
"Sony NEX-5N",
"Sony NEX-5R",
"Sony NEX-5T",
"Sony NEX-6",
"Sony NEX-7",
"Sony NEX-C3",
"Sony NEX-F3",
"Sony NEX-VG20",
"Sony NEX-VG30",
"Sony NEX-VG900",
"Sony SLT-A33",
"Sony SLT-A35",
"Sony SLT-A37",
"Sony SLT-A55V",
"Sony SLT-A57",
"Sony SLT-A58",
"Sony SLT-A65V",
"Sony SLT-A77V",
"Sony SLT-A99V",
"Sony XCD-SX910CR",
"Sony IMX135-mipi 13mp",
"Sony IMX135-QCOM",
"Sony IMX072-mipi",
"Sony IMX214",
"Sony IMX219",
"Sony IMX230",
"Sony IMX298-mipi 16mp",
"Sony IMX219-mipi 8mp",
"Sony Xperia L",
"STV680 VGA",
"PtGrey GRAS-50S5C",
"JaiPulnix BB-500CL",
"JaiPulnix BB-500GE",
"SVS SVS625CL",
"Yi M1",
"YUNEEC CGO3",
"YUNEEC CGO3P",
"YUNEEC CGO4",
"Xiaomi MI3",
"Xiaomi RedMi Note3 Pro",
"Xiaoyi YIAC3 (YI 4k)",
NULL
};
// clang-format on
const char **LibRaw::cameraList() { return static_camera_list; }
int LibRaw::cameraCount() { return (sizeof(static_camera_list) / sizeof(static_camera_list[0])) - 1; }
const char *LibRaw::strprogress(enum LibRaw_progress p)
{
switch (p)
{
case LIBRAW_PROGRESS_START:
return "Starting";
case LIBRAW_PROGRESS_OPEN:
return "Opening file";
case LIBRAW_PROGRESS_IDENTIFY:
return "Reading metadata";
case LIBRAW_PROGRESS_SIZE_ADJUST:
return "Adjusting size";
case LIBRAW_PROGRESS_LOAD_RAW:
return "Reading RAW data";
case LIBRAW_PROGRESS_REMOVE_ZEROES:
return "Clearing zero values";
case LIBRAW_PROGRESS_BAD_PIXELS:
return "Removing dead pixels";
case LIBRAW_PROGRESS_DARK_FRAME:
return "Subtracting dark frame data";
case LIBRAW_PROGRESS_FOVEON_INTERPOLATE:
return "Interpolating Foveon sensor data";
case LIBRAW_PROGRESS_SCALE_COLORS:
return "Scaling colors";
case LIBRAW_PROGRESS_PRE_INTERPOLATE:
return "Pre-interpolating";
case LIBRAW_PROGRESS_INTERPOLATE:
return "Interpolating";
case LIBRAW_PROGRESS_MIX_GREEN:
return "Mixing green channels";
case LIBRAW_PROGRESS_MEDIAN_FILTER:
return "Median filter";
case LIBRAW_PROGRESS_HIGHLIGHTS:
return "Highlight recovery";
case LIBRAW_PROGRESS_FUJI_ROTATE:
return "Rotating Fuji diagonal data";
case LIBRAW_PROGRESS_FLIP:
return "Flipping image";
case LIBRAW_PROGRESS_APPLY_PROFILE:
return "ICC conversion";
case LIBRAW_PROGRESS_CONVERT_RGB:
return "Converting to RGB";
case LIBRAW_PROGRESS_STRETCH:
return "Stretching image";
case LIBRAW_PROGRESS_THUMB_LOAD:
return "Loading thumbnail";
default:
return "Some strange things";
}
}
#undef ID
#include "../internal/libraw_x3f.cpp"
void x3f_clear(void *p) { x3f_delete((x3f_t *)p); }
static void utf2char(utf16_t *str, char *buffer, unsigned bufsz)
{
if(bufsz<1) return;
buffer[bufsz-1] = 0;
char *b = buffer;
while (*str != 0x00 && --bufsz>0)
{
char *chr = (char *)str;
*b++ = *chr;
str++;
}
*b = 0;
}
static void *lr_memmem(const void *l, size_t l_len, const void *s, size_t s_len)
{
register char *cur, *last;
const char *cl = (const char *)l;
const char *cs = (const char *)s;
/* we need something to compare */
if (l_len == 0 || s_len == 0)
return NULL;
/* "s" must be smaller or equal to "l" */
if (l_len < s_len)
return NULL;
/* special case where s_len == 1 */
if (s_len == 1)
return (void *)memchr(l, (int)*cs, l_len);
/* the last position where its possible to find "s" in "l" */
last = (char *)cl + l_len - s_len;
for (cur = (char *)cl; cur <= last; cur++)
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
return cur;
return NULL;
}
void LibRaw::parse_x3f()
{
x3f_t *x3f = x3f_new_from_file(libraw_internal_data.internal_data.input);
if (!x3f)
return;
_x3f_data = x3f;
x3f_header_t *H = NULL;
x3f_directory_section_t *DS = NULL;
H = &x3f->header;
// Parse RAW size from RAW section
x3f_directory_entry_t *DE = x3f_get_raw(x3f);
if (!DE)
return;
imgdata.sizes.flip = H->rotation;
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
imgdata.sizes.raw_width = ID->columns;
imgdata.sizes.raw_height = ID->rows;
// Parse other params from property section
DE = x3f_get_prop(x3f);
if ((x3f_load_data(x3f, DE) == X3F_OK))
{
// Parse property list
DEH = &DE->header;
x3f_property_list_t *PL = &DEH->data_subsection.property_list;
utf16_t *datap = (utf16_t*) PL->data;
uint32_t maxitems = PL->data_size/sizeof(utf16_t);
if (PL->property_table.size != 0)
{
int i;
x3f_property_t *P = PL->property_table.element;
for (i = 0; i < PL->num_properties; i++)
{
char name[100], value[100];
int noffset = (P[i].name - datap);
int voffset = (P[i].value - datap);
if(noffset < 0 || noffset>maxitems || voffset<0 || voffset>maxitems)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
int maxnsize = maxitems - (P[i].name - datap);
int maxvsize = maxitems - (P[i].value - datap);
utf2char(P[i].name, name,MIN(maxnsize,sizeof(name)));
utf2char(P[i].value, value,MIN(maxvsize,sizeof(value)));
if (!strcmp(name, "ISO"))
imgdata.other.iso_speed = atoi(value);
if (!strcmp(name, "CAMMANUF"))
strcpy(imgdata.idata.make, value);
if (!strcmp(name, "CAMMODEL"))
strcpy(imgdata.idata.model, value);
if (!strcmp(name, "CAMSERIAL"))
strcpy(imgdata.shootinginfo.BodySerial, value);
if (!strcmp(name, "WB_DESC"))
strcpy(imgdata.color.model2, value);
if (!strcmp(name, "TIME"))
imgdata.other.timestamp = atoi(value);
if (!strcmp(name, "SHUTTER"))
imgdata.other.shutter = atof(value);
if (!strcmp(name, "APERTURE"))
imgdata.other.aperture = atof(value);
if (!strcmp(name, "FLENGTH"))
imgdata.other.focal_len = atof(value);
if (!strcmp(name, "FLEQ35MM"))
imgdata.lens.makernotes.FocalLengthIn35mmFormat = atof(value);
if (!strcmp(name, "IMAGERTEMP"))
imgdata.other.SensorTemperature = atof(value);
if (!strcmp(name, "LENSARANGE"))
{
char *sp;
imgdata.lens.makernotes.MaxAp4CurFocal = imgdata.lens.makernotes.MinAp4CurFocal = atof(value);
sp = strrchr(value, ' ');
if (sp)
{
imgdata.lens.makernotes.MinAp4CurFocal = atof(sp);
if (imgdata.lens.makernotes.MaxAp4CurFocal > imgdata.lens.makernotes.MinAp4CurFocal)
my_swap(float, imgdata.lens.makernotes.MaxAp4CurFocal, imgdata.lens.makernotes.MinAp4CurFocal);
}
}
if (!strcmp(name, "LENSFRANGE"))
{
char *sp;
imgdata.lens.makernotes.MinFocal = imgdata.lens.makernotes.MaxFocal = atof(value);
sp = strrchr(value, ' ');
if (sp)
{
imgdata.lens.makernotes.MaxFocal = atof(sp);
if ((imgdata.lens.makernotes.MaxFocal + 0.17f) < imgdata.lens.makernotes.MinFocal)
my_swap(float, imgdata.lens.makernotes.MaxFocal, imgdata.lens.makernotes.MinFocal);
}
}
if (!strcmp(name, "LENSMODEL"))
{
char *sp;
imgdata.lens.makernotes.LensID = strtol(value, &sp, 16); // atoi(value);
if (imgdata.lens.makernotes.LensID)
imgdata.lens.makernotes.LensMount = Sigma_X3F;
}
}
imgdata.idata.raw_count = 1;
load_raw = &LibRaw::x3f_load_raw;
imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 6;
imgdata.idata.is_foveon = 1;
libraw_internal_data.internal_output_params.raw_color = 1; // Force adobe coeff
imgdata.color.maximum = 0x3fff; // To be reset by color table
libraw_internal_data.unpacker_data.order = 0x4949;
}
}
else
{
// No property list
if (imgdata.sizes.raw_width == 5888 || imgdata.sizes.raw_width == 2944 || imgdata.sizes.raw_width == 6656 ||
imgdata.sizes.raw_width == 3328 || imgdata.sizes.raw_width == 5504 ||
imgdata.sizes.raw_width == 2752) // Quattro
{
imgdata.idata.raw_count = 1;
load_raw = &LibRaw::x3f_load_raw;
imgdata.sizes.raw_pitch = imgdata.sizes.raw_width * 6;
imgdata.idata.is_foveon = 1;
libraw_internal_data.internal_output_params.raw_color = 1; // Force adobe coeff
libraw_internal_data.unpacker_data.order = 0x4949;
strcpy(imgdata.idata.make, "SIGMA");
#if 1
// Try to find model number in first 2048 bytes;
int pos = libraw_internal_data.internal_data.input->tell();
libraw_internal_data.internal_data.input->seek(0, SEEK_SET);
unsigned char buf[2048];
libraw_internal_data.internal_data.input->read(buf, 2048, 1);
libraw_internal_data.internal_data.input->seek(pos, SEEK_SET);
unsigned char *fnd = (unsigned char *)lr_memmem(buf, 2048, "SIGMA dp", 8);
unsigned char *fndsd = (unsigned char *)lr_memmem(buf, 2048, "sd Quatt", 8);
if (fnd)
{
unsigned char *nm = fnd + 8;
snprintf(imgdata.idata.model, 64, "dp%c Quattro", *nm <= '9' && *nm >= '0' ? *nm : '2');
}
else if (fndsd)
{
snprintf(imgdata.idata.model, 64, "%s", fndsd);
}
else
#endif
if (imgdata.sizes.raw_width == 6656 || imgdata.sizes.raw_width == 3328)
strcpy(imgdata.idata.model, "sd Quattro H");
else
strcpy(imgdata.idata.model, "dp2 Quattro");
}
// else
}
// Try to get thumbnail data
LibRaw_thumbnail_formats format = LIBRAW_THUMBNAIL_UNKNOWN;
if ((DE = x3f_get_thumb_jpeg(x3f)))
{
format = LIBRAW_THUMBNAIL_JPEG;
}
else if ((DE = x3f_get_thumb_plain(x3f)))
{
format = LIBRAW_THUMBNAIL_BITMAP;
}
if (DE)
{
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
imgdata.thumbnail.twidth = ID->columns;
imgdata.thumbnail.theight = ID->rows;
imgdata.thumbnail.tcolors = 3;
imgdata.thumbnail.tformat = format;
libraw_internal_data.internal_data.toffset = DE->input.offset;
write_thumb = &LibRaw::x3f_thumb_loader;
}
}
INT64 LibRaw::x3f_thumb_size()
{
try
{
x3f_t *x3f = (x3f_t *)_x3f_data;
if (!x3f)
return -1; // No data pointer set
x3f_directory_entry_t *DE = x3f_get_thumb_jpeg(x3f);
if (!DE)
DE = x3f_get_thumb_plain(x3f);
if (!DE)
return -1;
int64_t p = x3f_load_data_size(x3f, DE);
if (p < 0 || p > 0xffffffff)
return -1;
return p;
}
catch (...)
{
return -1;
}
}
void LibRaw::x3f_thumb_loader()
{
try
{
x3f_t *x3f = (x3f_t *)_x3f_data;
if (!x3f)
return; // No data pointer set
x3f_directory_entry_t *DE = x3f_get_thumb_jpeg(x3f);
if (!DE)
DE = x3f_get_thumb_plain(x3f);
if (!DE)
return;
if (X3F_OK != x3f_load_data(x3f, DE))
throw LIBRAW_EXCEPTION_IO_CORRUPT;
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
imgdata.thumbnail.twidth = ID->columns;
imgdata.thumbnail.theight = ID->rows;
imgdata.thumbnail.tcolors = 3;
if (imgdata.thumbnail.tformat == LIBRAW_THUMBNAIL_JPEG)
{
imgdata.thumbnail.thumb = (char *)malloc(ID->data_size);
merror(imgdata.thumbnail.thumb, "LibRaw::x3f_thumb_loader()");
memmove(imgdata.thumbnail.thumb, ID->data, ID->data_size);
imgdata.thumbnail.tlength = ID->data_size;
}
else if (imgdata.thumbnail.tformat == LIBRAW_THUMBNAIL_BITMAP)
{
imgdata.thumbnail.tlength = ID->columns * ID->rows * 3;
imgdata.thumbnail.thumb = (char *)malloc(ID->columns * ID->rows * 3);
merror(imgdata.thumbnail.thumb, "LibRaw::x3f_thumb_loader()");
char *src0 = (char *)ID->data;
for (int row = 0; row < ID->rows; row++)
{
int offset = row * ID->row_stride;
if (offset + ID->columns * 3 > ID->data_size)
break;
char *dest = &imgdata.thumbnail.thumb[row * ID->columns * 3];
char *src = &src0[offset];
memmove(dest, src, ID->columns * 3);
}
}
}
catch (...)
{
// do nothing
}
}
static inline uint32_t _clampbits(int x, uint32_t n)
{
uint32_t _y_temp;
if ((_y_temp = x >> n))
x = ~_y_temp >> (32 - n);
return x;
}
void LibRaw::x3f_dpq_interpolate_rg()
{
int w = imgdata.sizes.raw_width / 2;
int h = imgdata.sizes.raw_height / 2;
unsigned short *image = (ushort *)imgdata.rawdata.color3_image;
for (int color = 0; color < 2; color++)
{
for (int y = 2; y < (h - 2); y++)
{
uint16_t *row0 = &image[imgdata.sizes.raw_width * 3 * (y * 2) + color]; // dst[1]
uint16_t row0_3 = row0[3];
uint16_t *row1 = &image[imgdata.sizes.raw_width * 3 * (y * 2 + 1) + color]; // dst1[1]
uint16_t row1_3 = row1[3];
for (int x = 2; x < (w - 2); x++)
{
row1[0] = row1[3] = row0[3] = row0[0];
row0 += 6;
row1 += 6;
}
}
}
}
#define _ABS(a) ((a) < 0 ? -(a) : (a))
#undef CLIP
#define CLIP(value, high) ((value) > (high) ? (high) : (value))
void LibRaw::x3f_dpq_interpolate_af(int xstep, int ystep, int scale)
{
unsigned short *image = (ushort *)imgdata.rawdata.color3_image;
unsigned int rowpitch = imgdata.rawdata.sizes.raw_pitch / 2; // in 16-bit words
// Interpolate single pixel
for (int y = 0; y < imgdata.rawdata.sizes.height + imgdata.rawdata.sizes.top_margin; y += ystep)
{
if (y < imgdata.rawdata.sizes.top_margin)
continue;
if (y < scale)
continue;
if (y > imgdata.rawdata.sizes.raw_height - scale)
break;
uint16_t *row0 = &image[imgdata.sizes.raw_width * 3 * y]; // Наша строка
uint16_t *row_minus = &image[imgdata.sizes.raw_width * 3 * (y - scale)]; // Строка выше
uint16_t *row_plus = &image[imgdata.sizes.raw_width * 3 * (y + scale)]; // Строка ниже
for (int x = 0; x < imgdata.rawdata.sizes.width + imgdata.rawdata.sizes.left_margin; x += xstep)
{
if (x < imgdata.rawdata.sizes.left_margin)
continue;
if (x < scale)
continue;
if (x > imgdata.rawdata.sizes.raw_width - scale)
break;
uint16_t *pixel0 = &row0[x * 3];
uint16_t *pixel_top = &row_minus[x * 3];
uint16_t *pixel_bottom = &row_plus[x * 3];
uint16_t *pixel_left = &row0[(x - scale) * 3];
uint16_t *pixel_right = &row0[(x + scale) * 3];
uint16_t *pixf = pixel_top;
if (_ABS(pixf[2] - pixel0[2]) > _ABS(pixel_bottom[2] - pixel0[2]))
pixf = pixel_bottom;
if (_ABS(pixf[2] - pixel0[2]) > _ABS(pixel_left[2] - pixel0[2]))
pixf = pixel_left;
if (_ABS(pixf[2] - pixel0[2]) > _ABS(pixel_right[2] - pixel0[2]))
pixf = pixel_right;
int blocal = pixel0[2], bnear = pixf[2];
if (blocal < imgdata.color.black + 16 || bnear < imgdata.color.black + 16)
{
if (pixel0[0] < imgdata.color.black)
pixel0[0] = imgdata.color.black;
if (pixel0[1] < imgdata.color.black)
pixel0[1] = imgdata.color.black;
pixel0[0] = CLIP((pixel0[0] - imgdata.color.black) * 4 + imgdata.color.black, 16383);
pixel0[1] = CLIP((pixel0[1] - imgdata.color.black) * 4 + imgdata.color.black, 16383);
}
else
{
float multip = float(bnear - imgdata.color.black) / float(blocal - imgdata.color.black);
if (pixel0[0] < imgdata.color.black)
pixel0[0] = imgdata.color.black;
if (pixel0[1] < imgdata.color.black)
pixel0[1] = imgdata.color.black;
float pixf0 = pixf[0];
if (pixf0 < imgdata.color.black)
pixf0 = imgdata.color.black;
float pixf1 = pixf[1];
if (pixf1 < imgdata.color.black)
pixf1 = imgdata.color.black;
pixel0[0] = CLIP(((float(pixf0 - imgdata.color.black) * multip + imgdata.color.black) +
((pixel0[0] - imgdata.color.black) * 3.75 + imgdata.color.black)) /
2,
16383);
pixel0[1] = CLIP(((float(pixf1 - imgdata.color.black) * multip + imgdata.color.black) +
((pixel0[1] - imgdata.color.black) * 3.75 + imgdata.color.black)) /
2,
16383);
// pixel0[1] = float(pixf[1]-imgdata.color.black)*multip + imgdata.color.black;
}
}
}
}
void LibRaw::x3f_dpq_interpolate_af_sd(int xstart, int ystart, int xend, int yend, int xstep, int ystep, int scale)
{
unsigned short *image = (ushort *)imgdata.rawdata.color3_image;
unsigned int rowpitch = imgdata.rawdata.sizes.raw_pitch / 2; // in 16-bit words
// Interpolate single pixel
for (int y = ystart; y < yend && y < imgdata.rawdata.sizes.height + imgdata.rawdata.sizes.top_margin; y += ystep)
{
uint16_t *row0 = &image[imgdata.sizes.raw_width * 3 * y]; // Наша строка
uint16_t *row1 = &image[imgdata.sizes.raw_width * 3 * (y + 1)]; // Следующая строка
uint16_t *row_minus = &image[imgdata.sizes.raw_width * 3 * (y - scale)]; // Строка выше
uint16_t *row_plus =
&image[imgdata.sizes.raw_width * 3 * (y + scale)]; // Строка ниже AF-point (scale=2 -> ниже row1
uint16_t *row_minus1 = &image[imgdata.sizes.raw_width * 3 * (y - 1)];
for (int x = xstart; x < xend && x < imgdata.rawdata.sizes.width + imgdata.rawdata.sizes.left_margin; x += xstep)
{
uint16_t *pixel00 = &row0[x * 3]; // Current pixel
float sumR = 0.f, sumG = 0.f;
float cnt = 0.f;
for (int xx = -scale; xx <= scale; xx += scale)
{
sumR += row_minus[(x + xx) * 3];
sumR += row_plus[(x + xx) * 3];
sumG += row_minus[(x + xx) * 3 + 1];
sumG += row_plus[(x + xx) * 3 + 1];
cnt += 1.f;
if (xx)
{
cnt += 1.f;
sumR += row0[(x + xx) * 3];
sumG += row0[(x + xx) * 3 + 1];
}
}
pixel00[0] = sumR / 8.f;
pixel00[1] = sumG / 8.f;
if (scale == 2)
{
uint16_t *pixel0B = &row0[x * 3 + 3]; // right pixel
uint16_t *pixel1B = &row1[x * 3 + 3]; // right pixel
float sumG0 = 0, sumG1 = 0.f;
float cnt = 0.f;
for (int xx = -scale; xx <= scale; xx += scale)
{
sumG0 += row_minus1[(x + xx) * 3 + 2];
sumG1 += row_plus[(x + xx) * 3 + 2];
cnt += 1.f;
if (xx)
{
sumG0 += row0[(x + xx) * 3 + 2];
sumG1 += row1[(x + xx) * 3 + 2];
cnt += 1.f;
}
}
pixel0B[2] = sumG0 / cnt;
pixel1B[2] = sumG1 / cnt;
}
// uint16_t* pixel10 = &row1[x*3]; // Pixel below current
// uint16_t* pixel_bottom = &row_plus[x*3];
}
}
}
void LibRaw::x3f_load_raw()
{
// already in try/catch
int raise_error = 0;
x3f_t *x3f = (x3f_t *)_x3f_data;
if (!x3f)
return; // No data pointer set
if (X3F_OK == x3f_load_data(x3f, x3f_get_raw(x3f)))
{
x3f_directory_entry_t *DE = x3f_get_raw(x3f);
x3f_directory_entry_header_t *DEH = &DE->header;
x3f_image_data_t *ID = &DEH->data_subsection.image_data;
if (!ID)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
x3f_quattro_t *Q = ID->quattro;
x3f_huffman_t *HUF = ID->huffman;
x3f_true_t *TRU = ID->tru;
uint16_t *data = NULL;
if (ID->rows != S.raw_height || ID->columns != S.raw_width)
{
raise_error = 1;
goto end;
}
if (HUF != NULL)
data = HUF->x3rgb16.data;
if (TRU != NULL)
data = TRU->x3rgb16.data;
if (data == NULL)
{
raise_error = 1;
goto end;
}
size_t datasize = S.raw_height * S.raw_width * 3 * sizeof(unsigned short);
S.raw_pitch = S.raw_width * 3 * sizeof(unsigned short);
if (!(imgdata.rawdata.raw_alloc = malloc(datasize)))
throw LIBRAW_EXCEPTION_ALLOC;
imgdata.rawdata.color3_image = (ushort(*)[3])imgdata.rawdata.raw_alloc;
if (HUF)
memmove(imgdata.rawdata.raw_alloc, data, datasize);
else if (TRU && (!Q || !Q->quattro_layout))
memmove(imgdata.rawdata.raw_alloc, data, datasize);
else if (TRU && Q)
{
// Move quattro data in place
// R/B plane
for (int prow = 0; prow < TRU->x3rgb16.rows && prow < S.raw_height / 2; prow++)
{
ushort(*destrow)[3] =
(unsigned short(*)[3]) & imgdata.rawdata.color3_image[prow * 2 * S.raw_pitch / 3 / sizeof(ushort)][0];
ushort(*srcrow)[3] = (unsigned short(*)[3]) & data[prow * TRU->x3rgb16.row_stride];
for (int pcol = 0; pcol < TRU->x3rgb16.columns && pcol < S.raw_width / 2; pcol++)
{
destrow[pcol * 2][0] = srcrow[pcol][0];
destrow[pcol * 2][1] = srcrow[pcol][1];
}
}
for (int row = 0; row < Q->top16.rows && row < S.raw_height; row++)
{
ushort(*destrow)[3] =
(unsigned short(*)[3]) & imgdata.rawdata.color3_image[row * S.raw_pitch / 3 / sizeof(ushort)][0];
ushort(*srcrow) = (unsigned short *)&Q->top16.data[row * Q->top16.columns];
for (int col = 0; col < Q->top16.columns && col < S.raw_width; col++)
destrow[col][2] = srcrow[col];
}
}
#if 1
if (TRU && Q && (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_DP2Q_INTERPOLATEAF))
{
if (imgdata.sizes.raw_width == 5888 && imgdata.sizes.raw_height == 3672) // dpN Quattro normal
{
x3f_dpq_interpolate_af(32, 8, 2);
}
else if (imgdata.sizes.raw_width == 5888 && imgdata.sizes.raw_height == 3776) // sd Quattro normal raw
{
x3f_dpq_interpolate_af_sd(216, 464, imgdata.sizes.raw_width - 1, 3312, 16, 32, 2);
}
else if (imgdata.sizes.raw_width == 6656 && imgdata.sizes.raw_height == 4480) // sd Quattro H normal raw
{
x3f_dpq_interpolate_af_sd(232, 592, imgdata.sizes.raw_width - 1, 3920, 16, 32, 2);
}
else if (imgdata.sizes.raw_width == 3328 && imgdata.sizes.raw_height == 2240) // sd Quattro H half size
{
x3f_dpq_interpolate_af_sd(116, 296, imgdata.sizes.raw_width - 1, 2200, 8, 16, 1);
}
else if (imgdata.sizes.raw_width == 5504 && imgdata.sizes.raw_height == 3680) // sd Quattro H APS-C raw
{
x3f_dpq_interpolate_af_sd(8, 192, imgdata.sizes.raw_width - 1, 3185, 16, 32, 2);
}
else if (imgdata.sizes.raw_width == 2752 && imgdata.sizes.raw_height == 1840) // sd Quattro H APS-C half size
{
x3f_dpq_interpolate_af_sd(4, 96, imgdata.sizes.raw_width - 1, 1800, 8, 16, 1);
}
else if (imgdata.sizes.raw_width == 2944 && imgdata.sizes.raw_height == 1836) // dpN Quattro small raw
{
x3f_dpq_interpolate_af(16, 4, 1);
}
else if (imgdata.sizes.raw_width == 2944 && imgdata.sizes.raw_height == 1888) // sd Quattro small
{
x3f_dpq_interpolate_af_sd(108, 232, imgdata.sizes.raw_width - 1, 1656, 8, 16, 1);
}
}
#endif
if (TRU && Q && Q->quattro_layout && (imgdata.params.raw_processing_options & LIBRAW_PROCESSING_DP2Q_INTERPOLATERG))
x3f_dpq_interpolate_rg();
}
else
raise_error = 1;
end:
if (raise_error)
throw LIBRAW_EXCEPTION_IO_CORRUPT;
}
diff --git a/core/libs/rawengine/libraw/src/libraw_fuji_compressed.cpp b/core/libs/rawengine/libraw/src/libraw_fuji_compressed.cpp
index 108a8b6960..5564acb0da 100644
--- a/core/libs/rawengine/libraw/src/libraw_fuji_compressed.cpp
+++ b/core/libs/rawengine/libraw/src/libraw_fuji_compressed.cpp
@@ -1,1051 +1,1051 @@
/* -*- C++ -*-
* File: libraw_fuji_compressed.cpp
* Copyright (C) 2016 Alexey Danilchenko
*
* Adopted to LibRaw by Alex Tutubalin, lexa@lexa.ru
* LibRaw Fujifilm/compressed decoder
LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:
1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
(See file LICENSE.LGPL provided in LibRaw distribution archive for details).
2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
(See file LICENSE.CDDL provided in LibRaw distribution archive for details).
*/
// This file is #included in libraw_cxx
#ifdef _abs
#undef _abs
#undef _min
#undef _max
#endif
#define _abs(x) (((int)(x) ^ ((int)(x) >> 31)) - ((int)(x) >> 31))
#define _min(a, b) ((a) < (b) ? (a) : (b))
#define _max(a, b) ((a) > (b) ? (a) : (b))
struct int_pair
{
int value1;
int value2;
};
enum _xt_lines
{
_R0 = 0,
_R1,
_R2,
_R3,
_R4,
_G0,
_G1,
_G2,
_G3,
_G4,
_G5,
_G6,
_G7,
_B0,
_B1,
_B2,
_B3,
_B4,
_ltotal
};
struct fuji_compressed_block
{
int cur_bit; // current bit being read (from left to right)
int cur_pos; // current position in a buffer
INT64 cur_buf_offset; // offset of this buffer in a file
unsigned max_read_size; // Amount of data to be read
int cur_buf_size; // buffer size
uchar *cur_buf; // currently read block
int fillbytes; // Counter to add extra byte for block size N*16
LibRaw_abstract_datastream *input;
struct int_pair grad_even[3][41]; // tables of gradients
struct int_pair grad_odd[3][41];
ushort *linealloc;
ushort *linebuf[_ltotal];
};
static unsigned sgetn(int n, uchar *s)
{
unsigned result = 0;
while (n-- > 0)
result = (result << 8) | (*s++);
return result;
}
void LibRaw::init_fuji_compr(struct fuji_compressed_params *info)
{
int cur_val, i;
int8_t *qt;
if ((libraw_internal_data.unpacker_data.fuji_block_width % 3 &&
libraw_internal_data.unpacker_data.fuji_raw_type == 16) ||
(libraw_internal_data.unpacker_data.fuji_block_width & 1 &&
libraw_internal_data.unpacker_data.fuji_raw_type == 0))
derror();
info->q_table = (int8_t *)malloc(32768);
merror(info->q_table, "init_fuji_compr()");
if (libraw_internal_data.unpacker_data.fuji_raw_type == 16)
info->line_width = (libraw_internal_data.unpacker_data.fuji_block_width * 2) / 3;
else
info->line_width = libraw_internal_data.unpacker_data.fuji_block_width >> 1;
info->q_point[0] = 0;
info->q_point[1] = 0x12;
info->q_point[2] = 0x43;
info->q_point[3] = 0x114;
info->q_point[4] = (1 << libraw_internal_data.unpacker_data.fuji_bits) - 1;
info->min_value = 0x40;
cur_val = -info->q_point[4];
for (qt = info->q_table; cur_val <= info->q_point[4]; ++qt, ++cur_val)
{
if (cur_val <= -info->q_point[3])
*qt = -4;
else if (cur_val <= -info->q_point[2])
*qt = -3;
else if (cur_val <= -info->q_point[1])
*qt = -2;
else if (cur_val < 0)
*qt = -1;
else if (cur_val == 0)
*qt = 0;
else if (cur_val < info->q_point[1])
*qt = 1;
else if (cur_val < info->q_point[2])
*qt = 2;
else if (cur_val < info->q_point[3])
*qt = 3;
else
*qt = 4;
}
- // populting gradients
+ // populating gradients
if (info->q_point[4] == 0x3FFF)
{
info->total_values = 0x4000;
info->raw_bits = 14;
info->max_bits = 56;
info->maxDiff = 256;
}
else if (info->q_point[4] == 0xFFF)
{
info->total_values = 4096;
info->raw_bits = 12;
info->max_bits = 48;
info->maxDiff = 64;
}
else
derror();
}
#define XTRANS_BUF_SIZE 0x10000
static inline void fuji_fill_buffer(struct fuji_compressed_block *info)
{
if (info->cur_pos >= info->cur_buf_size)
{
info->cur_pos = 0;
info->cur_buf_offset += info->cur_buf_size;
#ifdef LIBRAW_USE_OPENMP
#pragma omp critical
#endif
{
#ifndef LIBRAW_USE_OPENMP
info->input->lock();
#endif
info->input->seek(info->cur_buf_offset, SEEK_SET);
info->cur_buf_size = info->input->read(info->cur_buf, 1, _min(info->max_read_size, XTRANS_BUF_SIZE));
#ifndef LIBRAW_USE_OPENMP
info->input->unlock();
#endif
if (info->cur_buf_size < 1) // nothing read
{
if (info->fillbytes > 0)
{
int ls = _max(1, _min(info->fillbytes, XTRANS_BUF_SIZE));
memset(info->cur_buf, 0, ls);
info->fillbytes -= ls;
}
else
throw LIBRAW_EXCEPTION_IO_EOF;
}
info->max_read_size -= info->cur_buf_size;
}
}
}
void LibRaw::init_fuji_block(struct fuji_compressed_block *info, const struct fuji_compressed_params *params,
INT64 raw_offset, unsigned dsize)
{
info->linealloc = (ushort *)calloc(sizeof(ushort), _ltotal * (params->line_width + 2));
merror(info->linealloc, "init_fuji_block()");
INT64 fsize = libraw_internal_data.internal_data.input->size();
info->max_read_size = _min(unsigned(fsize - raw_offset), dsize); // Data size may be incorrect?
info->fillbytes = 1;
info->input = libraw_internal_data.internal_data.input;
info->linebuf[_R0] = info->linealloc;
for (int i = _R1; i <= _B4; i++)
info->linebuf[i] = info->linebuf[i - 1] + params->line_width + 2;
// init buffer
info->cur_buf = (uchar *)malloc(XTRANS_BUF_SIZE);
merror(info->cur_buf, "init_fuji_block()");
info->cur_bit = 0;
info->cur_pos = 0;
info->cur_buf_offset = raw_offset;
for (int j = 0; j < 3; j++)
for (int i = 0; i < 41; i++)
{
info->grad_even[j][i].value1 = params->maxDiff;
info->grad_even[j][i].value2 = 1;
info->grad_odd[j][i].value1 = params->maxDiff;
info->grad_odd[j][i].value2 = 1;
}
info->cur_buf_size = 0;
fuji_fill_buffer(info);
}
void LibRaw::copy_line_to_xtrans(struct fuji_compressed_block *info, int cur_line, int cur_block, int cur_block_width)
{
ushort *lineBufB[3];
ushort *lineBufG[6];
ushort *lineBufR[3];
unsigned pixel_count;
ushort *line_buf;
int index;
int offset = libraw_internal_data.unpacker_data.fuji_block_width * cur_block + 6 * imgdata.sizes.raw_width * cur_line;
ushort *raw_block_data = imgdata.rawdata.raw_image + offset;
int row_count = 0;
for (int i = 0; i < 3; i++)
{
lineBufR[i] = info->linebuf[_R2 + i] + 1;
lineBufB[i] = info->linebuf[_B2 + i] + 1;
}
for (int i = 0; i < 6; i++)
lineBufG[i] = info->linebuf[_G2 + i] + 1;
while (row_count < 6)
{
pixel_count = 0;
while (pixel_count < cur_block_width)
{
switch (imgdata.idata.xtrans_abs[row_count][(pixel_count % 6)])
{
case 0: // red
line_buf = lineBufR[row_count >> 1];
break;
case 1: // green
default: // to make static analyzer happy
line_buf = lineBufG[row_count];
break;
case 2: // blue
line_buf = lineBufB[row_count >> 1];
break;
}
index = (((pixel_count * 2 / 3) & 0x7FFFFFFE) | ((pixel_count % 3) & 1)) + ((pixel_count % 3) >> 1);
raw_block_data[pixel_count] = line_buf[index];
++pixel_count;
}
++row_count;
raw_block_data += imgdata.sizes.raw_width;
}
}
void LibRaw::copy_line_to_bayer(struct fuji_compressed_block *info, int cur_line, int cur_block, int cur_block_width)
{
ushort *lineBufB[3];
ushort *lineBufG[6];
ushort *lineBufR[3];
unsigned pixel_count;
ushort *line_buf;
int fuji_bayer[2][2];
for (int r = 0; r < 2; r++)
for (int c = 0; c < 2; c++)
fuji_bayer[r][c] = FC(r, c); // We'll downgrade G2 to G below
int offset = libraw_internal_data.unpacker_data.fuji_block_width * cur_block + 6 * imgdata.sizes.raw_width * cur_line;
ushort *raw_block_data = imgdata.rawdata.raw_image + offset;
int row_count = 0;
for (int i = 0; i < 3; i++)
{
lineBufR[i] = info->linebuf[_R2 + i] + 1;
lineBufB[i] = info->linebuf[_B2 + i] + 1;
}
for (int i = 0; i < 6; i++)
lineBufG[i] = info->linebuf[_G2 + i] + 1;
while (row_count < 6)
{
pixel_count = 0;
while (pixel_count < cur_block_width)
{
switch (fuji_bayer[row_count & 1][pixel_count & 1])
{
case 0: // red
line_buf = lineBufR[row_count >> 1];
break;
case 1: // green
case 3: // second green
default: // to make static analyzer happy
line_buf = lineBufG[row_count];
break;
case 2: // blue
line_buf = lineBufB[row_count >> 1];
break;
}
raw_block_data[pixel_count] = line_buf[pixel_count >> 1];
++pixel_count;
}
++row_count;
raw_block_data += imgdata.sizes.raw_width;
}
}
#define fuji_quant_gradient(i, v1, v2) (9 * i->q_table[i->q_point[4] + (v1)] + i->q_table[i->q_point[4] + (v2)])
static inline void fuji_zerobits(struct fuji_compressed_block *info, int *count)
{
uchar zero = 0;
*count = 0;
while (zero == 0)
{
zero = (info->cur_buf[info->cur_pos] >> (7 - info->cur_bit)) & 1;
info->cur_bit++;
info->cur_bit &= 7;
if (!info->cur_bit)
{
++info->cur_pos;
fuji_fill_buffer(info);
}
if (zero)
break;
++*count;
}
}
static inline void fuji_read_code(struct fuji_compressed_block *info, int *data, int bits_to_read)
{
uchar bits_left = bits_to_read;
uchar bits_left_in_byte = 8 - (info->cur_bit & 7);
*data = 0;
if (!bits_to_read)
return;
if (bits_to_read >= bits_left_in_byte)
{
do
{
*data <<= bits_left_in_byte;
bits_left -= bits_left_in_byte;
*data |= info->cur_buf[info->cur_pos] & ((1 << bits_left_in_byte) - 1);
++info->cur_pos;
fuji_fill_buffer(info);
bits_left_in_byte = 8;
} while (bits_left >= 8);
}
if (!bits_left)
{
info->cur_bit = (8 - (bits_left_in_byte & 7)) & 7;
return;
}
*data <<= bits_left;
bits_left_in_byte -= bits_left;
*data |= ((1 << bits_left) - 1) & ((unsigned)info->cur_buf[info->cur_pos] >> bits_left_in_byte);
info->cur_bit = (8 - (bits_left_in_byte & 7)) & 7;
}
static inline int bitDiff(int value1, int value2)
{
int decBits = 0;
if (value2 < value1)
while (decBits <= 12 && (value2 << ++decBits) < value1)
;
return decBits;
}
static inline int fuji_decode_sample_even(struct fuji_compressed_block *info,
const struct fuji_compressed_params *params, ushort *line_buf, int pos,
struct int_pair *grads)
{
int interp_val = 0;
// ushort decBits;
int errcnt = 0;
int sample = 0, code = 0;
ushort *line_buf_cur = line_buf + pos;
int Rb = line_buf_cur[-2 - params->line_width];
int Rc = line_buf_cur[-3 - params->line_width];
int Rd = line_buf_cur[-1 - params->line_width];
int Rf = line_buf_cur[-4 - 2 * params->line_width];
int grad, gradient, diffRcRb, diffRfRb, diffRdRb;
grad = fuji_quant_gradient(params, Rb - Rf, Rc - Rb);
gradient = _abs(grad);
diffRcRb = _abs(Rc - Rb);
diffRfRb = _abs(Rf - Rb);
diffRdRb = _abs(Rd - Rb);
if (diffRcRb > diffRfRb && diffRcRb > diffRdRb)
interp_val = Rf + Rd + 2 * Rb;
else if (diffRdRb > diffRcRb && diffRdRb > diffRfRb)
interp_val = Rf + Rc + 2 * Rb;
else
interp_val = Rd + Rc + 2 * Rb;
fuji_zerobits(info, &sample);
if (sample < params->max_bits - params->raw_bits - 1)
{
int decBits = bitDiff(grads[gradient].value1, grads[gradient].value2);
fuji_read_code(info, &code, decBits);
code += sample << decBits;
}
else
{
fuji_read_code(info, &code, params->raw_bits);
code++;
}
if (code < 0 || code >= params->total_values)
errcnt++;
if (code & 1)
code = -1 - code / 2;
else
code /= 2;
grads[gradient].value1 += _abs(code);
if (grads[gradient].value2 == params->min_value)
{
grads[gradient].value1 >>= 1;
grads[gradient].value2 >>= 1;
}
grads[gradient].value2++;
if (grad < 0)
interp_val = (interp_val >> 2) - code;
else
interp_val = (interp_val >> 2) + code;
if (interp_val < 0)
interp_val += params->total_values;
else if (interp_val > params->q_point[4])
interp_val -= params->total_values;
if (interp_val >= 0)
line_buf_cur[0] = _min(interp_val, params->q_point[4]);
else
line_buf_cur[0] = 0;
return errcnt;
}
static inline int fuji_decode_sample_odd(struct fuji_compressed_block *info,
const struct fuji_compressed_params *params, ushort *line_buf, int pos,
struct int_pair *grads)
{
int interp_val = 0;
int errcnt = 0;
int sample = 0, code = 0;
ushort *line_buf_cur = line_buf + pos;
int Ra = line_buf_cur[-1];
int Rb = line_buf_cur[-2 - params->line_width];
int Rc = line_buf_cur[-3 - params->line_width];
int Rd = line_buf_cur[-1 - params->line_width];
int Rg = line_buf_cur[1];
int grad, gradient;
grad = fuji_quant_gradient(params, Rb - Rc, Rc - Ra);
gradient = _abs(grad);
if ((Rb > Rc && Rb > Rd) || (Rb < Rc && Rb < Rd))
interp_val = (Rg + Ra + 2 * Rb) >> 2;
else
interp_val = (Ra + Rg) >> 1;
fuji_zerobits(info, &sample);
if (sample < params->max_bits - params->raw_bits - 1)
{
int decBits = bitDiff(grads[gradient].value1, grads[gradient].value2);
fuji_read_code(info, &code, decBits);
code += sample << decBits;
}
else
{
fuji_read_code(info, &code, params->raw_bits);
code++;
}
if (code < 0 || code >= params->total_values)
errcnt++;
if (code & 1)
code = -1 - code / 2;
else
code /= 2;
grads[gradient].value1 += _abs(code);
if (grads[gradient].value2 == params->min_value)
{
grads[gradient].value1 >>= 1;
grads[gradient].value2 >>= 1;
}
grads[gradient].value2++;
if (grad < 0)
interp_val -= code;
else
interp_val += code;
if (interp_val < 0)
interp_val += params->total_values;
else if (interp_val > params->q_point[4])
interp_val -= params->total_values;
if (interp_val >= 0)
line_buf_cur[0] = _min(interp_val, params->q_point[4]);
else
line_buf_cur[0] = 0;
return errcnt;
}
static void fuji_decode_interpolation_even(int line_width, ushort *line_buf, int pos)
{
ushort *line_buf_cur = line_buf + pos;
int Rb = line_buf_cur[-2 - line_width];
int Rc = line_buf_cur[-3 - line_width];
int Rd = line_buf_cur[-1 - line_width];
int Rf = line_buf_cur[-4 - 2 * line_width];
int diffRcRb = _abs(Rc - Rb);
int diffRfRb = _abs(Rf - Rb);
int diffRdRb = _abs(Rd - Rb);
if (diffRcRb > diffRfRb && diffRcRb > diffRdRb)
*line_buf_cur = (Rf + Rd + 2 * Rb) >> 2;
else if (diffRdRb > diffRcRb && diffRdRb > diffRfRb)
*line_buf_cur = (Rf + Rc + 2 * Rb) >> 2;
else
*line_buf_cur = (Rd + Rc + 2 * Rb) >> 2;
}
static void fuji_extend_generic(ushort *linebuf[_ltotal], int line_width, int start, int end)
{
for (int i = start; i <= end; i++)
{
linebuf[i][0] = linebuf[i - 1][1];
linebuf[i][line_width + 1] = linebuf[i - 1][line_width];
}
}
static void fuji_extend_red(ushort *linebuf[_ltotal], int line_width)
{
fuji_extend_generic(linebuf, line_width, _R2, _R4);
}
static void fuji_extend_green(ushort *linebuf[_ltotal], int line_width)
{
fuji_extend_generic(linebuf, line_width, _G2, _G7);
}
static void fuji_extend_blue(ushort *linebuf[_ltotal], int line_width)
{
fuji_extend_generic(linebuf, line_width, _B2, _B4);
}
void LibRaw::xtrans_decode_block(struct fuji_compressed_block *info, const struct fuji_compressed_params *params,
int cur_line)
{
int r_even_pos = 0, r_odd_pos = 1;
int g_even_pos = 0, g_odd_pos = 1;
int b_even_pos = 0, b_odd_pos = 1;
int errcnt = 0;
const int line_width = params->line_width;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
fuji_decode_interpolation_even(line_width, info->linebuf[_R2] + 1, r_even_pos);
r_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G2] + 1, g_even_pos, info->grad_even[0]);
g_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_R2] + 1, r_odd_pos, info->grad_odd[0]);
r_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G2] + 1, g_odd_pos, info->grad_odd[0]);
g_odd_pos += 2;
}
}
fuji_extend_red(info->linebuf, line_width);
fuji_extend_green(info->linebuf, line_width);
g_even_pos = 0, g_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G3] + 1, g_even_pos, info->grad_even[1]);
g_even_pos += 2;
fuji_decode_interpolation_even(line_width, info->linebuf[_B2] + 1, b_even_pos);
b_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G3] + 1, g_odd_pos, info->grad_odd[1]);
g_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_B2] + 1, b_odd_pos, info->grad_odd[1]);
b_odd_pos += 2;
}
}
fuji_extend_green(info->linebuf, line_width);
fuji_extend_blue(info->linebuf, line_width);
r_even_pos = 0, r_odd_pos = 1;
g_even_pos = 0, g_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
if (r_even_pos & 3)
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_R3] + 1, r_even_pos, info->grad_even[2]);
else
fuji_decode_interpolation_even(line_width, info->linebuf[_R3] + 1, r_even_pos);
r_even_pos += 2;
fuji_decode_interpolation_even(line_width, info->linebuf[_G4] + 1, g_even_pos);
g_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_R3] + 1, r_odd_pos, info->grad_odd[2]);
r_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G4] + 1, g_odd_pos, info->grad_odd[2]);
g_odd_pos += 2;
}
}
fuji_extend_red(info->linebuf, line_width);
fuji_extend_green(info->linebuf, line_width);
g_even_pos = 0, g_odd_pos = 1;
b_even_pos = 0, b_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G5] + 1, g_even_pos, info->grad_even[0]);
g_even_pos += 2;
if ((b_even_pos & 3) == 2)
fuji_decode_interpolation_even(line_width, info->linebuf[_B3] + 1, b_even_pos);
else
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_B3] + 1, b_even_pos, info->grad_even[0]);
b_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G5] + 1, g_odd_pos, info->grad_odd[0]);
g_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_B3] + 1, b_odd_pos, info->grad_odd[0]);
b_odd_pos += 2;
}
}
fuji_extend_green(info->linebuf, line_width);
fuji_extend_blue(info->linebuf, line_width);
r_even_pos = 0, r_odd_pos = 1;
g_even_pos = 0, g_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
if ((r_even_pos & 3) == 2)
fuji_decode_interpolation_even(line_width, info->linebuf[_R4] + 1, r_even_pos);
else
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_R4] + 1, r_even_pos, info->grad_even[1]);
r_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G6] + 1, g_even_pos, info->grad_even[1]);
g_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_R4] + 1, r_odd_pos, info->grad_odd[1]);
r_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G6] + 1, g_odd_pos, info->grad_odd[1]);
g_odd_pos += 2;
}
}
fuji_extend_red(info->linebuf, line_width);
fuji_extend_green(info->linebuf, line_width);
g_even_pos = 0, g_odd_pos = 1;
b_even_pos = 0, b_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
fuji_decode_interpolation_even(line_width, info->linebuf[_G7] + 1, g_even_pos);
g_even_pos += 2;
if (b_even_pos & 3)
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_B4] + 1, b_even_pos, info->grad_even[2]);
else
fuji_decode_interpolation_even(line_width, info->linebuf[_B4] + 1, b_even_pos);
b_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G7] + 1, g_odd_pos, info->grad_odd[2]);
g_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_B4] + 1, b_odd_pos, info->grad_odd[2]);
b_odd_pos += 2;
}
}
fuji_extend_green(info->linebuf, line_width);
fuji_extend_blue(info->linebuf, line_width);
if (errcnt)
derror();
}
void LibRaw::fuji_bayer_decode_block(struct fuji_compressed_block *info, const struct fuji_compressed_params *params,
int cur_line)
{
int r_even_pos = 0, r_odd_pos = 1;
int g_even_pos = 0, g_odd_pos = 1;
int b_even_pos = 0, b_odd_pos = 1;
int errcnt = 0;
const int line_width = params->line_width;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_R2] + 1, r_even_pos, info->grad_even[0]);
r_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G2] + 1, g_even_pos, info->grad_even[0]);
g_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_R2] + 1, r_odd_pos, info->grad_odd[0]);
r_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G2] + 1, g_odd_pos, info->grad_odd[0]);
g_odd_pos += 2;
}
}
fuji_extend_red(info->linebuf, line_width);
fuji_extend_green(info->linebuf, line_width);
g_even_pos = 0, g_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G3] + 1, g_even_pos, info->grad_even[1]);
g_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_B2] + 1, b_even_pos, info->grad_even[1]);
b_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G3] + 1, g_odd_pos, info->grad_odd[1]);
g_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_B2] + 1, b_odd_pos, info->grad_odd[1]);
b_odd_pos += 2;
}
}
fuji_extend_green(info->linebuf, line_width);
fuji_extend_blue(info->linebuf, line_width);
r_even_pos = 0, r_odd_pos = 1;
g_even_pos = 0, g_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_R3] + 1, r_even_pos, info->grad_even[2]);
r_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G4] + 1, g_even_pos, info->grad_even[2]);
g_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_R3] + 1, r_odd_pos, info->grad_odd[2]);
r_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G4] + 1, g_odd_pos, info->grad_odd[2]);
g_odd_pos += 2;
}
}
fuji_extend_red(info->linebuf, line_width);
fuji_extend_green(info->linebuf, line_width);
g_even_pos = 0, g_odd_pos = 1;
b_even_pos = 0, b_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G5] + 1, g_even_pos, info->grad_even[0]);
g_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_B3] + 1, b_even_pos, info->grad_even[0]);
b_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G5] + 1, g_odd_pos, info->grad_odd[0]);
g_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_B3] + 1, b_odd_pos, info->grad_odd[0]);
b_odd_pos += 2;
}
}
fuji_extend_green(info->linebuf, line_width);
fuji_extend_blue(info->linebuf, line_width);
r_even_pos = 0, r_odd_pos = 1;
g_even_pos = 0, g_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_R4] + 1, r_even_pos, info->grad_even[1]);
r_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G6] + 1, g_even_pos, info->grad_even[1]);
g_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_R4] + 1, r_odd_pos, info->grad_odd[1]);
r_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G6] + 1, g_odd_pos, info->grad_odd[1]);
g_odd_pos += 2;
}
}
fuji_extend_red(info->linebuf, line_width);
fuji_extend_green(info->linebuf, line_width);
g_even_pos = 0, g_odd_pos = 1;
b_even_pos = 0, b_odd_pos = 1;
while (g_even_pos < line_width || g_odd_pos < line_width)
{
if (g_even_pos < line_width)
{
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_G7] + 1, g_even_pos, info->grad_even[2]);
g_even_pos += 2;
errcnt += fuji_decode_sample_even(info, params, info->linebuf[_B4] + 1, b_even_pos, info->grad_even[2]);
b_even_pos += 2;
}
if (g_even_pos > 8)
{
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_G7] + 1, g_odd_pos, info->grad_odd[2]);
g_odd_pos += 2;
errcnt += fuji_decode_sample_odd(info, params, info->linebuf[_B4] + 1, b_odd_pos, info->grad_odd[2]);
b_odd_pos += 2;
}
}
fuji_extend_green(info->linebuf, line_width);
fuji_extend_blue(info->linebuf, line_width);
if (errcnt)
derror();
}
void LibRaw::fuji_decode_strip(const struct fuji_compressed_params *info_common, int cur_block, INT64 raw_offset,
unsigned dsize)
{
int cur_block_width, cur_line;
unsigned line_size;
struct fuji_compressed_block info;
init_fuji_block(&info, info_common, raw_offset, dsize);
line_size = sizeof(ushort) * (info_common->line_width + 2);
cur_block_width = libraw_internal_data.unpacker_data.fuji_block_width;
if (cur_block + 1 == libraw_internal_data.unpacker_data.fuji_total_blocks)
{
cur_block_width = imgdata.sizes.raw_width - (libraw_internal_data.unpacker_data.fuji_block_width * cur_block);
/* Old code, may get incorrect results on GFX50, but luckily large optical black
cur_block_width = imgdata.sizes.raw_width % libraw_internal_data.unpacker_data.fuji_block_width;
*/
}
struct i_pair
{
int a, b;
};
const i_pair mtable[6] = {{_R0, _R3}, {_R1, _R4}, {_G0, _G6}, {_G1, _G7}, {_B0, _B3}, {_B1, _B4}},
ztable[3] = {{_R2, 3}, {_G2, 6}, {_B2, 3}};
for (cur_line = 0; cur_line < libraw_internal_data.unpacker_data.fuji_total_lines; cur_line++)
{
if (libraw_internal_data.unpacker_data.fuji_raw_type == 16)
xtrans_decode_block(&info, info_common, cur_line);
else
fuji_bayer_decode_block(&info, info_common, cur_line);
// copy data from line buffers and advance
for (int i = 0; i < 6; i++)
memcpy(info.linebuf[mtable[i].a], info.linebuf[mtable[i].b], line_size);
if (libraw_internal_data.unpacker_data.fuji_raw_type == 16)
copy_line_to_xtrans(&info, cur_line, cur_block, cur_block_width);
else
copy_line_to_bayer(&info, cur_line, cur_block, cur_block_width);
for (int i = 0; i < 3; i++)
{
memset(info.linebuf[ztable[i].a], 0, ztable[i].b * line_size);
info.linebuf[ztable[i].a][0] = info.linebuf[ztable[i].a - 1][1];
info.linebuf[ztable[i].a][info_common->line_width + 1] = info.linebuf[ztable[i].a - 1][info_common->line_width];
}
}
// release data
free(info.linealloc);
free(info.cur_buf);
}
void LibRaw::fuji_compressed_load_raw()
{
struct fuji_compressed_params common_info;
int cur_block;
unsigned line_size, *block_sizes;
INT64 raw_offset, *raw_block_offsets;
// struct fuji_compressed_block info;
init_fuji_compr(&common_info);
line_size = sizeof(ushort) * (common_info.line_width + 2);
// read block sizes
block_sizes = (unsigned *)malloc(sizeof(unsigned) * libraw_internal_data.unpacker_data.fuji_total_blocks);
merror(block_sizes, "fuji_compressed_load_raw()");
raw_block_offsets = (INT64 *)malloc(sizeof(INT64) * libraw_internal_data.unpacker_data.fuji_total_blocks);
merror(raw_block_offsets, "fuji_compressed_load_raw()");
raw_offset = sizeof(unsigned) * libraw_internal_data.unpacker_data.fuji_total_blocks;
if (raw_offset & 0xC)
raw_offset += 0x10 - (raw_offset & 0xC);
raw_offset += libraw_internal_data.unpacker_data.data_offset;
libraw_internal_data.internal_data.input->seek(libraw_internal_data.unpacker_data.data_offset, SEEK_SET);
libraw_internal_data.internal_data.input->read(
block_sizes, 1, sizeof(unsigned) * libraw_internal_data.unpacker_data.fuji_total_blocks);
raw_block_offsets[0] = raw_offset;
// calculating raw block offsets
for (cur_block = 0; cur_block < libraw_internal_data.unpacker_data.fuji_total_blocks; cur_block++)
{
unsigned bsize = sgetn(4, (uchar *)(block_sizes + cur_block));
block_sizes[cur_block] = bsize;
}
for (cur_block = 1; cur_block < libraw_internal_data.unpacker_data.fuji_total_blocks; cur_block++)
raw_block_offsets[cur_block] = raw_block_offsets[cur_block - 1] + block_sizes[cur_block - 1];
fuji_decode_loop(&common_info, libraw_internal_data.unpacker_data.fuji_total_blocks, raw_block_offsets, block_sizes);
free(block_sizes);
free(raw_block_offsets);
free(common_info.q_table);
}
void LibRaw::fuji_decode_loop(const struct fuji_compressed_params *common_info, int count, INT64 *raw_block_offsets,
unsigned *block_sizes)
{
int cur_block;
#ifdef LIBRAW_USE_OPENMP
#pragma omp parallel for private(cur_block)
#endif
for (cur_block = 0; cur_block < count; cur_block++)
{
fuji_decode_strip(common_info, cur_block, raw_block_offsets[cur_block], block_sizes[cur_block]);
}
}
void LibRaw::parse_fuji_compressed_header()
{
unsigned signature, version, h_raw_type, h_raw_bits, h_raw_height, h_raw_rounded_width, h_raw_width, h_block_size,
h_blocks_in_row, h_total_lines;
uchar header[16];
libraw_internal_data.internal_data.input->seek(libraw_internal_data.unpacker_data.data_offset, SEEK_SET);
libraw_internal_data.internal_data.input->read(header, 1, sizeof(header));
// read all header
signature = sgetn(2, header);
version = header[2];
h_raw_type = header[3];
h_raw_bits = header[4];
h_raw_height = sgetn(2, header + 5);
h_raw_rounded_width = sgetn(2, header + 7);
h_raw_width = sgetn(2, header + 9);
h_block_size = sgetn(2, header + 11);
h_blocks_in_row = header[13];
h_total_lines = sgetn(2, header + 14);
// general validation
if (signature != 0x4953 || version != 1 || h_raw_height > 0x3000 || h_raw_height < 6 || h_raw_height % 6 ||
h_block_size < 1 || h_raw_width > 0x3000 || h_raw_width < 0x300 || h_raw_width % 24 ||
h_raw_rounded_width > 0x3000 || h_raw_rounded_width < h_block_size || h_raw_rounded_width % h_block_size ||
h_raw_rounded_width - h_raw_width >= h_block_size || h_block_size != 0x300 || h_blocks_in_row > 0x10 ||
h_blocks_in_row == 0 || h_blocks_in_row != h_raw_rounded_width / h_block_size || h_total_lines > 0x800 ||
h_total_lines == 0 || h_total_lines != h_raw_height / 6 || (h_raw_bits != 12 && h_raw_bits != 14) ||
(h_raw_type != 16 && h_raw_type != 0))
return;
// modify data
libraw_internal_data.unpacker_data.fuji_total_lines = h_total_lines;
libraw_internal_data.unpacker_data.fuji_total_blocks = h_blocks_in_row;
libraw_internal_data.unpacker_data.fuji_block_width = h_block_size;
libraw_internal_data.unpacker_data.fuji_bits = h_raw_bits;
libraw_internal_data.unpacker_data.fuji_raw_type = h_raw_type;
imgdata.sizes.raw_width = h_raw_width;
imgdata.sizes.raw_height = h_raw_height;
libraw_internal_data.unpacker_data.data_offset += 16;
load_raw = &LibRaw::fuji_compressed_load_raw;
}
#undef _abs
#undef _min
diff --git a/core/libs/tags/taggingactionfactory.cpp b/core/libs/tags/taggingactionfactory.cpp
index 32b1bec4ab..11a4930f9b 100644
--- a/core/libs/tags/taggingactionfactory.cpp
+++ b/core/libs/tags/taggingactionfactory.cpp
@@ -1,466 +1,466 @@
/* ============================================================
*
* This file is a part of Tumorprofil
*
* Date : 11.09.2015
*
* Copyright (C) 2012 by Marcel Wiesweg <marcel dot wiesweg at uk-essen dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "taggingactionfactory.h"
// Qt includes
#include <QDebug>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "tagscache.h"
#include "coredbaccess.h"
#include "coredb.h"
namespace Digikam
{
class Q_DECL_HIDDEN TaggingActionFactory::Private
{
public:
explicit Private()
: parentTagId(0), // 0 means toplevel tag
nameMatchMode(MatchStartingWithFragment),
constraintInterface(0),
defaultIndex(-1),
valid(false)
{
}
public:
QString fragment;
int parentTagId;
NameMatchMode nameMatchMode;
ConstraintInterface* constraintInterface;
int defaultIndex; // use if valid is true
QList<TaggingAction> actions; // use if valid is true
bool valid;
TaggingAction defaultAction; // independent from valid
public:
void invalidate()
{
valid = false;
actions.clear();
defaultAction = TaggingAction();
}
};
TaggingActionFactory::TaggingActionFactory()
: d(new Private)
{
}
TaggingActionFactory::~TaggingActionFactory()
{
delete d;
}
QString TaggingActionFactory::fragment() const
{
return d->fragment;
}
void TaggingActionFactory::setFragment(const QString& fragment)
{
if (fragment == d->fragment)
{
return;
}
d->fragment = fragment;
d->invalidate();
}
int TaggingActionFactory::parentTagId() const
{
return d->parentTagId;
}
void TaggingActionFactory::setParentTag(int parentTagId)
{
if (parentTagId == d->parentTagId)
{
return;
}
d->parentTagId = parentTagId;
d->invalidate();
}
void TaggingActionFactory::setConstraintInterface(ConstraintInterface* const iface)
{
if (d->constraintInterface == iface)
{
return;
}
d->constraintInterface = iface;
d->invalidate();
}
TaggingActionFactory::ConstraintInterface* TaggingActionFactory::constraintInterface() const
{
return d->constraintInterface;
}
void TaggingActionFactory::setNameMatchMode(NameMatchMode mode)
{
if (d->nameMatchMode == mode)
{
return;
}
d->nameMatchMode = mode;
d->invalidate();
}
TaggingActionFactory::NameMatchMode TaggingActionFactory::nameMatchMode() const
{
return d->nameMatchMode;
}
void TaggingActionFactory::reset()
{
d->fragment.clear();
d->parentTagId = 0;
d->defaultIndex = -1;
d->nameMatchMode = MatchStartingWithFragment;
d->constraintInterface = 0;
d->invalidate();
}
QList<TaggingAction> TaggingActionFactory::actions() const
{
if (d->valid)
{
return d->actions;
}
QList<TaggingAction> actions;
int defaultActionIndex = 0;
// We use this action to find the right entry to select
TaggingAction defaultAction = defaultTaggingAction();
TaggingAction newUnderParent;
if (d->parentTagId)
{
newUnderParent = TaggingAction(d->fragment, d->parentTagId);
}
TaggingAction newToplevel(d->fragment, 0);
QList<int> completionEntries;
if (d->nameMatchMode == MatchStartingWithFragment)
{
completionEntries = TagsCache::instance()->tagsStartingWith(d->fragment);
}
else
{
completionEntries = TagsCache::instance()->tagsContaining(d->fragment);
}
QList<TaggingAction> assignActions;
// order the matched tags by chronological order, based on
// recently assigned tags
QList<int> recentTagIDs = CoreDbAccess().db()->getRecentlyAssignedTags();
QListIterator<int> recent_iter(recentTagIDs);
recent_iter.toBack();
while (recent_iter.hasPrevious())
{
int poz = completionEntries.indexOf(recent_iter.previous());
if (poz > 0)
{
completionEntries.move(poz, 0);
}
}
foreach (int id, completionEntries)
{
if (d->constraintInterface && !d->constraintInterface->matches(id))
{
continue;
}
assignActions << TaggingAction(id);
}
if (defaultAction.shallCreateNewTag())
{
// If it is the default action, we place the "Create Tag" entry at the top of the list.
if (newUnderParent.isValid() && newUnderParent == defaultAction)
{
//Case A
//a tag is currently selected in the listbox, we have the choice of toplevel and underparent for a new tag
//the entire text currently written by the user doesn't exist as a tag. However, it might be a part of a tag
foreach(const TaggingAction& assignAction, assignActions)
{
actions << assignAction;
}
actions << newUnderParent;
actions << newToplevel;
}
else // if (createItemTopLevel && createItemTopLevel->action() == defaultAction)
{
//Case B
//no tag is currently selected in the listbox, only toplevel choice for a new tag
//the entire text currently written by the user doesn't exist as a tag. However, it might be a part of a tag
foreach(const TaggingAction& assignAction, assignActions)
{
actions << assignAction;
}
actions << newToplevel;
}
}
else
{
//Case C
//the entire text currently written by the user exists as a tag
foreach (const TaggingAction& assignAction, assignActions)
{
actions << assignAction;
if (assignAction == defaultAction)
{
defaultActionIndex = actions.size()-1;
}
}
actions << newUnderParent;
actions << newToplevel;
}
Private* const ncd = const_cast<Private*>(d);
ncd->valid = true;
ncd->actions = actions;
ncd->defaultIndex = defaultActionIndex;
return actions;
}
int TaggingActionFactory::indexOfDefaultAction() const
{
if (!d->valid)
{
actions();
}
return d->defaultIndex;
}
/*
AddTagsCompletionBoxItem* AddTagsCompletionBox::Private::createItemForExistingTag(TAlbum* talbum, bool uniqueName)
{
if (!talbum || talbum->isRoot())
{
return 0;
}
AddTagsCompletionBoxItem* const item = new AddTagsCompletionBoxItem;
TAlbum* const parent = static_cast<TAlbum*>(talbum->parent());
if (parent->isRoot() || uniqueName)
{
item->setText(talbum->title());
}
else
{
item->setText(i18nc("<tag name> in <tag path>", "%1 in %2",
talbum->title(), parent->tagPath(false)));
}
if (model || filterModel)
{
QModelIndex index;
if (filterModel)
{
index = filterModel->indexForAlbum(talbum);
}
else if (model)
{
index = model->indexForAlbum(talbum);
}
item->setData(Qt::DecorationRole, index.data(Qt::DecorationRole));
}
item->setData(CompletionTextRole, talbum->title());
item->setAction(TaggingAction(talbum->id()));
return item;
}
AddTagsCompletionBoxItem* AddTagsCompletionBox::Private::createItemForNewTag(const QString& newName, TAlbum* parent)
{
int parentTagId = parent ? parent->id() : 0;
// If tag exists, do not add an entry to create it
if (TagsCache::instance()->tagForName(newName, parentTagId))
{
return 0;
}
AddTagsCompletionBoxItem* const item = new AddTagsCompletionBoxItem;
item->setData(Qt::DecorationRole, AlbumThumbnailLoader::instance()->getNewTagIcon());
item->setData(CompletionTextRole, newName);
item->setAction(TaggingAction(newName, parentTagId));
return item;
}
*/
QString TaggingActionFactory::suggestedUIString(const TaggingAction& action) const
{
if (!action.isValid())
{
return QString();
}
if (action.shallAssignTag())
{
QString tagName = TagsCache::instance()->tagName(action.tagId());
int tagsParentTagId = TagsCache::instance()->parentTag(action.tagId());
// check if it is a toplevel tag or there is only one tag with this name - then simply the tag name is sufficient
if (tagsParentTagId == 0 || TagsCache::instance()->tagsForName(tagName).size() == 1)
{
return tagName;
}
else
{
return i18nc("<tag name> in <tag path>", "%1 in %2",
tagName, TagsCache::instance()->tagPath(tagsParentTagId, TagsCache::NoLeadingSlash));
}
}
else // shallCreateNewTag
{
if (action.parentTagId())
{
return i18nc("Create New Tag <tag name> in <parent tag path>", "Create \"%1\" in %2",
action.newTagName(), TagsCache::instance()->tagPath(action.parentTagId(), TagsCache::NoLeadingSlash));
}
else
{
return i18n("Create \"%1\"", action.newTagName());
}
}
}
TaggingAction TaggingActionFactory::defaultTaggingAction() const
{
if (d->defaultAction.isValid())
{
return d->defaultAction;
}
if (d->fragment.isEmpty())
{
return TaggingAction();
}
Private* const ncd = const_cast<Private*>(d);
ncd->defaultAction = defaultTaggingAction(d->fragment, d->parentTagId);
return d->defaultAction;
}
TaggingAction TaggingActionFactory::defaultTaggingAction(const QString& tagName, int parentTagId)
{
- // We now take the presumedly best action, without autocompletion popup.
+ // We now take the presumably best action, without autocompletion popup.
// 1. Tag exists?
// a) Single tag? Assign.
// b) Multiple tags? 1. Existing tag under parent. 2. Toplevel tag 3. Alphabetically lowest tag
// 2. Create tag under parent. No parent selected? Toplevel
if (tagName.isEmpty())
{
return TaggingAction();
}
QList<int> tagIds = TagsCache::instance()->tagsForName(tagName);
if (!tagIds.isEmpty())
{
if (tagIds.count() == 1)
{
return TaggingAction(tagIds.first());
}
else
{
int tagId = 0;
if (parentTagId)
{
tagId = TagsCache::instance()->tagForName(tagName, parentTagId);
}
if (!tagId)
{
tagId = TagsCache::instance()->tagForName(tagName); // toplevel tag
}
if (!tagId)
{
// sort lexically
QMap<QString, int> map;
foreach (int id, tagIds)
{
map[TagsCache::instance()->tagPath(id, TagsCache::NoLeadingSlash)] = id;
}
tagId = map.begin().value();
}
return TaggingAction(tagId);
}
}
else
{
return TaggingAction(tagName, parentTagId);
}
}
} // namespace Digikam
diff --git a/core/libs/tags/taggingactionfactory.h b/core/libs/tags/taggingactionfactory.h
index 016b4e69d8..cfef940d06 100644
--- a/core/libs/tags/taggingactionfactory.h
+++ b/core/libs/tags/taggingactionfactory.h
@@ -1,110 +1,110 @@
/* ============================================================
*
* This file is a part of Tumorprofil
*
* Date : 11.09.2015
*
* Copyright (C) 2012 by Marcel Wiesweg <marcel dot wiesweg at uk-essen dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_TAGGING_ACTION_FACTORY_H
#define DIGIKAM_TAGGING_ACTION_FACTORY_H
// Qt includes
#include <QList>
// Local includes
#include "taggingaction.h"
namespace Digikam
{
class TaggingActionFactory
{
public:
class ConstraintInterface
{
public:
virtual ~ConstraintInterface()
{
}
virtual bool matches(int tagId) = 0;
};
public:
enum NameMatchMode
{
// Default: use the "startingWith" method
MatchStartingWithFragment,
// use the "contains" method
MatchContainingFragment
};
public:
explicit TaggingActionFactory();
virtual ~TaggingActionFactory();
// Set a fragment of a tag name to generate possible tags, as known from completers
void setFragment(const QString& fragment);
QString fragment() const;
// Set a tag which may by the user be intended to be the parent of a newly created tag
void setParentTag(int parentTagId);
int parentTagId() const;
// Allows to filter the scope of suggested tags. Pass an implementation of ConstraintInterface (reamins in your ownership).
// actions() will then only suggest to assign tags for which matches() is true
void setConstraintInterface(ConstraintInterface* const iface);
ConstraintInterface* constraintInterface() const;
// Set the matching mode for the tag name
void setNameMatchMode(NameMatchMode mode);
NameMatchMode nameMatchMode() const;
// reset all settings to the default (no fragment, no actions)
void reset();
// Returns the sorted list of suggested tagging actions, based on the above settings
QList<TaggingAction> actions() const;
- // Returns one single action, which is decided to be the presumedly best action based on the settings.
+ // Returns one single action, which is decided to be the presumably best action based on the settings.
TaggingAction defaultTaggingAction() const;
// Returns the index of the default action in the list returned by generate()
int indexOfDefaultAction() const;
// Returns a string to be used in the UI for the given TaggingAction, interpreted in the context of the current settings
QString suggestedUIString(const TaggingAction& action) const;
static TaggingAction defaultTaggingAction(const QString& tagName, int parentTagId = 0);
private:
TaggingActionFactory(const TaggingActionFactory&); // Disable
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_TAGGING_ACTION_FACTORY_H
diff --git a/core/libs/tags/tagsactionmngr.cpp b/core/libs/tags/tagsactionmngr.cpp
index dcaf8caeff..2a2fbb5f42 100644
--- a/core/libs/tags/tagsactionmngr.cpp
+++ b/core/libs/tags/tagsactionmngr.cpp
@@ -1,537 +1,537 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-01-24
* Description : Tags Action Manager
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "tagsactionmngr.h"
// Qt includes
#include <QList>
#include <QShortcut>
#include <QIcon>
#include <QKeySequence>
#include <QApplication>
#include <QAction>
// KDE includes
#include <klocalizedstring.h>
#include <kactioncollection.h>
// Local includes
#include "digikam_debug.h"
#include "album.h"
#include "coredb.h"
#include "albummanager.h"
#include "coredbaccess.h"
#include "coredbconstants.h"
#include "coredbwatch.h"
#include "coredbinfocontainers.h"
#include "digikamapp.h"
#include "dxmlguiwindow.h"
#include "digikamview.h"
#include "imagewindow.h"
#include "lighttablewindow.h"
#include "picklabelwidget.h"
#include "colorlabelwidget.h"
#include "tagscache.h"
#include "tagproperties.h"
#include "ratingwidget.h"
#include "slideshow.h"
#include "syncjob.h"
namespace Digikam
{
TagsActionMngr* TagsActionMngr::m_defaultManager = 0;
TagsActionMngr* TagsActionMngr::defaultManager()
{
return m_defaultManager;
}
class Q_DECL_HIDDEN TagsActionMngr::Private
{
public:
explicit Private()
: ratingShortcutPrefix(QLatin1String("rateshortcut")),
tagShortcutPrefix(QLatin1String("tagshortcut")),
pickShortcutPrefix(QLatin1String("pickshortcut")),
colorShortcutPrefix(QLatin1String("colorshortcut"))
{
}
QMultiMap<int, QAction*> tagsActionMap;
QList<KActionCollection*> actionCollectionList;
const QString ratingShortcutPrefix;
const QString tagShortcutPrefix;
const QString pickShortcutPrefix;
const QString colorShortcutPrefix;
};
// -------------------------------------------------------------------------------------------------
TagsActionMngr::TagsActionMngr(QWidget* const parent)
: QObject(parent),
d(new Private)
{
if (!m_defaultManager)
{
m_defaultManager = this;
}
connect(AlbumManager::instance(), SIGNAL(signalAlbumDeleted(Album*)),
this, SLOT(slotAlbumDeleted(Album*)));
connect(CoreDbAccess::databaseWatch(), SIGNAL(imageTagChange(ImageTagChangeset)),
this, SLOT(slotImageTagChanged(ImageTagChangeset)));
}
TagsActionMngr::~TagsActionMngr()
{
delete d;
if (m_defaultManager == this)
{
m_defaultManager = 0;
}
}
QString TagsActionMngr::ratingShortcutPrefix() const
{
return d->ratingShortcutPrefix;
}
QString TagsActionMngr::tagShortcutPrefix() const
{
return d->tagShortcutPrefix;
}
QString TagsActionMngr::pickShortcutPrefix() const
{
return d->pickShortcutPrefix;
}
QString TagsActionMngr::colorShortcutPrefix() const
{
return d->colorShortcutPrefix;
}
void TagsActionMngr::registerTagsActionCollections()
{
d->actionCollectionList.append(DigikamApp::instance()->actionCollection());
d->actionCollectionList.append(ImageWindow::imageWindow()->actionCollection());
d->actionCollectionList.append(LightTableWindow::lightTableWindow()->actionCollection());
// Create Tags shortcuts.
QList<int> tagIds = TagsCache::instance()->tagsWithProperty(TagPropertyName::tagKeyboardShortcut());
foreach(int tagId, tagIds)
{
createTagActionShortcut(tagId);
}
}
QList<KActionCollection*> TagsActionMngr::actionCollections() const
{
return d->actionCollectionList;
}
void TagsActionMngr::registerLabelsActions(KActionCollection* const ac)
{
// Create Rating shortcuts.
for (int i = RatingMin ; i <= RatingMax ; ++i)
{
createRatingActionShortcut(ac, i);
}
// Create Color Label shortcuts.
for (int i = NoColorLabel ; i <= WhiteLabel ; ++i)
{
createColorLabelActionShortcut(ac, i);
}
// Create Pick Label shortcuts.
for (int i = NoPickLabel ; i <= AcceptedLabel ; ++i)
{
createPickLabelActionShortcut(ac, i);
}
}
void TagsActionMngr::registerActionsToWidget(QWidget* const wdg)
{
DXmlGuiWindow* const win = dynamic_cast<DXmlGuiWindow*>(qApp->activeWindow());
if (win)
{
foreach(QAction* const ac, win->actionCollection()->actions())
{
if (ac->objectName().startsWith(d->ratingShortcutPrefix) ||
ac->objectName().startsWith(d->tagShortcutPrefix) ||
ac->objectName().startsWith(d->pickShortcutPrefix) ||
ac->objectName().startsWith(d->colorShortcutPrefix))
{
wdg->addAction(ac);
}
}
}
}
bool TagsActionMngr::createRatingActionShortcut(KActionCollection* const ac, int rating)
{
if (ac)
{
QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->ratingShortcutPrefix).arg(rating));
action->setText(i18n("Assign Rating \"%1 Star\"", rating));
ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("CTRL+%1").arg(rating)));
action->setIcon(RatingWidget::buildIcon(rating, 32));
action->setData(rating);
connect(action, SIGNAL(triggered()),
this, SLOT(slotAssignFromShortcut()));
return true;
}
return false;
}
bool TagsActionMngr::createPickLabelActionShortcut(KActionCollection* const ac, int pickId)
{
if (ac)
{
QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->pickShortcutPrefix).arg(pickId));
action->setText(i18n("Assign Pick Label \"%1\"", PickLabelWidget::labelPickName((PickLabel)pickId)));
ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("ALT+%1").arg(pickId)));
action->setIcon(PickLabelWidget::buildIcon((PickLabel)pickId));
action->setData(pickId);
connect(action, SIGNAL(triggered()),
this, SLOT(slotAssignFromShortcut()));
return true;
}
return false;
}
bool TagsActionMngr::createColorLabelActionShortcut(KActionCollection* const ac, int colorId)
{
if (ac)
{
QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->colorShortcutPrefix).arg(colorId));
action->setText(i18n("Assign Color Label \"%1\"", ColorLabelWidget::labelColorName((ColorLabel)colorId)));
ac->setDefaultShortcut(action, QKeySequence(QString::fromUtf8("ALT+CTRL+%1").arg(colorId)));
action->setIcon(ColorLabelWidget::buildIcon((ColorLabel)colorId, 32));
action->setData(colorId);
connect(action, SIGNAL(triggered()),
this, SLOT(slotAssignFromShortcut()));
return true;
}
return false;
}
bool TagsActionMngr::createTagActionShortcut(int tagId)
{
if (!tagId)
{
return false;
}
TAlbum* const talbum = AlbumManager::instance()->findTAlbum(tagId);
if (!talbum)
{
return false;
}
QString value = TagsCache::instance()->propertyValue(tagId, TagPropertyName::tagKeyboardShortcut());
if (value.isEmpty())
{
return false;
}
QKeySequence ks(value);
// FIXME: tag icons can be files on disk, or system icon names. Only the latter will work here.
QIcon icon(SyncJob::getTagThumbnail(talbum));
qCDebug(DIGIKAM_GENERAL_LOG) << "Create Shortcut " << ks.toString()
<< " to Tag " << talbum->title()
<< " (" << tagId << ")";
foreach(KActionCollection* const ac, d->actionCollectionList)
{
QAction* const action = ac->addAction(QString::fromUtf8("%1-%2").arg(d->tagShortcutPrefix).arg(tagId));
action->setText(i18n("Assign Tag \"%1\"", talbum->title()));
action->setParent(this);
ac->setDefaultShortcut(action, ks);
action->setIcon(icon);
action->setData(tagId);
connect(action, SIGNAL(triggered()),
this, SLOT(slotAssignFromShortcut()));
connect(action, SIGNAL(changed()),
this, SLOT(slotTagActionChanged()));
d->tagsActionMap.insert(tagId, action);
}
return true;
}
void TagsActionMngr::slotTagActionChanged()
{
QAction* const action = dynamic_cast<QAction*>(sender());
if (!action)
{
return;
}
int tagId = action->data().toInt();
QKeySequence ks;
QStringList lst = action->shortcut().toString().split(QLatin1Char(','));
if (!lst.isEmpty())
ks = QKeySequence(lst.first());
updateTagShortcut(tagId, ks);
}
void TagsActionMngr::updateTagShortcut(int tagId, const QKeySequence& ks)
{
if (!tagId)
{
return;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Tag Shortcut " << tagId << "Changed to " << ks;
QString value = TagsCache::instance()->propertyValue(tagId, TagPropertyName::tagKeyboardShortcut());
if (value == ks.toString())
{
return;
}
TagProperties tprop(tagId);
if (ks.isEmpty())
{
removeTagActionShortcut(tagId);
tprop.removeProperties(TagPropertyName::tagKeyboardShortcut());
}
else
{
removeTagActionShortcut(tagId);
tprop.setProperty(TagPropertyName::tagKeyboardShortcut(), ks.toString());
createTagActionShortcut(tagId);
}
}
void TagsActionMngr::slotAlbumDeleted(Album* album)
{
TAlbum* const talbum = dynamic_cast<TAlbum*>(album);
if (!talbum)
{
return;
}
removeTagActionShortcut(talbum->id());
qCDebug(DIGIKAM_GENERAL_LOG) << "Delete Shortcut assigned to tag " << album->id();
}
bool TagsActionMngr::removeTagActionShortcut(int tagId)
{
if (!d->tagsActionMap.contains(tagId))
{
return false;
}
foreach(QAction* const act, d->tagsActionMap.values(tagId))
{
if (act)
{
KActionCollection* const ac = dynamic_cast<KActionCollection*>(act->parent());
if (ac)
{
ac->takeAction(act);
}
delete act;
}
}
d->tagsActionMap.remove(tagId);
return true;
}
void TagsActionMngr::slotAssignFromShortcut()
{
QAction* const action = dynamic_cast<QAction*>(sender());
if (!action)
{
return;
}
int val = action->data().toInt();
qCDebug(DIGIKAM_GENERAL_LOG) << "Shortcut value: " << val;
QWidget* const w = qApp->activeWindow();
DigikamApp* const dkw = dynamic_cast<DigikamApp*>(w);
if (dkw)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by DigikamApp";
if (action->objectName().startsWith(d->ratingShortcutPrefix))
{
dkw->view()->slotAssignRating(val);
}
else if (action->objectName().startsWith(d->pickShortcutPrefix))
{
dkw->view()->slotAssignPickLabel(val);
}
else if (action->objectName().startsWith(d->colorShortcutPrefix))
{
dkw->view()->slotAssignColorLabel(val);
}
else if (action->objectName().startsWith(d->tagShortcutPrefix))
{
dkw->view()->toggleTag(val);
}
return;
}
ImageWindow* const imw = dynamic_cast<ImageWindow*>(w);
if (imw)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by ImageWindow";
if (action->objectName().startsWith(d->ratingShortcutPrefix))
{
imw->slotAssignRating(val);
}
else if (action->objectName().startsWith(d->pickShortcutPrefix))
{
imw->slotAssignPickLabel(val);
}
else if (action->objectName().startsWith(d->colorShortcutPrefix))
{
imw->slotAssignColorLabel(val);
}
else if (action->objectName().startsWith(d->tagShortcutPrefix))
{
imw->toggleTag(val);
}
return;
}
LightTableWindow* const ltw = dynamic_cast<LightTableWindow*>(w);
if (ltw)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by LightTableWindow";
if (action->objectName().startsWith(d->ratingShortcutPrefix))
{
ltw->slotAssignRating(val);
}
else if (action->objectName().startsWith(d->pickShortcutPrefix))
{
ltw->slotAssignPickLabel(val);
}
else if (action->objectName().startsWith(d->colorShortcutPrefix))
{
ltw->slotAssignColorLabel(val);
}
else if (action->objectName().startsWith(d->tagShortcutPrefix))
{
ltw->toggleTag(val);
}
return;
}
SlideShow* const sld = dynamic_cast<SlideShow*>(w);
if (sld)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "Handling by SlideShow";
if (action->objectName().startsWith(d->ratingShortcutPrefix))
{
sld->slotAssignRating(val);
}
else if (action->objectName().startsWith(d->pickShortcutPrefix))
{
sld->slotAssignPickLabel(val);
}
else if (action->objectName().startsWith(d->colorShortcutPrefix))
{
sld->slotAssignColorLabel(val);
}
else if (action->objectName().startsWith(d->tagShortcutPrefix))
{
sld->toggleTag(val);
}
return;
}
}
-// Special case with Slideshow which do not depand of database.
+// Special case with Slideshow which do not depend on database.
void TagsActionMngr::slotImageTagChanged(const ImageTagChangeset&)
{
QWidget* const w = qApp->activeWindow();
SlideShow* const sld = dynamic_cast<SlideShow*>(w);
if (sld)
{
QUrl url = sld->currentItem();
ImageInfo info = ImageInfo::fromUrl(url);
sld->updateTags(url, AlbumManager::instance()->tagNames(info.tagIds()));
}
}
} // namespace Digikam
diff --git a/core/libs/tags/tagsactionmngr.h b/core/libs/tags/tagsactionmngr.h
index a0a5acef51..c22fd6c523 100644
--- a/core/libs/tags/tagsactionmngr.h
+++ b/core/libs/tags/tagsactionmngr.h
@@ -1,120 +1,120 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-01-24
* Description : Tags Action Manager
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_TAGS_ACTION_MNGR_H
#define DIGIKAM_TAGS_ACTION_MNGR_H
// Qt includes
#include <QObject>
#include <QWidget>
class KActionCollection;
namespace Digikam
{
class Album;
class ImageTagChangeset;
class TagsActionMngr : public QObject
{
Q_OBJECT
public:
explicit TagsActionMngr(QWidget* const parent);
~TagsActionMngr();
- /** Register all tag actions to collections managed with keyboard shorcuts.
+ /** Register all tag actions to collections managed with keyboard shortcuts.
* Because Tags shortcuts are stored in database this method must be called after
* database initialization and after that all root window instances have been created.
*/
void registerTagsActionCollections();
- /** Register all labels actions to collections managed with keyboard shorcuts.
+ /** Register all labels actions to collections managed with keyboard shortcuts.
* Unlike tags actions, labels shortcuts are stored in XML GUI file of each root windows,
* to be able to customize it through KDE keyboards shortcuts config panel.
* This method must be called before to DXmlGuiWindow::createGUI(), typically
* when window actions are registered to ActionCollection instance.
*/
void registerLabelsActions(KActionCollection* const ac);
void registerActionsToWidget(QWidget* const wdg);
/** Return the list of whole action collections managed.
*/
QList<KActionCollection*> actionCollections() const;
/**
* Updates the shortcut action for a tag. Call this when a shortcut was
* added, removed or changed.
*/
void updateTagShortcut(int tagId, const QKeySequence& ks);
QString ratingShortcutPrefix() const;
QString tagShortcutPrefix() const;
QString pickShortcutPrefix() const;
QString colorShortcutPrefix() const;
static TagsActionMngr* defaultManager();
private Q_SLOTS:
/**
* Removes the shortcut actions associated with a tag.
*/
void slotAlbumDeleted(Album*);
/**
- * Wrapper around windows to run relevant code about keyboard shorcuts in GUI.
+ * Wrapper around windows to run relevant code about keyboard shortcuts in GUI.
*/
void slotAssignFromShortcut();
/**
* Called by KDE config shortcuts dialog, when user change action properties.
*/
void slotTagActionChanged();
void slotImageTagChanged(const ImageTagChangeset& changeset);
private:
bool createTagActionShortcut(int tagId);
bool removeTagActionShortcut(int tagId);
bool createRatingActionShortcut(KActionCollection* const ac, int rating);
bool createPickLabelActionShortcut(KActionCollection* const ac, int pickId);
bool createColorLabelActionShortcut(KActionCollection* const ac, int colorId);
private:
static TagsActionMngr* m_defaultManager;
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_TAGS_ACTION_MNGR_H
diff --git a/core/libs/tags/tagsmanager/models/tagmngrlistmodel.cpp b/core/libs/tags/tagsmanager/models/tagmngrlistmodel.cpp
index 628ca1f291..884532a19f 100644
--- a/core/libs/tags/tagsmanager/models/tagmngrlistmodel.cpp
+++ b/core/libs/tags/tagsmanager/models/tagmngrlistmodel.cpp
@@ -1,322 +1,322 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 20013-08-22
* Description : List View Model with support for mime data and drag-n-drop
*
* Copyright (C) 2013 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "tagmngrlistmodel.h"
// Qt includes
#include <QStringList>
#include <QSize>
#include <QBrush>
#include <QMimeData>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "tagmngrlistitem.h"
namespace Digikam
{
class Q_DECL_HIDDEN TagMngrListModel::Private
{
public:
explicit Private()
{
rootItem = 0;
}
ListItem* rootItem;
QList<int> dragNewSelection;
};
TagMngrListModel::TagMngrListModel(QObject* const parent)
: QAbstractItemModel(parent),
d(new Private())
{
QList<QVariant> rootData;
rootData << QLatin1String("Quick List");
d->rootItem = new ListItem(rootData);
}
TagMngrListModel::~TagMngrListModel()
{
delete d->rootItem;
delete d;
}
ListItem* TagMngrListModel::addItem(QList<QVariant> values)
{
emit layoutAboutToBeChanged();
ListItem* const item = new ListItem(values, d->rootItem);
/** containsItem will return a valid pointer if item with the same
* values is already added to it's children list.
*/
ListItem* const existingItem = d->rootItem->containsItem(item);
if(!existingItem)
{
d->rootItem->appendChild(item);
emit layoutChanged();
return item;
}
else
{
delete item;
return existingItem;
}
}
QList<ListItem*> TagMngrListModel::allItems() const
{
return d->rootItem->allChildren();
}
QList<int> TagMngrListModel::getDragNewSelection() const
{
return d->dragNewSelection;
}
void TagMngrListModel::deleteItem(ListItem* const item)
{
if(!item)
return;
emit layoutAboutToBeChanged();
d->rootItem->deleteChild(item);
emit layoutChanged();
}
int TagMngrListModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent);
return 1;
}
Qt::DropActions TagMngrListModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList TagMngrListModel::mimeTypes() const
{
QStringList types;
types << QLatin1String("application/vnd.text.list");
return types;
}
bool TagMngrListModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
Q_UNUSED(role);
ListItem* const parent = static_cast<ListItem*>(index.internalPointer());
if(!parent)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "No node found";
return false;
}
QList<QVariant> itemDa;
itemDa << value;
parent->appendChild(new ListItem(itemDa,parent));
return true;
}
QMimeData* TagMngrListModel::mimeData(const QModelIndexList& indexes) const
{
QMimeData* const mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
foreach(const QModelIndex& index, indexes)
{
if(index.isValid())
{
stream << index.row();
}
}
mimeData->setData(QLatin1String("application/vnd.text.list"), encodedData);
return mimeData;
}
bool TagMngrListModel::dropMimeData(const QMimeData* data, Qt::DropAction action,
int row, int column, const QModelIndex& parent)
{
Q_UNUSED(column);
Q_UNUSED(parent);
if(action == Qt::IgnoreAction)
return true;
if(!(data->hasFormat(QLatin1String("application/vnd.text.list"))))
return false;
QByteArray encodedData = data->data(QLatin1String("application/vnd.text.list"));
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QList<ListItem*> newItems;
QList<ListItem*> finalItems;
QList<int> toRemove;
int itemPoz;
int temp = 0;
while(!stream.atEnd())
{
stream >> itemPoz;
newItems << d->rootItem->child(itemPoz);
if(itemPoz < row)
{
temp++;
}
toRemove.append(itemPoz);
}
row -= temp;
emit layoutAboutToBeChanged();
for(QList<int>::iterator itr = toRemove.end() -1 ; itr != toRemove.begin() -1 ; --itr)
{
d->rootItem->deleteChild(*itr);
}
emit layoutChanged();
for(int it = 0; it < d->rootItem->childCount(); it++)
{
finalItems.append(d->rootItem->child(it));
if(it == row)
{
finalItems.append(newItems);
/** After drag-n-drop selection is messed up, store the interval were
- * new items are and TagsMngrListView will update seelction
+ * new items are and TagsMngrListView will update selection
*/
d->dragNewSelection.clear();
d->dragNewSelection << row;
d->dragNewSelection << row + newItems.size();
}
}
d->rootItem->removeAll();
d->rootItem->appendList(finalItems);
emit layoutChanged();
return true;
}
QVariant TagMngrListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
return QVariant();
if(role == Qt::SizeHintRole)
return QSize(30,30);
if(role == Qt::TextAlignmentRole)
return Qt::AlignCenter;
ListItem* const item = static_cast<ListItem*>(index.internalPointer());
return item->data(role);
}
Qt::ItemFlags TagMngrListModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return 0;
return Qt::ItemIsEnabled | Qt::ItemIsSelectable
| Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}
QVariant TagMngrListModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return QVariant(i18n("Quick Access List"));
return QVariant();
}
QModelIndex TagMngrListModel::index(int row, int column, const QModelIndex& parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
ListItem* parentItem = 0;
if (!parent.isValid())
parentItem = d->rootItem;
else
parentItem = static_cast<ListItem*>(parent.internalPointer());
ListItem* const childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
QModelIndex TagMngrListModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
ListItem* const childItem = static_cast<ListItem*>(index.internalPointer());
ListItem* const parentItem = childItem->parent();
if (parentItem == d->rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
int TagMngrListModel::rowCount(const QModelIndex &parent) const
{
ListItem* parentItem = 0;
if (parent.column() > 0)
return 0;
if (!parent.isValid())
parentItem = d->rootItem;
else
parentItem = static_cast<ListItem*>(parent.internalPointer());
return parentItem->childCount();
}
} // namespace Digikam
diff --git a/core/libs/threadimageio/videothumbnailerjob.h b/core/libs/threadimageio/videothumbnailerjob.h
index eac515fc2e..3575bc97f9 100644
--- a/core/libs/threadimageio/videothumbnailerjob.h
+++ b/core/libs/threadimageio/videothumbnailerjob.h
@@ -1,99 +1,99 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2016-04-21
* Description : a class to manage video thumbnails extraction
*
* Copyright (C) 2016-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2016-2018 by Maik Qualmann <metzpinguin at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_VIDEO_THUMB_NAILER_JOB_H
#define DIGIKAM_VIDEO_THUMB_NAILER_JOB_H
// Qt includes
#include <QStringList>
#include <QImage>
#include <QList>
#include <QThread>
#include <QObject>
// Local includes
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT VideoThumbnailerJob : public QThread
{
Q_OBJECT
public:
/** Standard constructor and destructor
*/
explicit VideoThumbnailerJob(QObject* const parent);
virtual ~VideoThumbnailerJob();
/** Set size of thumbnails to generate
*/
void setThumbnailSize(int size);
- /** Add a film strip on the left side of video thumnails
+ /** Add a film strip on the left side of video thumbnails
*/
void setCreateStrip(bool strip);
/** Set exif rotation of thumbnails to generate
*/
void setExifRotate(bool rotate);
/** Add new video files to process on the pending list
*/
void addItems(const QStringList&);
Q_SIGNALS:
- /** Emit when thumnail is generated and ready to use.
+ /** Emit when thumbnail is generated and ready to use.
*/
void signalThumbnailDone(const QString&, const QImage&);
/** Emit when thumbnail cannot be generated for a video file
*/
void signalThumbnailFailed(const QString&);
/* Emit when the pending list is empty
*/
void signalThumbnailJobFinished();
public Q_SLOTS:
void slotCancel();
private:
void run();
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_VIDEO_THUMB_NAILER_JOB_H
diff --git a/core/libs/threads/parallelworkers.h b/core/libs/threads/parallelworkers.h
index c440e7a255..a921b3d6b7 100644
--- a/core/libs/threads/parallelworkers.h
+++ b/core/libs/threads/parallelworkers.h
@@ -1,200 +1,200 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-01-13
* Description : Multithreaded worker objects, working in parallel
*
* Copyright (C) 2010-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_PARALLEL_WORKERS_H
#define DIGIKAM_PARALLEL_WORKERS_H
// Qt includes
#include <QObject>
// Local includes
#include "digikam_export.h"
#include "workerobject.h"
namespace Digikam
{
class DIGIKAM_EXPORT ParallelWorkers
{
public:
/**
* ParallelWorkers is a helper class to distribute work over
* several identical workers objects.
* See ParallelAdapter for guidance how to use it.
*/
explicit ParallelWorkers();
virtual ~ParallelWorkers();
/// The corresponding methods of all added worker objects will be called
void schedule();
void deactivate(WorkerObject::DeactivatingMode mode = WorkerObject::FlushSignals);
void wait();
void setPriority(QThread::Priority priority);
/// Returns true if the current number of added workers has reached the optimalWorkerCount()
bool optimalWorkerCountReached() const;
/**
* Regarding the number of logical CPUs on the current machine,
* returns the optimal count of concurrent workers
*/
static int optimalWorkerCount();
public:
/// Connects signals outbound from all workers to a given receiver
bool connect(const char* signal,
const QObject* receiver,
const char* method,
Qt::ConnectionType type = Qt::AutoConnection) const;
protected:
void add(WorkerObject* const worker);
// Internal implementation
// Replaces slot call distribution of the target QObject
int replacementQtMetacall(QMetaObject::Call _c, int _id, void** _a);
const QMetaObject* replacementMetaObject() const;
// Return the target QObject (double inheritance)
virtual QObject* asQObject() = 0;
// The qt_metacall of WorkerObject, one level above the target QObject
virtual int WorkerObjectQtMetacall(QMetaObject::Call _c, int _id, void** _a) = 0;
// The moc-generated metaObject of the target object
virtual const QMetaObject* mocMetaObject() const = 0;
int replacementStaticQtMetacall(QMetaObject::Call _c, int _id, void** _a);
typedef void (*StaticMetacallFunction)(QObject*, QMetaObject::Call, int, void**);
virtual StaticMetacallFunction staticMetacallPointer() = 0;
protected:
QList<WorkerObject*> m_workers;
int m_currentIndex;
QMetaObject* m_replacementMetaObject;
StaticMetacallFunction m_originalStaticMetacall;
};
// -------------------------------------------------------------------------------------------------
template <class A>
class ParallelAdapter : public A, public ParallelWorkers
{
public:
/**
* Instead of using a single WorkerObject, create a ParallelAdapter for
* your worker object subclass, and add() individual WorkerObjects.
* The load will be evenly distributed.
* Note: unlike with WorkerObject directly, there is no need to call schedule().
* For inbound connections (signals connected to a WorkerObject's slot, to be processed,
* use a Qt::DirectConnection on the adapter.
* For outbound connections (signals emitted from the WorkerObject),
* use ParallelAdapter's connect to have a connection from all added WorkerObjects.
*/
explicit ParallelAdapter() {}
~ParallelAdapter() {}
void add(A* const worker)
{
ParallelWorkers::add(worker);
}
- // Internal Implentation
+ // Internal Implementation
// I know this is a hack
int WorkerObjectQtMetacall(QMetaObject::Call _c, int _id, void** _a)
{
return WorkerObject::qt_metacall(_c, _id, _a);
}
const QMetaObject* mocMetaObject() const
{
return A::metaObject();
}
static void qt_static_metacall(QObject* o, QMetaObject::Call _c, int _id, void** _a)
{
static_cast<ParallelAdapter*>(o)->replacementStaticQtMetacall(_c, _id, _a);
}
virtual StaticMetacallFunction staticMetacallPointer()
{
return qt_static_metacall;
}
virtual const QMetaObject* metaObject() const
{
return ParallelWorkers::replacementMetaObject();
}
virtual int qt_metacall(QMetaObject::Call _c, int _id, void** _a)
{
return ParallelWorkers::replacementQtMetacall(_c, _id, _a);
}
virtual QObject* asQObject()
{
return this;
}
void schedule()
{
ParallelWorkers::schedule();
}
void deactivate(WorkerObject::DeactivatingMode mode = WorkerObject::FlushSignals)
{
ParallelWorkers::deactivate(mode);
}
void wait()
{
ParallelWorkers::wait();
}
bool connect(const char* signal,
const QObject* receiver,
const char* method,
Qt::ConnectionType type = Qt::AutoConnection) const
{
return ParallelWorkers::connect(signal, receiver, method, type);
}
};
} // namespace Digikam
#endif // DIGIKAM_PARALLEL_WORKERS_H
diff --git a/core/libs/transitionmngr/effectmngr_p.h b/core/libs/transitionmngr/effectmngr_p.h
index d094a58824..778c6928de 100644
--- a/core/libs/transitionmngr/effectmngr_p.h
+++ b/core/libs/transitionmngr/effectmngr_p.h
@@ -1,108 +1,108 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-05-24
* Description : video frame effects manager.
*
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_EFFECT_MNGR_PRIVATE_H
#define DIGIKAM_EFFECT_MNGR_PRIVATE_H
// C++ includes
#include <cmath>
// Qt includes
#include <QPointF>
#include <QRect>
#include <QRectF>
#include <QImage>
// Local includes
#include "effectmngr.h"
#include "digikam_config.h"
#include "digikam_debug.h"
namespace Digikam
{
class Q_DECL_HIDDEN EffectMngr::Private
{
public:
typedef int (EffectMngr::Private::*EffectMethod)(bool);
public:
explicit Private()
{
eff_curEffect = EffectMngr::None;
eff_isRunning = false;
eff_step = 0;
eff_imgFrames = 125;
registerEffects();
}
~Private()
{
}
QMap<EffectMngr::EffectType, EffectMethod> eff_effectList;
QImage eff_image;
QImage eff_curFrame;
QSize eff_outSize;
bool eff_isRunning;
EffectMngr::EffectType eff_curEffect;
int eff_step;
int eff_imgFrames;
public:
void registerEffects();
EffectMngr::EffectType getRandomEffect() const;
private:
// Internal functions to render an effect frame.
- // The effect mouvement must be adjusted accordingly with amount of image frames to encode.
+ // The effect movement must be adjusted accordingly with amount of image frames to encode.
// aInit is to true when effect is initialized (first call).
// The integer value is a tempo in ms to wait between frames,
// or -1 if the effect is completed.
int effectNone(bool aInit);
int effectRandom(bool aInit);
int effectKenBurnsZoomIn(bool aInit);
int effectKenBurnsZoomOut(bool aInit);
int effectKenBurnsPanLR(bool aInit);
int effectKenBurnsPanRL(bool aInit);
int effectKenBurnsPanTB(bool aInit);
int effectKenBurnsPanBT(bool aInit);
void updateCurrentFrame(const QRectF& area);
};
} // namespace Digikam
#endif // DIGIKAM_EFFECT_MNGR_PRIVATE_H
diff --git a/core/libs/versionmanager/versionmanager.cpp b/core/libs/versionmanager/versionmanager.cpp
index 22b7dbe8a2..69ec0109e6 100644
--- a/core/libs/versionmanager/versionmanager.cpp
+++ b/core/libs/versionmanager/versionmanager.cpp
@@ -1,773 +1,773 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-06-18
* Description : class for determining new file name in terms of version management
*
* Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2010 by Martin Klapetek <martin dot klapetek at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "versionmanager.h"
// Qt includes
#include <QDir>
#include <QUrl>
// KDE includes
#include <kconfiggroup.h>
// Local includes
#include "digikam_debug.h"
#include "dimgfiltermanager.h"
namespace Digikam
{
class Q_DECL_HIDDEN VersionManagerSettingsConfig
{
public:
static const QString configEnabled;
static const QString configIntermediateAfterEachSession;
static const QString configIntermediateAfterRawConversion;
static const QString configIntermediateWhenNotReproducible;
static const QString configViewShowIntermediates;
static const QString configViewShowOriginal;
static const QString configAutoSaveWhenClosingEditor;
static const QString configVersionStorageFormat;
};
const QString VersionManagerSettingsConfig::configEnabled(QLatin1String("Non-Destructive Editing Enabled"));
const QString VersionManagerSettingsConfig::configIntermediateAfterEachSession(QLatin1String("Save Intermediate After Each Session"));
const QString VersionManagerSettingsConfig::configIntermediateAfterRawConversion(QLatin1String("Save Intermediate After Raw Conversion"));
const QString VersionManagerSettingsConfig::configIntermediateWhenNotReproducible(QLatin1String("Save Intermediate When Not Reproducible"));
const QString VersionManagerSettingsConfig::configViewShowIntermediates(QLatin1String("Show Intermediates in View"));
const QString VersionManagerSettingsConfig::configViewShowOriginal(QLatin1String("Show Original in View"));
const QString VersionManagerSettingsConfig::configAutoSaveWhenClosingEditor(QLatin1String("Auto-Save When Closing Editor"));
const QString VersionManagerSettingsConfig::configVersionStorageFormat(QLatin1String("Saving Format for Versions"));
VersionManagerSettings::VersionManagerSettings()
{
enabled = true;
saveIntermediateVersions = NoIntermediates;
showInViewFlags = ShowOriginal;
editorClosingMode = AlwaysAsk;
format = QLatin1String("JPG");
}
void VersionManagerSettings::readFromConfig(KConfigGroup& group)
{
enabled = group.readEntry(VersionManagerSettingsConfig::configEnabled, true);
saveIntermediateVersions = NoIntermediates;
if (group.readEntry(VersionManagerSettingsConfig::configIntermediateAfterEachSession, false))
saveIntermediateVersions |= AfterEachSession;
if (group.readEntry(VersionManagerSettingsConfig::configIntermediateAfterRawConversion, false))
saveIntermediateVersions |= AfterRawConversion;
if (group.readEntry(VersionManagerSettingsConfig::configIntermediateWhenNotReproducible, false))
saveIntermediateVersions |= WhenNotReproducible;
showInViewFlags = OnlyShowCurrent;
if (group.readEntry(VersionManagerSettingsConfig::configViewShowOriginal, true))
showInViewFlags |= ShowOriginal;
if (group.readEntry(VersionManagerSettingsConfig::configViewShowIntermediates, true))
showInViewFlags |= ShowIntermediates;
bool autoSave = group.readEntry(VersionManagerSettingsConfig::configAutoSaveWhenClosingEditor, false);
editorClosingMode = autoSave ? AutoSave : AlwaysAsk;
format = group.readEntry(VersionManagerSettingsConfig::configVersionStorageFormat, QString::fromLatin1("JPG")).toUpper();
}
void VersionManagerSettings::writeToConfig(KConfigGroup& group) const
{
group.writeEntry(VersionManagerSettingsConfig::configEnabled, enabled);
group.writeEntry(VersionManagerSettingsConfig::configIntermediateAfterEachSession, bool(saveIntermediateVersions & AfterEachSession));
group.writeEntry(VersionManagerSettingsConfig::configIntermediateAfterRawConversion, bool(saveIntermediateVersions & AfterRawConversion));
group.writeEntry(VersionManagerSettingsConfig::configIntermediateWhenNotReproducible, bool(saveIntermediateVersions & WhenNotReproducible));
group.writeEntry(VersionManagerSettingsConfig::configViewShowIntermediates, bool(showInViewFlags & ShowIntermediates));
group.writeEntry(VersionManagerSettingsConfig::configViewShowOriginal, bool(showInViewFlags & ShowOriginal));
group.writeEntry(VersionManagerSettingsConfig::configAutoSaveWhenClosingEditor, bool(editorClosingMode == AutoSave));
group.writeEntry(VersionManagerSettingsConfig::configVersionStorageFormat, format);
}
// ---------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN DefaultVersionNamingScheme : public VersionNamingScheme
{
public:
virtual QString baseName(const QString& currentPath, const QString& filename,
QVariant* counter, QVariant* intermediateCounter);
virtual QString versionFileName(const QString& currentPath, const QString& filename,
const QVariant& counter);
virtual QString intermediateFileName(const QString& currentPath, const QString& filename,
const QVariant& version, const QVariant& counter);
virtual QString directory(const QString& currentPath, const QString& filename);
virtual QString intermediateDirectory(const QString& currentPath, const QString& fileName);
virtual QVariant initialCounter();
virtual QVariant incrementedCounter(const QVariant& counter);
};
QVariant DefaultVersionNamingScheme::initialCounter()
{
// start with _v1
return 1;
}
QVariant DefaultVersionNamingScheme::incrementedCounter(const QVariant& counter)
{
return counter.toInt() + 1;
}
QString DefaultVersionNamingScheme::baseName(const QString& currentPath, const QString& fileName,
QVariant* counter, QVariant* intermediateCounter)
{
Q_UNUSED(currentPath);
// Perl: /^(.+?)(_v\d+?)?(-\d+?)?\.([^\.]+?)$/
// But setMinimal() cannot replace Perl's non-greedy quantifiers, so we need three regexps
int index = fileName.lastIndexOf(QLatin1Char('.'));
QString completeBaseName = (index == -1) ? fileName : fileName.left(index);
// DSC000636_v5-3.JPG: intermediate
QRegExp versionIntermediate(QLatin1String("(.+)_v(\\d+)-(\\d+)"));
if (versionIntermediate.exactMatch(completeBaseName))
{
if (counter)
{
*counter = versionIntermediate.cap(2).toInt();
}
if (intermediateCounter)
{
*intermediateCounter = versionIntermediate.cap(3).toInt();
}
return versionIntermediate.cap(1);
}
// DSC000636_v5.JPG: version
QRegExp version(QLatin1String("(.+)_v(\\d+)"));
if (version.exactMatch(completeBaseName))
{
if (counter)
{
*counter = version.cap(2).toInt();
}
return version.cap(1);
}
// DSC000636.JPG: original file or different naming scheme
return completeBaseName;
}
QString DefaultVersionNamingScheme::versionFileName(const QString& currentPath,
const QString& baseName, const QVariant& counter)
{
Q_UNUSED(currentPath);
return QString::fromUtf8("%1_v%2").arg(baseName).arg(counter.toInt());
}
QString DefaultVersionNamingScheme::intermediateFileName(const QString& currentPath, const QString& baseName,
const QVariant& version, const QVariant& counter)
{
Q_UNUSED(currentPath);
return QString::fromUtf8("%1_v%2-%3").arg(baseName).arg(version.toInt()).arg(counter.toInt());
}
QString DefaultVersionNamingScheme::directory(const QString& currentPath, const QString& fileName)
{
Q_UNUSED(fileName);
return currentPath;
}
QString DefaultVersionNamingScheme::intermediateDirectory(const QString& currentPath, const QString& fileName)
{
Q_UNUSED(fileName);
return currentPath;
}
// -------------------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN VersionNameCreator
{
public:
VersionNameCreator(const VersionFileInfo& loadedFile,
const DImageHistory& m_resolvedInitialHistory, const DImageHistory& m_currentHistory,
VersionManager* const q);
void checkNeedNewVersion();
void fork();
void setSaveDirectory();
void setSaveFormat();
void setSaveFileName();
void setSaveDirectory(const QString& path);
void setSaveFormat(const QString& format);
void setSaveFileName(const QString& name);
void initOperation();
void checkIntermediates();
protected:
VersionFileInfo nextIntermediate(const QString& format);
void setFileSuffix(QString& fileName, const QString& format) const;
void addFileSuffix(QString& baseName, const QString& format, const QString& originalName = QString()) const;
public:
VersionManagerSettings m_settings;
VersionFileInfo m_result;
VersionFileInfo m_loadedFile;
VersionFileOperation m_operation;
const DImageHistory m_resolvedInitialHistory;
const DImageHistory m_currentHistory;
bool m_fromRaw;
bool m_newVersion;
QVariant m_version;
QVariant m_intermediateCounter;
QString m_baseName;
QString m_intermediatePath;
VersionManager* const q;
};
VersionNameCreator::VersionNameCreator(const VersionFileInfo& loadedFile,
const DImageHistory& m_resolvedInitialHistory,
const DImageHistory& m_currentHistory,
VersionManager* const q)
: m_settings(q->settings()),
m_loadedFile(loadedFile),
m_resolvedInitialHistory(m_resolvedInitialHistory),
m_currentHistory(m_currentHistory),
m_fromRaw(false),
m_newVersion(false), q(q)
{
m_loadedFile.format = m_loadedFile.format.toUpper();
m_fromRaw = (m_loadedFile.format.startsWith(QLatin1String("RAW"))); // also accept RAW-... format
m_version = q->namingScheme()->initialCounter();
m_intermediateCounter = q->namingScheme()->initialCounter();
}
void VersionNameCreator::checkNeedNewVersion()
{
// First we check if we have any other files available.
// The resolved initial history contains only referred files found in the collection
// Note: The loaded file will have type Current
qCDebug(DIGIKAM_GENERAL_LOG) << m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Original)
<< m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Intermediate)
<< m_fromRaw << q->workspaceFileFormats().contains(m_loadedFile.format);
if (!m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Original) &&
!m_resolvedInitialHistory.hasReferredImageOfType(HistoryImageId::Intermediate))
{
m_newVersion = true;
}
// We check the loaded format: If it is not one of the workspace formats, or even raw, we need a new version
else if (m_fromRaw || !q->workspaceFileFormats().contains(m_loadedFile.format))
{
m_newVersion = true;
}
else
{
m_newVersion = false;
}
}
void VersionNameCreator::fork()
{
m_newVersion = true;
}
void VersionNameCreator::setSaveDirectory()
{
m_result.path = q->namingScheme()->directory(m_loadedFile.path, m_loadedFile.fileName);
m_intermediatePath = q->namingScheme()->directory(m_loadedFile.path, m_loadedFile.fileName);
}
void VersionNameCreator::setSaveDirectory(const QString& path)
{
m_result.path = path;
m_intermediatePath = path;
}
void VersionNameCreator::setSaveFormat()
{
m_result.format = m_settings.format;
/*
if (m_fromRaw)
{
m_result.format = m_settings.formatForStoringRAW;
}
else
{
m_result.format = m_settings.formatForStoringSubversions;
}
*/
}
void VersionNameCreator::setSaveFormat(const QString& override)
{
m_result.format = override;
}
void VersionNameCreator::setSaveFileName()
{
qCDebug(DIGIKAM_GENERAL_LOG) << "need new version" << m_newVersion;
VersionNamingScheme* scheme = q->namingScheme();
// initialize m_baseName, m_version, and m_intermediateCounter for intermediates
m_baseName = scheme->baseName(m_loadedFile.path, m_loadedFile.fileName, &m_version, &m_intermediateCounter);
qCDebug(DIGIKAM_GENERAL_LOG) << "analyzing file" << m_loadedFile.fileName << m_version << m_intermediateCounter;
if (!m_newVersion)
{
m_result.fileName = m_loadedFile.fileName;
if (m_loadedFile.format != m_result.format)
{
setFileSuffix(m_result.fileName, m_result.format);
}
}
else
{
QDir dirInfo(m_result.path);
// To find the right number for the new version, go through all the items in the given dir,
// the version number won't be bigger than count()
for (uint i = 0; i <= dirInfo.count(); ++i)
{
QString suggestedName = scheme->versionFileName(m_result.path, m_baseName, m_version);
// Note: Always give a hard guarantee that the file does not exist
if (dirInfo.entryList(QStringList() << suggestedName + QLatin1String(".*"), QDir::Files).isEmpty())
{
m_result.fileName = suggestedName;
addFileSuffix(m_result.fileName, m_result.format, m_loadedFile.fileName);
break;
}
// increment for next attempt
m_version = scheme->incrementedCounter(m_version);
}
}
}
void VersionNameCreator::setSaveFileName(const QString& fileName)
{
m_result.fileName = fileName;
m_baseName = fileName.section(QLatin1Char('.'), 0, 0);
// m_version remains unknown
}
void VersionNameCreator::initOperation()
{
m_operation.loadedFile = m_loadedFile;
m_operation.saveFile = m_result;
if (m_newVersion)
{
m_operation.tasks |= VersionFileOperation::NewFile;
}
else
{
if (m_result.fileName == m_loadedFile.fileName)
{
m_operation.tasks |= VersionFileOperation::Replace;
}
else
{
m_operation.tasks |= VersionFileOperation::SaveAndDelete;
}
}
}
void VersionNameCreator::checkIntermediates()
{
// call when task has been determined
qCDebug(DIGIKAM_GENERAL_LOG) << "Will replace" << bool(m_operation.tasks & VersionFileOperation::Replace)
<< "save after each session" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession)
<< "save after raw" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::AfterRawConversion)
<< "save when not repro" << bool(m_settings.saveIntermediateVersions & VersionManagerSettings::WhenNotReproducible);
if ( (m_settings.saveIntermediateVersions & VersionManagerSettings::AfterEachSession) &&
(m_operation.tasks & VersionFileOperation::Replace) )
{
// We want a snapshot after each session. The main file will be overwritten by the current state.
// So we consider the current file as snapshot of the last session and move
// it to an intermediate before it is overwritten
m_operation.tasks |= VersionFileOperation::MoveToIntermediate;
m_operation.intermediateForLoadedFile = nextIntermediate(m_loadedFile.format);
// this amounts to storing firstStep - 1
}
// These are, inclusively, the first and last step the state after which we may have to store.
// m_resolvedInitialHistory.size() - 1 is the loaded file
// m_currentHistory.size() - 1 is the current version
int firstStep = m_resolvedInitialHistory.size();
int lastStep = m_currentHistory.size() - 2; // index of last but one entry
qCDebug(DIGIKAM_GENERAL_LOG) << "initial history" << m_resolvedInitialHistory.size()
<< "current history" << m_currentHistory.size()
<< "first step" << firstStep << "last step" << lastStep;
if (lastStep < firstStep)
{
// only a single editing step, or even "backwards" in history (possible with redo)
return;
}
if (!firstStep)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Invalid history: resolved initial history has no entries";
firstStep = 1;
}
if (m_settings.saveIntermediateVersions & VersionManagerSettings::AfterRawConversion)
{
int rawConversionStep = -1;
for (int i = firstStep; i <= lastStep; ++i)
{
if (DImgFilterManager::instance()->isRawConversion(m_currentHistory.action(i).identifier()))
{
rawConversionStep = i;
// break? multiple raw conversion steps??
}
}
if (rawConversionStep != -1)
{
m_operation.intermediates.insert(rawConversionStep, VersionFileInfo());
}
}
if (m_settings.saveIntermediateVersions & VersionManagerSettings::WhenNotReproducible)
{
for (int i = firstStep; i <= lastStep; ++i)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "step" << i
- << "is reproducable"
+ << "is reproducible"
<< (m_currentHistory.action(i).category() == FilterAction::ReproducibleFilter);
switch (m_currentHistory.action(i).category())
{
case FilterAction::ReproducibleFilter:
break;
case FilterAction::ComplexFilter:
case FilterAction::DocumentedHistory:
m_operation.intermediates.insert(i, VersionFileInfo());
break;
}
}
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Save intermediates after steps" << m_operation.intermediates.keys();
if (!m_operation.intermediates.isEmpty())
{
m_operation.tasks |= VersionFileOperation::StoreIntermediates;
// now all steps are available, already ordered thanks to QMap. Just fill in the empty VersionFileInfos.
QMap<int,VersionFileInfo>::iterator it;
for (it = m_operation.intermediates.begin(); it != m_operation.intermediates.end(); ++it)
{
it.value() = nextIntermediate(m_result.format);
}
}
}
VersionFileInfo VersionNameCreator::nextIntermediate(const QString& format)
{
VersionNamingScheme* scheme = q->namingScheme();
VersionFileInfo intermediate;
intermediate.path = m_intermediatePath;
intermediate.format = format;
QDir dirInfo(m_intermediatePath);
for (uint i = 0; i <= dirInfo.count(); ++i)
{
QString suggestedName = scheme->intermediateFileName(m_intermediatePath, m_baseName,
m_version, m_intermediateCounter);
// it is important to increment before returning - we may have to produce a number of files
m_intermediateCounter = scheme->incrementedCounter(m_intermediateCounter);
// Note: Always give a hard guarantee that the file does not exist
if (dirInfo.entryList(QStringList() << suggestedName + QLatin1String(".*"), QDir::Files).isEmpty())
{
intermediate.fileName = suggestedName;
setFileSuffix(intermediate.fileName, format);
break;
}
}
return intermediate;
}
void VersionNameCreator::setFileSuffix(QString& fileName, const QString& format) const
{
if (fileName.isEmpty())
{
return;
}
// QFileInfo uses the same string manipulation
int lastDot = fileName.lastIndexOf(QLatin1Char('.'));
bool isLower = true;
if (lastDot == -1)
{
fileName += QLatin1Char('.');
lastDot = fileName.size() - 1;
}
else
{
isLower = fileName.at(fileName.size() - 1).isLower();
}
int suffixBegin = lastDot + 1;
if (fileName.mid(suffixBegin).compare(format, Qt::CaseInsensitive))
{
fileName.replace(suffixBegin, fileName.size() - suffixBegin, isLower ? format.toLower() : format);
}
}
void VersionNameCreator::addFileSuffix(QString& fileName, const QString& format, const QString& originalName) const
{
if (fileName.isEmpty())
{
return;
}
bool isLower = true;
if (!originalName.isEmpty())
{
// if original name had upper case suffix, continue using upper case
isLower = originalName.at(originalName.size() - 1).isLower();
}
if (!fileName.endsWith(QLatin1Char('.')))
{
fileName += QLatin1Char('.');
}
fileName += (isLower ? format.toLower() : format);
}
// -----------------------------------------------------------------------------------------------------------
bool VersionFileInfo::isNull() const
{
return fileName.isNull();
}
QString VersionFileInfo::filePath() const
{
return path + QLatin1Char('/') + fileName;
}
QUrl VersionFileInfo::fileUrl() const
{
QUrl url = QUrl::fromLocalFile(path);
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + QLatin1Char('/') + fileName);
return url;
}
QStringList VersionFileOperation::allFilePaths() const
{
QStringList paths;
if (!saveFile.isNull())
paths << saveFile.filePath();
if (!intermediateForLoadedFile.isNull())
paths << intermediateForLoadedFile.filePath();
foreach(const VersionFileInfo& intermediate, intermediates)
{
paths << intermediate.filePath();
}
return paths;
}
// ------------------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN VersionManager::VersionManagerPriv
{
public:
VersionManagerPriv()
{
scheme = 0;
}
VersionManagerSettings settings;
VersionNamingScheme* scheme;
DefaultVersionNamingScheme defaultScheme;
};
// ------------------------------------------------------------------------------------------------------
VersionManager::VersionManager()
: d(new VersionManagerPriv)
{
}
VersionManager::~VersionManager()
{
delete d->scheme;
delete d;
}
void VersionManager::setSettings(const VersionManagerSettings& settings)
{
d->settings = settings;
}
VersionManagerSettings VersionManager::settings() const
{
return d->settings;
}
void VersionManager::setNamingScheme(VersionNamingScheme* scheme)
{
d->scheme = scheme;
}
VersionNamingScheme* VersionManager::namingScheme() const
{
if (d->scheme)
{
return d->scheme;
}
else
{
return &d->defaultScheme;
}
}
VersionFileOperation VersionManager::operation(FileNameType request, const VersionFileInfo& loadedFile,
const DImageHistory& initialResolvedHistory,
const DImageHistory& currentHistory)
{
VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this);
if (request == CurrentVersionName)
{
name.checkNeedNewVersion();
}
else if (request == NewVersionName)
{
name.fork();
}
name.setSaveDirectory();
name.setSaveFormat();
name.setSaveFileName();
name.initOperation();
name.checkIntermediates();
return name.m_operation;
}
VersionFileOperation VersionManager::operationNewVersionInFormat(const VersionFileInfo& loadedFile,
const QString& format,
const DImageHistory& initialResolvedHistory,
const DImageHistory& currentHistory)
{
VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this);
name.fork();
name.setSaveDirectory();
name.setSaveFormat(format);
name.setSaveFileName();
name.initOperation();
name.checkIntermediates();
return name.m_operation;
}
VersionFileOperation VersionManager::operationNewVersionAs(const VersionFileInfo& loadedFile,
const VersionFileInfo& saveLocation,
const DImageHistory& initialResolvedHistory,
const DImageHistory& currentHistory)
{
VersionNameCreator name(loadedFile, initialResolvedHistory, currentHistory, this);
name.fork();
name.setSaveDirectory(saveLocation.path);
name.setSaveFormat(saveLocation.format);
name.setSaveFileName(saveLocation.fileName);
name.initOperation();
name.checkIntermediates();
return name.m_operation;
}
QString VersionManager::toplevelDirectory(const QString& path)
{
Q_UNUSED(path);
return QLatin1String("/");
}
QStringList VersionManager::workspaceFileFormats() const
{
QStringList formats;
formats << QLatin1String("JPG") << QLatin1String("PNG") << QLatin1String("TIFF") << QLatin1String("PGF") << QLatin1String("JP2");
QString f = d->settings.format.toUpper();
if (!formats.contains(f))
{
formats << f;
}
/*
f = d->settings.formatForStoringSubversions.toUpper();
if (!formats.contains(f))
{
formats << f;
}
*/
return formats;
}
} // namespace Digikam
diff --git a/core/libs/widgets/colors/dhuesaturationselect.cpp b/core/libs/widgets/colors/dhuesaturationselect.cpp
index cd9d081bcc..7d75cb8d22 100644
--- a/core/libs/widgets/colors/dhuesaturationselect.cpp
+++ b/core/libs/widgets/colors/dhuesaturationselect.cpp
@@ -1,408 +1,408 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 1997-02-20
* Description : color chooser widgets
*
* Copyright (C) 1997 by Martin Jones (mjones at kde dot org)
* Copyright (C) 2015-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dhuesaturationselect.h"
// Qt includes
#include <QStyle>
#include <QPainter>
#include <QStyleOptionFrame>
#include <QMouseEvent>
// Local includes
#include "digikam_debug.h"
#include "dcolorchoosermode_p.h"
namespace Digikam
{
class Q_DECL_HIDDEN DPointSelect::Private
{
public:
explicit Private(DPointSelect* const q):
q(q),
px(0),
py(0),
xPos(0),
yPos(0),
minX(0),
maxX(100),
minY(0),
maxY(100),
m_markerColor(Qt::white)
{
}
void setValues(int _xPos, int _yPos);
public:
DPointSelect* q;
int px;
int py;
int xPos;
int yPos;
int minX;
int maxX;
int minY;
int maxY;
QColor m_markerColor;
};
void DPointSelect::Private::setValues(int _xPos, int _yPos)
{
int w = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
xPos = _xPos;
yPos = _yPos;
if ( xPos > maxX )
xPos = maxX;
else if ( xPos < minX )
xPos = minX;
if ( yPos > maxY )
yPos = maxY;
else if ( yPos < minY )
yPos = minY;
Q_ASSERT(maxX != minX);
int xp = w + (q->width() - 2 * w) * xPos / (maxX - minX);
Q_ASSERT(maxY != minY);
int yp = q->height() - w - (q->height() - 2 * w) * yPos / (maxY - minY);
q->setPosition( xp, yp );
}
DPointSelect::DPointSelect(QWidget* const parent)
: QWidget(parent),
d(new Private(this))
{
}
DPointSelect::~DPointSelect()
{
delete d;
}
int DPointSelect::xValue() const
{
return d->xPos;
}
int DPointSelect::yValue() const
{
return d->yPos;
}
void DPointSelect::setRange(int _minX, int _minY, int _maxX, int _maxY)
{
if (_maxX == _minX)
{
qCWarning(DIGIKAM_GENERAL_LOG) << "DPointSelect::setRange invalid range: " << _maxX << " == " << _minX << " (for X) ";
return;
}
if (_maxY == _minY)
{
qCWarning(DIGIKAM_GENERAL_LOG) << "DPointSelect::setRange invalid range: " << _maxY << " == " << _minY << " (for Y) ";
return;
}
int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
d->px = w;
d->py = w;
d->minX = _minX;
d->minY = _minY;
d->maxX = _maxX;
d->maxY = _maxY;
}
void DPointSelect::setXValue(int _xPos)
{
setValues(_xPos, d->yPos);
}
void DPointSelect::setYValue(int _yPos)
{
setValues(d->xPos, _yPos);
}
void DPointSelect::setValues(int _xPos, int _yPos)
{
d->setValues(_xPos, _yPos);
}
void DPointSelect::setMarkerColor( const QColor &col )
{
d->m_markerColor = col;
}
QRect DPointSelect::contentsRect() const
{
int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
return rect().adjusted(w, w, -w, -w);
}
QSize DPointSelect::minimumSizeHint() const
{
int w = style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
return QSize( 2 * w, 2 * w );
}
void DPointSelect::paintEvent( QPaintEvent * /* ev */ )
{
QStyleOptionFrame opt;
opt.initFrom(this);
QPainter painter;
painter.begin(this);
drawContents(&painter);
drawMarker(&painter, d->px, d->py);
style()->drawPrimitive(QStyle::PE_Frame, &opt, &painter, this);
painter.end();
}
void DPointSelect::mousePressEvent(QMouseEvent* e)
{
mouseMoveEvent(e);
}
void DPointSelect::mouseMoveEvent(QMouseEvent* e)
{
int xVal, yVal;
int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
valuesFromPosition( e->pos().x() - w, e->pos().y() - w, xVal, yVal );
setValues( xVal, yVal );
emit valueChanged( d->xPos, d->yPos );
}
void DPointSelect::wheelEvent(QWheelEvent* e)
{
if ( e->orientation() == Qt::Horizontal )
setValues( xValue() + e->delta()/120, yValue() );
else
setValues( xValue(), yValue() + e->delta()/120 );
emit valueChanged( d->xPos, d->yPos );
}
void DPointSelect::valuesFromPosition(int x, int y, int& xVal, int& yVal) const
{
int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
xVal = ( ( d->maxX - d->minX ) * ( x - w ) ) / ( width() - 2 * w );
yVal = d->maxY - ( ( ( d->maxY - d->minY ) * (y - w) ) / ( height() - 2 * w ) );
if ( xVal > d->maxX )
xVal = d->maxX;
else if ( xVal < d->minX )
xVal = d->minX;
if ( yVal > d->maxY )
yVal = d->maxY;
else if ( yVal < d->minY )
yVal = d->minY;
}
void DPointSelect::setPosition(int xp, int yp)
{
int w = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
if ( xp < w )
xp = w;
else if ( xp > width() - w )
xp = width() - w;
if ( yp < w )
yp = w;
else if ( yp > height() - w )
yp = height() - w;
d->px = xp;
d->py = yp;
update();
}
void DPointSelect::drawMarker(QPainter* p, int xp, int yp)
{
QPen pen(d->m_markerColor);
p->setPen(pen);
p->drawEllipse(xp - 4, yp - 4, 8, 8);
}
// --------------------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN DHueSaturationSelector::Private
{
public:
explicit Private(DHueSaturationSelector* const q)
: q(q),
mode(ChooserClassic),
hue(0),
saturation(0),
color(0)
{
}
DHueSaturationSelector* q;
QPixmap pixmap;
/**
* Stores the chooser mode
*/
DColorChooserMode mode;
/**
- * Stores the values for hue, saturation and lumniousity
+ * Stores the values for hue, saturation and luminosity
*/
int hue;
int saturation;
int color;
};
DHueSaturationSelector::DHueSaturationSelector(QWidget* const parent)
: DPointSelect(parent),
d(new Private(this))
{
setChooserMode(ChooserClassic);
}
DHueSaturationSelector::~DHueSaturationSelector()
{
delete d;
}
DColorChooserMode DHueSaturationSelector::chooserMode() const
{
return d->mode;
}
void DHueSaturationSelector::setChooserMode(DColorChooserMode chooserMode)
{
int x = 0;
int y = 255;
switch (chooserMode)
{
case ChooserSaturation:
case ChooserValue:
x = 359;
break;
default:
x = 255;
break;
}
setRange(0, 0, x, y);
d->mode = chooserMode;
}
int DHueSaturationSelector::hue() const
{
return d->hue;
}
void DHueSaturationSelector::setHue(int hue)
{
d->hue = hue;
}
int DHueSaturationSelector::saturation() const
{
return d->saturation;
}
void DHueSaturationSelector::setSaturation(int saturation)
{
d->saturation = saturation;
}
int DHueSaturationSelector::colorValue() const
{
return d->color;
}
void DHueSaturationSelector::setColorValue(int color)
{
d->color = color;
}
void DHueSaturationSelector::updateContents()
{
drawPalette(&d->pixmap);
}
void DHueSaturationSelector::resizeEvent(QResizeEvent*)
{
updateContents();
}
void DHueSaturationSelector::drawContents(QPainter* painter)
{
painter->drawPixmap(contentsRect().x(), contentsRect().y(), d->pixmap);
}
void DHueSaturationSelector::drawPalette(QPixmap* pixmap)
{
int xSteps = componentXSteps(chooserMode());
int ySteps = componentYSteps(chooserMode());
QColor color;
color.setHsv(hue(), saturation(), chooserMode() == ChooserClassic ? 192 : colorValue());
QImage image(QSize(xSteps + 1, ySteps + 1), QImage::Format_RGB32);
for (int y = 0; y <= ySteps; ++y)
{
setComponentY(color, chooserMode(), y * (1.0 / ySteps));
for (int x = 0; x <= xSteps; ++x)
{
setComponentX(color, chooserMode(), x * (1.0 / xSteps));
image.setPixel(x, ySteps - y, color.rgb());
}
}
QPixmap pix(contentsRect().size());
QPainter painter(&pix);
// Bilinear filtering
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
QRectF srcRect(0.5, 0.5, xSteps, ySteps);
QRectF destRect(QPointF(0, 0), contentsRect().size());
painter.drawImage(destRect, image, srcRect);
painter.end();
*pixmap = pix;
}
} // namespace Digikam
diff --git a/core/libs/widgets/colors/dhuesaturationselect.h b/core/libs/widgets/colors/dhuesaturationselect.h
index c89fbea325..df21c43a66 100644
--- a/core/libs/widgets/colors/dhuesaturationselect.h
+++ b/core/libs/widgets/colors/dhuesaturationselect.h
@@ -1,263 +1,263 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 1997-02-20
* Description : color chooser widgets
*
* Copyright (C) 1997 by Martin Jones (mjones at kde dot org)
* Copyright (C) 2015-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DHUE_SATURATION_SELECT_H
#define DIGIKAM_DHUE_SATURATION_SELECT_H
// Qt includes
#include <QWidget>
#include <QPixmap>
// Local includes
#include "digikam_export.h"
#include "dcolorchoosermode.h"
namespace Digikam
{
class DIGIKAM_EXPORT DPointSelect : public QWidget
{
Q_OBJECT
Q_PROPERTY(int xValue READ xValue WRITE setXValue)
Q_PROPERTY(int yValue READ yValue WRITE setYValue)
public:
/**
* Constructs a two-dimensional selector widget which
* has a value range of [0..100] in both directions.
*/
explicit DPointSelect(QWidget* const parent);
~DPointSelect();
/**
* Sets the current values in horizontal and
* vertical direction.
* @param xPos the horizontal value
* @param yPos the vertical value
*/
void setValues(int xPos, int yPos);
/**
* Sets the current horizontal value
* @param xPos the horizontal value
*/
void setXValue(int xPos);
/**
* Sets the current vertical value
* @param yPos the vertical value
*/
void setYValue(int yPos);
/**
* Sets the range of possible values.
*/
void setRange(int minX, int minY, int maxX, int maxY);
/**
* Sets the color used to draw the marker
* @param col the color
*/
void setMarkerColor(const QColor& col);
/**
* @return the current value in horizontal direction.
*/
int xValue() const;
/**
* @return the current value in vertical direction.
*/
int yValue() const;
/**
* @return the rectangle on which subclasses should draw.
*/
QRect contentsRect() const;
/**
* Reimplemented to give the widget a minimum size
*/
virtual QSize minimumSizeHint() const;
Q_SIGNALS:
/**
* This signal is emitted whenever the user chooses a value,
* e.g. by clicking with the mouse on the widget.
*/
void valueChanged(int x, int y);
protected:
/**
* Override this function to draw the contents of the widget.
* The default implementation does nothing.
*
* Draw within contentsRect() only.
*/
virtual void drawContents(QPainter*) {};
/**
* Override this function to draw the marker which
* indicates the currently selected value pair.
*/
virtual void drawMarker(QPainter* p, int xp, int yp);
virtual void paintEvent(QPaintEvent* e);
virtual void mousePressEvent(QMouseEvent* e);
virtual void mouseMoveEvent(QMouseEvent* e);
virtual void wheelEvent(QWheelEvent*);
/**
* Converts a pixel position to its corresponding values.
*/
void valuesFromPosition(int x, int y, int& xVal, int& yVal) const;
private:
void setPosition(int xp, int yp);
private:
DPointSelect(); // Disable default constructor.
Q_DISABLE_COPY(DPointSelect)
class Private;
friend class Private;
Private* const d;
};
// --------------------------------------------------------------------------------
class DIGIKAM_EXPORT DHueSaturationSelector : public DPointSelect
{
Q_OBJECT
public:
/**
* Constructs a hue/saturation selection widget.
*/
explicit DHueSaturationSelector(QWidget* const parent = 0);
/**
* Destructor.
*/
~DHueSaturationSelector();
/**
* Sets the chooser mode. The allowed modes are defined
* in DColorChooserMode.
*
* @param The chooser mode as defined in DColorChooserMode
*/
void setChooserMode(DColorChooserMode chooserMode);
/**
* Returns the chooser mode.
*
* @return The chooser mode (defined in DColorChooserMode)
*/
DColorChooserMode chooserMode() const;
/**
* Returns the hue value
*
* @return The hue value (0-360)
*/
int hue() const;
/**
* Sets the hue value (0-360)
*
* @param hue The hue value (0-360)
*/
void setHue(int hue);
/**
* Returns the saturation (0-255)
*
* @return The saturation (0-255)
*/
int saturation() const;
/**
* Sets the saturation (0-255)
*
* @param saturation The saturation (0-255)
*/
void setSaturation(int saturation);
/**
- * Returns the color value (also known as lumniousity, 0-255)
+ * Returns the color value (also known as luminosity, 0-255)
*
* @return The color value (0-255)
*/
int colorValue() const;
/**
* Sets the color value (0-255)
*
* @param colorValue The color value (0-255)
*/
void setColorValue(int color);
/**
* Updates the contents
*/
void updateContents();
protected:
/**
* Draws the contents of the widget on a pixmap,
* which is used for buffering.
*/
virtual void drawPalette(QPixmap* pixmap);
virtual void resizeEvent(QResizeEvent*);
/**
* Reimplemented from DPointSelect. This drawing is
* buffered in a pixmap here. As real drawing
* routine, drawPalette() is used.
*/
virtual void drawContents(QPainter* painter);
private:
Q_DISABLE_COPY(DHueSaturationSelector)
class Private;
friend class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_DHUE_SATURATION_SELECT_H
diff --git a/core/libs/widgets/fonts/dfontproperties.h b/core/libs/widgets/fonts/dfontproperties.h
index e84e28e768..a205eb6d11 100644
--- a/core/libs/widgets/fonts/dfontproperties.h
+++ b/core/libs/widgets/fonts/dfontproperties.h
@@ -1,296 +1,296 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-12-23
* Description : a widget to change font properties.
*
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 1996 by Bernd Johannes Wuebben <wuebben at kde dot org>
* Copyright (c) 1999 by Preston Brown <pbrown at kde dot org>
* Copyright (c) 1999 by Mario Weilguni <mweilguni at kde dot org>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DFONT_PROPERTIES_H
#define DIGIKAM_DFONT_PROPERTIES_H
// Qt includes
#include <QWidget>
#include <QColor>
#include <QFont>
#include <QStringList>
// Local includes
#include "digikam_export.h"
class QFont;
class QStringList;
namespace Digikam
{
class DIGIKAM_EXPORT DFontProperties : public QWidget
{
Q_OBJECT
Q_PROPERTY(QFont font READ font WRITE setFont NOTIFY fontSelected USER true)
Q_PROPERTY(QColor color READ color WRITE setColor)
Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
Q_PROPERTY(Qt::CheckState sizeIsRelative READ sizeIsRelative WRITE setSizeIsRelative)
Q_PROPERTY(QString sampleText READ sampleText WRITE setSampleText)
public:
/**
* @li @p FamilyList - Identifies the family (leftmost) list.
* @li @p StyleList - Identifies the style (center) list.
* @li @p SizeList - Identifies the size (rightmost) list.
*/
enum FontColumn
{
FamilyList = 0x01,
StyleList = 0x02,
SizeList = 0x04
};
/**
* @li @p FontDiffFamily - Identifies a requested change in the font family.
* @li @p FontDiffStyle - Identifies a requested change in the font style.
* @li @p FontDiffSize - Identifies a requested change in the font size.
*/
enum FontDiff
{
NoFontDiffFlags = 0,
FontDiffFamily = 1,
FontDiffStyle = 2,
FontDiffSize = 4,
AllFontDiffs = FontDiffFamily | FontDiffStyle | FontDiffSize
};
Q_DECLARE_FLAGS(FontDiffFlags, FontDiff)
/**
* @li @p FixedFontsOnly only show fixed fonts, excluding proportional fonts
* @li @p DisplayFrame show a visual frame around the chooser
* @li @p ShowDifferences display the font differences interfaces
*/
enum DisplayFlag
{
NoDisplayFlags = 0,
FixedFontsOnly = 1,
DisplayFrame = 2,
ShowDifferences = 4
};
Q_DECLARE_FLAGS(DisplayFlags, DisplayFlag)
/**
* The selection criteria for the font families shown in the dialog.
* @li @p FixedWidthFont when included only fixed-width fonts are returned.
* The fonts where the width of every character is equal.
* @li @p ScalableFont when included only scalable fonts are returned;
* certain configurations allow bitmap fonts to remain unscaled and
* thus these fonts have limited number of sizes.
* @li @p SmoothScalableFont when included only return smooth scalable fonts.
* this will return only non-bitmap fonts which are scalable to any size requested.
- * Setting this option to true will mean the "scalable" flag is irrelavant.
+ * Setting this option to true will mean the "scalable" flag is irrelevant.
*/
enum FontListCriteria
{
FixedWidthFonts = 0x01,
ScalableFonts = 0x02,
SmoothScalableFonts = 0x04
};
public:
/**
* Constructs a font picker widget.
* It normally comes up with all font families present on the system; the
* getFont method below does allow some more fine-tuning of the selection of fonts
* that will be displayed in the dialog.
*
* @param parent The parent widget.
* @param flags Defines how the font chooser is displayed. @see DisplayFlags
* @param fontList A list of fonts to display, in XLFD format.
* @param visibleListSize The minimum number of visible entries in the
* fontlists.
* @param sizeIsRelativeState If not zero the widget will show a
* checkbox where the user may choose whether the font size
* is to be interpreted as relative size.
* Initial state of this checkbox will be set according to
* *sizeIsRelativeState, user choice may be retrieved by
* calling sizeIsRelative().
*/
explicit DFontProperties(QWidget* const parent = 0,
const DisplayFlags& flags = DisplayFrame,
const QStringList& fontList = QStringList(),
int visibleListSize = 8,
Qt::CheckState* const sizeIsRelativeState = 0);
/**
* Destructs the font chooser.
*/
virtual ~DFontProperties();
/**
* Enables or disable a font column in the chooser.
*
* Use this
* function if your application does not need or supports all font
* properties.
*
* @param column Specify the columns. An or'ed combination of
* @p FamilyList, @p StyleList and @p SizeList is possible.
* @param state If @p false the columns are disabled.
*/
void enableColumn(int column, bool state);
/**
* Makes a font column in the chooser visible or invisible.
*
* Use this
* function if your application does not need to show all font
* properties.
*
* @param column Specify the columns. An or'ed combination of
* @p FamilyList, @p StyleList and @p SizeList is possible.
* @param state If @p false the columns are made invisible.
*/
void makeColumnVisible(int column, bool state);
/**
* Sets the currently selected font in the chooser.
*
* @param font The font to select.
* @param onlyFixed Readjust the font list to display only fixed
* width fonts if @p true, or vice-versa.
*/
void setFont(const QFont& font, bool onlyFixed = false);
/**
* @return The bitmask corresponding to the attributes the user
* wishes to change.
*/
FontDiffFlags fontDiffFlags() const;
/**
* @return The currently selected font in the chooser.
*/
QFont font() const;
/**
* Sets the color to use in the preview.
*/
void setColor(const QColor& col);
/**
* @return The color currently used in the preview (default: the text
* color of the active color group)
*/
QColor color() const;
/**
* Sets the background color to use in the preview.
*/
void setBackgroundColor(const QColor& col);
/**
* @return The background color currently used in the preview (default:
* the base color of the active colorgroup)
*/
QColor backgroundColor() const;
/**
* Sets the state of the checkbox indicating whether the font size
* is to be interpreted as relative size.
* NOTE: If parameter sizeIsRelative was not set in the constructor
* of the widget this setting will be ignored.
*/
void setSizeIsRelative(Qt::CheckState relative);
/**
* @return Whether the font size is to be interpreted as relative size
* (default: QButton:Off)
*/
Qt::CheckState sizeIsRelative() const;
/**
* @return The current text in the sample text input area.
*/
QString sampleText() const;
/**
* Sets the sample text.
*
* Normally you should not change this
* text, but it can be better to do this if the default text is
* too large for the edit area when using the default font of your
* application.
*
* @param text The new sample text. The current will be removed.
*/
void setSampleText(const QString& text);
/**
* Shows or hides the sample text box.
*
* @param visible Set it to true to show the box, to false to hide it.
*/
void setSampleBoxVisible(bool visible);
/**
* Creates a list of font strings.
*
* @param list The list is returned here.
* @param fontListCriteria should contain all the restrictions for font selection as OR-ed values
* @see DFontProperties::FontListCriteria for the individual values
*/
static void getFontList(QStringList& list, uint fontListCriteria);
/**
* Reimplemented for internal reasons.
*/
QSize sizeHint(void) const Q_DECL_OVERRIDE;
Q_SIGNALS:
/**
* Emitted whenever the selected font changes.
*/
void fontSelected(const QFont& font);
private:
class Private;
Private* const d;
Q_DISABLE_COPY(DFontProperties)
Q_PRIVATE_SLOT(d, void _d_toggled_checkbox())
Q_PRIVATE_SLOT(d, void _d_family_chosen_slot(const QString&))
Q_PRIVATE_SLOT(d, void _d_size_chosen_slot(const QString&))
Q_PRIVATE_SLOT(d, void _d_style_chosen_slot(const QString&))
Q_PRIVATE_SLOT(d, void _d_displaySample(const QFont& font))
Q_PRIVATE_SLOT(d, void _d_size_value_slot(double))
};
Q_DECLARE_OPERATORS_FOR_FLAGS(DFontProperties::DisplayFlags)
} // namespace Digikam
#endif // DIGIKAM_DFONT_PROPERTIES_H
diff --git a/core/libs/widgets/graphicsview/itemvisibilitycontroller.h b/core/libs/widgets/graphicsview/itemvisibilitycontroller.h
index 3a430f8daa..06ab7b56c1 100644
--- a/core/libs/widgets/graphicsview/itemvisibilitycontroller.h
+++ b/core/libs/widgets/graphicsview/itemvisibilitycontroller.h
@@ -1,314 +1,314 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-09-20
* Description : Managing visibility state with animations
*
* Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_ITEM_VISIBILITY_CONTROLLER_H
#define DIGIKAM_ITEM_VISIBILITY_CONTROLLER_H
// Qt includes
#include <QAbstractAnimation>
#include <QVariant>
// Local includes
#include "digikam_export.h"
class QEasingCurve;
class QPropertyAnimation;
namespace Digikam
{
class DIGIKAM_EXPORT ItemVisibilityController : public QObject
{
Q_OBJECT
Q_PROPERTY(bool shallBeShown READ shallBeShown WRITE setShallBeShown)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible)
Q_ENUMS(State)
public:
/**
* This class handles complex visibility situations for items.
* There is a 3-tiered approach:
* 1) shallBeShown determines if the items shall at any time be shown.
* If it is false, items will never be shown.
* Default is true, so you can ignore this setting.
* 2) visible determines if the items shall be shown now.
* Only takes effect if shallBeShown is true.
* Default is false: Initially, controlled items are hidden.
- * 3) Opacitiy and individual item visibility:
+ * 3) Opacity and individual item visibility:
* When showing, items are first set to individually visible,
* then their opacity is increased from 0 to 1.
* When hiding, opacity is first decreased from 1 to 0,
* then they are set individually to hidden.
* Different types of items can be handled:
* - a group of items with an "opacity" and "visible" property
* - a single item with an "opacity" and "visible" property
* - a proxy object with these properties (see above)
*/
enum State
{
Hidden,
FadingIn,
Visible,
FadingOut
};
enum IncludeFadingOutMode
{
/// In addition to items visible or fading in, return those fading out
IncludeFadingOut,
/// Do not return those items currently fading out (soon to be hidden)
ExcludeFadingOut
};
public:
explicit ItemVisibilityController(QObject* const parent = 0);
~ItemVisibilityController();
bool shallBeShown() const;
bool isVisible() const;
State state() const;
/**
* This returns the "result" of isVisible and shallBeShown:
* Something is indeed visible on the scene.
* Also returns false if no items are available.
*/
bool hasVisibleItems(IncludeFadingOutMode mode = IncludeFadingOut) const;
/// Remove all animations
void clear();
/**
* Add and remove objects. The given objects shall provide
* an "opacity" and a "visible" property.
* You can, for convenience, use a ItemVisibilityControllerPropertyObject
* as a value container, if your items do not provide these properties directly.
* No ownership is taken, so the objects should live as long as this object
* is used.
*/
void addItem(QObject* object);
void removeItem(QObject* object);
/**
* Returns all items under control
*/
QList<QObject*> items() const;
/**
* Returns all currently visible items.
*/
QList<QObject*> visibleItems(IncludeFadingOutMode mode = IncludeFadingOut) const;
/**
* Allows to change the default parameters of all animations.
*/
void setEasingCurve(const QEasingCurve& easing);
void setAnimationDuration(int msecs);
Q_SIGNALS:
/// Emitted when the (main) transition has finished
void propertiesAssigned(bool visible);
/**
* Emitted when a transition for a single item finished
* (see setItemVisible())
*/
void propertiesAssigned(QObject* item, bool visible);
/// Emitted when hideAndRemoveItem has finished
void hiddenAndRemoved(QObject* item);
public Q_SLOTS:
/// Adjusts the first condition - the items are shown if shallBeShown is true and isVisible is true
void setShallBeShown(bool shallBeShown);
void setShallBeShownDirectly(bool shallBeShown);
/**
* Sets a single item to be shown. Calling setVisible() will effectively
* effect only this single item, as if calling setItemVisible().
* Reset by calling with 0 or setShallBeShown().
*/
void setItemThatShallBeShown(QObject* item);
/**
* Adjusts the main condition.
* All items are affected.
* If any items were shown or hidden separately, they will be resynchronized.
* "Directly" means no animation is employed.
*/
void show();
void hide();
void setVisible(bool visible);
void setDirectlyVisible(bool visible);
/**
* Shows or hides a single item.
* The item's status is changed individually.
* The next call to the "global" method will take precedence again.
* "Directly" means no animation is employed.
*/
void showItem(QObject* item);
void hideItem(QObject* item);
void setItemVisible(QObject* item, bool visible);
void setItemDirectlyVisible(QObject* item, bool visible);
/**
* Hide the item, and then remove it.
* When finished, hiddenAndRemoved() is emitted.
*/
void hideAndRemoveItem(QObject* item);
protected:
/**
* Creates the animation for showing and hiding the given item.
* The item is given for information only, you do not need to use it.
* The default implementation creates and animation for "opacity"
* from 0.0 to 1.0, using default easing curve and duration,
* which can and will be changed by setEasingCurve and setAnimationDuration.
*/
virtual QPropertyAnimation* createAnimation(QObject* item);
protected Q_SLOTS:
void animationFinished();
void objectDestroyed(QObject*);
private:
class Private;
Private* const d;
};
// ------------------------------------------------------------------------------------------
class DIGIKAM_EXPORT ItemVisibilityControllerPropertyObject : public QObject
{
Q_OBJECT
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged)
Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged)
public:
/**
* You can use this object as a container providing the properties
* set by ItemVisibilityController.
* Connect to the signals accordingly, e.g. to trigger a repaint.
*/
explicit ItemVisibilityControllerPropertyObject(QObject* parent = 0);
qreal opacity() const;
void setOpacity(qreal opacity);
bool isVisible() const;
void setVisible(bool visible);
Q_SIGNALS:
void opacityChanged();
void visibleChanged();
protected:
qreal m_opacity;
qreal m_visible;
};
// ------------------------------------------------------------------------------------------
class DIGIKAM_EXPORT AnimatedVisibility : public ItemVisibilityControllerPropertyObject
{
Q_OBJECT
public:
/** A convenience class:
* The property object brings its own controller.
* Ready to use: Just construct an object and connect to the signals.
* Please note the difference between controller()->setVisible() and setVisible():
* You want to call the controller's method!
*/
explicit AnimatedVisibility(QObject* const parent = 0);
ItemVisibilityController* controller() const;
protected:
ItemVisibilityController* m_controller;
};
// ------------------------------------------------------------------------------------------
class DIGIKAM_EXPORT HidingStateChanger : public ItemVisibilityController
{
Q_OBJECT
public:
/**
* This class provides a state change while fading in and out:
* When changeValue is called, first the items are hidden,
* when this is finished, the property is assigned to the object.
* Afterwards, the items are shown again.
* Note that the targetObject is not necessarily a controlled item!
*/
explicit HidingStateChanger(QObject* const parent = 0);
/// Convenience constructor: Sets target and property name
HidingStateChanger(QObject* const target, const QByteArray& property, QObject* const parent = 0);
void setTargetObject(QObject* const object);
void setPropertyName(const QByteArray& propertyName);
public Q_SLOTS:
void changeValue(const QVariant& value);
Q_SIGNALS:
/// Emitted when the items were hidden and the target object's property changed
void stateChanged();
/// Emitted when the items were hidden, the target object's property changed, and the items shown again
void finished();
protected Q_SLOTS:
void slotPropertiesAssigned(bool);
protected:
QObject* m_object;
QByteArray m_property;
QVariant m_value;
};
} // namespace Digikam
#endif // DIGIKAM_ITEM_VISIBILITY_CONTROLLER_H
diff --git a/core/libs/widgets/itemview/dcategorizedsortfilterproxymodel.h b/core/libs/widgets/itemview/dcategorizedsortfilterproxymodel.h
index 3f7614bc03..befde522fc 100644
--- a/core/libs/widgets/itemview/dcategorizedsortfilterproxymodel.h
+++ b/core/libs/widgets/itemview/dcategorizedsortfilterproxymodel.h
@@ -1,185 +1,185 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-01-16
* Description : categorize item view based on DCategorizedView
*
* Copyright (C) 2007 by Rafael Fernández López <ereslibre at kde dot org>
* Copyright (C) 2007 by John Tapsell <tapsell at kde dot org>
* Copyright (C) 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DCATEGORIZED_SORT_FILTER_PROXY_MODEL_H
#define DIGIKAM_DCATEGORIZED_SORT_FILTER_PROXY_MODEL_H
// Qt includes
#include <QSortFilterProxyModel>
// Local includes
#include "digikam_export.h"
class QItemSelection;
namespace Digikam
{
/**
* This class lets you categorize a view. It is meant to be used along with
* DCategorizedView class.
*
* In general terms all you need to do is to reimplement subSortLessThan() and
* compareCategories() methods. In order to make categorization work, you need
* to also call setCategorizedModel() class to enable it, since the categorization
* is disabled by default.
*/
class DIGIKAM_EXPORT DCategorizedSortFilterProxyModel : public QSortFilterProxyModel
{
public:
enum AdditionalRoles
{
// Note: use printf "0x%08X\n" $(($RANDOM*$RANDOM))
// to define additional roles.
CategoryDisplayRole = 0x17CE990A, ///< This role is used for asking the category to a given index
CategorySortRole = 0x27857E60 ///< This role is used for sorting categories. You can return a
///< string or a long long value. Strings will be sorted alphabetically
///< while long long will be sorted by their value. Please note that this
///< value won't be shown on the view, is only for sorting purposes. What will
///< be shown as "Category" on the view will be asked with the role
///< CategoryDisplayRole.
};
explicit DCategorizedSortFilterProxyModel(QObject* const parent = 0);
virtual ~DCategorizedSortFilterProxyModel();
/**
* Overridden from QSortFilterProxyModel. Sorts the source model using
* @p column for the given @p order.
*/
virtual void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
/**
* @return whether the model is categorized or not. Disabled by default.
*/
bool isCategorizedModel() const;
/**
* Enables or disables the categorization feature.
*
* @param categorizedModel whether to enable or disable the categorization feature.
*/
void setCategorizedModel(bool categorizedModel);
/**
* @return the column being used for sorting.
*/
int sortColumn() const;
/**
* @return the sort order being used for sorting.
*/
Qt::SortOrder sortOrder() const;
/**
* Set if the sorting using CategorySortRole will use a natural comparison
* in the case that strings were returned. If enabled, QCollator
* will be used for sorting.
*
* @param sortCategoriesByNaturalComparison whether to sort using a natural comparison or not.
*/
void setSortCategoriesByNaturalComparison(bool sortCategoriesByNaturalComparison);
/**
* @return whether it is being used a natural comparison for sorting. Enabled by default.
*/
bool sortCategoriesByNaturalComparison() const;
protected:
/**
* Overridden from QSortFilterProxyModel. If you are subclassing
* DCategorizedSortFilterProxyModel, you will probably not need to reimplement this
* method.
*
* It calls compareCategories() to sort by category. If the both items are in the
* same category (i.e. compareCategories returns 0), then subSortLessThan is called.
*
* @return Returns true if the item @p left is less than the item @p right when sorting.
*
* @warning You usually won't need to reimplement this method when subclassing
* from DCategorizedSortFilterProxyModel.
*/
virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
/**
* This method has a similar purpose as lessThan() has on QSortFilterProxyModel.
* It is used for sorting items that are in the same category.
*
* @return Returns true if the item @p left is less than the item @p right when sorting.
*/
virtual bool subSortLessThan(const QModelIndex &left, const QModelIndex &right) const;
/**
* This method compares the category of the @p left index with the category
* of the @p right index.
*
* Internally and if not reimplemented, this method will ask for @p left and
* @p right models for role CategorySortRole. In order to correctly sort
- * categories, the data() metod of the model should return a qlonglong (or numeric) value, or
+ * categories, the data() method of the model should return a qlonglong (or numeric) value, or
* a QString object. QString objects will be sorted with QString::localeAwareCompare if
* sortCategoriesByNaturalComparison() is true.
*
* @note Please have present that:
* QString(QChar(QChar::ObjectReplacementCharacter)) >
* QString(QChar(QChar::ReplacementCharacter)) >
* [ all possible strings ] >
* QString();
*
* This means that QString() will be sorted the first one, while
* QString(QChar(QChar::ObjectReplacementCharacter)) and
* QString(QChar(QChar::ReplacementCharacter)) will be sorted in last
* position.
*
* @warning Please note that data() method of the model should return always
* information of the same type. If you return a QString for an index,
* you should return always QStrings for all indexes for role CategorySortRole
* in order to correctly sort categories. You can't mix by returning
* a QString for one index, and a qlonglong for other.
*
* @note If you need a more complex layout, you will have to reimplement this
* method.
*
* @return A negative value if the category of @p left should be placed before the
* category of @p right. 0 if @p left and @p right are on the same category, and
* a positive value if the category of @p left should be placed after the
* category of @p right.
*/
virtual int compareCategories(const QModelIndex &left, const QModelIndex &right) const;
private:
class Private;
Private *const d;
};
} // namespace Digikam
#endif // DIGIKAM_DCATEGORIZED_SORT_FILTER_PROXY_MODEL_H
diff --git a/core/libs/widgets/itemview/dcategorizedview.cpp b/core/libs/widgets/itemview/dcategorizedview.cpp
index 4119a98ddd..cdef8a9248 100644
--- a/core/libs/widgets/itemview/dcategorizedview.cpp
+++ b/core/libs/widgets/itemview/dcategorizedview.cpp
@@ -1,1954 +1,1954 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-01-16
* Description : Item view for listing items in a categorized fashion optionally
*
* Copyright (C) 2007 by Rafael Fernández López <ereslibre at kde dot org>
* Copyright (C) 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dcategorizedview.h"
#include "dcategorizedview_p.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QPainter>
#include <QScrollBar>
#include <QPaintEvent>
// Local includes
#include "dcategorizedsortfilterproxymodel.h"
#include "dcategorydrawer.h"
// By defining DOLPHIN_DRAGANDDROP the custom drag and drop implementation of
// DCategorizedView is bypassed to have a consistent drag and drop look for all
// views. Hopefully transparent pixmaps for drag objects will be supported in
// Qt 4.4, so that this workaround can be skipped.
#define DOLPHIN_DRAGANDDROP
namespace Digikam
{
DCategorizedView::Private::Private(DCategorizedView* const lv)
: listView(lv),
categoryDrawer(0),
biggestItemSize(QSize(0, 0)),
mouseButtonPressed(false),
rightMouseButtonPressed(false),
dragLeftViewport(false),
drawItemsWhileDragging(true),
forcedSelectionPosition(0),
proxyModel(0)
{
}
DCategorizedView::Private::~Private()
{
}
const QModelIndexList& DCategorizedView::Private::intersectionSet(const QRect& rect)
{
QModelIndex index;
QRect indexVisualRect;
int itemHeight;
intersectedIndexes.clear();
if (listView->gridSize().isEmpty())
{
itemHeight = biggestItemSize.height();
}
else
{
itemHeight = listView->gridSize().height();
}
// Lets find out where we should start
int top = proxyModel->rowCount() - 1;
int bottom = 0;
int middle = (top + bottom) / 2;
while (bottom <= top)
{
middle = (top + bottom) / 2;
index = proxyModel->index(middle, 0);
indexVisualRect = visualRect(index);
// We need the whole height (not only the visualRect). This will help us to update
// all needed indexes correctly (ereslibre)
indexVisualRect.setHeight(indexVisualRect.height() + (itemHeight - indexVisualRect.height()));
if (qMax(indexVisualRect.topLeft().y(), indexVisualRect.bottomRight().y()) <
qMin(rect.topLeft().y(), rect.bottomRight().y()))
{
bottom = middle + 1;
}
else
{
top = middle - 1;
}
}
for (int i = middle; i < proxyModel->rowCount(); ++i)
{
index = proxyModel->index(i, 0);
indexVisualRect = visualRect(index);
if (rect.intersects(indexVisualRect))
{
intersectedIndexes.append(index);
}
// If we passed next item, stop searching for hits
if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
qMin(indexVisualRect.topLeft().y(), indexVisualRect.bottomRight().y()))
{
break;
}
}
return intersectedIndexes;
}
QRect DCategorizedView::Private::visualRectInViewport(const QModelIndex& index) const
{
if (!index.isValid())
{
return QRect();
}
QRect retRect;
QString curCategory = elementsInfo[index.row()].category;
const bool leftToRightFlow = (listView->flow() == QListView::LeftToRight);
if (leftToRightFlow)
{
if (listView->layoutDirection() == Qt::LeftToRight)
{
retRect = QRect(listView->spacing(), listView->spacing() * 2 +
categoryDrawer->categoryHeight(index, listView->viewOptions()), 0, 0);
}
else
{
retRect = QRect(listView->viewport()->width() - listView->spacing(), listView->spacing() * 2 +
categoryDrawer->categoryHeight(index, listView->viewOptions()), 0, 0);
}
}
else
{
retRect = QRect(listView->spacing(), listView->spacing() * 2 +
categoryDrawer->categoryHeight(index, listView->viewOptions()), 0, 0);
}
int viewportWidth = listView->viewport()->width() - listView->spacing();
int itemHeight;
int itemWidth;
if (listView->gridSize().isEmpty() && leftToRightFlow)
{
itemHeight = biggestItemSize.height();
itemWidth = biggestItemSize.width();
}
else if (leftToRightFlow)
{
itemHeight = listView->gridSize().height();
itemWidth = listView->gridSize().width();
}
else if (listView->gridSize().isEmpty() && !leftToRightFlow)
{
itemHeight = biggestItemSize.height();
itemWidth = listView->viewport()->width() - listView->spacing() * 2;
}
else
{
itemHeight = listView->gridSize().height();
itemWidth = listView->gridSize().width() - listView->spacing() * 2;
}
int itemWidthPlusSeparation = listView->spacing() + itemWidth;
if (!itemWidthPlusSeparation)
{
++itemWidthPlusSeparation;
}
int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
if (!elementsPerRow)
{
++elementsPerRow;
}
int column;
int row;
if (leftToRightFlow)
{
column = elementsInfo[index.row()].relativeOffsetToCategory % elementsPerRow;
row = elementsInfo[index.row()].relativeOffsetToCategory / elementsPerRow;
if (listView->layoutDirection() == Qt::LeftToRight)
{
retRect.setLeft(retRect.left() + column * listView->spacing() +
column * itemWidth);
}
else
{
retRect.setLeft(retRect.right() - column * listView->spacing() -
column * itemWidth - itemWidth);
retRect.setRight(retRect.right() - column * listView->spacing() -
column * itemWidth);
}
}
else
{
elementsPerRow = 1;
column = elementsInfo[index.row()].relativeOffsetToCategory % elementsPerRow;
row = elementsInfo[index.row()].relativeOffsetToCategory / elementsPerRow;
}
foreach(const QString& category, categories)
{
if (category == curCategory)
{
break;
}
float rows = (float) ((float) categoriesIndexes[category].count() /
(float) elementsPerRow);
int rowsInt = categoriesIndexes[category].count() / elementsPerRow;
if (rows - trunc(rows))
{
++rowsInt;
}
retRect.setTop(retRect.top() +
(rowsInt * itemHeight) +
categoryDrawer->categoryHeight(index, listView->viewOptions()) +
listView->spacing() * 2);
if (listView->gridSize().isEmpty())
{
retRect.setTop(retRect.top() +
(rowsInt * listView->spacing()));
}
}
if (listView->gridSize().isEmpty())
{
retRect.setTop(retRect.top() + row * listView->spacing() +
(row * itemHeight));
}
else
{
retRect.setTop(retRect.top() + (row * itemHeight));
}
retRect.setWidth(itemWidth);
QModelIndex heightIndex = proxyModel->index(index.row(), 0);
if (listView->gridSize().isEmpty())
{
retRect.setHeight(listView->sizeHintForIndex(heightIndex).height());
}
else
{
const QSize sizeHint = listView->sizeHintForIndex(heightIndex);
if (sizeHint.width() < itemWidth && leftToRightFlow)
{
retRect.setWidth(sizeHint.width());
retRect.moveLeft(retRect.left() + (itemWidth - sizeHint.width()) / 2);
}
retRect.setHeight(qMin(sizeHint.height(), listView->gridSize().height()));
}
return retRect;
}
QRect DCategorizedView::Private::visualCategoryRectInViewport(const QString& category) const
{
QRect retRect(listView->spacing(),
listView->spacing(),
listView->viewport()->width() - listView->spacing() * 2,
0);
if (!proxyModel || !categoryDrawer || !proxyModel->isCategorizedModel() ||
!proxyModel->rowCount() || !categories.contains(category))
{
return QRect();
}
QModelIndex index = proxyModel->index(0, 0, QModelIndex());
int viewportWidth = listView->viewport()->width() - listView->spacing();
int itemHeight;
int itemWidth;
if (listView->gridSize().isEmpty())
{
itemHeight = biggestItemSize.height();
itemWidth = biggestItemSize.width();
}
else
{
itemHeight = listView->gridSize().height();
itemWidth = listView->gridSize().width();
}
int itemWidthPlusSeparation = listView->spacing() + itemWidth;
int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
if (!elementsPerRow)
{
++elementsPerRow;
}
if (listView->flow() == QListView::TopToBottom)
{
elementsPerRow = 1;
}
foreach(const QString& itCategory, categories)
{
if (itCategory == category)
{
break;
}
float rows = (float) ((float) categoriesIndexes[itCategory].count() /
(float) elementsPerRow);
int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
if (rows - trunc(rows))
{
++rowsInt;
}
retRect.setTop(retRect.top() +
(rowsInt * itemHeight) +
categoryDrawer->categoryHeight(index, listView->viewOptions()) +
listView->spacing() * 2);
if (listView->gridSize().isEmpty())
{
retRect.setTop(retRect.top() +
(rowsInt * listView->spacing()));
}
}
retRect.setHeight(categoryDrawer->categoryHeight(index, listView->viewOptions()));
return retRect;
}
// We're sure elementsPosition doesn't contain index
const QRect& DCategorizedView::Private::cacheIndex(const QModelIndex& index)
{
QRect rect = visualRectInViewport(index);
QHash<int, QRect>::iterator it = elementsPosition.insert(index.row(), rect);
return *it;
}
// We're sure categoriesPosition doesn't contain category
const QRect& DCategorizedView::Private::cacheCategory(const QString& category)
{
QRect rect = visualCategoryRectInViewport(category);
QHash<QString, QRect>::iterator it = categoriesPosition.insert(category, rect);
return *it;
}
const QRect& DCategorizedView::Private::cachedRectIndex(const QModelIndex& index)
{
QHash<int, QRect>::const_iterator it = elementsPosition.constFind(index.row());
if (it != elementsPosition.constEnd()) // If we have it cached
{
// return it
return *it;
}
else // Otherwise, cache it
{
// and return it
return cacheIndex(index);
}
}
const QRect& DCategorizedView::Private::cachedRectCategory(const QString& category)
{
QHash<QString, QRect>::const_iterator it = categoriesPosition.constFind(category);
if (it != categoriesPosition.constEnd()) // If we have it cached
{
// return it
return *it;
}
else // Otherwise, cache it and
{
// return it
return cacheCategory(category);
}
}
QRect DCategorizedView::Private::visualRect(const QModelIndex& index)
{
QRect retRect = cachedRectIndex(index);
int dx = -listView->horizontalOffset();
int dy = -listView->verticalOffset();
retRect.adjust(dx, dy, dx, dy);
return retRect;
}
QRect DCategorizedView::Private::categoryVisualRect(const QString& category)
{
QRect retRect = cachedRectCategory(category);
int dx = -listView->horizontalOffset();
int dy = -listView->verticalOffset();
retRect.adjust(dx, dy, dx, dy);
return retRect;
}
QSize DCategorizedView::Private::contentsSize()
{
// find the last index in the last category
QModelIndex lastIndex = categoriesIndexes.isEmpty() ? QModelIndex()
: proxyModel->index(categoriesIndexes[categories.last()].last(), 0);
int lastItemBottom = cachedRectIndex(lastIndex).top() +
listView->spacing() +
(listView->gridSize().isEmpty() ? biggestItemSize.height()
: listView->gridSize().height()) - listView->viewport()->height();
return QSize(listView->viewport()->width(), lastItemBottom);
}
void DCategorizedView::Private::drawNewCategory(const QModelIndex& index, int sortRole, const QStyleOption& option, QPainter* painter)
{
if (!index.isValid())
{
return;
}
QStyleOption optionCopy = option;
const QString category = proxyModel->data(index, DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
optionCopy.state &= ~QStyle::State_Selected;
if ((listView->selectionMode() != SingleSelection) && (listView->selectionMode() != NoSelection))
{
if ((category == hoveredCategory) && !mouseButtonPressed)
{
optionCopy.state |= QStyle::State_MouseOver;
}
else if ((category == hoveredCategory) && mouseButtonPressed)
{
QPoint initialPressPosition = listView->viewport()->mapFromGlobal(QCursor::pos());
initialPressPosition.setY(initialPressPosition.y() + listView->verticalOffset());
initialPressPosition.setX(initialPressPosition.x() + listView->horizontalOffset());
if (initialPressPosition == this->initialPressPosition)
{
optionCopy.state |= QStyle::State_Selected;
}
}
}
categoryDrawer->drawCategory(index, sortRole, optionCopy, painter);
}
void DCategorizedView::Private::updateScrollbars()
{
listView->horizontalScrollBar()->setRange(0, 0);
if (listView->verticalScrollMode() == QAbstractItemView::ScrollPerItem)
{
listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
}
if (listView->horizontalScrollMode() == QAbstractItemView::ScrollPerItem)
{
listView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
}
listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
listView->verticalScrollBar()->setRange(0, contentsSize().height());
}
void DCategorizedView::Private::drawDraggedItems(QPainter* painter)
{
QStyleOptionViewItem option = listView->viewOptions();
option.state &= ~QStyle::State_MouseOver;
foreach(const QModelIndex& index, listView->selectionModel()->selectedIndexes())
{
const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
option.rect = visualRect(index);
option.rect.adjust(dx, dy, dx, dy);
if (option.rect.intersects(listView->viewport()->rect()))
{
listView->itemDelegate(index)->paint(painter, option, index);
}
}
}
void DCategorizedView::Private::drawDraggedItems()
{
QRect rectToUpdate;
QRect currentRect;
foreach(const QModelIndex& index, listView->selectionModel()->selectedIndexes())
{
int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
currentRect = visualRect(index);
currentRect.adjust(dx, dy, dx, dy);
if (currentRect.intersects(listView->viewport()->rect()))
{
rectToUpdate = rectToUpdate.united(currentRect);
}
}
listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate));
lastDraggedItemsRect = rectToUpdate;
}
// ------------------------------------------------------------------------------------------------
DCategorizedView::DCategorizedView(QWidget* const parent)
: QListView(parent),
d(new Private(this))
{
}
DCategorizedView::~DCategorizedView()
{
delete d;
}
void DCategorizedView::setGridSize(const QSize& size)
{
QListView::setGridSize(size);
slotLayoutChanged();
}
void DCategorizedView::setModel(QAbstractItemModel* model)
{
d->lastSelection = QItemSelection();
d->forcedSelectionPosition = 0;
d->hovered = QModelIndex();
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
d->elementsInfo.clear();
d->elementsPosition.clear();
d->categoriesIndexes.clear();
d->categoriesPosition.clear();
d->categories.clear();
d->intersectedIndexes.clear();
if (d->proxyModel)
{
QObject::disconnect(d->proxyModel, SIGNAL(layoutChanged()),
this, SLOT(slotLayoutChanged()));
QObject::disconnect(d->proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(rowsRemoved(QModelIndex,int,int)));
}
QListView::setModel(model);
d->proxyModel = dynamic_cast<DCategorizedSortFilterProxyModel*>(model);
if (d->proxyModel)
{
QObject::connect(d->proxyModel, SIGNAL(layoutChanged()),
this, SLOT(slotLayoutChanged()));
QObject::connect(d->proxyModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
this, SLOT(rowsRemoved(QModelIndex,int,int)));
if (d->proxyModel->rowCount())
{
slotLayoutChanged();
}
}
}
QRect DCategorizedView::visualRect(const QModelIndex& index) const
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return QListView::visualRect(index);
}
if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
{
return d->visualRect(d->proxyModel->mapFromSource(index));
}
return d->visualRect(index);
}
QRect DCategorizedView::categoryVisualRect(const QModelIndex& index) const
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return QRect();
}
if (!index.isValid())
{
return QRect();
}
QString category = d->elementsInfo[index.row()].category;
return d->categoryVisualRect(category);
}
QModelIndex DCategorizedView::categoryAt(const QPoint& point) const
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return QModelIndex();
}
// We traverse the categories and find the first where point.y() is below the visualRect
int y = 0, lastY = 0;
QString lastCategory;
foreach(const QString& category, d->categories)
{
y = d->categoryVisualRect(category).top();
if (point.y() >= lastY && point.y() < y)
{
break;
}
lastY = y;
y = 0;
lastCategory = category;
}
// if lastCategory is the last one in the list y will be 0
if (!lastCategory.isNull() && point.y() >= lastY && (point.y() < y || !y))
{
return d->proxyModel->index(d->categoriesIndexes[lastCategory][0], d->proxyModel->sortColumn());
}
return QModelIndex();
}
QItemSelectionRange DCategorizedView::categoryRange(const QModelIndex& index) const
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return QItemSelectionRange();
}
if (!index.isValid())
{
return QItemSelectionRange();
}
QString category = d->elementsInfo[index.row()].category;
QModelIndex first = d->proxyModel->index(d->categoriesIndexes[category].first(), d->proxyModel->sortColumn());
QModelIndex last = d->proxyModel->index(d->categoriesIndexes[category].last(), d->proxyModel->sortColumn());
return QItemSelectionRange(first, last);
}
DCategoryDrawer* DCategorizedView::categoryDrawer() const
{
return d->categoryDrawer;
}
void DCategorizedView::setCategoryDrawer(DCategoryDrawer* categoryDrawer)
{
d->lastSelection = QItemSelection();
d->forcedSelectionPosition = 0;
d->hovered = QModelIndex();
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
d->elementsInfo.clear();
d->elementsPosition.clear();
d->categoriesIndexes.clear();
d->categoriesPosition.clear();
d->categories.clear();
d->intersectedIndexes.clear();
d->categoryDrawer = categoryDrawer;
if (categoryDrawer)
{
if (d->proxyModel)
{
if (d->proxyModel->rowCount())
{
slotLayoutChanged();
}
}
}
else
{
updateGeometries();
}
}
void DCategorizedView::setDrawDraggedItems(bool drawDraggedItems)
{
d->drawItemsWhileDragging = drawDraggedItems;
}
QModelIndex DCategorizedView::indexAt(const QPoint& point) const
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return QListView::indexAt(point);
}
QModelIndex index;
const QModelIndexList item = d->intersectionSet(QRect(point, point));
if (item.count() == 1)
{
index = item[0];
}
return index;
}
QModelIndexList DCategorizedView::categorizedIndexesIn(const QRect& rect) const
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return QModelIndexList();
}
return d->intersectionSet(rect);
}
void DCategorizedView::reset()
{
QListView::reset();
d->lastSelection = QItemSelection();
d->forcedSelectionPosition = 0;
d->hovered = QModelIndex();
d->biggestItemSize = QSize(0, 0);
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
d->elementsInfo.clear();
d->elementsPosition.clear();
d->categoriesIndexes.clear();
d->categoriesPosition.clear();
d->categories.clear();
d->intersectedIndexes.clear();
}
void DCategorizedView::paintEvent(QPaintEvent* event)
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
QListView::paintEvent(event);
return;
}
bool alternatingRows = alternatingRowColors();
QStyleOptionViewItem option = viewOptions();
option.widget = this;
if (wordWrap())
{
option.features |= QStyleOptionViewItem::WrapText;
}
QPainter painter(viewport());
QRect area = event->rect();
const bool focus = (hasFocus() || viewport()->hasFocus()) && currentIndex().isValid();
const QStyle::State state = option.state;
const bool enabled = (state & QStyle::State_Enabled) != 0;
painter.save();
QModelIndexList dirtyIndexes = d->intersectionSet(area);
bool alternate = false;
if (dirtyIndexes.count())
{
alternate = dirtyIndexes[0].row() % 2;
}
foreach(const QModelIndex& index, dirtyIndexes)
{
if (alternatingRows && alternate)
{
option.features |= QStyleOptionViewItem::Alternate;
alternate = false;
}
else if (alternatingRows)
{
option.features &= ~QStyleOptionViewItem::Alternate;
alternate = true;
}
option.state = state;
option.rect = visualRect(index);
if (selectionModel() && selectionModel()->isSelected(index))
{
option.state |= QStyle::State_Selected;
}
if (enabled)
{
QPalette::ColorGroup cg;
if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
{
option.state &= ~QStyle::State_Enabled;
cg = QPalette::Disabled;
}
else
{
cg = QPalette::Normal;
}
option.palette.setCurrentColorGroup(cg);
}
if (focus && currentIndex() == index)
{
option.state |= QStyle::State_HasFocus;
if (this->state() == EditingState)
{
option.state |= QStyle::State_Editing;
}
}
if (index == d->hovered)
{
option.state |= QStyle::State_MouseOver;
}
else
{
option.state &= ~QStyle::State_MouseOver;
}
itemDelegate(index)->paint(&painter, option, index);
}
// Redraw categories
QStyleOptionViewItem otherOption;
bool intersectedInThePast = false;
foreach(const QString& category, d->categories)
{
otherOption = option;
otherOption.rect = d->categoryVisualRect(category);
otherOption.state &= ~QStyle::State_MouseOver;
if (otherOption.rect.intersects(area))
{
intersectedInThePast = true;
QModelIndex indexToDraw = d->proxyModel->index(d->categoriesIndexes[category][0],
d->proxyModel->sortColumn());
d->drawNewCategory(indexToDraw, d->proxyModel->sortRole(), otherOption, &painter);
}
else if (intersectedInThePast)
{
break; // the visible area has been finished, we don't need to keep asking, the rest won't intersect
// this is doable because we know that categories are correctly ordered on the list
}
}
if ((selectionMode() != SingleSelection) && (selectionMode() != NoSelection))
{
if (d->mouseButtonPressed && QListView::state() != DraggingState)
{
QPoint start, end, initialPressPosition;
initialPressPosition = d->initialPressPosition;
initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
if (d->initialPressPosition.x() > d->mousePosition.x() ||
d->initialPressPosition.y() > d->mousePosition.y())
{
start = d->mousePosition;
end = initialPressPosition;
}
else
{
start = initialPressPosition;
end = d->mousePosition;
}
QStyleOptionRubberBand yetAnotherOption;
yetAnotherOption.initFrom(this);
yetAnotherOption.shape = QRubberBand::Rectangle;
yetAnotherOption.opaque = false;
yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
painter.save();
style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
painter.restore();
}
}
if (d->drawItemsWhileDragging && QListView::state() == DraggingState && !d->dragLeftViewport)
{
painter.setOpacity(0.5);
d->drawDraggedItems(&painter);
}
painter.restore();
}
void DCategorizedView::resizeEvent(QResizeEvent* event)
{
QListView::resizeEvent(event);
// Clear the items positions cache
d->elementsPosition.clear();
d->categoriesPosition.clear();
d->forcedSelectionPosition = 0;
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return;
}
d->updateScrollbars();
}
QItemSelection DCategorizedView::Private::selectionForRect(const QRect& rect)
{
QItemSelection selection;
QModelIndex tl, br;
QModelIndexList intersectedIndexes = intersectionSet(rect);
QList<QModelIndex>::const_iterator it = intersectedIndexes.constBegin();
for (; it != intersectedIndexes.constEnd(); ++it)
{
if (!tl.isValid() && !br.isValid())
{
tl = br = *it;
}
else if ((*it).row() == (tl.row() - 1))
{
tl = *it; // expand current range
}
else if ((*it).row() == (br.row() + 1))
{
br = (*it); // expand current range
}
else
{
selection.select(tl, br); // select current range
tl = br = *it; // start new range
}
}
if (tl.isValid() && br.isValid())
{
selection.select(tl, br);
}
else if (tl.isValid())
{
selection.select(tl, tl);
}
else if (br.isValid())
{
selection.select(br, br);
}
return selection;
}
void DCategorizedView::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags command)
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
QListView::setSelection(rect, command);
return;
}
QItemSelection selection;
// QRect contentsRect = rect.translated(horizontalOffset(), verticalOffset());
QModelIndexList intersectedIndexes = d->intersectionSet(rect);
if (rect.width() == 1 && rect.height() == 1)
{
QModelIndex tl;
if (!intersectedIndexes.isEmpty())
{
tl = intersectedIndexes.last(); // special case for mouse press; only select the top item
}
if (tl.isValid() && (tl.flags() & Qt::ItemIsEnabled))
{
selection.select(tl, tl);
}
}
else
{
if (state() == DragSelectingState)
{
// visual selection mode (rubberband selection)
selection = d->selectionForRect(rect);
}
else
{
// logical selection mode (key and mouse click selection)
QModelIndex tl, br;
// get the first item
const QRect topLeft(rect.left(), rect.top(), 1, 1);
intersectedIndexes = d->intersectionSet(topLeft);
if (!intersectedIndexes.isEmpty())
{
tl = intersectedIndexes.last();
}
// get the last item
const QRect bottomRight(rect.right(), rect.bottom(), 1, 1);
intersectedIndexes = d->intersectionSet(bottomRight);
if (!intersectedIndexes.isEmpty())
{
br = intersectedIndexes.last();
}
// get the ranges
if (tl.isValid() && br.isValid() &&
(tl.flags() & Qt::ItemIsEnabled) &&
(br.flags() & Qt::ItemIsEnabled))
{
// first, middle, last in content coordinates
QRect middle;
QRect first = d->cachedRectIndex(tl);
QRect last = d->cachedRectIndex(br);
QSize fullSize = d->contentsSize();
if (flow() == LeftToRight)
{
QRect& top = first;
QRect& bottom = last;
// if bottom is above top, swap them
if (top.center().y() > bottom.center().y())
{
QRect tmp = top;
top = bottom;
bottom = tmp;
}
// if the rect are on differnet lines, expand
if (top.top() != bottom.top())
{
// top rectangle
if (isRightToLeft())
{
top.setLeft(0);
}
else
{
top.setRight(fullSize.width());
}
// bottom rectangle
if (isRightToLeft())
{
bottom.setRight(fullSize.width());
}
else
{
bottom.setLeft(0);
}
}
else if (top.left() > bottom.right())
{
if (isRightToLeft())
{
bottom.setLeft(top.right());
}
else
{
bottom.setRight(top.left());
}
}
else
{
if (isRightToLeft())
{
top.setLeft(bottom.right());
}
else
{
top.setRight(bottom.left());
}
}
// middle rectangle
if (top.bottom() < bottom.top())
{
middle.setTop(top.bottom() + 1);
middle.setLeft(qMin(top.left(), bottom.left()));
middle.setBottom(bottom.top() - 1);
middle.setRight(qMax(top.right(), bottom.right()));
}
}
else
{
// TopToBottom
QRect& left = first;
QRect& right = last;
if (left.center().x() > right.center().x())
{
std::swap(left, right);
}
int ch = fullSize.height();
if (left.left() != right.left())
{
// left rectangle
if (isRightToLeft())
{
left.setTop(0);
}
else
{
left.setBottom(ch);
}
// top rectangle
if (isRightToLeft())
{
right.setBottom(ch);
}
else
{
right.setTop(0);
}
// only set middle if the
middle.setTop(0);
middle.setBottom(ch);
middle.setLeft(left.right() + 1);
middle.setRight(right.left() - 1);
}
else if (left.bottom() < right.top())
{
left.setBottom(right.top() - 1);
}
else
{
right.setBottom(left.top() - 1);
}
}
// get viewport coordinates
first = first.translated( - horizontalOffset(), - verticalOffset());
middle = middle.translated( - horizontalOffset(), - verticalOffset());
last = last.translated( - horizontalOffset(), - verticalOffset());
// do the selections
QItemSelection topSelection = d->selectionForRect(first);
QItemSelection middleSelection = d->selectionForRect(middle);
QItemSelection bottomSelection = d->selectionForRect(last);
// merge
selection.merge(topSelection, QItemSelectionModel::Select);
selection.merge(middleSelection, QItemSelectionModel::Select);
selection.merge(bottomSelection, QItemSelectionModel::Select);
}
}
}
selectionModel()->select(selection, command);
}
void DCategorizedView::mouseMoveEvent(QMouseEvent* event)
{
QListView::mouseMoveEvent(event);
// was a dragging started?
if (state() == DraggingState)
{
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
if (d->drawItemsWhileDragging)
{
viewport()->update(d->lastDraggedItemsRect);
}
}
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return;
}
const QModelIndexList item = d->intersectionSet(QRect(event->pos(), event->pos()));
if (item.count() == 1)
{
d->hovered = item[0];
}
else
{
d->hovered = QModelIndex();
}
const QString previousHoveredCategory = d->hoveredCategory;
d->mousePosition = event->pos();
d->hoveredCategory.clear();
// Redraw categories
foreach(const QString& category, d->categories)
{
if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
{
d->hoveredCategory = category;
viewport()->update(d->categoryVisualRect(category));
}
else if ((category == previousHoveredCategory) &&
(!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos()))))
{
viewport()->update(d->categoryVisualRect(category));
}
}
QRect rect;
if (d->mouseButtonPressed && QListView::state() != DraggingState)
{
QPoint start, end, initialPressPosition;
initialPressPosition = d->initialPressPosition;
initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
if (d->initialPressPosition.x() > d->mousePosition.x() ||
d->initialPressPosition.y() > d->mousePosition.y())
{
start = d->mousePosition;
end = initialPressPosition;
}
else
{
start = initialPressPosition;
end = d->mousePosition;
}
rect = QRect(start, end).adjusted(-16, -16, 16, 16);
rect = rect.united(QRect(start, end).adjusted(16, 16, -16, -16)).intersected(viewport()->rect());
viewport()->update(rect);
}
}
void DCategorizedView::mousePressEvent(QMouseEvent* event)
{
d->dragLeftViewport = false;
QListView::mousePressEvent(event);
if (event->button() == Qt::LeftButton)
{
d->mouseButtonPressed = true;
d->initialPressPosition = event->pos();
d->initialPressPosition.setY(d->initialPressPosition.y() + verticalOffset());
d->initialPressPosition.setX(d->initialPressPosition.x() + horizontalOffset());
}
else if (event->button() == Qt::RightButton)
{
d->rightMouseButtonPressed = true;
}
if (selectionModel())
{
d->lastSelection = selectionModel()->selection();
}
viewport()->update(d->categoryVisualRect(d->hoveredCategory));
}
void DCategorizedView::mouseReleaseEvent(QMouseEvent* event)
{
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
QListView::mouseReleaseEvent(event);
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return;
}
QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
if ((selectionMode() != SingleSelection) && (selectionMode() != NoSelection) &&
(initialPressPosition == d->initialPressPosition))
{
foreach(const QString& category, d->categories)
{
if (d->categoryVisualRect(category).contains(event->pos()) &&
selectionModel())
{
QItemSelection selection = selectionModel()->selection();
const QVector<int> &indexList = d->categoriesIndexes[category];
foreach(int row, indexList)
{
QModelIndex selectIndex = d->proxyModel->index(row, 0);
selection << QItemSelectionRange(selectIndex);
}
selectionModel()->select(selection, QItemSelectionModel::SelectCurrent);
break;
}
}
}
QRect rect;
if (state() != DraggingState)
{
QPoint start, end, initialPressPosition;
initialPressPosition = d->initialPressPosition;
initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
if (d->initialPressPosition.x() > d->mousePosition.x() ||
d->initialPressPosition.y() > d->mousePosition.y())
{
start = d->mousePosition;
end = initialPressPosition;
}
else
{
start = initialPressPosition;
end = d->mousePosition;
}
rect = QRect(start, end).adjusted(-16, -16, 16, 16);
rect = rect.united(QRect(start, end).adjusted(16, 16, -16, -16)).intersected(viewport()->rect());
viewport()->update(rect);
}
if (d->hovered.isValid())
{
viewport()->update(visualRect(d->hovered));
}
else if (!d->hoveredCategory.isEmpty())
{
viewport()->update(d->categoryVisualRect(d->hoveredCategory));
}
}
void DCategorizedView::leaveEvent(QEvent* event)
{
d->hovered = QModelIndex();
d->hoveredCategory.clear();
QListView::leaveEvent(event);
}
void DCategorizedView::startDrag(Qt::DropActions supportedActions)
{
// FIXME: QAbstractItemView does far better here since it sets the
// pixmap of selected icons to the dragging cursor, but it sets a non
// ARGB window so it is no transparent. Use QAbstractItemView when
// this is fixed on Qt.
// QAbstractItemView::startDrag(supportedActions);
#if defined(DOLPHIN_DRAGANDDROP)
Q_UNUSED(supportedActions);
#else
QListView::startDrag(supportedActions);
#endif
}
void DCategorizedView::dragMoveEvent(QDragMoveEvent* event)
{
d->mousePosition = event->pos();
d->dragLeftViewport = false;
#if defined(DOLPHIN_DRAGANDDROP)
QAbstractItemView::dragMoveEvent(event);
#else
QListView::dragMoveEvent(event);
#endif
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
return;
}
d->hovered = indexAt(event->pos());
#if !defined(DOLPHIN_DRAGANDDROP)
d->drawDraggedItems();
#endif
}
void DCategorizedView::dragLeaveEvent(QDragLeaveEvent* event)
{
d->dragLeftViewport = true;
#if defined(DOLPHIN_DRAGANDDROP)
QAbstractItemView::dragLeaveEvent(event);
#else
QListView::dragLeaveEvent(event);
#endif
}
void DCategorizedView::dropEvent(QDropEvent* event)
{
#if defined(DOLPHIN_DRAGANDDROP)
QAbstractItemView::dropEvent(event);
#else
QListView::dropEvent(event);
#endif
}
QModelIndex DCategorizedView::moveCursor(CursorAction cursorAction,
Qt::KeyboardModifiers modifiers)
{
if ((viewMode() != DCategorizedView::IconMode) ||
!d->proxyModel ||
!d->categoryDrawer ||
d->categories.isEmpty() ||
!d->proxyModel->isCategorizedModel())
{
return QListView::moveCursor(cursorAction, modifiers);
}
int viewportWidth = viewport()->width() - spacing();
int itemWidth;
if (gridSize().isEmpty())
{
itemWidth = d->biggestItemSize.width();
}
else
{
itemWidth = gridSize().width();
}
int itemWidthPlusSeparation = spacing() + itemWidth;
if (!itemWidthPlusSeparation)
{
++itemWidthPlusSeparation;
}
int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
if (!elementsPerRow)
{
++elementsPerRow;
}
QModelIndex current = selectionModel() ? selectionModel()->currentIndex()
: QModelIndex();
if (!current.isValid())
{
if (cursorAction == MoveEnd)
{
current = model()->index(model()->rowCount() - 1, 0, QModelIndex());
//d->forcedSelectionPosition = d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow;
}
else
{
current = model()->index(0, 0, QModelIndex());
d->forcedSelectionPosition = 0;
}
return current;
}
QString lastCategory = d->categories.first();
QString theCategory = d->categories.first();
QString afterCategory = d->categories.first();
bool hasToBreak = false;
foreach(const QString& category, d->categories)
{
if (hasToBreak)
{
afterCategory = category;
break;
}
if (category == d->elementsInfo[current.row()].category)
{
theCategory = category;
hasToBreak = true;
}
if (!hasToBreak)
{
lastCategory = category;
}
}
switch (cursorAction)
{
case QAbstractItemView::MovePageUp:
{
- // We need to reimplemt PageUp/Down as well because
+ // We need to reimplement PageUp/Down as well because
// default QListView implementation will not work properly with our custom layout
QModelIndexList visibleIndexes = d->intersectionSet(viewport()->rect());
if (!visibleIndexes.isEmpty())
{
int indexToMove = qMax(current.row() - visibleIndexes.size(), 0);
return d->proxyModel->index(indexToMove, 0);
}
break;
}
// fall through
case QAbstractItemView::MoveUp:
{
if (d->elementsInfo[current.row()].relativeOffsetToCategory >= elementsPerRow)
{
int indexToMove = current.row();
indexToMove -= qMin(((d->elementsInfo[current.row()].relativeOffsetToCategory) +
d->forcedSelectionPosition), elementsPerRow - d->forcedSelectionPosition +
(d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow));
return d->proxyModel->index(indexToMove, 0);
}
else
{
int lastCategoryLastRow = (d->categoriesIndexes[lastCategory].count() - 1) % elementsPerRow;
int indexToMove = current.row() - d->elementsInfo[current.row()].relativeOffsetToCategory;
if (d->forcedSelectionPosition >= lastCategoryLastRow)
{
indexToMove -= 1;
}
else
{
indexToMove -= qMin((lastCategoryLastRow - d->forcedSelectionPosition + 1),
d->forcedSelectionPosition + elementsPerRow + 1);
}
return d->proxyModel->index(indexToMove, 0);
}
}
case QAbstractItemView::MovePageDown:
{
QModelIndexList visibleIndexes = d->intersectionSet(viewport()->rect());
if (!visibleIndexes.isEmpty())
{
int indexToMove = qMin(current.row() + visibleIndexes.size(), d->elementsInfo.size() - 1);
return d->proxyModel->index(indexToMove, 0);
}
}
// fall through
case QAbstractItemView::MoveDown:
{
if (d->elementsInfo[current.row()].relativeOffsetToCategory < (d->categoriesIndexes[theCategory].count() - 1 - ((d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow)))
{
int indexToMove = current.row();
indexToMove += qMin(elementsPerRow, d->categoriesIndexes[theCategory].count() - 1 -
d->elementsInfo[current.row()].relativeOffsetToCategory);
return d->proxyModel->index(indexToMove, 0);
}
else
{
int afterCategoryLastRow = qMin(elementsPerRow, d->categoriesIndexes[afterCategory].count());
int indexToMove = current.row() + (d->categoriesIndexes[theCategory].count() -
d->elementsInfo[current.row()].relativeOffsetToCategory);
if (d->forcedSelectionPosition >= afterCategoryLastRow)
{
indexToMove += afterCategoryLastRow - 1;
}
else
{
indexToMove += qMin(d->forcedSelectionPosition, elementsPerRow);
}
return d->proxyModel->index(indexToMove, 0);
}
}
case QAbstractItemView::MoveLeft:
if (layoutDirection() == Qt::RightToLeft)
{
if (current.row() + 1 == d->elementsInfo.size() ||
!(d->elementsInfo[current.row() + 1].relativeOffsetToCategory % elementsPerRow))
{
return current;
}
return d->proxyModel->index(current.row() + 1, 0);
}
if (current.row() == 0 ||
!(d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow))
{
return current;
}
return d->proxyModel->index(current.row() - 1, 0);
case QAbstractItemView::MoveRight:
if (layoutDirection() == Qt::RightToLeft)
{
if (current.row() == 0 ||
!(d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow))
{
return current;
}
return d->proxyModel->index(current.row() - 1, 0);
}
if (current.row() + 1 == d->elementsInfo.size() ||
!(d->elementsInfo[current.row() + 1].relativeOffsetToCategory % elementsPerRow))
{
return current;
}
return d->proxyModel->index(current.row() + 1, 0);
default:
break;
}
return QListView::moveCursor(cursorAction, modifiers);
}
void DCategorizedView::rowsInserted(const QModelIndex& parent, int start, int end)
{
QListView::rowsInserted(parent, start, end);
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
d->forcedSelectionPosition = 0;
d->hovered = QModelIndex();
d->biggestItemSize = QSize(0, 0);
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
d->elementsInfo.clear();
d->elementsPosition.clear();
d->categoriesIndexes.clear();
d->categoriesPosition.clear();
d->categories.clear();
d->intersectedIndexes.clear();
return;
}
rowsInsertedArtifficial(parent, start, end);
}
int DCategorizedView::Private::categoryUpperBound(SparseModelIndexVector& modelIndexList, int begin, int averageSize)
{
int end = modelIndexList.size();
QString category = proxyModel->data(modelIndexList[begin],
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
// First case: Small category with <10 entries
const int smallEnd = qMin(end, begin + 10);
for (int k=begin; k < smallEnd; ++k)
{
if (category != proxyModel->data(modelIndexList[k],
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString())
{
return k;
}
}
begin += 10;
// Second case: only one category, test last value
QString value = proxyModel->data(modelIndexList[end - 1],
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
if (value == category)
{
return end;
}
// Third case: use average of last category sizes
if (averageSize && begin + averageSize < end)
{
if (category != proxyModel->data(modelIndexList[begin + averageSize],
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString())
{
end = begin + averageSize;
}
else if (begin + 2*averageSize < end)
{
if (category != proxyModel->data(modelIndexList[begin + 2*averageSize],
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString())
{
end = begin + 2 * averageSize;
}
}
}
// now apply a binary search - the model is sorted by category
// from qUpperBound, Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
int middle;
int n = end - begin;
int half;
while (n > 0)
{
half = n >> 1;
middle = begin + half;
if (category != proxyModel->data(modelIndexList[middle],
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString())
{
n = half;
}
else
{
begin = middle + 1;
n -= half + 1;
}
}
return begin;
}
void DCategorizedView::rowsInsertedArtifficial(const QModelIndex& parent, int start, int end)
{
Q_UNUSED(parent);
d->forcedSelectionPosition = 0;
d->hovered = QModelIndex();
d->biggestItemSize = QSize(0, 0);
d->mouseButtonPressed = false;
d->rightMouseButtonPressed = false;
d->elementsInfo.clear();
d->elementsPosition.clear();
d->categoriesIndexes.clear();
d->categoriesPosition.clear();
d->categories.clear();
d->intersectedIndexes.clear();
if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
{
return;
}
// Add all elements mapped to the source model and explore categories
const int rowCount = d->proxyModel->rowCount();
const int sortColumn = d->proxyModel->sortColumn();
QString lastCategory = d->proxyModel->data(d->proxyModel->index(0, sortColumn),
DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
int offset = -1;
SparseModelIndexVector modelIndexList(rowCount, d->proxyModel, sortColumn);
d->elementsInfo = QVector<struct Private::ElementInfo>(rowCount);
int categorySizes = 0;
int categoryCounts = 0;
if (uniformItemSizes())
{
// use last index as sample for size hint
QModelIndex sample = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex());
d->biggestItemSize = sizeHintForIndex(sample);
}
else
{
QStyleOptionViewItem option = viewOptions();
for (int k = 0 ; k < rowCount ; ++k)
{
QModelIndex indexSize = (sortColumn == 0) ? modelIndexList[k] : d->proxyModel->index(k, 0);
QSize hint = itemDelegate(indexSize)->sizeHint(option, indexSize);
d->biggestItemSize = QSize(qMax(hint.width(), d->biggestItemSize.width()),
qMax(hint.height(), d->biggestItemSize.height()));
}
}
for (int k = 0; k < rowCount; )
{
lastCategory = d->proxyModel->data(modelIndexList[k], DCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
int upperBound = d->categoryUpperBound(modelIndexList, k, categorySizes / ++categoryCounts);
categorySizes += upperBound - k;
offset = 0;
QVector<int> rows(upperBound - k);
for (int i = k ; i < upperBound ; ++i, ++offset)
{
rows[offset] = i;
struct Private::ElementInfo& elementInfo = d->elementsInfo[i];
elementInfo.category = lastCategory;
elementInfo.relativeOffsetToCategory = offset;
}
k = upperBound;
d->categoriesIndexes.insert(lastCategory, rows);
d->categories << lastCategory;
}
d->updateScrollbars();
// FIXME: We need to safely save the last selection. This is on my TODO
// list (ereslibre).
// Note: QItemSelectionModel will save it selection in persistend indexes
// on layoutChanged(). All works fine for me.
//selectionModel()->clear();
}
void DCategorizedView::rowsRemoved(const QModelIndex& parent, int start, int end)
{
Q_UNUSED(parent);
Q_UNUSED(start);
Q_UNUSED(end);
if (d->proxyModel && d->categoryDrawer && d->proxyModel->isCategorizedModel())
{
// Force the view to update all elements
rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
}
}
void DCategorizedView::updateGeometries()
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
QListView::updateGeometries();
return;
}
// Avoid QListView::updateGeometries(), since it will try to set another
// range to our scroll bars, what we don't want (ereslibre)
QAbstractItemView::updateGeometries();
}
void DCategorizedView::slotLayoutChanged()
{
if (d->proxyModel && d->categoryDrawer && d->proxyModel->isCategorizedModel())
{
// all cached values are invalidated, recompute immediately
rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
}
}
void DCategorizedView::currentChanged(const QModelIndex& current, const QModelIndex& previous)
{
if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
{
QListView::currentChanged(current, previous);
return;
}
// We need to update the forcedSelectionPosition property in order to correctly
// navigate after with keyboard using up & down keys
int viewportWidth = viewport()->width() - spacing();
// int itemHeight;
int itemWidth;
if (gridSize().isEmpty())
{
// itemHeight = d->biggestItemSize.height();
itemWidth = d->biggestItemSize.width();
}
else
{
// itemHeight = gridSize().height();
itemWidth = gridSize().width();
}
int itemWidthPlusSeparation = spacing() + itemWidth;
if (!itemWidthPlusSeparation)
{
++itemWidthPlusSeparation;
}
int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
if (!elementsPerRow)
{
++elementsPerRow;
}
if (current.isValid())
{
d->forcedSelectionPosition = d->elementsInfo[current.row()].relativeOffsetToCategory % elementsPerRow;
}
QListView::currentChanged(current, previous);
}
} // namespace Digikam
diff --git a/core/libs/widgets/itemview/dcategorydrawer.h b/core/libs/widgets/itemview/dcategorydrawer.h
index 9fe3dc4046..e24c410567 100644
--- a/core/libs/widgets/itemview/dcategorydrawer.h
+++ b/core/libs/widgets/itemview/dcategorydrawer.h
@@ -1,184 +1,184 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-01-16
* Description : drawing item view based on DCategorizedView
*
* Copyright (C) 2007 by Rafael Fernández López <ereslibre at kde dot org>
* Copyright (C) 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_DCATEGORY_DRAWER_H
#define DIGIKAM_DCATEGORY_DRAWER_H
// Qt includes
#include <QObject>
#include <QMouseEvent>
// Local includes
#include "digikam_export.h"
class QPainter;
class QModelIndex;
class QStyleOption;
namespace Digikam
{
class DCategorizedView;
/**
* The category drawing is performed by this class. It also gives information about the category
* height and margins.
*
*/
class DIGIKAM_EXPORT DCategoryDrawer : public QObject
{
Q_OBJECT
friend class DCategorizedView;
public:
/*
* Construct a category drawer for a given view
*
* @since 5.0
*/
explicit DCategoryDrawer(DCategorizedView* const view);
virtual ~DCategoryDrawer();
/**
* @return The view this category drawer is associated with.
*/
DCategorizedView* view() const;
/**
* This method purpose is to draw a category represented by the given
* @param index with the given @param sortRole sorting role
*
* @note This method will be called one time per category, always with the
* first element in that category
*/
virtual void drawCategory(const QModelIndex& index,
int sortRole,
const QStyleOption& option,
QPainter* painter) const;
/**
- * @return The category height for the category representated by index @p index with
+ * @return The category height for the category represented by index @p index with
* style options @p option.
*/
virtual int categoryHeight(const QModelIndex& index, const QStyleOption& option) const;
/**
* @note 0 by default
*
*/
virtual int leftMargin() const;
/**
* @note 0 by default
*
*/
virtual int rightMargin() const;
Q_SIGNALS:
/**
* This signal becomes emitted when collapse or expand has been clicked.
*/
void collapseOrExpandClicked(const QModelIndex& index);
/**
* Emit this signal on your subclass implementation to notify that something happened. Usually
* this will be triggered when you have received an event, and its position matched some "hot spot".
*
* You give this action the integer you want, and having connected this signal to your code,
* the connected slot can perform the needed changes (view, model, selection model, delegate...)
*/
void actionRequested(int action, const QModelIndex& index);
protected:
/**
* Method called when the mouse button has been pressed.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*
* @warning You explicitly have to determine whether the event has been accepted or not. You
* have to call event->accept() or event->ignore() at all possible case branches in
* your code.
*/
virtual void mouseButtonPressed(const QModelIndex& index, const QRect& blockRect, QMouseEvent* event);
/**
* Method called when the mouse button has been released.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*
* @warning You explicitly have to determine whether the event has been accepted or not. You
* have to call event->accept() or event->ignore() at all possible case branches in
* your code.
*/
virtual void mouseButtonReleased(const QModelIndex& index, const QRect& blockRect, QMouseEvent* event);
/**
* Method called when the mouse has been moved.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*/
virtual void mouseMoved(const QModelIndex& index, const QRect& blockRect, QMouseEvent* event);
/**
* Method called when the mouse button has been double clicked.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
* @param event The mouse event.
*
* @warning You explicitly have to determine whether the event has been accepted or not. You
* have to call event->accept() or event->ignore() at all possible case branches in
* your code.
*/
virtual void mouseButtonDoubleClicked(const QModelIndex& index, const QRect& blockRect, QMouseEvent* event);
/**
* Method called when the mouse button has left this block.
*
* @param index The representative index of the block of items.
* @param blockRect The rect occupied by the block of items.
*/
virtual void mouseLeft(const QModelIndex& index, const QRect& blockRect);
private:
class Private;
Private *const d;
};
} // namespace Digikam
#endif // DIGIKAM_DCATEGORY_DRAWER_H
diff --git a/core/libs/widgets/itemview/itemviewcategorized.cpp b/core/libs/widgets/itemview/itemviewcategorized.cpp
index 1a2eaabad0..19fdc1f24b 100644
--- a/core/libs/widgets/itemview/itemviewcategorized.cpp
+++ b/core/libs/widgets/itemview/itemviewcategorized.cpp
@@ -1,1062 +1,1062 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-01-16
* Description : Qt item view for images
*
* Copyright (C) 2009-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "itemviewcategorized.h"
// Qt includes
#include <QClipboard>
#include <QHelpEvent>
#include <QScrollBar>
#include <QSortFilterProxyModel>
#include <QStyle>
#include <QApplication>
// Local includes
#include "digikam_debug.h"
#include "thememanager.h"
#include "ditemdelegate.h"
#include "abstractitemdragdrophandler.h"
#include "itemviewtooltip.h"
namespace Digikam
{
class Q_DECL_HIDDEN ItemViewCategorized::Private
{
public:
explicit Private(ItemViewCategorized* const q)
: delegate(0),
toolTip(0),
notificationToolTip(0),
showToolTip(false),
usePointingHand(true),
scrollStepFactor(10),
currentMouseEvent(0),
ensureOneSelectedItem(false),
ensureInitialSelectedItem(false),
scrollCurrentToCenter(false),
mouseButtonPressed(Qt::NoButton),
hintAtSelectionRow(-1),
q(q)
{
}
QModelIndex scrollPositionHint() const;
public:
DItemDelegate* delegate;
ItemViewToolTip* toolTip;
ItemViewToolTip* notificationToolTip;
bool showToolTip;
bool usePointingHand;
int scrollStepFactor;
QMouseEvent* currentMouseEvent;
bool ensureOneSelectedItem;
bool ensureInitialSelectedItem;
bool scrollCurrentToCenter;
Qt::MouseButton mouseButtonPressed;
QPersistentModelIndex hintAtSelectionIndex;
int hintAtSelectionRow;
QPersistentModelIndex hintAtScrollPosition;
ItemViewCategorized* const q;
};
QModelIndex ItemViewCategorized::Private::scrollPositionHint() const
{
if (q->verticalScrollBar()->value() == q->verticalScrollBar()->minimum())
{
return QModelIndex();
}
QModelIndex hint = q->currentIndex();
// If the user scrolled, do not take current item, but first visible
if (!hint.isValid() || !q->viewport()->rect().intersects(q->visualRect(hint)))
{
QList<QModelIndex> visibleIndexes = q->categorizedIndexesIn(q->viewport()->rect());
if (!visibleIndexes.isEmpty())
{
hint = visibleIndexes.first();
}
}
return hint;
}
// -------------------------------------------------------------------------------
ItemViewCategorized::ItemViewCategorized(QWidget* const parent)
: DCategorizedView(parent),
d(new Private(this))
{
setViewMode(QListView::IconMode);
setLayoutDirection(Qt::LeftToRight);
setFlow(QListView::LeftToRight);
setResizeMode(QListView::Adjust);
setMovement(QListView::Static);
setWrapping(true);
// important optimization for layouting
setUniformItemSizes(true);
// disable "feature" from DCategorizedView
setDrawDraggedItems(false);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setDragEnabled(true);
setEditTriggers(QAbstractItemView::NoEditTriggers);
viewport()->setAcceptDrops(true);
setMouseTracking(true);
connect(this, SIGNAL(activated(QModelIndex)),
this, SLOT(slotActivated(QModelIndex)));
connect(this, SIGNAL(clicked(QModelIndex)),
this, SLOT(slotClicked(QModelIndex)));
connect(this, SIGNAL(entered(QModelIndex)),
this, SLOT(slotEntered(QModelIndex)));
connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()),
this, SLOT(slotThemeChanged()));
}
ItemViewCategorized::~ItemViewCategorized()
{
delete d;
}
void ItemViewCategorized::setToolTip(ItemViewToolTip* tip)
{
d->toolTip = tip;
}
void ItemViewCategorized::setItemDelegate(DItemDelegate* delegate)
{
if (d->delegate == delegate)
{
return;
}
if (d->delegate)
{
disconnect(d->delegate, SIGNAL(gridSizeChanged(QSize)),
this, SLOT(slotGridSizeChanged(QSize)));
}
d->delegate = delegate;
DCategorizedView::setItemDelegate(d->delegate);
connect(d->delegate, SIGNAL(gridSizeChanged(QSize)),
this, SLOT(slotGridSizeChanged(QSize)));
}
void ItemViewCategorized::setSpacing(int spacing)
{
d->delegate->setSpacing(spacing);
}
void ItemViewCategorized::setUsePointingHandCursor(bool useCursor)
{
d->usePointingHand = useCursor;
}
void ItemViewCategorized::setScrollStepGranularity(int factor)
{
d->scrollStepFactor = qMax(1, factor);
}
DItemDelegate* ItemViewCategorized::delegate() const
{
return d->delegate;
}
int ItemViewCategorized::numberOfSelectedIndexes() const
{
return selectedIndexes().size();
}
void ItemViewCategorized::toFirstIndex()
{
QModelIndex index = moveCursor(MoveHome, Qt::NoModifier);
clearSelection();
setCurrentIndex(index);
scrollToTop();
}
void ItemViewCategorized::toLastIndex()
{
QModelIndex index = moveCursor(MoveEnd, Qt::NoModifier);
clearSelection();
setCurrentIndex(index);
scrollToBottom();
}
void ItemViewCategorized::toNextIndex()
{
toIndex(moveCursor(MoveNext, Qt::NoModifier));
}
void ItemViewCategorized::toPreviousIndex()
{
toIndex(moveCursor(MovePrevious, Qt::NoModifier));
}
void ItemViewCategorized::toIndex(const QModelIndex& index)
{
if (!index.isValid())
{
return;
}
clearSelection();
setCurrentIndex(index);
scrollTo(index);
}
void ItemViewCategorized::awayFromSelection()
{
QItemSelection selection = selectionModel()->selection();
if (selection.isEmpty())
{
return;
}
const QModelIndex first = model()->index(0, 0);
const QModelIndex last = model()->index(model()->rowCount() - 1, 0);
if (selection.contains(first) && selection.contains(last))
{
QItemSelection remaining(first, last);
remaining.merge(selection, QItemSelectionModel::Toggle);
QList<QModelIndex> indexes = remaining.indexes();
if (indexes.isEmpty())
{
clearSelection();
setCurrentIndex(QModelIndex());
}
else
{
toIndex(remaining.indexes().first());
}
}
else if (selection.contains(last))
{
setCurrentIndex(selection.indexes().first());
toPreviousIndex();
}
else
{
setCurrentIndex(selection.indexes().last());
toNextIndex();
}
}
void ItemViewCategorized::scrollToRelaxed(const QModelIndex& index, QAbstractItemView::ScrollHint hint)
{
if (viewport()->rect().intersects(visualRect(index)))
{
return;
}
scrollTo(index, hint);
}
void ItemViewCategorized::invertSelection()
{
const QModelIndex topLeft = model()->index(0, 0);
const QModelIndex bottomRight = model()->index(model()->rowCount() - 1, 0);
const QItemSelection selection(topLeft, bottomRight);
selectionModel()->select(selection, QItemSelectionModel::Toggle);
}
void ItemViewCategorized::setSelectedIndexes(const QList<QModelIndex>& indexes)
{
if (selectedIndexes() == indexes)
{
return;
}
QItemSelection mySelection;
foreach(const QModelIndex& index, indexes)
{
mySelection.select(index, index);
}
selectionModel()->select(mySelection, QItemSelectionModel::ClearAndSelect);
}
void ItemViewCategorized::setToolTipEnabled(bool enable)
{
d->showToolTip = enable;
}
bool ItemViewCategorized::isToolTipEnabled() const
{
return d->showToolTip;
}
void ItemViewCategorized::slotThemeChanged()
{
viewport()->update();
}
void ItemViewCategorized::slotSetupChanged()
{
viewport()->update();
}
void ItemViewCategorized::slotGridSizeChanged(const QSize& gridSize)
{
setGridSize(gridSize);
if (!gridSize.isNull())
{
horizontalScrollBar()->setSingleStep(gridSize.width() / d->scrollStepFactor);
verticalScrollBar()->setSingleStep(gridSize.height() / d->scrollStepFactor);
}
}
void ItemViewCategorized::updateDelegateSizes()
{
QStyleOptionViewItem option = viewOptions();
/*
int frameAroundContents = 0;
if (style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents))
{
frameAroundContents = style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2;
}
const int contentWidth = viewport()->width() - 1
- frameAroundContents
- style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, verticalScrollBar());
const int contentHeight = viewport()->height() - 1
- frameAroundContents
- style()->pixelMetric(QStyle::PM_ScrollBarExtent, 0, horizontalScrollBar());
option.rect = QRect(0, 0, contentWidth, contentHeight);
*/
option.rect = QRect(QPoint(0, 0), viewport()->size());
d->delegate->setDefaultViewOptions(option);
}
void ItemViewCategorized::slotActivated(const QModelIndex& index)
{
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
if (d->currentMouseEvent)
{
// Ignore activation if Ctrl or Shift is pressed (for selection)
modifiers = d->currentMouseEvent->modifiers();
const bool shiftKeyPressed = modifiers & Qt::ShiftModifier;
const bool controlKeyPressed = modifiers & Qt::ControlModifier;
if (shiftKeyPressed || controlKeyPressed)
{
return;
}
const bool rightClick = d->currentMouseEvent->button() & Qt::RightButton;
if (rightClick)
{
return;
}
// if the activation is caused by mouse click (not keyboard)
// we need to check the hot area
if (d->currentMouseEvent->isAccepted() &&
!d->delegate->acceptsActivation(d->currentMouseEvent->pos(), visualRect(index), index))
{
return;
}
}
d->currentMouseEvent = 0;
indexActivated(index, modifiers);
}
void ItemViewCategorized::slotClicked(const QModelIndex& index)
{
if (d->currentMouseEvent)
{
emit clicked(d->currentMouseEvent, index);
}
}
void ItemViewCategorized::slotEntered(const QModelIndex& index)
{
if (d->currentMouseEvent)
{
emit entered(d->currentMouseEvent, index);
}
}
void ItemViewCategorized::reset()
{
DCategorizedView::reset();
- // FIXME : Emiting this causes a crash importstackedview, because the model is not yet set.
- // atm there's a check agaisnt null models though.
+ // FIXME : Emitting this causes a crash importstackedview, because the model is not yet set.
+ // atm there's a check against null models though.
emit selectionChanged();
emit selectionCleared();
d->ensureInitialSelectedItem = true;
d->hintAtScrollPosition = QModelIndex();
d->hintAtSelectionIndex = QModelIndex();
d->hintAtSelectionRow = -1;
verticalScrollBar()->setValue(verticalScrollBar()->minimum());
horizontalScrollBar()->setValue(horizontalScrollBar()->minimum());
}
void ItemViewCategorized::selectionChanged(const QItemSelection& selectedItems, const QItemSelection& deselectedItems)
{
DCategorizedView::selectionChanged(selectedItems, deselectedItems);
emit selectionChanged();
if (!selectionModel()->hasSelection())
{
emit selectionCleared();
}
userInteraction();
}
void ItemViewCategorized::rowsInserted(const QModelIndex& parent, int start, int end)
{
DCategorizedView::rowsInserted(parent, start, end);
if (start == 0)
{
ensureSelectionAfterChanges();
}
}
void ItemViewCategorized::rowsRemoved(const QModelIndex& parent, int start, int end)
{
DCategorizedView::rowsRemoved(parent, start, end);
if (d->scrollCurrentToCenter)
{
scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter);
}
}
void ItemViewCategorized::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
DCategorizedView::rowsAboutToBeRemoved(parent, start, end);
// Ensure one selected item
int totalToRemove = end - start + 1;
bool remainingRows = model()->rowCount(parent) > totalToRemove;
if (!remainingRows)
{
return;
}
QItemSelection removed(model()->index(start, 0), model()->index(end, 0));
if (selectionModel()->hasSelection())
{
// find out which selected indexes are left after rows are removed
QItemSelection selected = selectionModel()->selection();
QModelIndex current = currentIndex();
QModelIndex indexToAnchor;
if (selected.contains(current))
{
indexToAnchor = current;
}
else if (!selected.isEmpty())
{
indexToAnchor = selected.first().topLeft();
}
selected.merge(removed, QItemSelectionModel::Deselect);
if (selected.isEmpty())
{
QModelIndex newCurrent = nextIndexHint(indexToAnchor, removed.first() /*a range*/);
setCurrentIndex(newCurrent);
}
}
QModelIndex hint = d->scrollPositionHint();
if (removed.contains(hint))
{
d->hintAtScrollPosition = nextIndexHint(hint, removed.first() /*a range*/);
}
}
void ItemViewCategorized::layoutAboutToBeChanged()
{
if(selectionModel())
{
d->ensureOneSelectedItem = selectionModel()->hasSelection();
}
else
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Called without selection model, check whether the models are ok..";
}
QModelIndex current = currentIndex();
// store some hints so that if all selected items were removed do not need to default to 0,0.
if (d->ensureOneSelectedItem)
{
QItemSelection currentSelection = selectionModel()->selection();
QModelIndex indexToAnchor;
if (currentSelection.contains(current))
{
indexToAnchor = current;
}
else if (!currentSelection.isEmpty())
{
indexToAnchor = currentSelection.first().topLeft();
}
if (indexToAnchor.isValid())
{
d->hintAtSelectionRow = indexToAnchor.row();
d->hintAtSelectionIndex = nextIndexHint(indexToAnchor, QItemSelectionRange(indexToAnchor));
}
}
// some precautions to keep current scroll position
d->hintAtScrollPosition = d->scrollPositionHint();
}
QModelIndex ItemViewCategorized::nextIndexHint(const QModelIndex& indexToAnchor, const QItemSelectionRange& removed) const
{
Q_UNUSED(indexToAnchor);
if (removed.bottomRight().row() == model()->rowCount() - 1)
{
if (removed.topLeft().row() == 0)
{
return QModelIndex();
}
return model()->index(removed.topLeft().row() - 1, 0); // last remaining, no next one left
}
else
{
return model()->index(removed.bottomRight().row() + 1, 0); // next remaining
}
}
void ItemViewCategorized::layoutWasChanged()
{
// connected queued to layoutChanged()
ensureSelectionAfterChanges();
if (d->hintAtScrollPosition.isValid())
{
scrollToRelaxed(d->hintAtScrollPosition);
d->hintAtScrollPosition = QModelIndex();
}
else
{
scrollToRelaxed(currentIndex());
}
}
void ItemViewCategorized::userInteraction()
{
// as soon as the user did anything affecting selection, we don't interfere anymore
d->ensureInitialSelectedItem = false;
d->hintAtSelectionIndex = QModelIndex();
}
void ItemViewCategorized::ensureSelectionAfterChanges()
{
if (d->ensureInitialSelectedItem && model()->rowCount())
{
// Ensure the item (0,0) is selected, if the model was reset previously
// and the user did not change the selection since reset.
// Caveat: Item at (0,0) may have changed.
bool hadInitial = d->ensureInitialSelectedItem;
d->ensureInitialSelectedItem = false;
d->ensureOneSelectedItem = false;
QModelIndex index = model()->index(0,0);
if (index.isValid())
{
selectionModel()->select(index, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Clear);
setCurrentIndex(index);
// we want ensureInitial set to false if and only if the selection
// is done from any other place than the previous line (i.e., by user action)
// Effect: we select whatever is the current index(0,0)
if (hadInitial)
{
d->ensureInitialSelectedItem = true;
}
}
}
else if (d->ensureOneSelectedItem)
{
// ensure we have a selection if there was one before
d->ensureOneSelectedItem = false;
if (model()->rowCount() && selectionModel()->selection().isEmpty())
{
QModelIndex index;
if (d->hintAtSelectionIndex.isValid())
{
index = d->hintAtSelectionIndex;
}
else if (d->hintAtSelectionRow != -1)
{
index = model()->index(qMin(model()->rowCount(), d->hintAtSelectionRow), 0);
}
else
{
index = currentIndex();
}
if (!index.isValid())
{
index = model()->index(0,0);
}
d->hintAtSelectionRow = -1;
d->hintAtSelectionIndex = QModelIndex();
if (index.isValid())
{
setCurrentIndex(index);
selectionModel()->select(index, QItemSelectionModel::SelectCurrent);
}
}
}
}
QModelIndex ItemViewCategorized::indexForCategoryAt(const QPoint& pos) const
{
return categoryAt(pos);
}
QModelIndex ItemViewCategorized::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
QModelIndex current = currentIndex();
if (!current.isValid())
{
return DCategorizedView::moveCursor(cursorAction, modifiers);
}
// We want a simple wrapping navigation.
// Default behavior we do not want: right/left does never change row; Next/Previous is equivalent to Down/Up
switch (cursorAction)
{
case MoveNext:
case MoveRight:
{
QModelIndex next = model()->index(current.row() + 1, 0);
if (next.isValid())
{
return next;
}
else
{
return current;
}
break;
}
case MovePrevious:
case MoveLeft:
{
QModelIndex previous = model()->index(current.row() - 1, 0);
if (previous.isValid())
{
return previous;
}
else
{
return current;
}
break;
}
default:
break;
}
return DCategorizedView::moveCursor(cursorAction, modifiers);
}
void ItemViewCategorized::showContextMenuOnIndex(QContextMenuEvent*, const QModelIndex&)
{
// implemented in subclass
}
void ItemViewCategorized::showContextMenu(QContextMenuEvent*)
{
// implemented in subclass
}
void ItemViewCategorized::indexActivated(const QModelIndex&, Qt::KeyboardModifiers)
{
}
bool ItemViewCategorized::showToolTip(const QModelIndex& index, QStyleOptionViewItem& option, QHelpEvent* he)
{
QRect innerRect;
QPoint pos;
if (he)
{
pos = he->pos();
}
else
{
pos = option.rect.center();
}
if (d->delegate->acceptsToolTip(he->pos(), option.rect, index, &innerRect))
{
if (!innerRect.isNull())
{
option.rect = innerRect;
}
d->toolTip->show(option, index);
return true;
}
return false;
}
void ItemViewCategorized::contextMenuEvent(QContextMenuEvent* event)
{
userInteraction();
QModelIndex index = indexAt(event->pos());
if (index.isValid())
{
showContextMenuOnIndex(event, index);
}
else
{
showContextMenu(event);
}
}
void ItemViewCategorized::leaveEvent(QEvent*)
{
hideIndexNotification();
if (d->mouseButtonPressed != Qt::RightButton)
{
d->mouseButtonPressed = Qt::NoButton;
}
}
void ItemViewCategorized::mousePressEvent(QMouseEvent* event)
{
userInteraction();
const QModelIndex index = indexAt(event->pos());
// Clear selection on click on empty area. Standard behavior, but not done by QAbstractItemView for some reason.
Qt::KeyboardModifiers modifiers = event->modifiers();
const Qt::MouseButton button = event->button();
const bool rightButtonPressed = button & Qt::RightButton;
const bool shiftKeyPressed = modifiers & Qt::ShiftModifier;
const bool controlKeyPressed = modifiers & Qt::ControlModifier;
d->mouseButtonPressed = button;
if (!index.isValid() && !rightButtonPressed && !shiftKeyPressed && !controlKeyPressed)
{
clearSelection();
}
// store event for entered(), clicked(), activated() signal handlers
if (!rightButtonPressed)
{
d->currentMouseEvent = event;
}
else
{
d->currentMouseEvent = 0;
}
DCategorizedView::mousePressEvent(event);
if (!index.isValid())
{
emit viewportClicked(event);
}
}
void ItemViewCategorized::mouseReleaseEvent(QMouseEvent* event)
{
userInteraction();
if (d->scrollCurrentToCenter)
{
scrollTo(currentIndex(), QAbstractItemView::PositionAtCenter);
}
DCategorizedView::mouseReleaseEvent(event);
}
void ItemViewCategorized::mouseMoveEvent(QMouseEvent* event)
{
QModelIndex index = indexAt(event->pos());
QRect indexVisualRect;
if (index.isValid())
{
indexVisualRect = visualRect(index);
if (d->usePointingHand &&
d->delegate->acceptsActivation(event->pos(), indexVisualRect, index))
{
setCursor(Qt::PointingHandCursor);
}
else
{
unsetCursor();
}
}
else
{
unsetCursor();
}
if (d->notificationToolTip && d->notificationToolTip->isVisible())
{
if (!d->notificationToolTip->rect().adjusted(-50, -50, 50, 50).contains(event->pos()))
{
hideIndexNotification();
}
}
DCategorizedView::mouseMoveEvent(event);
d->delegate->mouseMoved(event, indexVisualRect, index);
}
void ItemViewCategorized::wheelEvent(QWheelEvent* event)
{
// DCategorizedView updates the single step at some occasions in a private methody
horizontalScrollBar()->setSingleStep(d->delegate->gridSize().height() / d->scrollStepFactor);
verticalScrollBar()->setSingleStep(d->delegate->gridSize().width() / d->scrollStepFactor);
if (event->modifiers() & Qt::ControlModifier)
{
const int delta = event->delta();
if (delta > 0)
{
emit zoomInStep();
}
else if (delta < 0)
{
emit zoomOutStep();
}
event->accept();
return;
}
if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff && event->orientation() == Qt::Vertical)
{
QWheelEvent n(event->pos(), event->globalPos(), event->delta(),
event->buttons(), event->modifiers(), Qt::Horizontal);
QApplication::sendEvent(horizontalScrollBar(), &n);
event->setAccepted(n.isAccepted());
}
else
{
DCategorizedView::wheelEvent(event);
}
}
void ItemViewCategorized::keyPressEvent(QKeyEvent* event)
{
userInteraction();
if (event == QKeySequence::Copy)
{
copy();
event->accept();
return;
}
else if (event == QKeySequence::Paste)
{
paste();
event->accept();
return;
}
/*
// from dolphincontroller.cpp
const QItemSelectionModel* selModel = m_itemView->selectionModel();
const QModelIndex currentIndex = selModel->currentIndex();
const bool trigger = currentIndex.isValid() &&
((event->key() == Qt::Key_Return) || (event->key() == Qt::Key_Enter)) &&
(selModel->selectedIndexes().count() > 0);
if (trigger)
{
const QModelIndexList indexList = selModel->selectedIndexes();
foreach(const QModelIndex& index, indexList)
{
emit itemTriggered(itemForIndex(index));
}
}
*/
DCategorizedView::keyPressEvent(event);
emit keyPressed(event);
}
void ItemViewCategorized::resizeEvent(QResizeEvent* e)
{
QModelIndex oldPosition = d->scrollPositionHint();
DCategorizedView::resizeEvent(e);
updateDelegateSizes();
scrollToRelaxed(oldPosition, QAbstractItemView::PositionAtTop);
}
bool ItemViewCategorized::viewportEvent(QEvent* event)
{
switch (event->type())
{
case QEvent::FontChange:
{
updateDelegateSizes();
break;
}
case QEvent::ToolTip:
{
if (!d->showToolTip)
{
return true;
}
QHelpEvent* he = static_cast<QHelpEvent*>(event);
const QModelIndex index = indexAt(he->pos());
if (!index.isValid())
{
break;
}
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(index);
option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None);
showToolTip(index, option, he);
return true;
}
default:
break;
}
return DCategorizedView::viewportEvent(event);
}
void ItemViewCategorized::showIndexNotification(const QModelIndex& index, const QString& message)
{
hideIndexNotification();
if (!index.isValid())
{
return;
}
if (!d->notificationToolTip)
{
d->notificationToolTip = new ItemViewToolTip(this);
}
d->notificationToolTip->setTipContents(message);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(index);
option.state |= (index == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None);
d->notificationToolTip->show(option, index);
}
void ItemViewCategorized::hideIndexNotification()
{
if (d->notificationToolTip)
{
d->notificationToolTip->hide();
}
}
/**
* cut(), copy(), paste(), dragEnterEvent(), dragMoveEvent(), dropEvent(), startDrag()
* are implemented by DragDropViewImplementation
*/
QModelIndex ItemViewCategorized::mapIndexForDragDrop(const QModelIndex& index) const
{
return filterModel()->mapToSource(index);
}
QPixmap ItemViewCategorized::pixmapForDrag(const QList<QModelIndex>& indexes) const
{
QStyleOptionViewItem option = viewOptions();
option.rect = viewport()->rect();
return d->delegate->pixmapForDrag(option, indexes);
}
void ItemViewCategorized::setScrollCurrentToCenter(bool enabled)
{
d->scrollCurrentToCenter = enabled;
}
void ItemViewCategorized::scrollTo(const QModelIndex& index, ScrollHint hint)
{
if (d->scrollCurrentToCenter && d->mouseButtonPressed == Qt::NoButton)
{
hint = QAbstractItemView::PositionAtCenter;
}
d->mouseButtonPressed = Qt::NoButton;
DCategorizedView::scrollTo(index, hint);
}
} // namespace Digikam
diff --git a/core/libs/widgets/mainview/daboutdata.cpp b/core/libs/widgets/mainview/daboutdata.cpp
index ed2c4bc119..cfb6c44a92 100644
--- a/core/libs/widgets/mainview/daboutdata.cpp
+++ b/core/libs/widgets/mainview/daboutdata.cpp
@@ -1,413 +1,413 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-07-30
* Description : digiKam about data.
*
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "daboutdata.h"
// Qt includes
#include <QIcon>
#include <QAction>
// KDE includes
#include <klocalizedstring.h>
#include <kaboutdata.h>
// Local includes
#include "dxmlguiwindow.h"
namespace Digikam
{
DAboutData::DAboutData(DXmlGuiWindow* const parent)
: QObject(parent)
{
}
DAboutData::~DAboutData()
{
}
const QString DAboutData::digiKamSloganFormated()
{
- return i18nc("This is the slogan formated string displayed in splashscreen. "
+ return i18nc("This is the slogan formatted string displayed in splashscreen. "
"Please translate using short words else the slogan can be truncated.",
"<qt><font color=\"white\">"
"<b>Professional</b> Photo <b>Management</b> with the Power of <b>Open Source</b>"
"</font></qt>"
);
}
const QString DAboutData::digiKamSlogan()
{
return i18n("Professional Photo Management with the Power of Open Source");
}
const QString DAboutData::copyright()
{
return i18n("(c) 2002-2018, digiKam developers team");
}
const QUrl DAboutData::webProjectUrl()
{
return QUrl(QLatin1String("http://www.digikam.org"));
}
void DAboutData::authorsRegistration(KAboutData& aboutData)
{
// -- Core team --------------------------------------------------------------
aboutData.addAuthor ( ki18n("Caulier Gilles").toString(),
ki18n("Coordinator, Developer, and Mentoring").toString(),
QLatin1String("caulier dot gilles at gmail dot com"),
QLatin1String("https://plus.google.com/+GillesCaulier")
);
aboutData.addAuthor ( ki18n("Marcel Wiesweg").toString(),
ki18n("Developer and Mentoring").toString(),
QLatin1String("marcel dot wiesweg at gmx dot de"),
QLatin1String("https://www.facebook.com/marcel.wiesweg")
);
aboutData.addAuthor ( ki18n("Maik Qualmann").toString(),
ki18n("Developer and Mentoring").toString(),
QLatin1String("metzpinguin at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/107171232114475191915")
);
aboutData.addAuthor ( ki18n("Mohamed Anwer").toString(), // krazy:exclude=spelling
ki18n("Developer and Mentoring").toString(),
QLatin1String("mohammed dot ahmed dot anwer at gmail dot com"), // krazy:exclude=spelling
QLatin1String("https://plus.google.com/106020792892118847381")
);
aboutData.addAuthor ( ki18n("Michael G. Hansen").toString(),
ki18n("Developer and Mentoring").toString(),
QLatin1String("mike at mghansen dot de"),
QLatin1String("http://www.mghansen.de")
);
aboutData.addAuthor ( ki18n("Teemu Rytilahti").toString(),
ki18n("Developer").toString(),
QLatin1String("tpr at iki dot fi"),
QLatin1String("https://plus.google.com/u/0/105136119348505864693")
);
// -- Contributors -----------------------------------------------------------
aboutData.addAuthor ( ki18n("Matthias Welwarsky").toString(),
ki18n("Developer").toString(),
QLatin1String("matze at welwarsky dot de"),
QLatin1String("https://plus.google.com/s/Matthias%20Welwarsky")
);
aboutData.addAuthor ( ki18n("Julien Narboux").toString(),
ki18n("Developer").toString(),
QLatin1String("Julien at narboux dot fr"),
QLatin1String("https://plus.google.com/+JulienNarboux")
);
aboutData.addAuthor ( ki18n("Mario Frank").toString(),
ki18n("Advanced Searches Tool Improvements").toString(),
QLatin1String("mario.frank@uni-potsdam.de")
);
aboutData.addAuthor ( ki18n("Nicolas Lécureuil").toString(),
ki18n("Releases Manager").toString(),
QLatin1String("neoclust dot kde at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/111733995327568706391")
);
// -- Students ---------------------------------------------------------------
aboutData.addCredit ( ki18n("Veaceslav Munteanu").toString(),
ki18n("Tags Manager").toString(),
QLatin1String("veaceslav dot munteanu90 at gmail dot com"),
QLatin1String("https://plus.google.com/114906808699351374523")
);
aboutData.addCredit ( ki18n("Thanh Trung Dinh").toString(),
ki18n("Port web-service tools to OAuth and factoring").toString(),
QLatin1String("dinhthanhtrung1996 at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/105288296237200960831")
);
aboutData.addCredit ( ki18n("Tarek Talaat").toString(),
ki18n("New OneDrive, Pinterrest, and Box export tools").toString(),
QLatin1String("tarektalaat93 at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/108164047195873182769")
);
aboutData.addCredit ( ki18n("Yingjie Liu").toString(),
ki18n("Face-engine improvements and manual icon-view sort").toString(),
QLatin1String("yingjiewudi at gmail dot com"),
QLatin1String("https://yjwudi.github.io")
);
aboutData.addCredit ( ki18n("Yiou Wang").toString(),
ki18n("Model/View Port of Image Editor Canvas").toString(),
QLatin1String("geow812 at gmail dot com"),
QLatin1String("https://plus.google.com/101883964009694930513")
);
aboutData.addCredit ( ki18n("Gowtham Ashok").toString(),
ki18n("Image Quality Sorter").toString(),
QLatin1String("gwty93 at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/113235187016472722859")
);
aboutData.addCredit ( ki18n("Aditya Bhatt").toString(),
ki18n("Face Detection").toString(),
QLatin1String("aditya at bhatts dot org"),
QLatin1String("https://twitter.com/aditya_bhatt")
);
aboutData.addCredit ( ki18n("Martin Klapetek").toString(),
ki18n("Non-destructive image editing").toString(),
QLatin1String("martin dot klapetek at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/101026761070865237619")
);
aboutData.addCredit ( ki18n("Gabriel Voicu").toString(),
ki18n("Reverse Geo-Coding").toString(),
QLatin1String("ping dot gabi at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/101476692615103604273")
);
aboutData.addCredit ( ki18n("Mahesh Hegde").toString(),
ki18n("Face Recognition").toString(),
QLatin1String("maheshmhegade at gmail dot com"),
QLatin1String("https://plus.google.com/113704327590506304403")
);
aboutData.addCredit ( ki18n("Pankaj Kumar").toString(),
ki18n("Multi-core Support in Batch Queue Manager and Mentoring").toString(),
QLatin1String("me at panks dot me"),
QLatin1String("https://plus.google.com/114958890691877878308")
);
aboutData.addCredit ( ki18n("Smit Mehta").toString(),
ki18n("UPnP / DLNA Export tool and Mentoring").toString(),
QLatin1String("smit dot tmeh at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/113404087048256151794")
);
aboutData.addCredit ( ki18n("Islam Wazery").toString(),
ki18n("Model/View port of Import Tool and Mentoring").toString(),
QLatin1String("wazery at ubuntu dot com"),
QLatin1String("https://plus.google.com/u/0/114444774108176364727")
);
aboutData.addCredit ( ki18n("Abhinav Badola").toString(),
ki18n("Video Metadata Support and Mentoring").toString(),
QLatin1String("mail dot abu dot to at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/107198225472060439855")
);
aboutData.addCredit ( ki18n("Benjamin Girault").toString(),
ki18n("Panorama Tool and Mentoring").toString(),
QLatin1String("benjamin dot girault at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/109282675370620103497")
);
aboutData.addCredit ( ki18n("Victor Dodon").toString(),
ki18n("XML based GUI port of tools").toString(),
QLatin1String("dodonvictor at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/107198225472060439855")
);
aboutData.addCredit ( ki18n("Sayantan Datta").toString(),
ki18n("Auto Noise Reduction").toString(),
QLatin1String("sayantan dot knz at gmail dot com"),
QLatin1String("https://plus.google.com/100302360459800439676")
);
// -- Former contributors ----------------------------------------------------
aboutData.addAuthor ( ki18n("Ananta Palani").toString(),
ki18n("Windows Port and Release Manager").toString(),
QLatin1String("anantapalani at gmail dot com"),
QLatin1String("https://plus.google.com/u/0/+AnantaPalani")
);
aboutData.addAuthor ( ki18n("Andi Clemens").toString(),
ki18n("Developer").toString(),
QLatin1String("andi dot clemens at gmail dot com"),
QLatin1String("https://plus.google.com/110531606986594589135")
);
aboutData.addAuthor ( ki18n("Patrick Spendrin").toString(),
ki18n("Developer and Windows port").toString(),
QLatin1String("patrick_spendrin at gmx dot de"),
QLatin1String("https://plus.google.com/u/0/107813275713575797754")
);
aboutData.addCredit ( ki18n("Francesco Riosa").toString(),
ki18n("LCMS2 library port").toString(),
QLatin1String("francesco plus kde at pnpitalia dot it"),
QLatin1String("https://plus.google.com/u/0/113237307210359236747")
);
aboutData.addCredit ( ki18n("Johannes Wienke").toString(),
ki18n("Developer").toString(),
QLatin1String("languitar at semipol dot de"),
QLatin1String("https://www.facebook.com/languitar")
);
aboutData.addAuthor ( ki18n("Julien Pontabry").toString(),
ki18n("Developer").toString(),
QLatin1String("julien dot pontabry at ulp dot u-strasbg dot fr"),
QLatin1String("https://www.facebook.com/julien.pontabry")
);
aboutData.addAuthor ( ki18n("Arnd Baecker").toString(),
ki18n("Developer").toString(),
QLatin1String("arnd dot baecker at web dot de")
);
aboutData.addAuthor ( ki18n("Francisco J. Cruz").toString(),
ki18n("Color Management").toString(),
QLatin1String("fj dot cruz at supercable dot es"),
QLatin1String("https://plus.google.com/u/0/+FranciscoJCruz")
);
aboutData.addCredit ( ki18n("Pieter Edelman").toString(),
ki18n("Developer").toString(),
QLatin1String("p dot edelman at gmx dot net"),
QLatin1String("https://www.facebook.com/pieter.edelman")
);
aboutData.addCredit ( ki18n("Holger Foerster").toString(),
ki18n("MySQL interface").toString(),
QLatin1String("hamsi2k at freenet dot de")
);
aboutData.addCredit ( ki18n("Risto Saukonpaa").toString(),
ki18n("Design, icons, logo, banner, mockup, beta tester").toString(),
QLatin1String("paristo at gmail dot com")
);
aboutData.addCredit ( ki18n("Mikolaj Machowski").toString(),
ki18n("Bug reports and patches").toString(),
QLatin1String("mikmach at wp dot pl"),
QLatin1String("https://www.facebook.com/mikolaj.machowski")
);
aboutData.addCredit ( ki18n("Achim Bohnet").toString(),
ki18n("Bug reports and patches").toString(),
QLatin1String("ach at mpe dot mpg dot de"),
QLatin1String("https://www.facebook.com/achim.bohnet")
);
aboutData.addCredit ( ki18n("Luka Renko").toString(),
ki18n("Developer").toString(),
QLatin1String("lure at kubuntu dot org"),
QLatin1String("https://www.facebook.com/luka.renko")
);
aboutData.addCredit ( ki18n("Angelo Naselli").toString(),
ki18n("Developer").toString(),
QLatin1String("a dot naselli at libero dot it"),
QLatin1String("https://plus.google.com/u/0/s/Angelo%20Naselli")
);
aboutData.addCredit ( ki18n("Fabien Salvi").toString(),
ki18n("Webmaster").toString(),
QLatin1String("fabien dot ubuntu at gmail dot com")
);
aboutData.addCredit ( ki18n("Todd Shoemaker").toString(),
ki18n("Developer").toString(),
QLatin1String("todd at theshoemakers dot net")
);
aboutData.addCredit ( ki18n("Gerhard Kulzer").toString(),
ki18n("Handbook writer, alpha tester, webmaster").toString(),
QLatin1String("gerhard at kulzer dot net"),
QLatin1String("https://plus.google.com/u/0/+GerhardKulzer")
);
aboutData.addCredit ( ki18n("Oliver Doerr").toString(),
ki18n("Beta tester").toString(),
QLatin1String("oliver at doerr-privat dot de")
);
aboutData.addCredit ( ki18n("Charles Bouveyron").toString(),
ki18n("Beta tester").toString(),
QLatin1String("c dot bouveyron at tuxfamily dot org")
);
aboutData.addCredit ( ki18n("Richard Taylor").toString(),
ki18n("Feedback and patches. Handbook writer").toString(),
QLatin1String("rjt-digicam at thegrindstone dot me dot uk")
);
aboutData.addCredit ( ki18n("Hans Karlsson").toString(),
ki18n("digiKam website banner and application icons").toString(),
QLatin1String("karlsson dot h at home dot se")
);
aboutData.addCredit ( ki18n("Aaron Seigo").toString(),
ki18n("Various usability fixes and general application polishing").toString(),
QLatin1String("aseigo at kde dot org"),
QLatin1String("https://plus.google.com/u/0/+AaronSeigo")
);
aboutData.addCredit ( ki18n("Yves Chaufour").toString(),
ki18n("digiKam website, Feedback").toString(),
QLatin1String("yves dot chaufour at wanadoo dot fr")
);
aboutData.addCredit ( ki18n("Tung Nguyen").toString(),
ki18n("Bug reports, feedback and icons").toString(),
QLatin1String("ntung at free dot fr")
);
// -- Former Members ---------------------------------------------------------
aboutData.addAuthor ( ki18n("Renchi Raju").toString(),
ki18n("Developer (2002-2005)").toString(),
QLatin1String("renchi dot raju at gmail dot com"),
QLatin1String("https://www.facebook.com/renchi.raju")
);
aboutData.addAuthor ( ki18n("Joern Ahrens").toString(),
ki18n("Developer (2004-2005)").toString(),
QLatin1String("kde at jokele dot de"),
QLatin1String("http://www.jokele.de/")
);
aboutData.addAuthor ( ki18n("Tom Albers").toString(),
ki18n("Developer (2004-2005)").toString(),
QLatin1String("tomalbers at kde dot nl"),
QLatin1String("https://plus.google.com/u/0/+TomAlbers")
);
aboutData.addAuthor ( ki18n("Ralf Holzer").toString(),
ki18n("Developer (2004)").toString(),
QLatin1String("kde at ralfhoelzer dot com")
);
}
} // namespace Digikam
diff --git a/core/libs/widgets/mainview/dxmlguiwindow.cpp b/core/libs/widgets/mainview/dxmlguiwindow.cpp
index 5a8ec226c7..9b9ac71913 100644
--- a/core/libs/widgets/mainview/dxmlguiwindow.cpp
+++ b/core/libs/widgets/mainview/dxmlguiwindow.cpp
@@ -1,1370 +1,1370 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2013-04-29
* Description : digiKam XML GUI window
*
* Copyright (C) 2013-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dxmlguiwindow.h"
// Qt includes
#include <QString>
#include <QList>
#include <QMap>
#include <QVBoxLayout>
#include <QCheckBox>
#include <QToolButton>
#include <QEvent>
#include <QHoverEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QKeySequence>
#include <QMenuBar>
#include <QStatusBar>
#include <QMenu>
#include <QUrl>
#include <QUrlQuery>
#include <QIcon>
#include <QDir>
#include <QFileInfo>
#include <QResource>
#include <QStandardPaths>
// KDE includes
#include <ktogglefullscreenaction.h>
#include <ktoolbar.h>
#include <ktoggleaction.h>
#include <kstandardaction.h>
#include <kactioncollection.h>
#include <klocalizedstring.h>
#include <kwindowconfig.h>
#include <ksharedconfig.h>
#include <kshortcutsdialog.h>
#include <kedittoolbar.h>
#ifdef HAVE_KNOTIFYCONFIG
# include <knotifyconfigwidget.h>
#endif
// Local includes
#include "digikam_debug.h"
#include "digikam_globals.h"
#include "daboutdata.h"
#include "webbrowserdlg.h"
#include "wsstarter.h"
namespace Digikam
{
class Q_DECL_HIDDEN DXmlGuiWindow::Private
{
public:
explicit Private()
{
fsOptions = FS_NONE;
fullScreenAction = 0;
fullScreenBtn = 0;
dirtyMainToolBar = false;
fullScreenHideToolBars = false;
fullScreenHideThumbBar = true;
fullScreenHideSideBars = false;
thumbbarVisibility = true;
menubarVisibility = true;
statusbarVisibility = true;
libsInfoAction = 0;
showMenuBarAction = 0;
showStatusBarAction = 0;
about = 0;
dbStatAction = 0;
anim = 0;
}
public:
/**
* Settings taken from managed window configuration to handle toolbar visibility in full-screen mode
*/
bool fullScreenHideToolBars;
/**
* Settings taken from managed window configuration to handle thumbbar visibility in full-screen mode
*/
bool fullScreenHideThumbBar;
/**
* Settings taken from managed window configuration to handle toolbar visibility in full-screen mode
*/
bool fullScreenHideSideBars;
/**
* Full-Screen options. See FullScreenOptions enum and setFullScreenOptions() for details.
*/
int fsOptions;
/**
* Action plug in managed window to switch fullscreen state
*/
KToggleFullScreenAction* fullScreenAction;
/**
* Show only if toolbar is hidden
*/
QToolButton* fullScreenBtn;
/**
* Used by slotToggleFullScreen() to manage state of full-screen button on managed window
*/
bool dirtyMainToolBar;
/**
* Store previous visibility of toolbars before ful-screen mode.
*/
QMap<KToolBar*, bool> toolbarsVisibility;
/**
* Store previous visibility of thumbbar before ful-screen mode.
*/
bool thumbbarVisibility;
/**
* Store previous visibility of menubar before ful-screen mode.
*/
bool menubarVisibility;
/**
* Store previous visibility of statusbar before ful-screen mode.
*/
bool statusbarVisibility;
// Common Help actions
QAction* dbStatAction;
QAction* libsInfoAction;
QAction* showMenuBarAction;
QAction* showStatusBarAction;
DAboutData* about;
DLogoAction* anim;
QString configGroupName;
};
// --------------------------------------------------------------------------------------------------
DXmlGuiWindow::DXmlGuiWindow(QWidget* const parent, Qt::WindowFlags f)
: KXmlGuiWindow(parent, f),
d(new Private)
{
m_expoBlendingAction = 0;
m_panoramaAction = 0;
m_videoslideshowAction = 0;
m_htmlGalleryAction = 0;
m_sendByMailAction = 0;
m_printCreatorAction = 0;
m_calendarAction = 0;
m_presentationAction = 0;
m_metadataEditAction = 0;
m_geolocationEditAction = 0;
m_mediaServerAction = 0;
m_timeAdjustAction = 0;
m_animLogo = 0;
// Export tools
m_exportDropboxAction = 0;
m_exportOnedriveAction = 0;
m_exportPinterestAction = 0;
m_exportBoxAction = 0;
m_exportFacebookAction = 0;
m_exportFlickrAction = 0;
m_exportGdriveAction = 0;
m_exportGphotoAction = 0;
m_exportImageshackAction = 0;
m_exportImgurAction = 0;
m_exportPiwigoAction = 0;
m_exportRajceAction = 0;
m_exportSmugmugAction = 0;
m_exportYandexfotkiAction = 0;
m_exportMediawikiAction = 0;
#ifdef HAVE_VKONTAKTE
m_exportVkontakteAction = 0;
#endif
#ifdef HAVE_KIO
m_exportFileTransferAction = 0;
#endif
// Import tools
m_importGphotoAction = 0;
m_importSmugmugAction = 0;
#ifdef HAVE_KIO
m_importFileTransferAction = 0;
#endif
#ifdef HAVE_KSANE
m_ksaneAction = 0;
#endif
installEventFilter(this);
}
DXmlGuiWindow::~DXmlGuiWindow()
{
delete d;
}
void DXmlGuiWindow::setConfigGroupName(const QString& name)
{
d->configGroupName = name;
}
QString DXmlGuiWindow::configGroupName() const
{
return d->configGroupName;
}
void DXmlGuiWindow::closeEvent(QCloseEvent* e)
{
if (fullScreenIsActive())
slotToggleFullScreen(false);
if (!testAttribute(Qt::WA_DeleteOnClose))
{
setVisible(false);
e->ignore();
return;
}
KXmlGuiWindow::closeEvent(e);
e->accept();
}
void DXmlGuiWindow::setFullScreenOptions(int options)
{
d->fsOptions = options;
}
void DXmlGuiWindow::createHelpActions(bool coreOptions)
{
d->libsInfoAction = new QAction(QIcon::fromTheme(QLatin1String("help-about")), i18n("Components Information"), this);
connect(d->libsInfoAction, SIGNAL(triggered()), this, SLOT(slotComponentsInfo()));
actionCollection()->addAction(QLatin1String("help_librariesinfo"), d->libsInfoAction);
d->about = new DAboutData(this);
QAction* const rawCameraListAction = new QAction(QIcon::fromTheme(QLatin1String("image-x-adobe-dng")), i18n("Supported RAW Cameras"), this);
connect(rawCameraListAction, SIGNAL(triggered()), this, SLOT(slotRawCameraList()));
actionCollection()->addAction(QLatin1String("help_rawcameralist"), rawCameraListAction);
QAction* const donateMoneyAction = new QAction(QIcon::fromTheme(QLatin1String("globe")), i18n("Donate..."), this);
connect(donateMoneyAction, SIGNAL(triggered()), this, SLOT(slotDonateMoney()));
actionCollection()->addAction(QLatin1String("help_donatemoney"), donateMoneyAction);
QAction* const recipesBookAction = new QAction(QIcon::fromTheme(QLatin1String("globe")), i18n("Recipes Book..."), this);
connect(recipesBookAction, SIGNAL(triggered()), this, SLOT(slotRecipesBook()));
actionCollection()->addAction(QLatin1String("help_recipesbook"), recipesBookAction);
QAction* const contributeAction = new QAction(QIcon::fromTheme(QLatin1String("globe")), i18n("Contribute..."), this);
connect(contributeAction, SIGNAL(triggered()), this, SLOT(slotContribute()));
actionCollection()->addAction(QLatin1String("help_contribute"), contributeAction);
QAction* const helpAction = new QAction(QIcon::fromTheme(QLatin1String("help-contents")), i18n("Online Handbook..."), this);
connect(helpAction, SIGNAL(triggered()), this, SLOT(slotHelpContents()));
actionCollection()->addAction(QLatin1String("help_handbook"), helpAction);
m_animLogo = new DLogoAction(this);
actionCollection()->addAction(QLatin1String("logo_action"), m_animLogo);
// Add options only for core components (typically all excepted Showfoto)
if (coreOptions)
{
d->dbStatAction = new QAction(QIcon::fromTheme(QLatin1String("network-server-database")), i18n("Database Statistics"), this);
connect(d->dbStatAction, SIGNAL(triggered()), this, SLOT(slotDBStat()));
actionCollection()->addAction(QLatin1String("help_dbstat"), d->dbStatAction);
}
}
void DXmlGuiWindow::cleanupActions()
{
QAction* ac = actionCollection()->action(QLatin1String("help_about_kde"));
if (ac) actionCollection()->removeAction(ac);
ac = actionCollection()->action(QLatin1String("help_donate"));
if (ac) actionCollection()->removeAction(ac);
ac = actionCollection()->action(QLatin1String("help_contents"));
if (ac) actionCollection()->removeAction(ac);
/*
QList<QAction*> lst = actionCollection()->actions();
foreach (QAction* const act, lst)
qCDebug(DIGIKAM_WIDGETS_LOG) << "action: " << act->objectName();
*/
}
void DXmlGuiWindow::createSidebarActions()
{
KActionCollection* const ac = actionCollection();
QAction* const tlsb = new QAction(i18n("Toggle Left Side-bar"), this);
connect(tlsb, SIGNAL(triggered()), this, SLOT(slotToggleLeftSideBar()));
ac->addAction(QLatin1String("toggle-left-sidebar"), tlsb);
ac->setDefaultShortcut(tlsb, Qt::CTRL + Qt::META + Qt::Key_Left);
QAction* const trsb = new QAction(i18n("Toggle Right Side-bar"), this);
connect(trsb, SIGNAL(triggered()), this, SLOT(slotToggleRightSideBar()));
ac->addAction(QLatin1String("toggle-right-sidebar"), trsb);
ac->setDefaultShortcut(trsb, Qt::CTRL + Qt::META + Qt::Key_Right);
QAction* const plsb = new QAction(i18n("Previous Left Side-bar Tab"), this);
connect(plsb, SIGNAL(triggered()), this, SLOT(slotPreviousLeftSideBarTab()));
ac->addAction(QLatin1String("previous-left-sidebar-tab"), plsb);
ac->setDefaultShortcut(plsb, Qt::CTRL + Qt::META + Qt::Key_Home);
QAction* const nlsb = new QAction(i18n("Next Left Side-bar Tab"), this);
connect(nlsb, SIGNAL(triggered()), this, SLOT(slotNextLeftSideBarTab()));
ac->addAction(QLatin1String("next-left-sidebar-tab"), nlsb);
ac->setDefaultShortcut(nlsb, Qt::CTRL + Qt::META + Qt::Key_End);
QAction* const prsb = new QAction(i18n("Previous Right Side-bar Tab"), this);
connect(prsb, SIGNAL(triggered()), this, SLOT(slotPreviousRightSideBarTab()));
ac->addAction(QLatin1String("previous-right-sidebar-tab"), prsb);
ac->setDefaultShortcut(prsb, Qt::CTRL + Qt::META + Qt::Key_PageUp);
QAction* const nrsb = new QAction(i18n("Next Right Side-bar Tab"), this);
connect(nrsb, SIGNAL(triggered()), this, SLOT(slotNextRightSideBarTab()));
ac->addAction(QLatin1String("next-right-sidebar-tab"), nrsb);
ac->setDefaultShortcut(nrsb, Qt::CTRL + Qt::META + Qt::Key_PageDown);
}
void DXmlGuiWindow::createSettingsActions()
{
d->showMenuBarAction = KStandardAction::showMenubar(this, SLOT(slotShowMenuBar()), actionCollection());
#ifdef Q_OS_OSX
// Under MacOS the menu bar visibility is managed by desktop.
d->showMenuBarAction->setVisible(false);
#endif
d->showStatusBarAction = actionCollection()->action(QLatin1String("options_show_statusbar"));
if (!d->showStatusBarAction)
{
qCWarning(DIGIKAM_WIDGETS_LOG) << "Status bar menu action cannot be found in action collection";
d->showStatusBarAction = new QAction(i18n("Show Statusbar"), this);
d->showStatusBarAction->setCheckable(true);
d->showStatusBarAction->setChecked(true);
connect(d->showStatusBarAction, SIGNAL(toggled(bool)), this, SLOT(slotShowStatusBar()));
actionCollection()->addAction(QLatin1String("options_show_statusbar"), d->showStatusBarAction);
}
KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection());
KStandardAction::preferences(this, SLOT(slotSetup()), actionCollection());
KStandardAction::configureToolbars(this, SLOT(slotConfToolbars()), actionCollection());
#ifdef HAVE_KNOTIFYCONFIG
KStandardAction::configureNotifications(this, SLOT(slotConfNotifications()), actionCollection());
#endif
}
QAction* DXmlGuiWindow::showMenuBarAction() const
{
return d->showMenuBarAction;
}
QAction* DXmlGuiWindow::showStatusBarAction() const
{
return d->showStatusBarAction;
}
void DXmlGuiWindow::slotShowMenuBar()
{
menuBar()->setVisible(d->showMenuBarAction->isChecked());
}
void DXmlGuiWindow::slotShowStatusBar()
{
statusBar()->setVisible(d->showStatusBarAction->isChecked());
}
void DXmlGuiWindow::slotConfNotifications()
{
#ifdef HAVE_KNOTIFYCONFIG
KNotifyConfigWidget::configure(this);
#endif
}
void DXmlGuiWindow::editKeyboardShortcuts(KActionCollection* const extraac, const QString& actitle)
{
KShortcutsDialog dialog(KShortcutsEditor::AllActions,
KShortcutsEditor::LetterShortcutsAllowed, this);
dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General"));
if (extraac)
dialog.addCollection(extraac, actitle);
dialog.configure();
}
void DXmlGuiWindow::slotConfToolbars()
{
KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName());
saveMainWindowSettings(group);
KEditToolBar dlg(factory(), this);
connect(&dlg, SIGNAL(newToolbarConfig()),
this, SLOT(slotNewToolbarConfig()));
dlg.exec();
}
void DXmlGuiWindow::slotNewToolbarConfig()
{
KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName());
applyMainWindowSettings(group);
}
void DXmlGuiWindow::createGeolocationEditAction()
{
#ifdef HAVE_MARBLE
m_geolocationEditAction = new QAction(QIcon::fromTheme(QLatin1String("globe")), i18n("Edit Geolocation..."), this);
actionCollection()->addAction(QLatin1String("geolocation_edit"), m_geolocationEditAction);
actionCollection()->setDefaultShortcut(m_geolocationEditAction, Qt::CTRL + Qt::SHIFT + Qt::Key_G);
connect(m_geolocationEditAction, SIGNAL(triggered(bool)),
this, SLOT(slotEditGeolocation()));
#endif
}
void DXmlGuiWindow::createMetadataEditAction()
{
m_metadataEditAction = new QAction(QIcon::fromTheme(QLatin1String("format-text-code")), i18n("Edit Metadata..."), this);
actionCollection()->addAction(QLatin1String("metadata_edit"), m_metadataEditAction);
actionCollection()->setDefaultShortcut(m_metadataEditAction, Qt::CTRL + Qt::SHIFT + Qt::Key_M);
connect(m_metadataEditAction, SIGNAL(triggered(bool)),
this, SLOT(slotEditMetadata()));
}
void DXmlGuiWindow::createPresentationAction()
{
m_presentationAction = new QAction(QIcon::fromTheme(QLatin1String("view-presentation")), i18n("Presentation..."), this);
actionCollection()->addAction(QLatin1String("presentation"), m_presentationAction);
actionCollection()->setDefaultShortcut(m_presentationAction, Qt::ALT+Qt::SHIFT+Qt::Key_F9);
connect(m_presentationAction, SIGNAL(triggered()),
this, SLOT(slotPresentation()));
}
void DXmlGuiWindow::createExpoBlendingAction()
{
m_expoBlendingAction = new QAction(QIcon::fromTheme(QLatin1String("expoblending")),
i18nc("@action", "Create Stacked Images..."),
this);
actionCollection()->addAction(QLatin1String("expoblending"), m_expoBlendingAction);
connect(m_expoBlendingAction, SIGNAL(triggered(bool)),
this, SLOT(slotExpoBlending()));
}
void DXmlGuiWindow::createPanoramaAction()
{
#ifdef HAVE_PANORAMA
m_panoramaAction = new QAction(QIcon::fromTheme(QLatin1String("panorama")),
i18nc("@action", "Create panorama..."),
this);
actionCollection()->addAction(QLatin1String("panorama"), m_panoramaAction);
connect(m_panoramaAction, SIGNAL(triggered(bool)),
this, SLOT(slotPanorama()));
#endif
}
void DXmlGuiWindow::createVideoSlideshowAction()
{
#ifdef HAVE_MEDIAPLAYER
m_videoslideshowAction = new QAction(QIcon::fromTheme(QLatin1String("media-record")),
i18nc("@action", "Create video slideshow..."),
this);
actionCollection()->addAction(QLatin1String("videoslideshow"), m_videoslideshowAction);
connect(m_videoslideshowAction, SIGNAL(triggered(bool)),
this, SLOT(slotVideoSlideshow()));
#endif
}
void DXmlGuiWindow::createCalendarAction()
{
m_calendarAction = new QAction(QIcon::fromTheme(QLatin1String("view-calendar")),
i18nc("@action", "Create Calendar..."),
this);
actionCollection()->addAction(QLatin1String("calendar"), m_calendarAction);
connect(m_calendarAction, SIGNAL(triggered(bool)),
this, SLOT(slotCalendar()));
}
void DXmlGuiWindow::createSendByMailAction()
{
m_sendByMailAction = new QAction(QIcon::fromTheme(QLatin1String("mail-send")),
i18nc("@action", "Send by Mail..."),
this);
actionCollection()->addAction(QLatin1String("sendbymail"), m_sendByMailAction);
connect(m_sendByMailAction, SIGNAL(triggered(bool)),
this, SLOT(slotSendByMail()));
}
void DXmlGuiWindow::createPrintCreatorAction()
{
m_printCreatorAction = new QAction(QIcon::fromTheme(QLatin1String("document-print")),
i18nc("@action", "Print Creator..."),
this);
actionCollection()->addAction(QLatin1String("printcreator"), m_printCreatorAction);
connect(m_printCreatorAction, SIGNAL(triggered(bool)),
this, SLOT(slotPrintCreator()));
}
void DXmlGuiWindow::createHtmlGalleryAction()
{
#ifdef HAVE_HTMLGALLERY
m_htmlGalleryAction = new QAction(QIcon::fromTheme(QLatin1String("text-html")),
i18nc("@action", "Create Html gallery..."),
this);
actionCollection()->setDefaultShortcut(m_htmlGalleryAction, Qt::ALT+Qt::SHIFT+Qt::Key_H);
actionCollection()->addAction(QLatin1String("htmlgallery"), m_htmlGalleryAction);
connect(m_htmlGalleryAction, SIGNAL(triggered(bool)),
this, SLOT(slotHtmlGallery()));
#endif
}
void DXmlGuiWindow::createMediaServerAction()
{
m_mediaServerAction = new QAction(QIcon::fromTheme(QLatin1String("arrow-right-double")),
i18n("Share with DLNA"),
this);
actionCollection()->addAction(QLatin1String("mediaserver"), m_mediaServerAction);
connect(m_mediaServerAction, SIGNAL(triggered(bool)),
this, SLOT(slotMediaServer()));
}
void DXmlGuiWindow::createTimeAdjustAction()
{
m_timeAdjustAction = new QAction(QIcon::fromTheme(QLatin1String("appointment-new")), i18n("Adjust Time && Date..."), this);
actionCollection()->addAction(QLatin1String("timeadjust_edit"), m_timeAdjustAction);
//actionCollection()->setDefaultShortcut(m_metadataEditAction, Qt::CTRL + Qt::SHIFT + Qt::Key_M);
connect(m_timeAdjustAction, SIGNAL(triggered(bool)),
this, SLOT(slotTimeAdjust()));
}
void DXmlGuiWindow::createFullScreenAction(const QString& name)
{
d->fullScreenAction = KStandardAction::fullScreen(0, 0, this, this);
actionCollection()->addAction(name, d->fullScreenAction);
d->fullScreenBtn = new QToolButton(this);
d->fullScreenBtn->setDefaultAction(d->fullScreenAction);
d->fullScreenBtn->hide();
connect(d->fullScreenAction, SIGNAL(toggled(bool)),
this, SLOT(slotToggleFullScreen(bool)));
}
void DXmlGuiWindow::readFullScreenSettings(const KConfigGroup& group)
{
if (d->fsOptions & FS_TOOLBARS)
d->fullScreenHideToolBars = group.readEntry(s_configFullScreenHideToolBarsEntry, false);
if (d->fsOptions & FS_THUMBBAR)
d->fullScreenHideThumbBar = group.readEntry(s_configFullScreenHideThumbBarEntry, true);
if (d->fsOptions & FS_SIDEBARS)
d->fullScreenHideSideBars = group.readEntry(s_configFullScreenHideSideBarsEntry, false);
}
void DXmlGuiWindow::slotToggleFullScreen(bool set)
{
KToggleFullScreenAction::setFullScreen(this, set);
customizedFullScreenMode(set);
if (!set)
{
qCDebug(DIGIKAM_WIDGETS_LOG) << "TURN OFF fullscreen";
// restore menubar
if (d->menubarVisibility)
menuBar()->setVisible(true);
// restore statusbar
if (d->statusbarVisibility)
statusBar()->setVisible(true);
// restore sidebars
if ((d->fsOptions & FS_SIDEBARS) && d->fullScreenHideSideBars)
showSideBars(true);
- // restore thummbbar
+ // restore thumbbar
if ((d->fsOptions & FS_THUMBBAR) && d->fullScreenHideThumbBar)
showThumbBar(d->thumbbarVisibility);
// restore toolbars and manage full-screen button
showToolBars(true);
d->fullScreenBtn->hide();
if (d->dirtyMainToolBar)
{
KToolBar* const mainbar = mainToolBar();
if (mainbar)
{
mainbar->removeAction(d->fullScreenAction);
}
}
}
else
{
qCDebug(DIGIKAM_WIDGETS_LOG) << "TURN ON fullscreen";
// hide menubar
#ifdef Q_OS_WIN
d->menubarVisibility = d->showMenuBarAction->isChecked();
#else
d->menubarVisibility = menuBar()->isVisible();
#endif
menuBar()->setVisible(false);
// hide statusbar
#ifdef Q_OS_WIN
d->statusbarVisibility = d->showStatusBarAction->isChecked();
#else
d->statusbarVisibility = statusBar()->isVisible();
#endif
statusBar()->setVisible(false);
// hide sidebars
if ((d->fsOptions & FS_SIDEBARS) && d->fullScreenHideSideBars)
showSideBars(false);
- // hide thummbbar
+ // hide thumbbar
d->thumbbarVisibility = thumbbarVisibility();
if ((d->fsOptions & FS_THUMBBAR) && d->fullScreenHideThumbBar)
showThumbBar(false);
// hide toolbars and manage full-screen button
if ((d->fsOptions & FS_TOOLBARS) && d->fullScreenHideToolBars)
{
showToolBars(false);
}
else
{
showToolBars(true);
// add fullscreen action if necessary in toolbar
KToolBar* const mainbar = mainToolBar();
if (mainbar && !mainbar->actions().contains(d->fullScreenAction))
{
if (mainbar->actions().isEmpty())
{
mainbar->addAction(d->fullScreenAction);
}
else
{
mainbar->insertAction(mainbar->actions().first(), d->fullScreenAction);
}
d->dirtyMainToolBar = true;
}
else
{
// If FullScreen button is enabled in toolbar settings,
// we shall not remove it when leaving of fullscreen mode.
d->dirtyMainToolBar = false;
}
}
}
}
bool DXmlGuiWindow::fullScreenIsActive() const
{
if (d->fullScreenAction)
return d->fullScreenAction->isChecked();
qCDebug(DIGIKAM_WIDGETS_LOG) << "FullScreenAction is not initialized";
return false;
}
bool DXmlGuiWindow::eventFilter(QObject* obj, QEvent* ev)
{
if (obj == this)
{
if (ev && (ev->type() == QEvent::HoverMove) && fullScreenIsActive())
{
// We will handle a stand alone FullScreen button action on top/right corner of screen
// only if managed window tool bar is hidden, and if we switched already in Full Screen mode.
KToolBar* const mainbar = mainToolBar();
if (mainbar)
{
if (((d->fsOptions & FS_TOOLBARS) && d->fullScreenHideToolBars) || !mainbar->isVisible())
{
QHoverEvent* const mev = dynamic_cast<QHoverEvent*>(ev);
if (mev)
{
QPoint pos(mev->pos());
QRect desktopRect = QApplication::desktop()->screenGeometry(this);
QRect sizeRect(QPoint(0, 0), d->fullScreenBtn->size());
QRect topLeft, topRight;
QRect topRightLarger;
desktopRect = QRect(desktopRect.y(), desktopRect.y(), desktopRect.width(), desktopRect.height());
topLeft = sizeRect;
topRight = sizeRect;
topLeft.moveTo(desktopRect.x(), desktopRect.y());
topRight.moveTo(desktopRect.x() + desktopRect.width() - sizeRect.width() - 1, topLeft.y());
topRightLarger = topRight.adjusted(-25, 0, 0, 10);
if (topRightLarger.contains(pos))
{
d->fullScreenBtn->move(topRight.topLeft());
d->fullScreenBtn->show();
}
else
{
d->fullScreenBtn->hide();
}
return false;
}
}
}
}
}
// pass the event on to the parent class
return QObject::eventFilter(obj, ev);
}
void DXmlGuiWindow::keyPressEvent(QKeyEvent* e)
{
if (e->key() == Qt::Key_Escape)
{
if (fullScreenIsActive())
{
d->fullScreenAction->activate(QAction::Trigger);
}
}
}
KToolBar* DXmlGuiWindow::mainToolBar() const
{
QList<KToolBar*> toolbars = toolBars();
KToolBar* mainToolbar = 0;
foreach (KToolBar* const toolbar, toolbars)
{
if (toolbar && (toolbar->objectName() == QLatin1String("mainToolBar")))
{
mainToolbar = toolbar;
break;
}
}
return mainToolbar;
}
void DXmlGuiWindow::showToolBars(bool visible)
{
// We will hide toolbars: store previous state for future restoring.
if (!visible)
{
d->toolbarsVisibility.clear();
foreach (KToolBar* const toolbar, toolBars())
{
if (toolbar)
{
bool visibility = toolbar->isVisible();
d->toolbarsVisibility.insert(toolbar, visibility);
}
}
}
// Switch toolbars visibility
for (QMap<KToolBar*, bool>::const_iterator it = d->toolbarsVisibility.constBegin(); it != d->toolbarsVisibility.constEnd(); ++it)
{
KToolBar* const toolbar = it.key();
bool visibility = it.value();
if (toolbar)
{
if (visible && visibility)
toolbar->show();
else
toolbar->hide();
}
}
// We will show toolbars: restore previous state.
if (visible)
{
for (QMap<KToolBar*, bool>::const_iterator it = d->toolbarsVisibility.constBegin(); it != d->toolbarsVisibility.constEnd(); ++it)
{
KToolBar* const toolbar = it.key();
bool visibility = it.value();
if (toolbar)
{
visibility ? toolbar->show() : toolbar->hide();
}
}
}
}
void DXmlGuiWindow::showSideBars(bool visible)
{
Q_UNUSED(visible);
}
void DXmlGuiWindow::showThumbBar(bool visible)
{
Q_UNUSED(visible);
}
void DXmlGuiWindow::customizedFullScreenMode(bool set)
{
Q_UNUSED(set);
}
bool DXmlGuiWindow::thumbbarVisibility() const
{
return true;
}
void DXmlGuiWindow::slotHelpContents()
{
openHandbook();
}
void DXmlGuiWindow::openHandbook()
{
QUrl url = QUrl(QString::fromUtf8("https://docs.kde.org/trunk5/en/extragear-graphics/%1/index.html")
.arg(QApplication::applicationName()));
WebBrowserDlg* const browser = new WebBrowserDlg(url, qApp->activeWindow());
browser->show();
}
void DXmlGuiWindow::restoreWindowSize(QWindow* const win, const KConfigGroup& group)
{
KWindowConfig::restoreWindowSize(win, group);
}
void DXmlGuiWindow::saveWindowSize(QWindow* const win, KConfigGroup& group)
{
KWindowConfig::saveWindowSize(win, group);
}
QAction* DXmlGuiWindow::buildStdAction(StdActionType type, const QObject* const recvr,
const char* const slot, QObject* const parent)
{
switch(type)
{
case StdCopyAction:
return KStandardAction::copy(recvr, slot, parent);
break;
case StdPasteAction:
return KStandardAction::paste(recvr, slot, parent);
break;
case StdCutAction:
return KStandardAction::cut(recvr, slot, parent);
break;
case StdQuitAction:
return KStandardAction::quit(recvr, slot, parent);
break;
case StdCloseAction:
return KStandardAction::close(recvr, slot, parent);
break;
case StdZoomInAction:
return KStandardAction::zoomIn(recvr, slot, parent);
break;
case StdZoomOutAction:
return KStandardAction::zoomOut(recvr, slot, parent);
break;
case StdOpenAction:
#ifndef __clang_analyzer__
// NOTE: disable false positive report from scan build about open()
return KStandardAction::open(recvr, slot, parent);
#endif
break;
case StdSaveAction:
return KStandardAction::save(recvr, slot, parent);
break;
case StdSaveAsAction:
return KStandardAction::saveAs(recvr, slot, parent);
break;
case StdRevertAction:
return KStandardAction::revert(recvr, slot, parent);
break;
case StdBackAction:
return KStandardAction::back(recvr, slot, parent);
break;
case StdForwardAction:
return KStandardAction::forward(recvr, slot, parent);
break;
default:
return 0;
break;
}
}
void DXmlGuiWindow::slotRawCameraList()
{
showRawCameraList();
}
void DXmlGuiWindow::slotDonateMoney()
{
WebBrowserDlg* const browser
= new WebBrowserDlg(QUrl(QLatin1String("https://www.digikam.org/donate/")),
qApp->activeWindow());
browser->show();
}
void DXmlGuiWindow::slotRecipesBook()
{
WebBrowserDlg* const browser
= new WebBrowserDlg(QUrl(QLatin1String("https://www.digikam.org/recipes_book/")),
qApp->activeWindow());
browser->show();
}
void DXmlGuiWindow::slotContribute()
{
WebBrowserDlg* const browser
= new WebBrowserDlg(QUrl(QLatin1String("https://www.digikam.org/contribute/")),
qApp->activeWindow());
browser->show();
}
void DXmlGuiWindow::setupIconTheme()
{
// Let QStandardPaths handle this, it will look for app local stuff
// this means e.g. for mac: "<APPDIR>/../Resources" and for win: "<APPDIR>/data".
bool hasBreeze = false;
const QString breezeIcons = QStandardPaths::locate(QStandardPaths::DataLocation, QLatin1String("breeze.rcc"));
if (!breezeIcons.isEmpty() && QFile::exists(breezeIcons))
{
QResource::registerResource(breezeIcons);
hasBreeze = true;
}
bool hasBreezeDark = false;
const QString breezeDarkIcons = QStandardPaths::locate(QStandardPaths::DataLocation, QLatin1String("breeze-dark.rcc"));
if (!breezeDarkIcons.isEmpty() && QFile::exists(breezeDarkIcons))
{
QResource::registerResource(breezeDarkIcons);
hasBreezeDark = true;
}
if (hasBreeze || hasBreezeDark)
{
// Tell Qt about the theme
QIcon::setThemeSearchPaths(QStringList() << QLatin1String(":/icons"));
// Tell icons loader an co. about the theme
KConfigGroup cg(KSharedConfig::openConfig(), "Icons");
if (hasBreeze)
{
QIcon::setThemeName(QLatin1String("breeze"));
cg.writeEntry("Theme", "breeze");
qCDebug(DIGIKAM_WIDGETS_LOG) << "Breeze icons resource file found";
}
else if (hasBreezeDark)
{
QIcon::setThemeName(QLatin1String("breeze-dark"));
cg.writeEntry("Theme", "breeze-dark");
qCDebug(DIGIKAM_WIDGETS_LOG) << "Breeze-dark icons resource file found";
}
else
{
qCDebug(DIGIKAM_WIDGETS_LOG) << "No icons resource file found";
}
cg.sync();
}
}
void DXmlGuiWindow::createExportActions()
{
m_exportDropboxAction = new QAction(i18n("Export to &Dropbox..."), this);
m_exportDropboxAction->setIcon(QIcon::fromTheme(QLatin1String("dropbox")));
actionCollection()->addAction(QLatin1String("export_dropbox"), m_exportDropboxAction);
actionCollection()->setDefaultShortcut(m_exportDropboxAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_D);
connect(m_exportDropboxAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportOnedriveAction = new QAction(i18n("Export to &Onedrive..."), this);
m_exportOnedriveAction->setIcon(QIcon::fromTheme(QString::fromLatin1("onedrive")));
actionCollection()->addAction(QLatin1String("export_onedrive"), m_exportOnedriveAction);
actionCollection()->setDefaultShortcut(m_exportOnedriveAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_O);
connect(m_exportOnedriveAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportPinterestAction = new QAction(i18n("Export to &Pinterest..."), this);
m_exportPinterestAction->setIcon(QIcon::fromTheme(QString::fromLatin1("pinterest")));
actionCollection()->addAction(QLatin1String("export_pinterest"), m_exportPinterestAction);
actionCollection()->setDefaultShortcut(m_exportPinterestAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_I);
connect(m_exportPinterestAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportBoxAction = new QAction(i18n("Export to &Box..."), this);
m_exportBoxAction->setIcon(QIcon::fromTheme(QString::fromLatin1("box")));
actionCollection()->addAction(QLatin1String("export_box"), m_exportBoxAction);
actionCollection()->setDefaultShortcut(m_exportBoxAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_B);
connect(m_exportBoxAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportFacebookAction = new QAction(i18n("Export to &Facebook..."), this);
/*
m_exportFacebookAction->setIcon(QIcon::fromTheme(QString::fromLatin1("facebook")));
actionCollection()->addAction(QLatin1String("export_facebook"), m_exportFacebookAction);
actionCollection()->setDefaultShortcut(m_exportFacebookAction, Qt::ALT + Qt::SHIFT + Qt::Key_F);
*/
connect(m_exportFacebookAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportFlickrAction = new QAction(i18n("Export to Flick&r..."), this);
m_exportFlickrAction->setIcon(QIcon::fromTheme(QLatin1String("flickr")));
actionCollection()->addAction(QLatin1String("export_flickr"), m_exportFlickrAction);
actionCollection()->setDefaultShortcut(m_exportFlickrAction, Qt::ALT + Qt::SHIFT + Qt::Key_R);
connect(m_exportFlickrAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportGdriveAction = new QAction(i18n("Export to &Google Drive..."), this);
m_exportGdriveAction->setIcon(QIcon::fromTheme(QLatin1String("googledrive")));
actionCollection()->addAction(QLatin1String("export_googledrive"), m_exportGdriveAction);
actionCollection()->setDefaultShortcut(m_exportGdriveAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_G);
connect(m_exportGdriveAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportGphotoAction = new QAction(i18n("Export to &Google Photos..."), this);
m_exportGphotoAction->setIcon(QIcon::fromTheme(QLatin1String("googlephoto")));
actionCollection()->addAction(QLatin1String("export_googlephoto"), m_exportGphotoAction);
actionCollection()->setDefaultShortcut(m_exportGphotoAction, Qt::ALT + Qt::SHIFT + Qt::Key_P);
connect(m_exportGphotoAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportImageshackAction = new QAction(i18n("Export to &Imageshack..."), this);
m_exportImageshackAction->setIcon(QIcon::fromTheme(QLatin1String("imageshack")));
actionCollection()->addAction(QLatin1String("export_imageshack"), m_exportImageshackAction);
actionCollection()->setDefaultShortcut(m_exportImageshackAction, Qt::ALT + Qt::SHIFT + Qt::Key_M);
connect(m_exportImageshackAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportImgurAction = new QAction(i18n("Export to &Imgur.."), this);
m_exportImgurAction->setIcon(QIcon::fromTheme(QLatin1String("imgur")));
actionCollection()->addAction(QLatin1String("export_imgur"), m_exportImgurAction);
connect(m_exportImgurAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportPiwigoAction = new QAction(i18n("Export to &Piwigo..."), this);
m_exportPiwigoAction->setIcon(QIcon::fromTheme(QLatin1String("piwigo")));
actionCollection()->addAction(QLatin1String("export_piwigo"), m_exportPiwigoAction);
connect(m_exportPiwigoAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportRajceAction = new QAction(i18n("Export to &Rajce.net..."), this);
m_exportRajceAction->setIcon(QIcon::fromTheme(QLatin1String("rajce")));
actionCollection()->addAction(QLatin1String("export_rajce"), m_exportRajceAction);
actionCollection()->setDefaultShortcut(m_exportRajceAction, Qt::ALT + Qt::SHIFT + Qt::Key_J);
connect(m_exportRajceAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportSmugmugAction = new QAction(i18n("Export to &SmugMug..."), this);
m_exportSmugmugAction->setIcon(QIcon::fromTheme(QLatin1String("smugmug")));
actionCollection()->addAction(QLatin1String("export_smugmug"), m_exportSmugmugAction);
actionCollection()->setDefaultShortcut(m_exportSmugmugAction, Qt::ALT + Qt::SHIFT + Qt::Key_S);
connect(m_exportSmugmugAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportYandexfotkiAction = new QAction(i18n("Export to &Yandex.Fotki..."), this);
m_exportYandexfotkiAction->setIcon(QIcon::fromTheme(QLatin1String("internet-web-browser")));
actionCollection()->addAction(QLatin1String("export_yandexfotki"), m_exportYandexfotkiAction);
actionCollection()->setDefaultShortcut(m_exportYandexfotkiAction, Qt::ALT + Qt::SHIFT + Qt::Key_Y);
connect(m_exportYandexfotkiAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
m_exportMediawikiAction = new QAction(i18n("Export to MediaWiki..."), this);
m_exportMediawikiAction->setIcon(QIcon::fromTheme(QLatin1String("MediaWiki")));
actionCollection()->addAction(QLatin1String("export_MediaWiki"), m_exportMediawikiAction);
connect(m_exportMediawikiAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
#ifdef HAVE_VKONTAKTE
m_exportVkontakteAction = new QAction(i18n("Export to &VKontakte..."), this);
m_exportVkontakteAction->setIcon(QIcon::fromTheme(QLatin1String("preferences-web-browser-shortcuts")));
actionCollection()->addAction(QLatin1String("export_vkontakte"), m_exportVkontakteAction);
connect(m_exportVkontakteAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
#endif
#ifdef HAVE_KIO
m_exportFileTransferAction = new QAction(i18n("Export to remote storage..."), this);
m_exportFileTransferAction->setIcon(QIcon::fromTheme(QLatin1String("folder-html")));
actionCollection()->addAction(QLatin1String("export_filetransfer"), m_exportFileTransferAction);
actionCollection()->setDefaultShortcut(m_exportYandexfotkiAction, Qt::ALT + Qt::SHIFT + Qt::Key_K);
connect(m_exportFileTransferAction, SIGNAL(triggered(bool)),
this, SLOT(slotExportTool()));
#endif
}
void DXmlGuiWindow::createImportActions()
{
m_importGphotoAction = new QAction(i18n("Import from &Google Photos..."), this);
m_importGphotoAction->setIcon(QIcon::fromTheme(QLatin1String("googlephoto")));
actionCollection()->addAction(QLatin1String("import_googlephoto"), m_importGphotoAction);
actionCollection()->setDefaultShortcut(m_importGphotoAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_P);
connect(m_importGphotoAction, SIGNAL(triggered(bool)),
this, SLOT(slotImportTool()));
m_importSmugmugAction = new QAction(i18n("Import from &SmugMug..."), this);
m_importSmugmugAction->setIcon(QIcon::fromTheme(QLatin1String("smugmug")));
actionCollection()->addAction(QLatin1String("import_smugmug"), m_importSmugmugAction);
actionCollection()->setDefaultShortcut(m_importSmugmugAction, Qt::ALT + Qt::SHIFT + Qt::CTRL + Qt::Key_S);
connect(m_importSmugmugAction, SIGNAL(triggered(bool)),
this, SLOT(slotImportTool()));
#ifdef HAVE_KIO
m_importFileTransferAction = new QAction(i18n("Import from remote storage..."), this);
m_importFileTransferAction->setIcon(QIcon::fromTheme(QLatin1String("folder-html")));
actionCollection()->addAction(QLatin1String("import_filetransfer"), m_importFileTransferAction);
actionCollection()->setDefaultShortcut(m_importFileTransferAction, Qt::ALT + Qt::SHIFT + Qt::Key_I);
connect(m_importFileTransferAction, SIGNAL(triggered(bool)),
this, SLOT(slotImportTool()));
#endif
#ifdef HAVE_KSANE
m_ksaneAction = new KSaneAction(this);
actionCollection()->addAction(QLatin1String("import_scan"), m_ksaneAction);
connect(m_ksaneAction, SIGNAL(triggered(bool)),
this, SLOT(slotImportFromScanner()));
#endif
}
QList<QAction*> DXmlGuiWindow::exportActions() const
{
return QList<QAction*>() << m_exportDropboxAction
<< m_exportOnedriveAction
<< m_exportPinterestAction
<< m_exportBoxAction
<< m_exportFacebookAction
<< m_exportFlickrAction
<< m_exportGdriveAction
<< m_exportGphotoAction
<< m_exportImageshackAction
<< m_exportImgurAction
<< m_exportPiwigoAction
<< m_exportRajceAction
<< m_exportSmugmugAction
<< m_exportYandexfotkiAction
<< m_exportMediawikiAction
#ifdef HAVE_VKONTAKTE
<< m_exportVkontakteAction
#endif
#ifdef HAVE_KIO
<< m_exportFileTransferAction;
#endif
;
}
QList<QAction*> DXmlGuiWindow::importActions() const
{
return QList<QAction*>() << m_importGphotoAction
<< m_importSmugmugAction
#ifdef HAVE_KIO
<< m_importFileTransferAction
#endif
#ifdef HAVE_KSANE
<< m_ksaneAction
#endif
;
}
int DXmlGuiWindow::actionToWebService(QAction* const action) const
{
if (action == m_exportBoxAction)
{
return WSStarter::ExportBox;
}
else if (action == m_exportDropboxAction)
{
return WSStarter::ExportDropbox;
}
else if (action == m_exportFacebookAction)
{
return WSStarter::ExportFacebook;
}
#ifdef HAVE_KIO
else if (action == m_exportFileTransferAction)
{
return WSStarter::ExportFileTransfer;
}
#endif
else if (action == m_exportFlickrAction)
{
return WSStarter::ExportFlickr;
}
else if (action == m_exportGdriveAction)
{
return WSStarter::ExportGdrive;
}
else if (action == m_exportGphotoAction)
{
return WSStarter::ExportGphoto;
}
else if (action == m_exportImageshackAction)
{
return WSStarter::ExportImageshack;
}
else if (action == m_exportImgurAction)
{
return WSStarter::ExportImgur;
}
else if (action == m_exportMediawikiAction)
{
return WSStarter::ExportMediawiki;
}
else if (action == m_exportOnedriveAction)
{
return WSStarter::ExportOnedrive;
}
else if (action == m_exportPinterestAction)
{
return WSStarter::ExportPinterest;
}
else if (action == m_exportPiwigoAction)
{
return WSStarter::ExportPiwigo;
}
else if (action == m_exportRajceAction)
{
return WSStarter::ExportRajce;
}
else if (action == m_exportSmugmugAction)
{
return WSStarter::ExportSmugmug;
}
#ifdef HAVE_VKONTAKTE
else if (action == m_exportVkontakteAction)
{
return WSStarter::ExportVkontakte;
}
#endif
else if (action == m_exportYandexfotkiAction)
{
return WSStarter::ExportYandexfotki;
}
else if (action == m_importGphotoAction)
{
return WSStarter::ImportGphoto;
}
#ifdef HAVE_KIO
else if (action == m_importFileTransferAction)
{
return WSStarter::ImportFileTransfer;
}
#endif
else if (action == m_importSmugmugAction)
{
return WSStarter::ImportSmugmug;
}
return WSStarter::ExportUnknown;
}
} // namespace Digikam
diff --git a/core/libs/widgets/mainview/schememanager.h b/core/libs/widgets/mainview/schememanager.h
index 011ad2a9cb..cee64b03af 100644
--- a/core/libs/widgets/mainview/schememanager.h
+++ b/core/libs/widgets/mainview/schememanager.h
@@ -1,471 +1,471 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-08-02
* Description : colors scheme manager
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2007 by Matthew Woehlke <mw_triad at users dot sourceforge dot net>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_SCHEME_MANAGER_H
#define DIGIKAM_SCHEME_MANAGER_H
// Qt includes
#include <QExplicitlySharedDataPointer>
#include <QPalette>
#include <QColor>
#include <QBrush>
// KDE includes
#include <ksharedconfig.h>
// Local includes
#include "digikam_export.h"
namespace Digikam
{
class SchemeManagerPrivate;
/**
* A set of methods used to work with colors.
*
* SchemeManager currently provides access to the system color palette that the
* user has selected (in the future, it is expected to do more). It greatly
* expands on QPalette by providing five distinct "sets" with several color
* choices each, covering background, foreground, and decoration colors.
*
* A SchemeManager instance represents colors corresponding to a "set", where a
* set consists of those colors used to draw a particular type of element, such
* as a menu, button, view, selected text, or tooltip. Each set has a distinct
* set of colors, so you should always use the correct set for drawing and
* never assume that a particular foreground for one set is the same as the
* foreground for any other set. Individual colors may be quickly referenced by
* creating an anonymous instance and invoking a lookup member.
*
* @note
* The color palettes for the various states of a widget (active, inactive,
* disabled) may be wildly different. Therefore, it is important to take the
* state into account. This is why the SchemeManager constructor requires a
* QPalette::ColorGroup as an argument.
*
* To facilitate working with potentially-varying states, two convenience API's
* are provided. These are SchemeManager::adjustBackground and its sister
* SchemeManager::adjustForeground, and the helper class ::KStatefulBrush.
*
* @see SchemeManager::ColorSet, SchemeManager::ForegroundRole,
* SchemeManager::BackgroundRole, SchemeManager::DecorationRole,
* SchemeManager::ShadeRole
*/
class DIGIKAM_EXPORT SchemeManager
{
public:
/**
* This enumeration describes the color set for which a color is being
* selected.
*
* Color sets define a color "environment", suitable for drawing all parts
* of a given region. Colors from different sets should not be combined.
*/
enum ColorSet
{
/**
* Views; for example, frames, input fields, etc.
*
* If it contains things that can be selected, it is probably a View.
*/
View,
/**
* Non-editable window elements; for example, menus.
*
* If it isn't a Button, View, or Tooltip, it is probably a Window.
*/
Window,
/**
* Buttons and button-like controls.
*
* In addition to buttons, "button-like" controls such as non-editable
* dropdowns, scrollbar sliders, slider handles, etc. should also use
* this role.
*/
Button,
/**
* Selected items in views.
*
* Note that unfocused or disabled selections should use the Window
* role. This makes it more obvious to the user that the view
* containing the selection does not have input focus.
*/
Selection,
/**
* Tooltips.
*
* The tooltip set can often be substituted for the view
* set when editing is not possible, but the Window set is deemed
* inappropriate. "What's This" help is an excellent example, another
* might be pop-up notifications (depending on taste).
*/
Tooltip,
/**
* Complementary areas.
*
* Some applications want some areas to have a different color scheme.
- * Ususally dark areas over a light theme. For instance the fullscreen UI
+ * Usually dark areas over a light theme. For instance the fullscreen UI
* of a picture viewer, or the logout/lock screen of the plasma workspace
* ask for a dark color scheme even on light themes.
* @since 5.19
*/
Complementary
};
/**
* This enumeration describes the background color being selected from the
* given set.
*
* Background colors are suitable for drawing under text, and should never
* be used to draw text. In combination with one of the overloads of
* SchemeManager::shade, they may be used to generate colors for drawing
* frames, bevels, and similar decorations.
*/
enum BackgroundRole
{
/**
* Normal background.
*/
NormalBackground = 0,
/**
* Alternate background; for example, for use in lists.
*
* This color may be the same as BackgroundNormal, especially in sets
* other than View and Window.
*/
AlternateBackground = 1,
/**
* Third color; for example, items which are new, active, requesting
* attention, etc.
*
* Alerting the user that a certain field must be filled out would be a
* good usage (although NegativeBackground could be used to the same
* effect, depending on what you are trying to achieve). Unlike
* ActiveText, this should not be used for mouseover effects.
*/
ActiveBackground = 2,
/**
* Fourth color; corresponds to (unvisited) links.
*
* Exactly what this might be used for is somewhat harder to qualify;
* it might be used for bookmarks, as a 'you can click here' indicator,
* or to highlight recent content (i.e. in a most-recently-accessed
* list).
*/
LinkBackground = 3,
/**
* Fifth color; corresponds to visited links.
*
* This can also be used to indicate "not recent" content, especially
* when a color is needed to denote content which is "old" or
* "archival".
*/
VisitedBackground = 4,
/**
* Sixth color; for example, errors, untrusted content, etc.
*/
NegativeBackground = 5,
/**
* Seventh color; for example, warnings, secure/encrypted content.
*/
NeutralBackground = 6,
/**
* Eigth color; for example, success messages, trusted content.
*/
PositiveBackground = 7
};
/**
* This enumeration describes the foreground color being selected from the
* given set.
*
* Foreground colors are suitable for drawing text or glyphs (such as the
* symbols on window decoration buttons, assuming a suitable background
* brush is used), and should never be used to draw backgrounds.
*
* For window decorations, the following is suggested, but not set in
* stone:
* @li Maximize - PositiveText
* @li Minimize - NeutralText
* @li Close - NegativeText
* @li WhatsThis - LinkText
* @li Sticky - ActiveText
*/
enum ForegroundRole
{
/**
* Normal foreground.
*/
NormalText = 0,
/**
* Second color; for example, comments, items which are old, inactive
* or disabled. Generally used for things that are meant to be "less
* important". InactiveText is not the same role as NormalText in the
* inactive state.
*/
InactiveText = 1,
/**
* Third color; for example items which are new, active, requesting
* attention, etc. May be used as a hover color for clickable items.
*/
ActiveText = 2,
/**
* Fourth color; use for (unvisited) links. May also be used for other
* clickable items or content that indicates relationships, items that
* indicate somewhere the user can visit, etc.
*/
LinkText = 3,
/**
* Fifth color; used for (visited) links. As with LinkText, may be used
* for items that have already been "visited" or accessed. May also be
* used to indicate "historical" (i.e. "old") items or information,
* especially if InactiveText is being used in the same context to
* express something different.
*/
VisitedText = 4,
/**
* Sixth color; for example, errors, untrusted content, deletions,
* etc.
*/
NegativeText = 5,
/**
* Seventh color; for example, warnings, secure/encrypted content.
*/
NeutralText = 6,
/**
* Eigth color; for example, additions, success messages, trusted
* content.
*/
PositiveText = 7
};
/**
* This enumeration describes the decoration color being selected from the
* given set.
*
* Decoration colors are used to draw decorations (such as frames) for
* special purposes. Like color shades, they are neither foreground nor
* background colors. Text should not be painted over a decoration color,
* and decoration colors should not be used to draw text.
*/
enum DecorationRole
{
/**
* Color used to draw decorations for items which have input focus.
*/
FocusColor,
/**
* Color used to draw decorations for items which will be activated by
* clicking.
*/
HoverColor
};
/**
* This enumeration describes the color shade being selected from the given
* set.
*
* Color shades are used to draw "3d" elements, such as frames and bevels.
* They are neither foreground nor background colors. Text should not be
* painted over a shade, and shades should not be used to draw text.
*/
enum ShadeRole
{
/**
* The light color is lighter than dark() or shadow() and contrasts
* with the base color.
*/
LightShade,
/**
* The midlight color is in between base() and light().
*/
MidlightShade,
/**
* The mid color is in between base() and dark().
*/
MidShade,
/**
* The dark color is in between mid() and shadow().
*/
DarkShade,
/**
* The shadow color is darker than light() or midlight() and contrasts
* the base color.
*/
ShadowShade
};
public:
/**
* Construct a copy of another SchemeManager.
*/
SchemeManager(const SchemeManager&);
/**
* Destructor
*/
virtual ~SchemeManager();
/**
* Standard assignment operator
*/
SchemeManager& operator=(const SchemeManager&);
/**
* Construct a palette from given color set and state, using the colors
* from the given KConfig (if null, the system colors are used).
*/
explicit SchemeManager(QPalette::ColorGroup, ColorSet = View, KSharedConfigPtr = KSharedConfigPtr());
/**
* Retrieve the requested background brush.
*/
QBrush background(BackgroundRole = NormalBackground) const;
/**
* Retrieve the requested foreground brush.
*/
QBrush foreground(ForegroundRole = NormalText) const;
/**
* Retrieve the requested decoration brush.
*/
QBrush decoration(DecorationRole) const;
/**
* Retrieve the requested shade color, using
* SchemeManager::background(SchemeManager::NormalBackground)
* as the base color and the contrast setting from the KConfig used to
* create this SchemeManager instance (the system contrast setting, if no
* KConfig was specified).
*
* @note Shades are chosen such that all shades would contrast with the
* base color. This means that if base is very dark, the 'dark' shades will
* be lighter than the base color, with midlight() == shadow().
* Conversely, if the base color is very light, the 'light' shades will be
* darker than the base color, with light() == mid().
*/
QColor shade(ShadeRole) const;
/**
* Returns the contrast for borders.
* @return the contrast (between 0 for minimum and 10 for maximum
* contrast)
*/
static int contrast();
/**
* Returns the contrast for borders as a floating point value.
* @param config pointer to the config from which to read the contrast
* setting (the default is to use KSharedConfig::openConfig())
* @return the contrast (between 0.0 for minimum and 1.0 for maximum
* contrast)
*/
static qreal contrastF(const KSharedConfigPtr& config = KSharedConfigPtr());
/**
* Retrieve the requested shade color, using the specified color as the
* base color and the system contrast setting.
*
* @note Shades are chosen such that all shades would contrast with the
* base color. This means that if base is very dark, the 'dark' shades will
* be lighter than the base color, with midlight() == shadow().
* Conversely, if the base color is very light, the 'light' shades will be
* darker than the base color, with light() == mid().
*/
static QColor shade(const QColor&, ShadeRole);
/**
* Retrieve the requested shade color, using the specified color as the
* base color and the specified contrast.
*
* @param contrast Amount roughly specifying the contrast by which to
* adjust the base color, between -1.0 and 1.0 (values between 0.0 and 1.0
* correspond to the value from SchemeManager::contrastF)
* @param chromaAdjust (optional) Amount by which to adjust the chroma of
* the shade (1.0 means no adjustment)
*
* @note Shades are chosen such that all shades would contrast with the
* base color. This means that if base is very dark, the 'dark' shades will
* be lighter than the base color, with midlight() == shadow().
* Conversely, if the base color is very light, the 'light' shades will be
* darker than the base color, with light() == mid().
*
*/
static QColor shade(const QColor&, ShadeRole,
qreal contrast, qreal chromaAdjust = 0.0);
/**
* Adjust a QPalette by replacing the specified QPalette::ColorRole with
* the requested background color for all states. Using this method is
* safer than replacing individual states, as it insulates you against
* changes in QPalette::ColorGroup.
*
* @note Although it is possible to replace a foreground color using this
* method, it's bad usability to do so. Just say "no".
*/
static void adjustBackground(QPalette&,
BackgroundRole newRole = NormalBackground,
QPalette::ColorRole color = QPalette::Base,
ColorSet set = View,
KSharedConfigPtr = KSharedConfigPtr());
/**
* Adjust a QPalette by replacing the specified QPalette::ColorRole with
* the requested foreground color for all states. Using this method is
* safer than replacing individual states, as it insulates you against
* changes in QPalette::ColorGroup.
*
* @note Although it is possible to replace a background color using this
* method, it's bad usability to do so. Just say "no".
*/
static void adjustForeground(QPalette&,
ForegroundRole newRole = NormalText,
QPalette::ColorRole color = QPalette::Text,
ColorSet set = View,
KSharedConfigPtr = KSharedConfigPtr());
/**
* Used to obtain the QPalette that will be used to set the application
* palette from KDE Platform theme.
*
* @param config KConfig from which to load the colors
*
* @returns the QPalette
*/
static QPalette createApplicationPalette(const KSharedConfigPtr& config);
private:
QExplicitlySharedDataPointer<SchemeManagerPrivate> d;
};
} // namespace Digikam
#endif // DIGIKAM_SCHEME_MANAGER_H
diff --git a/core/libs/widgets/mainview/sidebar.h b/core/libs/widgets/mainview/sidebar.h
index dfe589c7c0..1520691d0a 100644
--- a/core/libs/widgets/mainview/sidebar.h
+++ b/core/libs/widgets/mainview/sidebar.h
@@ -1,530 +1,530 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-03-22
* Description : a widget to manage sidebar in GUI.
*
* Copyright (C) 2005-2006 by Joern Ahrens <joern dot ahrens at kdemail dot net>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2008-2013 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2001-2003 by Joseph Wenninger <jowenn at kde dot org>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_SIDE_BAR_H
#define DIGIKAM_SIDE_BAR_H
// Qt includes
#include <QPixmap>
#include <QSplitter>
#include <QPushButton>
#include <QWidget>
#include <QList>
#include <QStyleOptionToolButton>
#include <QBoxLayout>
// KDE includes
#include <kconfiggroup.h>
// Local includes
#include "digikam_export.h"
#include "statesavingobject.h"
namespace Digikam
{
class DMultiTabBarButton;
class DMultiTabBarTab;
/**
* A Widget for horizontal and vertical tabs.
*/
class DIGIKAM_EXPORT DMultiTabBar: public QWidget
{
Q_OBJECT
public:
/**
* The list of available styles for DMultiTabBar
*/
enum TextStyle
{
ActiveIconText = 0, /// Always shows icon, only show the text of active tabs.
AllIconsText = 2 /// Always shows the text and icons.
};
public:
explicit DMultiTabBar(Qt::Edge pos, QWidget* const parent=0);
virtual ~DMultiTabBar();
/**
* append a new button to the button area. The button can later on be accessed with button(ID)
* eg for connecting signals to it
* @param pic a pixmap for the button
- * @param id an arbitraty ID value. It will be emitted in the clicked signal for identifying the button
+ * @param id an arbitrary ID value. It will be emitted in the clicked signal for identifying the button
* if more than one button is connected to a signals.
* @param popup A popup menu which should be displayed if the button is clicked
* @param not_used_yet will be used for a popup text in the future
*/
int appendButton(const QPixmap &pic, int id=-1, QMenu* const popup=0, const QString& not_used_yet=QString());
/**
* remove a button with the given ID
*/
void removeButton(int id);
/**
- * append a new tab to the tab area. It can be accessed lateron with tabb(id);
+ * append a new tab to the tab area. It can be accessed later on with tabb(id);
* @param pic a bitmap for the tab
* @param id an arbitrary ID which can be used later on to identify the tab
* @param text if a mode with text is used it will be the tab text, otherwise a mouse over hint
*/
int appendTab(const QPixmap& pic,int id=-1,const QString& text=QString());
/**
* remove a tab with a given ID
*/
void removeTab(int id);
/**
* set a tab to "raised"
* @param id The ID of the tab to manipulate
* @param state true == activated/raised, false == not active
*/
void setTab(int id, bool state);
/**
* return the state of a tab, identified by its ID
*/
bool isTabRaised(int id) const;
/**
* get a pointer to a button within the button area identified by its ID
*/
DMultiTabBarButton* button(int id) const;
/**
- * get a pointer to a tab within the tab area, identiifed by its ID
+ * get a pointer to a tab within the tab area, identified by its ID
*/
DMultiTabBarTab* tab(int id) const;
/**
* set the real position of the widget.
* @param pos if the mode is horizontal, only use top, bottom, if it is vertical use left or right
*/
void setPosition(Qt::Edge pos);
/**
* get the tabbar position.
* @return position
*/
Qt::Edge position() const;
/**
* set the display style of the tabs
*/
void setStyle(TextStyle style);
/**
* get the display style of the tabs
* @return display style
*/
TextStyle tabStyle() const;
protected:
void updateSeparator();
virtual void fontChange(const QFont&);
private:
friend class DMultiTabBarButton;
class Private;
Private* const d;
};
// -------------------------------------------------------------------------------------
class DIGIKAM_EXPORT DMultiTabBarButton: public QPushButton
{
Q_OBJECT
public:
int id() const;
virtual ~DMultiTabBarButton();
public Q_SLOTS:
void setText(const QString& text);
Q_SIGNALS:
/**
* this is emitted if the button is clicked
* @param id the ID identifying the button
*/
void clicked(int id);
protected Q_SLOTS:
virtual void slotClicked();
protected:
DMultiTabBarButton(const QPixmap& pic, const QString&, int id, QWidget* const parent);
virtual void hideEvent(QHideEvent*);
virtual void showEvent(QShowEvent*);
virtual void paintEvent(QPaintEvent*);
private:
friend class DMultiTabBar;
int m_id;
};
// -------------------------------------------------------------------------------------
class DIGIKAM_EXPORT DMultiTabBarTab: public DMultiTabBarButton
{
Q_OBJECT
public:
virtual ~DMultiTabBarTab();
virtual QSize sizeHint() const;
virtual QSize minimumSizeHint() const;
public Q_SLOTS:
/**
- * this is used internaly, but can be used by the user.
+ * this is used internally, but can be used by the user.
* It the according call of DMultiTabBar is invoked though this modifications will be overwritten
*/
void setPosition(Qt::Edge);
/**
- * this is used internaly, but can be used by the user.
+ * this is used internally, but can be used by the user.
* It the according call of DMultiTabBar is invoked though this modifications will be overwritten
*/
void setStyle(DMultiTabBar::TextStyle);
/**
* set the active state of the tab
* @param state true==active false==not active
*/
void setState(bool state);
void setIcon(const QString&);
void setIcon(const QPixmap&);
protected:
void computeMargins (int* hMargin, int* vMargin) const;
QSize computeSizeHint(bool withText) const;
bool shouldDrawText() const;
bool isVertical() const;
QPixmap iconPixmap() const;
void initStyleOption(QStyleOptionToolButton* opt) const;
friend class DMultiTabBarFrame;
/**
* This class should never be created except with the appendTab call of DMultiTabBar
*/
DMultiTabBarTab(const QPixmap& pic, const QString&, int id, QWidget* const parent,
Qt::Edge pos, DMultiTabBar::TextStyle style);
virtual void paintEvent(QPaintEvent*);
private:
class Private;
Private* const d;
};
// -------------------------------------------------------------------------------------
class DMultiTabBarFrame: public QFrame
{
Q_OBJECT
public:
explicit DMultiTabBarFrame(QWidget* const parent, Qt::Edge pos);
virtual ~DMultiTabBarFrame();
int appendTab(const QPixmap&, int = -1, const QString& = QString());
DMultiTabBarTab* tab(int) const;
void removeTab(int);
void setPosition(Qt::Edge pos);
void setStyle(DMultiTabBar::TextStyle style);
void showActiveTabTexts(bool show);
QList<DMultiTabBarTab*>* tabs();
protected:
/**
* Reimplemented from QScrollView
* in order to ignore all mouseEvents on the viewport, so that the
* parent can handle them.
*/
virtual void contentsMousePressEvent(QMouseEvent*);
virtual void mousePressEvent(QMouseEvent*);
private:
friend class DMultiTabBar;
class Private;
Private* const d;
};
// -------------------------------------------------------------------------------------
class SidebarSplitter;
/**
* This class handles a sidebar view
*
* Since this class derives from StateSavingObject, you can call
* StateSavingObject#loadState() and StateSavingObject#saveState()
* for loading/saving of settings. However, if you use multiple
* sidebar instances in your program, you have to remember to either
* call QObject#setObjectName(), StateSavingObject#setEntryPrefix() or
* StateSavingObject#setConfigGroup() first.
*/
class DIGIKAM_EXPORT Sidebar : public DMultiTabBar, public StateSavingObject
{
Q_OBJECT
public:
/**
* Creates a new sidebar
* @param parent sidebar's parent
* @param sp sets the splitter, which should handle the width. The splitter normally
* is part of the main view. Internally, the width of the widget stack can
* be changed by a QSplitter.
* @param side where the sidebar should be displayed. At the left or right border.
Use Qt::LeftEdge or Qt::RightEdge.
* @param minimizedDefault hide the sidebar when the program is started the first time.
*/
explicit Sidebar(QWidget* const parent, SidebarSplitter* const sp, Qt::Edge side = Qt::LeftEdge,
bool minimizedDefault=false);
virtual ~Sidebar();
SidebarSplitter* splitter() const;
/**
* Appends a new tab to the sidebar
* @param w widget which is activated by this tab
* @param pic icon which is shown in this tab
* @param title text which is shown it this tab
*/
void appendTab(QWidget* const w, const QIcon& pic, const QString& title);
/**
* Deletes a tab from the tabbar
*/
void deleteTab(QWidget* const w);
/**
* Activates a tab
*/
void setActiveTab(QWidget* const w);
/**
- * Activates a next tab from current one. If current one is last, first one is actived.
+ * Activates a next tab from current one. If current one is last, first one is activated.
*/
void activeNextTab();
/**
- * Activates a previous tab from current one. If current one is first, last one is actived.
+ * Activates a previous tab from current one. If current one is first, last one is activated.
*/
void activePreviousTab();
/**
* Returns the currently activated tab, or 0 if no tab is active
*/
QWidget* getActiveTab() const;
/**
* Hides the sidebar (display only the activation buttons)
*/
void shrink();
/**
* redisplays the whole sidebar
*/
void expand();
/**
* hide sidebar and backup minimized state.
*/
void backup();
/**
* Hide sidebar and backup minimized state.
* If there are other widgets in this splitter, stores
* their sizes in the provided list.
*/
void backup(const QList<QWidget*> thirdWidgetsToBackup, QList<int>* const sizes);
/**
* show sidebar and restore minimized state.
*/
void restore();
/**
* Show sidebar and restore minimized state.
* Restores other widgets' sizes in splitter.
*/
void restore(const QList<QWidget*> thirdWidgetsToRestore, const QList<int>& sizes);
/**
* return the visible status of current sidebar tab.
*/
bool isExpanded() const;
protected:
/**
* load the last view state from disk - called by StateSavingObject#loadState()
*/
void doLoadState();
/**
* save the view state to disk - called by StateSavingObject#saveState()
*/
void doSaveState();
private:
bool eventFilter(QObject* o, QEvent* e);
void switchTabAndStackToTab(int tab);
private Q_SLOTS:
/**
* Activates a tab
*/
void clicked(int tab);
void slotDragSwitchTimer();
void slotSplitterBtnClicked();
Q_SIGNALS:
/**
* is emitted, when another tab is activated
*/
void signalChangedTab(QWidget* w);
/**
* is emitted, when tab is shrink or expanded
*/
void signalViewChanged();
private:
friend class SidebarSplitter;
class Private;
Private* const d;
};
// -----------------------------------------------------------------------------
class DIGIKAM_EXPORT SidebarSplitter : public QSplitter
{
Q_OBJECT
public:
const static QString DEFAULT_CONFIG_KEY;
/**
* This is a QSplitter with better support for storing its state
* in config files, especially if Sidebars are contained in the splitter.
*/
explicit SidebarSplitter(QWidget* const parent = 0);
explicit SidebarSplitter(Qt::Orientation orientation, QWidget* const parent = 0);
~SidebarSplitter();
/**
* Saves the splitter state to group, handling minimized sidebars correctly.
* DEFAULT_CONFIG_KEY is used for storing the state.
*/
void saveState(KConfigGroup& group);
/**
* Saves the splitter state to group, handling minimized sidebars correctly.
* This version uses a specified key in the config group.
*/
void saveState(KConfigGroup& group, const QString& key);
/**
* Restores the splitter state from group, handling minimized sidebars correctly.
* DEFAULT_CONFIG_KEY is used for restoring the state.
*/
void restoreState(KConfigGroup& group);
/**
* Restores the splitter state from group, handling minimized sidebars correctly.
* This version uses a specified key in the config group.
*/
void restoreState(KConfigGroup& group, const QString& key);
/**
* Returns the value of sizes() that corresponds to the given Sidebar or splitter child widget.
*/
int size(Sidebar* const bar) const;
int size(QWidget* const widget) const;
/**
* Sets the splitter size for the given sidebar or splitter child widget to size.
* Special value -1: Sets the minimum size hint of the widget.
*/
void setSize(Sidebar* const bar, int size);
void setSize(QWidget* const widget, int size);
void addSplitterCollapserButton(QWidget* const widget);
private Q_SLOTS:
void slotSplitterMoved(int pos, int index);
private:
friend class Sidebar;
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_SIDE_BAR_H
diff --git a/core/libs/widgets/metadata/colorlabelwidget.h b/core/libs/widgets/metadata/colorlabelwidget.h
index cf27354826..8e7953c0cc 100644
--- a/core/libs/widgets/metadata/colorlabelwidget.h
+++ b/core/libs/widgets/metadata/colorlabelwidget.h
@@ -1,148 +1,148 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-01-28
* Description : color label widget
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_COLOR_LABEL_WIDGET_H
#define DIGIKAM_COLOR_LABEL_WIDGET_H
// Qt includes
#include <QColor>
#include <QPushButton>
#include <QEvent>
#include <QList>
#include <QMetaType>
#include <QMenu>
// Local includes
#include "dlayoutbox.h"
#include "digikam_globals.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT ColorLabelWidget : public DVBox
{
Q_OBJECT
public:
explicit ColorLabelWidget(QWidget* const parent=0);
virtual ~ColorLabelWidget();
/**
- * Show or not on the bottom view the description of label with shorcuts.
+ * Show or not on the bottom view the description of label with shortcuts.
*/
void setDescriptionBoxVisible(bool b);
/**
* Set all Color Label buttons exclusive or not. Default is true as only one can be selected.
* Non-exclusive mode is dedicated for Advanced Search tool.
*/
void setButtonsExclusive(bool b);
/**
* Turn on Color Label buttons using list. Pass an empty list to clear all selection.
*/
void setColorLabels(const QList<ColorLabel>& list);
/**
* Return the list of Color Label buttons turned on or an empty list of none.
*/
QList<ColorLabel> colorLabels() const;
static QColor labelColor(ColorLabel label);
static QString labelColorName(ColorLabel label);
static QIcon buildIcon(ColorLabel label, int size=12);
Q_SIGNALS:
void signalColorLabelChanged(int);
protected:
bool eventFilter(QObject* obj, QEvent* ev);
private:
void updateDescription(ColorLabel label);
private:
class Private;
Private* const d;
};
// ------------------------------------------------------------------------------
class DIGIKAM_EXPORT ColorLabelSelector : public QPushButton
{
Q_OBJECT
public:
explicit ColorLabelSelector(QWidget* const parent=0);
virtual ~ColorLabelSelector();
void setColorLabel(ColorLabel label);
ColorLabel colorLabel();
ColorLabelWidget* colorLabelWidget() const;
Q_SIGNALS:
void signalColorLabelChanged(int);
private Q_SLOTS:
void slotColorLabelChanged(int);
private:
class Private;
Private* const d;
};
// ------------------------------------------------------------------------------
class DIGIKAM_EXPORT ColorLabelMenuAction : public QMenu
{
Q_OBJECT
public:
explicit ColorLabelMenuAction(QMenu* const parent=0);
virtual ~ColorLabelMenuAction();
Q_SIGNALS:
void signalColorLabelChanged(int);
};
} // namespace Digikam
Q_DECLARE_METATYPE(QList<Digikam::ColorLabel>)
#endif // DIGIKAM_COLOR_LABEL_WIDGET_H
diff --git a/core/libs/widgets/metadata/picklabelwidget.h b/core/libs/widgets/metadata/picklabelwidget.h
index bd00bddea6..bd9ff04158 100644
--- a/core/libs/widgets/metadata/picklabelwidget.h
+++ b/core/libs/widgets/metadata/picklabelwidget.h
@@ -1,147 +1,147 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-02-14
* Description : pick label widget
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_PICK_LABEL_WIDGET_H
#define DIGIKAM_PICK_LABEL_WIDGET_H
// Qt includes
#include <QColor>
#include <QPushButton>
#include <QEvent>
#include <QList>
#include <QMetaType>
#include <QMenu>
// Local includes
#include "dlayoutbox.h"
#include "digikam_globals.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT PickLabelWidget : public DVBox
{
Q_OBJECT
public:
explicit PickLabelWidget(QWidget* const parent=0);
virtual ~PickLabelWidget();
/**
- * Show or not on the bottom view the description of label with shorcuts.
+ * Show or not on the bottom view the description of label with shortcuts.
*/
void setDescriptionBoxVisible(bool b);
/**
* Set all Color Label buttons exclusive or not. Default is true as only one can be selected.
* Non-exclusive mode is dedicated for Advanced Search tool.
*/
void setButtonsExclusive(bool b);
/**
* Turn on Color Label buttons using list. Pass an empty list to clear all selection.
*/
void setPickLabels(const QList<PickLabel>& list);
/**
* Return the list of Color Label buttons turned on or an empty list of none.
*/
QList<PickLabel> colorLabels() const;
static QString labelPickName(PickLabel label);
static QIcon buildIcon(PickLabel label);
Q_SIGNALS:
void signalPickLabelChanged(int);
protected:
bool eventFilter(QObject* obj, QEvent* ev);
private:
void updateDescription(PickLabel label);
private:
class Private;
Private* const d;
};
// ------------------------------------------------------------------------------
class DIGIKAM_EXPORT PickLabelSelector : public QPushButton
{
Q_OBJECT
public:
explicit PickLabelSelector(QWidget* const parent=0);
virtual ~PickLabelSelector();
void setPickLabel(PickLabel label);
PickLabel colorLabel();
PickLabelWidget* pickLabelWidget() const;
Q_SIGNALS:
void signalPickLabelChanged(int);
private Q_SLOTS:
void slotPickLabelChanged(int);
private:
class Private;
Private* const d;
};
// ------------------------------------------------------------------------------
class DIGIKAM_EXPORT PickLabelMenuAction : public QMenu
{
Q_OBJECT
public:
explicit PickLabelMenuAction(QMenu* const parent=0);
virtual ~PickLabelMenuAction();
Q_SIGNALS:
void signalPickLabelChanged(int);
};
} // namespace Digikam
Q_DECLARE_METATYPE(QList<Digikam::PickLabel>)
#endif // DIGIKAM_PICK_LABEL_WIDGET_H
diff --git a/core/libs/widgets/range/dsliderspinbox.cpp b/core/libs/widgets/range/dsliderspinbox.cpp
index 4e1f3d28d1..ebaac84705 100644
--- a/core/libs/widgets/range/dsliderspinbox.cpp
+++ b/core/libs/widgets/range/dsliderspinbox.cpp
@@ -1,1171 +1,1171 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2014-11-30
* Description : Save space slider widget
*
* Copyright (C) 2014-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2010 by Justin Noel <justin at ics dot com>
* Copyright (C) 2010 by Cyrille Berger <cberger at cberger dot net>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dsliderspinbox.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QStyle>
#include <QPainter>
#include <QLineEdit>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QApplication>
#include <QIntValidator>
#include <QDoubleSpinBox>
namespace Digikam
{
class Q_DECL_HIDDEN DAbstractSliderSpinBoxPrivate
{
public:
DAbstractSliderSpinBoxPrivate()
{
edit = 0;
validator = 0;
dummySpinBox = 0;
upButtonDown = false;
downButtonDown = false;
factor = 1.0;
fastSliderStep = 5;
slowFactor = 0.1;
shiftPercent = 0.0;
shiftMode = false;
exponentRatio = 0.0;
value = 0;
maximum = 100;
minimum = 0;
singleStep = 1;
style = STYLE_NOQUIRK;
blockUpdateSignalOnDrag = false;
isDragging = false;
}
enum Style
{
STYLE_NOQUIRK,
STYLE_PLASTIQUE,
STYLE_BREEZE,
STYLE_FUSION,
};
QLineEdit* edit;
QDoubleValidator* validator;
QSpinBox* dummySpinBox;
bool upButtonDown;
bool downButtonDown;
int factor;
int fastSliderStep;
double slowFactor;
double shiftPercent;
bool shiftMode;
QString prefix;
QString suffix;
double exponentRatio;
int value;
int maximum;
int minimum;
int singleStep;
Style style;
bool blockUpdateSignalOnDrag;
bool isDragging;
};
DAbstractSliderSpinBox::DAbstractSliderSpinBox(QWidget* const parent, DAbstractSliderSpinBoxPrivate* const q)
: QWidget(parent),
d_ptr(q)
{
Q_D(DAbstractSliderSpinBox);
QEvent e(QEvent::StyleChange);
changeEvent(&e);
d->edit = new QLineEdit(this);
d->edit->setContentsMargins(0, 0, 0, 0);
d->edit->setAlignment(Qt::AlignCenter);
d->edit->installEventFilter(this);
d->edit->setFrame(false);
d->edit->hide();
// Make edit transparent
d->edit->setAutoFillBackground(false);
QPalette pal = d->edit->palette();
pal.setColor(QPalette::Base, Qt::transparent);
d->edit->setPalette(pal);
connect(d->edit, SIGNAL(editingFinished()),
this, SLOT(editLostFocus()));
d->validator = new QDoubleValidator(d->edit);
d->edit->setValidator(d->validator);
setExponentRatio(1.0);
// Set sane defaults
setFocusPolicy(Qt::StrongFocus);
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
// dummy needed to fix a bug in the polyester theme
d->dummySpinBox = new QSpinBox(this);
d->dummySpinBox->hide();
}
DAbstractSliderSpinBox::~DAbstractSliderSpinBox()
{
Q_D(DAbstractSliderSpinBox);
delete d;
}
void DAbstractSliderSpinBox::showEdit()
{
Q_D(DAbstractSliderSpinBox);
if (d->edit->isVisible())
return;
if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE)
{
d->edit->setGeometry(progressRect(spinBoxOptions()).adjusted(0, 0, -2, 0));
}
else
{
d->edit->setGeometry(progressRect(spinBoxOptions()));
}
d->edit->setText(valueString());
d->edit->selectAll();
d->edit->show();
d->edit->setFocus(Qt::OtherFocusReason);
update();
}
void DAbstractSliderSpinBox::hideEdit()
{
Q_D(DAbstractSliderSpinBox);
d->edit->hide();
update();
}
void DAbstractSliderSpinBox::paintEvent(QPaintEvent* e)
{
Q_D(DAbstractSliderSpinBox);
Q_UNUSED(e)
QPainter painter(this);
switch (d->style)
{
case DAbstractSliderSpinBoxPrivate::STYLE_FUSION:
paintFusion(painter);
break;
case DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
paintPlastique(painter);
break;
case DAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
paintBreeze(painter);
break;
default:
paint(painter);
break;
}
painter.end();
}
void DAbstractSliderSpinBox::paint(QPainter& painter)
{
Q_D(DAbstractSliderSpinBox);
// Create options to draw spin box parts
QStyleOptionSpinBox spinOpts = spinBoxOptions();
spinOpts.rect.adjust(0, 2, 0, -2);
// Draw "SpinBox".Clip off the area of the lineEdit to avoid double
// borders being drawn
painter.save();
painter.setClipping(true);
QRect eraseRect(QPoint(rect().x(), rect().y()),
QPoint(progressRect(spinOpts).right(), rect().bottom()));
painter.setClipRegion(QRegion(rect()).subtracted(eraseRect));
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.setClipping(false);
painter.restore();
QStyleOptionProgressBar progressOpts = progressBarOptions();
progressOpts.rect.adjust(0, 2, 0, -2);
style()->drawControl(QStyle::CE_ProgressBar, &progressOpts, &painter, 0);
// Draw focus if necessary
if (hasFocus() && d->edit->hasFocus())
{
QStyleOptionFocusRect focusOpts;
focusOpts.initFrom(this);
focusOpts.rect = progressOpts.rect;
focusOpts.backgroundColor = palette().color(QPalette::Window);
style()->drawPrimitive(QStyle::PE_FrameFocusRect, &focusOpts, &painter, this);
}
}
void DAbstractSliderSpinBox::paintFusion(QPainter& painter)
{
Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
spinOpts.frame = true;
spinOpts.rect.adjust(0, -1, 0, 1);
// spinOpts.palette().setBrush(QPalette::Base, palette().highlight());
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.save();
QRect rect = progressOpts.rect.adjusted(1, 2, -4, -2);
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - double(progressOpts.minimum)) / qMax(double(1.0),
double(progressOpts.maximum) - progressOpts.minimum) * rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width())
{
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
}
else if (progressIndicatorPos > rect.width())
{
painter.setPen(palette().highlightedText().color());
}
else
{
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible()))
{
painter.setClipRegion(rightRect);
painter.setClipping(true);
painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
painter.setClipping(false);
}
if (!leftRect.isNull())
{
painter.setClipRect(leftRect.adjusted(0, -1, 1, 1));
painter.setPen(palette().highlight().color());
painter.setBrush(palette().highlight());
spinOpts.palette.setBrush(QPalette::Base, palette().highlight());
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
if (!(d->edit && d->edit->isVisible()))
{
painter.setPen(palette().highlightedText().color());
painter.setClipping(true);
painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
}
painter.setClipping(false);
}
painter.restore();
}
void DAbstractSliderSpinBox::paintPlastique(QPainter& painter)
{
Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, d->dummySpinBox);
painter.save();
QRect rect = progressOpts.rect.adjusted(2, 0, -2, 0);
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - double(progressOpts.minimum)) / qMax(double(1.0),
double(progressOpts.maximum) - progressOpts.minimum) * rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= rect.width())
{
leftRect = QRect(rect.left(), rect.top(), progressIndicatorPos, rect.height());
}
else if (progressIndicatorPos > rect.width())
{
painter.setPen(palette().highlightedText().color());
}
else
{
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = rect;
rightRect = rightRect.subtracted(leftRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible()))
{
painter.setClipRegion(rightRect);
painter.setClipping(true);
painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
painter.setClipping(false);
}
if (!leftRect.isNull())
{
painter.setPen(palette().highlight().color());
painter.setBrush(palette().highlight());
painter.drawRect(leftRect.adjusted(0, 0, 0, -1));
if (!(d->edit && d->edit->isVisible()))
{
painter.setPen(palette().highlightedText().color());
painter.setClipRect(leftRect.adjusted(0, 0, 1, 0));
painter.setClipping(true);
painter.drawText(rect.adjusted(-2, 0, 2, 0), progressOpts.text, textOption);
painter.setClipping(false);
}
}
painter.restore();
}
void DAbstractSliderSpinBox::paintBreeze(QPainter& painter)
{
Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QStyleOptionProgressBar progressOpts = progressBarOptions();
QString valueText = progressOpts.text;
progressOpts.text = QLatin1String("");
progressOpts.rect.adjust(0, 1, 0, -1);
style()->drawComplexControl(QStyle::CC_SpinBox, &spinOpts, &painter, this);
style()->drawControl(QStyle::CE_ProgressBarGroove, &progressOpts, &painter, this);
painter.save();
QRect leftRect;
int progressIndicatorPos = (progressOpts.progress - double(progressOpts.minimum)) / qMax(double(1.0),
double(progressOpts.maximum) - progressOpts.minimum) * progressOpts.rect.width();
if (progressIndicatorPos >= 0 && progressIndicatorPos <= progressOpts.rect.width())
{
leftRect = QRect(progressOpts.rect.left(), progressOpts.rect.top(), progressIndicatorPos, progressOpts.rect.height());
}
else if (progressIndicatorPos > progressOpts.rect.width())
{
painter.setPen(palette().highlightedText().color());
}
else
{
painter.setPen(palette().buttonText().color());
}
QRegion rightRect = progressOpts.rect;
rightRect = rightRect.subtracted(leftRect);
painter.setClipRegion(rightRect);
QTextOption textOption(Qt::AlignAbsolute | Qt::AlignHCenter | Qt::AlignVCenter);
textOption.setWrapMode(QTextOption::NoWrap);
if (!(d->edit && d->edit->isVisible()))
{
painter.drawText(progressOpts.rect, valueText, textOption);
}
if (!leftRect.isNull())
{
painter.setPen(palette().highlightedText().color());
painter.setClipRect(leftRect);
style()->drawControl(QStyle::CE_ProgressBarContents, &progressOpts, &painter, this);
if (!(d->edit && d->edit->isVisible()))
{
painter.drawText(progressOpts.rect, valueText, textOption);
}
}
painter.restore();
}
void DAbstractSliderSpinBox::mousePressEvent(QMouseEvent* e)
{
Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
// Depress buttons or highlight slider
// Also used to emulate mouse grab...
if (e->buttons() & Qt::LeftButton)
{
if (upButtonRect(spinOpts).contains(e->pos()))
{
d->upButtonDown = true;
}
else if (downButtonRect(spinOpts).contains(e->pos()))
{
d->downButtonDown = true;
}
}
else if (e->buttons() & Qt::RightButton)
{
showEdit();
}
update();
}
void DAbstractSliderSpinBox::mouseReleaseEvent(QMouseEvent* e)
{
Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
d->isDragging = false;
// Step up/down for buttons
- // Emualting mouse grab too
+ // Emulating mouse grab too
if (upButtonRect(spinOpts).contains(e->pos()) && d->upButtonDown)
{
setInternalValue(d->value + d->singleStep);
}
else if (downButtonRect(spinOpts).contains(e->pos()) && d->downButtonDown)
{
setInternalValue(d->value - d->singleStep);
}
else if (progressRect(spinOpts).contains(e->pos()) &&
!(d->edit->isVisible()) &&
!(d->upButtonDown || d->downButtonDown))
{
// Snap to percentage for progress area
setInternalValue(valueForX(e->pos().x(),e->modifiers()));
}
else
{
// Confirm the last known value, since we might be ignoring move events
setInternalValue(d->value);
}
d->upButtonDown = false;
d->downButtonDown = false;
update();
}
void DAbstractSliderSpinBox::mouseMoveEvent(QMouseEvent* e)
{
Q_D(DAbstractSliderSpinBox);
if (e->modifiers() & Qt::ShiftModifier)
{
if (!d->shiftMode)
{
d->shiftPercent = pow(double(d->value - d->minimum) / double(d->maximum - d->minimum), 1 / double(d->exponentRatio));
d->shiftMode = true;
}
}
else
{
d->shiftMode = false;
}
// Respect emulated mouse grab.
if (e->buttons() & Qt::LeftButton && !(d->downButtonDown || d->upButtonDown))
{
d->isDragging = true;
setInternalValue(valueForX(e->pos().x(),e->modifiers()), d->blockUpdateSignalOnDrag);
update();
}
}
void DAbstractSliderSpinBox::keyPressEvent(QKeyEvent* e)
{
Q_D(DAbstractSliderSpinBox);
switch (e->key())
{
case Qt::Key_Up:
case Qt::Key_Right:
setInternalValue(d->value + d->singleStep);
if (d->edit->isVisible())
{
d->edit->setText(valueString());
}
update();
break;
case Qt::Key_Down:
case Qt::Key_Left:
setInternalValue(d->value - d->singleStep);
if (d->edit->isVisible())
{
d->edit->setText(valueString());
}
update();
break;
case Qt::Key_Shift:
d->shiftPercent = pow(double(d->value - d->minimum) / double(d->maximum - d->minimum), 1 / double(d->exponentRatio));
d->shiftMode = true;
break;
case Qt::Key_Enter: // Line edit isn't "accepting" key strokes...
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Control:
case Qt::Key_Alt:
case Qt::Key_AltGr:
case Qt::Key_Super_L:
case Qt::Key_Super_R:
break;
default:
showEdit();
d->edit->event(e);
break;
}
}
void DAbstractSliderSpinBox::wheelEvent(QWheelEvent *e)
{
Q_D(DAbstractSliderSpinBox);
if (e->delta() > 0)
{
setInternalValue(d->value + d->singleStep);
}
else
{
setInternalValue(d->value - d->singleStep);
}
update();
e->accept();
}
void DAbstractSliderSpinBox::focusInEvent(QFocusEvent* e)
{
if (e->reason() == Qt::TabFocusReason)
{
showEdit();
}
e->accept();
}
bool DAbstractSliderSpinBox::eventFilter(QObject* recv, QEvent* e)
{
Q_D(DAbstractSliderSpinBox);
if (recv == static_cast<QObject*>(d->edit) && e->type() == QEvent::KeyRelease)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
switch (keyEvent->key())
{
case Qt::Key_Enter:
case Qt::Key_Return:
setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor);
hideEdit();
return true;
case Qt::Key_Escape:
d->edit->setText(valueString());
hideEdit();
return true;
default:
break;
}
}
else if (d->edit->isVisible() && e->type() == QEvent::ShortcutOverride)
{
QKeyEvent* const keyEvent = static_cast<QKeyEvent*>(e);
switch (keyEvent->key())
{
case Qt::Key_Tab:
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
e->accept();
return true;
default:
break;
}
}
return false;
}
QSize DAbstractSliderSpinBox::sizeHint() const
{
const Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QFont ft(font());
if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK)
{
// Some styles use bold font in progressbars
// unfortunately there is no reliable way to check for that
ft.setBold(true);
}
QFontMetrics fm(ft);
QSize hint(fm.boundingRect(d->prefix + QString::number(d->maximum) + d->suffix).size());
hint += QSize(0, 2);
switch (d->style)
{
case DAbstractSliderSpinBoxPrivate::STYLE_FUSION:
hint += QSize(8, 8);
break;
case DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
hint += QSize(8, 0);
break;
case DAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
hint += QSize(2, 0);
break;
case DAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK:
// almost all "modern" styles have a margin around controls
hint += QSize(6, 6);
break;
default:
break;
}
// Getting the size of the buttons is a pain as the calcs require a rect
// that is "big enough". We run the calc twice to get the "smallest" buttons
// This code was inspired by QAbstractSpinBox
QSize extra(1000, 0);
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
spinOpts.rect.setSize(hint + extra);
extra += hint - style()->subControlRect(QStyle::CC_SpinBox, &spinOpts,
QStyle::SC_SpinBoxEditField, this).size();
hint += extra;
spinOpts.rect.setSize(hint);
return style()->sizeFromContents(QStyle::CT_SpinBox, &spinOpts, hint)
.expandedTo(QApplication::globalStrut());
}
QSize DAbstractSliderSpinBox::minimumSizeHint() const
{
return sizeHint();
}
QSize DAbstractSliderSpinBox::minimumSize() const
{
return QWidget::minimumSize().expandedTo(minimumSizeHint());
}
QStyleOptionSpinBox DAbstractSliderSpinBox::spinBoxOptions() const
{
const Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox opts;
opts.initFrom(this);
opts.frame = false;
opts.buttonSymbols = QAbstractSpinBox::UpDownArrows;
opts.subControls = QStyle::SC_SpinBoxUp | QStyle::SC_SpinBoxDown;
// Disable non-logical buttons
if (d->value == d->minimum)
{
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled;
}
else if (d->value == d->maximum)
{
opts.stepEnabled = QAbstractSpinBox::StepDownEnabled;
}
else
{
opts.stepEnabled = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;
}
// Deal with depressed buttons
if (d->upButtonDown)
{
opts.activeSubControls = QStyle::SC_SpinBoxUp;
}
else if (d->downButtonDown)
{
opts.activeSubControls = QStyle::SC_SpinBoxDown;
}
else
{
opts.activeSubControls = 0;
}
return opts;
}
QStyleOptionProgressBar DAbstractSliderSpinBox::progressBarOptions() const
{
const Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
// Create opts for drawing the progress portion
QStyleOptionProgressBar progressOpts;
progressOpts.initFrom(this);
progressOpts.maximum = d->maximum;
progressOpts.minimum = d->minimum;
double minDbl = d->minimum;
double dValues = (d->maximum - minDbl);
progressOpts.progress = dValues * pow((d->value - minDbl) / dValues, 1.0 / d->exponentRatio) + minDbl;
progressOpts.text = d->prefix + valueString() + d->suffix;
progressOpts.textAlignment = Qt::AlignCenter;
progressOpts.textVisible = !(d->edit->isVisible());
// Change opts rect to be only the ComboBox's text area
progressOpts.rect = progressRect(spinOpts);
return progressOpts;
}
QRect DAbstractSliderSpinBox::progressRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
const Q_D(DAbstractSliderSpinBox);
QRect ret = style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxEditField);
switch (d->style)
{
case DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE:
ret.adjust(-2, 0, 1, 0);
break;
case DAbstractSliderSpinBoxPrivate::STYLE_BREEZE:
ret.adjust(1, 0, 0, 0);
break;
default:
break;
}
return ret;
}
QRect DAbstractSliderSpinBox::upButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxUp);
}
QRect DAbstractSliderSpinBox::downButtonRect(const QStyleOptionSpinBox& spinBoxOptions) const
{
return style()->subControlRect(QStyle::CC_SpinBox, &spinBoxOptions,
QStyle::SC_SpinBoxDown);
}
int DAbstractSliderSpinBox::valueForX(int x, Qt::KeyboardModifiers modifiers) const
{
const Q_D(DAbstractSliderSpinBox);
QStyleOptionSpinBox spinOpts = spinBoxOptions();
QRect correctedProgRect;
if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_FUSION)
{
correctedProgRect = progressRect(spinOpts).adjusted(2, 0, -2, 0);
}
else if (d->style == DAbstractSliderSpinBoxPrivate::STYLE_BREEZE)
{
correctedProgRect = progressRect(spinOpts);
}
else
{
// Adjust for magic number in style code (margins)
correctedProgRect = progressRect(spinOpts).adjusted(2, 2, -2, -2);
}
// Compute the distance of the progress bar, in pixel
double leftDbl = correctedProgRect.left();
double xDbl = x - leftDbl;
// Compute the ration of the progress bar used, linearly (ignoring the exponent)
double rightDbl = correctedProgRect.right();
double minDbl = d->minimum;
double maxDbl = d->maximum;
double dValues = (maxDbl - minDbl);
double percent = (xDbl / (rightDbl - leftDbl));
// If SHIFT is pressed, movement should be slowed.
if (modifiers & Qt::ShiftModifier)
{
percent = d->shiftPercent + (percent - d->shiftPercent) * d->slowFactor;
}
// Final value
double realvalue = ((dValues * pow(percent, d->exponentRatio)) + minDbl);
// If key CTRL is pressed, round to the closest step.
if (modifiers & Qt::ControlModifier)
{
double fstep = d->fastSliderStep;
if (modifiers & Qt::ShiftModifier)
{
fstep *= d->slowFactor;
}
realvalue = floor((realvalue+fstep / 2) / fstep) * fstep;
}
return int(realvalue);
}
void DAbstractSliderSpinBox::setPrefix(const QString& prefix)
{
Q_D(DAbstractSliderSpinBox);
d->prefix = prefix;
}
void DAbstractSliderSpinBox::setSuffix(const QString& suffix)
{
Q_D(DAbstractSliderSpinBox);
d->suffix = suffix;
}
void DAbstractSliderSpinBox::setExponentRatio(double dbl)
{
Q_D(DAbstractSliderSpinBox);
Q_ASSERT(dbl > 0);
d->exponentRatio = dbl;
}
void DAbstractSliderSpinBox::setBlockUpdateSignalOnDrag(bool blockUpdateSignal)
{
Q_D(DAbstractSliderSpinBox);
d->blockUpdateSignalOnDrag = blockUpdateSignal;
}
void DAbstractSliderSpinBox::contextMenuEvent(QContextMenuEvent* event)
{
event->accept();
}
void DAbstractSliderSpinBox::editLostFocus()
{
Q_D(DAbstractSliderSpinBox);
if (!d->edit->hasFocus())
{
if (d->edit->isModified())
{
setInternalValue(QLocale::system().toDouble(d->edit->text()) * d->factor);
}
hideEdit();
}
}
void DAbstractSliderSpinBox::setInternalValue(int value)
{
setInternalValue(value, false);
}
bool DAbstractSliderSpinBox::isDragging() const
{
Q_D(const DAbstractSliderSpinBox);
return d->isDragging;
}
void DAbstractSliderSpinBox::changeEvent(QEvent* e)
{
Q_D(DAbstractSliderSpinBox);
QWidget::changeEvent(e);
if (e->type() == QEvent::StyleChange)
{
if (style()->objectName() == QLatin1String("fusion"))
{
d->style = DAbstractSliderSpinBoxPrivate::STYLE_FUSION;
}
else if (style()->objectName() == QLatin1String("plastique"))
{
d->style = DAbstractSliderSpinBoxPrivate::STYLE_PLASTIQUE;
}
else if (style()->objectName() == QLatin1String("breeze"))
{
d->style = DAbstractSliderSpinBoxPrivate::STYLE_BREEZE;
}
else
{
d->style = DAbstractSliderSpinBoxPrivate::STYLE_NOQUIRK;
}
}
}
// ---------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN DSliderSpinBoxPrivate : public DAbstractSliderSpinBoxPrivate
{
};
DSliderSpinBox::DSliderSpinBox(QWidget* const parent)
: DAbstractSliderSpinBox(parent, new DSliderSpinBoxPrivate)
{
setRange(0, 99);
}
DSliderSpinBox::~DSliderSpinBox()
{
}
void DSliderSpinBox::setRange(int minimum, int maximum)
{
Q_D(DSliderSpinBox);
d->minimum = minimum;
d->maximum = maximum;
d->fastSliderStep = (maximum-minimum + 1) / 20;
d->validator->setRange(minimum, maximum, 0);
update();
}
int DSliderSpinBox::minimum() const
{
const Q_D(DSliderSpinBox);
return d->minimum;
}
void DSliderSpinBox::setMinimum(int minimum)
{
Q_D(DSliderSpinBox);
setRange(minimum, d->maximum);
}
int DSliderSpinBox::maximum() const
{
const Q_D(DSliderSpinBox);
return d->maximum;
}
void DSliderSpinBox::setMaximum(int maximum)
{
Q_D(DSliderSpinBox);
setRange(d->minimum, maximum);
}
int DSliderSpinBox::fastSliderStep() const
{
const Q_D(DSliderSpinBox);
return d->fastSliderStep;
}
void DSliderSpinBox::setFastSliderStep(int step)
{
Q_D(DSliderSpinBox);
d->fastSliderStep = step;
}
int DSliderSpinBox::value()
{
Q_D(DSliderSpinBox);
return d->value;
}
void DSliderSpinBox::setValue(int value)
{
setInternalValue(value, false);
update();
}
QString DSliderSpinBox::valueString() const
{
const Q_D(DSliderSpinBox);
QLocale locale;
return locale.toString((double)d->value, 'f', d->validator->decimals());
}
void DSliderSpinBox::setSingleStep(int value)
{
Q_D(DSliderSpinBox);
d->singleStep = value;
}
void DSliderSpinBox::setPageStep(int value)
{
Q_UNUSED(value);
}
void DSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
Q_D(DAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
if (!blockUpdateSignal)
{
emit(valueChanged(value()));
}
}
// ---------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN DDoubleSliderSpinBoxPrivate : public DAbstractSliderSpinBoxPrivate
{
};
DDoubleSliderSpinBox::DDoubleSliderSpinBox(QWidget* const parent)
: DAbstractSliderSpinBox(parent, new DDoubleSliderSpinBoxPrivate)
{
}
DDoubleSliderSpinBox::~DDoubleSliderSpinBox()
{
}
void DDoubleSliderSpinBox::setRange(double minimum, double maximum, int decimals)
{
Q_D(DDoubleSliderSpinBox);
d->factor = pow(10.0, decimals);
d->minimum = minimum * d->factor;
d->maximum = maximum * d->factor;
// This code auto-compute a new step when pressing control.
// A flag defaulting to "do not change the fast step" should be added, but it implies changing every call
if (maximum - minimum >= 2.0 || decimals <= 0)
{
// Quick step on integers
d->fastSliderStep = int(pow(10.0, decimals));
}
else if (decimals == 1)
{
d->fastSliderStep = (maximum - minimum) * d->factor / 10;
}
else
{
d->fastSliderStep = (maximum - minimum) * d->factor / 20;
}
d->validator->setRange(minimum, maximum, decimals);
update();
setValue(value());
}
double DDoubleSliderSpinBox::minimum() const
{
const Q_D(DAbstractSliderSpinBox);
return d->minimum / d->factor;
}
void DDoubleSliderSpinBox::setMinimum(double minimum)
{
Q_D(DAbstractSliderSpinBox);
setRange(minimum, d->maximum);
}
double DDoubleSliderSpinBox::maximum() const
{
const Q_D(DAbstractSliderSpinBox);
return d->maximum / d->factor;
}
void DDoubleSliderSpinBox::setMaximum(double maximum)
{
Q_D(DAbstractSliderSpinBox);
setRange(d->minimum, maximum);
}
double DDoubleSliderSpinBox::fastSliderStep() const
{
const Q_D(DAbstractSliderSpinBox);
return d->fastSliderStep;
}
void DDoubleSliderSpinBox::setFastSliderStep(double step)
{
Q_D(DAbstractSliderSpinBox);
d->fastSliderStep = step;
}
double DDoubleSliderSpinBox::value()
{
Q_D(DAbstractSliderSpinBox);
return (double)d->value / d->factor;
}
void DDoubleSliderSpinBox::setValue(double value)
{
Q_D(DAbstractSliderSpinBox);
setInternalValue(d->value = qRound(value * d->factor), false);
update();
}
void DDoubleSliderSpinBox::setSingleStep(double value)
{
Q_D(DAbstractSliderSpinBox);
d->singleStep = value * d->factor;
}
QString DDoubleSliderSpinBox::valueString() const
{
const Q_D(DAbstractSliderSpinBox);
QLocale locale;
return locale.toString((double)d->value / d->factor, 'f', d->validator->decimals());
}
void DDoubleSliderSpinBox::setInternalValue(int _value, bool blockUpdateSignal)
{
Q_D(DAbstractSliderSpinBox);
d->value = qBound(d->minimum, _value, d->maximum);
if (!blockUpdateSignal)
{
emit(valueChanged(value()));
}
}
} // namespace Digikam
diff --git a/core/tests/albummodel/albummodeltest.h b/core/tests/albummodel/albummodeltest.h
index 6253172cb8..9de94ac021 100644
--- a/core/tests/albummodel/albummodeltest.h
+++ b/core/tests/albummodel/albummodeltest.h
@@ -1,113 +1,113 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-12-11
* Description : test cases for the various album models
*
* Copyright (C) 2009 by Johannes Wienke <languitar at semipol dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_ALBUM_MODEL_TEST_H
#define DIGIKAM_ALBUM_MODEL_TEST_H
// Qt includes
#include <QObject>
#include <QMap>
#include <QAbstractItemModel>
namespace Digikam
{
class PAlbum;
class TAlbum;
class AlbumModel;
}
class AlbumModelTest: public QObject
{
Q_OBJECT
public:
AlbumModelTest();
virtual ~AlbumModelTest();
private Q_SLOTS:
void initTestCase();
void cleanupTestCase();
void init();
void cleanup();
void testPAlbumModel();
void testDisablePAlbumCount();
void testDAlbumModel();
void testDAlbumCount();
void testDAlbumContainsAlbums();
void testDAlbumSorting();
void testTAlbumModel();
void testSAlbumModel();
void testStartAlbumModel();
void deletePAlbum(Digikam::PAlbum* album);
void setLastPAlbumCountMap(const QMap<int, int> &map);
// slots for ensuring signal order while scanning albums
void slotStartModelRowsInserted(const QModelIndex& parent, int start, int end);
void slotStartModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
void ensureItemCounts();
private:
const QString albumCategory;
QString dbPath;
QString tempSuffix;
Digikam::PAlbum* palbumRoot0;
Digikam::PAlbum* palbumRoot1;
Digikam::PAlbum* palbumRoot2;
Digikam::PAlbum* palbumChild0Root0;
Digikam::PAlbum* palbumChild1Root0;
Digikam::PAlbum* palbumChild2Root0;
Digikam::PAlbum* palbumChild0Root1;
Digikam::TAlbum* rootTag;
Digikam::TAlbum* talbumRoot0;
Digikam::TAlbum* talbumRoot1;
Digikam::TAlbum* talbumChild0Root0;
Digikam::TAlbum* talbumChild1Root0;
Digikam::TAlbum* talbumChild0Child1Root0;
Digikam::TAlbum* talbumChild0Root1;
QMap<int, int> palbumCountMap;
/**
* This model is used to ensure that adding and changing signals are emitted
- * correctly if the model is created beorre the scanning starts.
+ * correctly if the model is created before the scanning starts.
*/
Digikam::AlbumModel* startModel;
QList<int> addedIds;
};
#endif // DIGIKAM_ALBUM_MODEL_TEST_H
diff --git a/core/tests/dialogs/browser.cpp b/core/tests/dialogs/browser.cpp
index 22e772e597..6099533ce5 100644
--- a/core/tests/dialogs/browser.cpp
+++ b/core/tests/dialogs/browser.cpp
@@ -1,51 +1,51 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-05-25
- * Description : a stand alone tool to brower a web page.
+ * Description : a stand alone tool to browse a web page.
*
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
// Qt includes
#include <QApplication>
#include <QUrl>
#include <QDebug>
// Local includes
#include "webbrowserdlg.h"
using namespace Digikam;
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
if (argc == 1)
{
qDebug() << "browser - web page url to show";
qDebug() << "Usage: url top open";
return -1;
}
WebBrowserDlg browser(QUrl(QString::fromUtf8(argv[1])));
browser.show();
return a.exec();
}
diff --git a/core/tests/dimg/dimghistorygraphtest.cpp b/core/tests/dimg/dimghistorygraphtest.cpp
index b743a49197..f2bd0217a4 100644
--- a/core/tests/dimg/dimghistorygraphtest.cpp
+++ b/core/tests/dimg/dimghistorygraphtest.cpp
@@ -1,484 +1,484 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-08-01
* Description : a test for the DImageHistory
*
* Copyright (C) 2010 by Marcel Wiesweg <user dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dimghistorygraphtest.h"
// Qt includes
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTime>
#include <QTreeView>
#include <QDebug>
#include <QTest>
#include <QUrl>
// Local includes
#include "coredb.h"
#include "collectionlocation.h"
#include "collectionmanager.h"
#include "collectionscanner.h"
#include "editorcore.h"
#include "dmetadata.h"
#include "imageinfo.h"
#include "imagehistorygraph.h"
#include "imagehistorygraphdata.h"
#include "imagehistorygraphmodel.h"
#include "iofilesettings.h"
#include "tagscache.h"
#include "../modeltest/modeltest.h"
using namespace Digikam;
QTEST_MAIN(DImgHistoryGraphTest)
void DImgHistoryGraphTest::initTestCase()
{
initBaseTestCase();
QString name = tempFileName(QLatin1String("collection"));
collectionDir = QDir::temp();
collectionDir.mkdir(name);
collectionDir.cd(name);
QVERIFY(collectionDir.exists());
dbFile = tempFilePath(QLatin1String("database"));
qDebug() << "Using database path for test: " << dbFile;
DbEngineParameters params(QLatin1String("QSQLITE"), dbFile, QLatin1String("QSQLITE"), dbFile);
CoreDbAccess::setParameters(params, CoreDbAccess::MainApplication);
QVERIFY(CoreDbAccess::checkReadyForUse(0));
QVERIFY(QFile(dbFile).exists());
CollectionManager::instance()->addLocation(QUrl::fromLocalFile(collectionDir.path()));
CollectionManager::instance()->addLocation(QUrl::fromLocalFile(imagePath()));
QList<CollectionLocation> locs = CollectionManager::instance()->allAvailableLocations();
QVERIFY(locs.size() == 2);
rescan();
QList<AlbumShortInfo> albums = CoreDbAccess().db()->getAlbumShortInfos();
QVERIFY(albums.size() >= 2);
foreach(const AlbumShortInfo& album, albums)
{
//qDebug() << album.relativePath << album.id;
//qDebug() << CollectionManager::instance()->albumRootPath(album.albumRootId);
//qDebug() << CoreDbAccess().db()->getItemURLsInAlbum(album.id);
readOnlyImages << CoreDbAccess().db()->getItemURLsInAlbum(album.id);
}
foreach(const QString& file, readOnlyImages)
{
ids << ImageInfo::fromLocalFile(file).id();
}
QVERIFY(!ids.contains(-1));
QVERIFY(ids.size() >= 6); // Nb of files in data sub dir.
}
void DImgHistoryGraphTest::cleanupTestCase()
{
cleanupBaseTestCase();
QFile(dbFile).remove();
QDir dir(collectionDir.path());
dir.removeRecursively();
qDebug() << "deleted test folder " << collectionDir.path();
}
void DImgHistoryGraphTest::rescan()
{
CollectionScanner().completeScan();
}
template <typename from, typename to>
QList<to> mapList(const QList<from>& l, const QMap<from,to> map)
{
QList<to> r;
foreach(const from& f, l)
{
r << map.value(f);
}
return r;
}
void DImgHistoryGraphTest::testEditing()
{
QDir imageDir(imagePath());
/*
orig
1
2
3
4
*/
IOFileSettings container;
m_im->load(readOnlyImages.first(), &container);
m_loop.exec(); // krazy:exclude=crashy
applyFilters1();
m_im->saveAs(collectionDir.filePath(QLatin1String("1.jpg")), &container, true, QString(), QString());
m_loop.exec(); // krazy:exclude=crashy
applyFilters2();
m_im->saveAs(collectionDir.filePath(QLatin1String("2.jpg")), &container, true, QString(), QString());
m_loop.exec(); // krazy:exclude=crashy
applyFilters3();
m_im->saveAs(collectionDir.filePath(QLatin1String("3.jpg")), &container, true, QString(), QString());
m_loop.exec(); // krazy:exclude=crashy
m_im->load(collectionDir.filePath(QLatin1String("2.jpg")), &container);
m_loop.exec(); // krazy:exclude=crashy
applyFilters4();
m_im->saveAs(collectionDir.filePath(QLatin1String("4.jpg")), &container, true, QString(), QString());
m_loop.exec(); // krazy:exclude=crashy
CollectionScanner().completeScan();
ImageInfo orig = ImageInfo::fromLocalFile(readOnlyImages.first());
ImageInfo one = ImageInfo::fromLocalFile(collectionDir.filePath(QLatin1String("1.jpg"))),
two = ImageInfo::fromLocalFile(collectionDir.filePath(QLatin1String("2.jpg"))),
three = ImageInfo::fromLocalFile(collectionDir.filePath(QLatin1String("3.jpg"))),
four = ImageInfo::fromLocalFile(collectionDir.filePath(QLatin1String("4.jpg")));
typedef QPair<qlonglong, qlonglong> IdPair;
QList<IdPair> controlCloud;
controlCloud << IdPair(one.id(), orig.id()); //X
controlCloud << IdPair(two.id(), one.id()); //X
controlCloud << IdPair(three.id(), two.id()); //X
controlCloud << IdPair(four.id(), two.id()); //X
controlCloud << IdPair(three.id(), one.id());
controlCloud << IdPair(four.id(), one.id());
controlCloud << IdPair(two.id(), orig.id());
controlCloud << IdPair(three.id(), orig.id());
controlCloud << IdPair(four.id(), orig.id());
std::sort(controlCloud.begin(), controlCloud.end());
ImageHistoryGraph graph1 = ImageHistoryGraph::fromInfo(three);
qDebug() << graph1;
ImageHistoryGraph graph2 = ImageHistoryGraph::fromInfo(four);
qDebug() << graph2;
ImageHistoryGraph graph3 = ImageHistoryGraph::fromInfo(one);
qDebug() << graph3;
// all three must have the full cloud
QVERIFY(graph1.data().vertexCount() == 5);
QVERIFY(graph2.data().vertexCount() == 5);
QVERIFY(graph3.data().vertexCount() == 5);
QList<IdPair> cloud = graph3.relationCloud();
std::sort(cloud.begin(), cloud.end());
QVERIFY(cloud == controlCloud);
int needResolvingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needResolvingHistory());
int needTaggingTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::needTaggingHistoryGraph());
int originalVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::originalVersion());
int currentVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::currentVersion());
int intermediateVersionTag = TagsCache::instance()->getOrCreateInternalTag(InternalTagName::intermediateVersion());
//qDebug() << orig.tagIds() << one.tagIds() << two.tagIds() << three.tagIds() << four.tagIds();
QVERIFY(!orig.tagIds().contains(needResolvingTag));
QVERIFY(!orig.tagIds().contains(needTaggingTag));
QVERIFY(orig.tagIds().contains(originalVersionTag));
QVERIFY(one.tagIds().contains(intermediateVersionTag));
QVERIFY(two.tagIds().contains(intermediateVersionTag));
QVERIFY(three.tagIds().contains(currentVersionTag));
QVERIFY(four.tagIds().contains(currentVersionTag));
QFile fileTwo(two.filePath());
fileTwo.remove();
QVERIFY(!fileTwo.exists());
CollectionScanner().completeScan();
graph2 = ImageHistoryGraph::fromInfo(four);
// graph is prepared for display, vertex of removed file cleared
QVERIFY(graph2.data().vertexCount() == 4);
qDebug() << graph2;
- // Check that removal of current version leads to resetting of current verion tag
+ // Check that removal of current version leads to resetting of current version tag
QFile fileThree(three.filePath());
fileThree.remove();
QFile fileFour(four.filePath());
fileFour.remove();
CollectionScanner().completeScan();
qDebug() << originalVersionTag << currentVersionTag << intermediateVersionTag<< orig.tagIds() << one.tagIds();
QVERIFY(one.tagIds().contains(currentVersionTag));
QVERIFY(!one.tagIds().contains(intermediateVersionTag));
}
void DImgHistoryGraphTest::testHistory()
{
ImageHistoryGraph graph;
ImageInfo subject(ids.first());
graph.addHistory(history1(), subject);
graph.reduceEdges();
QCOMPARE(graph.data().vertexCount(), 3);
QCOMPARE(graph.data().edges().size(), 2);
}
class Q_DECL_HIDDEN lessThanById
{
public:
explicit lessThanById(const QMap<HistoryGraph::Vertex, qlonglong>& vertexToId)
: vertexToId(vertexToId)
{
}
bool operator()(const HistoryGraph::Vertex& a, const HistoryGraph::Vertex& b)
{
return vertexToId.value(a) < vertexToId.value(b);
}
public:
QMap<HistoryGraph::Vertex, qlonglong> vertexToId;
};
void DImgHistoryGraphTest::testGraph()
{
/*
1
2
8
9
19
20
21
10
3
4
11
12
22
23
|
13-- 24
5
14
6
15
7
16
17
18
*/
QList<qlonglong> controlLeaves;
controlLeaves << 8 << 19 << 20 << 21 << 10 << 3 << 11 << 22 << 24 << 14 << 15 << 16 << 17 << 18;
std::sort(controlLeaves.begin(), controlLeaves.end());
QList<qlonglong> controlRoots;
controlRoots << 1;
QList<qlonglong> controlLongestPathEighteen;
controlLongestPathEighteen << 1 << 7 << 18;
QList<qlonglong> controlLongestPathTwentyFour;
controlLongestPathTwentyFour << 1 << 4 << 12 << 23 << 24;
QList<qlonglong> controlSubgraphTwo;
controlSubgraphTwo << 2 << 8 << 9 << 10 << 19 << 20 << 21;
QList<qlonglong> controlSubgraphTwoSorted;
controlSubgraphTwoSorted << 2 << 8 << 9 << 19 << 20 << 21 << 10;
QList<qlonglong> controlSubgraphFour;
controlSubgraphFour << 4 << 11 << 12 << 13 << 22 << 23 << 24;
QList<qlonglong> controlRootsOfEighteen;
controlRootsOfEighteen << 1;
QList<qlonglong> controlLeavesFromTwo;
controlLeavesFromTwo << 8 << 10 << 19 << 20 << 21;
typedef QPair<qlonglong, qlonglong> IdPair;
QList<IdPair> pairs;
/**
* The following description of the tree-like graph above (24 breaks (poly)tree definition)
* is longer than needed (transitive reduction) and less than possible (transitive closure):
* Pairs marked with "X" must remain when building the transitive reduction.
* The transitive closure must additionally contain all pairs not marked,
* and the pairs commented out.
*/
pairs << IdPair(2,1); //X
pairs << IdPair(3,1); //X
pairs << IdPair(4,1); //X
pairs << IdPair(5,1); //X
pairs << IdPair(6,1); //X
pairs << IdPair(7,1); //X
pairs << IdPair(8,1);
//pairs << IdPair(9,1);
pairs << IdPair(10,1);
pairs << IdPair(11,1);
pairs << IdPair(12,1);
pairs << IdPair(13,1);
pairs << IdPair(14,1);
pairs << IdPair(15,1);
pairs << IdPair(16,1);
pairs << IdPair(17,1);
pairs << IdPair(18,1);
pairs << IdPair(22,4);
pairs << IdPair(23,4);
pairs << IdPair(24,4);
pairs << IdPair(14,5); //X
pairs << IdPair(15,6); //X
//pairs << IdPair(19,1);
//pairs << IdPair(20,1);
//pairs << IdPair(21,1);
pairs << IdPair(22,1);
pairs << IdPair(23,1);
pairs << IdPair(24,1);
pairs << IdPair(8,2); //X
pairs << IdPair(9,2); //X
pairs << IdPair(10,2); //X
//pairs << IdPair(19,2);
//pairs << IdPair(20,2);
//pairs << IdPair(21,2);
pairs << IdPair(11,4); //X
pairs << IdPair(12,4); //X
pairs << IdPair(13,4); //X
pairs << IdPair(16,7); //X
pairs << IdPair(17,7); //X
pairs << IdPair(18,7); //X
pairs << IdPair(19,9); //X
pairs << IdPair(20,9); //X
pairs << IdPair(21,9); //X
pairs << IdPair(22,12); //X
pairs << IdPair(23,12); //X
// no more a polytree
pairs << IdPair(24,13); //X
pairs << IdPair(24,23); //X
pairs << IdPair(24,4);
pairs << IdPair(24,1);
pairs << IdPair(24,12);
ImageHistoryGraph graph;
graph.addRelations(pairs);
qDebug() << "Initial graph:" << graph;
graph.reduceEdges();
qDebug() << "Transitive reduction:" << graph;
QList<IdPair> cloud = graph.relationCloud();
qDebug() << "Transitive closure:" << cloud;
QVERIFY(cloud.contains(IdPair(7,1)));
QVERIFY(cloud.contains(IdPair(8,1)));
QVERIFY(cloud.contains(IdPair(9,1)));
/*
QBENCHMARK
{
ImageHistoryGraph benchGraph;
graph.addRelations(pairs);
graph.finish();
graph.relationCloud();
}
*/
QMap<qlonglong,HistoryGraph::Vertex> idToVertex;
QMap<HistoryGraph::Vertex, qlonglong> vertexToId;
foreach(const HistoryGraph::Vertex& v, graph.data().vertices())
{
HistoryVertexProperties props = graph.data().properties(v);
idToVertex[props.infos.first().id()] = v;
vertexToId[v] = props.infos.first().id();
}
QList<qlonglong> leaves = mapList(graph.data().leaves(), vertexToId);
std::sort(leaves.begin(), leaves.end());
QVERIFY(leaves == controlLeaves);
QList<qlonglong> roots = mapList(graph.data().roots(), vertexToId);
std::sort(roots.begin(), roots.end());
QVERIFY(roots == controlRoots);
QList<qlonglong> longestPath1 = mapList(graph.data().longestPathTouching(idToVertex.value(18)), vertexToId);
QVERIFY(longestPath1 == controlLongestPathEighteen);
QList<qlonglong> longestPath2 = mapList(graph.data().longestPathTouching(idToVertex.value(24)), vertexToId);
QVERIFY(longestPath2 == controlLongestPathTwentyFour);
// depth-first
QList<qlonglong> subgraphTwo = mapList(graph.data().verticesDominatedBy(idToVertex.value(2), idToVertex.value(1),
HistoryGraph::DepthFirstOrder), vertexToId);
std::sort(subgraphTwo.begin(), subgraphTwo.end());
QVERIFY(subgraphTwo == controlSubgraphTwo);
// breadth-first
QList<qlonglong> subgraphFour = mapList(graph.data().verticesDominatedBy(idToVertex.value(4), idToVertex.value(1)), vertexToId);
QVERIFY(subgraphFour.indexOf(22) > subgraphFour.indexOf(13));
std::sort(subgraphFour.begin(), subgraphFour.end());
QVERIFY(subgraphFour == controlSubgraphFour);
// depth-first
QList<qlonglong> subgraphTwoSorted = mapList(
graph.data().verticesDominatedByDepthFirstSorted(idToVertex.value(2), idToVertex.value(1),lessThanById(vertexToId)),
vertexToId);
// no sorting this time
QVERIFY(subgraphTwoSorted == controlSubgraphTwoSorted);
QList<qlonglong> rootsOfEighteen = mapList(graph.data().rootsOf(idToVertex.value(18)), vertexToId);
std::sort(rootsOfEighteen.begin(), rootsOfEighteen.end());
QVERIFY(rootsOfEighteen == controlRootsOfEighteen);
QList<qlonglong> leavesFromTwo = mapList(graph.data().leavesFrom(idToVertex.value(2)), vertexToId);
std::sort(leavesFromTwo.begin(), leavesFromTwo.end());
QVERIFY(leavesFromTwo == controlLeavesFromTwo);
}
void DImgHistoryGraphTest::slotImageLoaded(const QString& fileName, bool success)
{
QVERIFY(success);
qDebug() << "Loaded" << fileName;
m_loop.quit();
}
void DImgHistoryGraphTest::slotImageSaved(const QString& fileName, bool success)
{
QVERIFY(success);
m_im->setLastSaved(fileName);
qDebug() << "Saved to" << fileName;
m_loop.quit();
}
diff --git a/core/tests/fileio/filesaveoptionsboxtest.cpp b/core/tests/fileio/filesaveoptionsboxtest.cpp
index 8f94d2b9bc..1c797d15ff 100644
--- a/core/tests/fileio/filesaveoptionsboxtest.cpp
+++ b/core/tests/fileio/filesaveoptionsboxtest.cpp
@@ -1,75 +1,75 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-12-06
* Description : test for the filesaveoptionsbox
*
* Copyright (C) 2009 by Johannes Wienke <languitar at semipol dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "filesaveoptionsboxtest.h"
// Qt includes
#include <QTest>
-// Local inclues
+// Local includes
#include "filesaveoptionsbox.h"
#include "dimg.h"
using namespace Digikam;
QTEST_MAIN(FileSaveOptionsBoxTest)
void FileSaveOptionsBoxTest::testDiscoverFormat_data()
{
QTest::addColumn<QString>("filename");
QTest::addColumn<int>("format");
QTest::newRow("jpg") << "test.jpg" << (int) DImg::JPEG;
QTest::newRow("jpeg") << "test.jpeg" << (int) DImg::JPEG;
QTest::newRow("JPG") << "test.JPG" << (int) DImg::JPEG;
QTest::newRow("jpg") << "jpg" << (int) DImg::JPEG;
QTest::newRow("jpeg") << "jpeg" << (int) DImg::JPEG;
QTest::newRow("bla.tiff.jpeg") << "bla.tiff.jpeg" << (int) DImg::JPEG;
QTest::newRow("bla.jpg.tiff") << "bla.jpg.tiff" << (int) DImg::TIFF;
#ifdef HAVE_JASPER
QTest::newRow("bla.png.jpeg.pgx") << "bla.png.jpeg.pgx" << (int) DImg::JP2K;
#endif // HAVE_JASPER
QTest::newRow("pgf") << "PGF" << (int) DImg::PGF;
QTest::newRow("unknwon") << "i.dont.know" << (int) DImg::NONE; // krazy:exclude=spelling
}
void FileSaveOptionsBoxTest::testDiscoverFormat()
{
QFETCH(QString, filename);
QFETCH(int, format);
FileSaveOptionsBox box;
QCOMPARE((int) box.discoverFormat(filename), format);
}
void FileSaveOptionsBoxTest::testDiscoverFormatDefault()
{
FileSaveOptionsBox box;
QCOMPARE(box.discoverFormat(QLatin1String("unknown")), DImg::NONE);
QCOMPARE(box.discoverFormat(QLatin1String("unknown"), DImg::PGF), DImg::PGF);
}
diff --git a/core/tests/rawengine/raw2png.cpp b/core/tests/rawengine/raw2png.cpp
index 6a34aa6166..732e73573f 100644
--- a/core/tests/rawengine/raw2png.cpp
+++ b/core/tests/rawengine/raw2png.cpp
@@ -1,140 +1,140 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-15-09
* Description : a command line tool to convert RAW file to PNG
*
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
// Qt includes
#include <QString>
#include <QFile>
#include <QFileInfo>
#include <QDebug>
// Local includes
#include "drawdecoder.h"
#include "drawdecodersettings.h"
using namespace Digikam;
int main(int argc, char** argv)
{
if (argc != 2)
{
qDebug() << "raw2png - RAW Camera Image to PNG Converter";
qDebug() << "Usage: <rawfile>";
return -1;
}
QString filePath = QString::fromLatin1(argv[1]);
QFileInfo input(filePath);
QString previewFilePath(input.baseName() + QString::QString::fromLatin1(".preview.png"));
QFileInfo previewOutput(previewFilePath);
QString halfFilePath(input.baseName() + QString::fromLatin1(".half.png"));
QFileInfo halfOutput(halfFilePath);
QString fullFilePath(input.baseName() + QString::fromLatin1(".full.png"));
QFileInfo fullOutput(fullFilePath);
QImage image;
RawInfo identify;
// -----------------------------------------------------------
qDebug() << "raw2png: Identify RAW image from " << input.fileName();
DRawDecoder rawProcessor;
if (!rawProcessor.rawFileIdentify(identify, filePath))
{
- qDebug() << "raw2png: Idendify RAW image failed. Aborted...";
+ qDebug() << "raw2png: Identifying RAW image failed. Aborted...";
return -1;
}
int width = identify.imageSize.width();
int height = identify.imageSize.height();
qDebug() << "raw2png: Raw image info:";
qDebug() << "--- Date: " << identify.dateTime.toString(Qt::ISODate);
qDebug() << "--- Make: " << identify.make;
qDebug() << "--- Model: " << identify.model;
qDebug() << "--- Size: " << width << "x" << height;
qDebug() << "--- Filter: " << identify.filterPattern;
qDebug() << "--- Colors: " << identify.rawColors;
// -----------------------------------------------------------
qDebug() << "raw2png: Loading RAW image preview";
if (!rawProcessor.loadRawPreview(image, filePath))
{
qDebug() << "raw2png: Loading RAW image preview failed. Aborted...";
return -1;
}
qDebug() << "raw2png: Saving preview image to "
<< previewOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
image.save(previewFilePath, "PNG");
// -----------------------------------------------------------
qDebug() << "raw2png: Loading half RAW image";
image = QImage();
if (!rawProcessor.loadHalfPreview(image, filePath))
{
qDebug() << "raw2png: Loading half RAW image failed. Aborted...";
return -1;
}
qDebug() << "raw2png: Saving half image to "
<< halfOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
image.save(halfFilePath, "PNG");
// -----------------------------------------------------------
qDebug() << "raw2png: Loading full RAW image";
image = QImage();
DRawDecoderSettings settings;
settings.halfSizeColorImage = false;
settings.sixteenBitsImage = false;
settings.RGBInterpolate4Colors = false;
settings.RAWQuality = DRawDecoderSettings::BILINEAR;
if (!rawProcessor.loadFullImage(image, filePath, settings))
{
qDebug() << "raw2png: Loading full RAW image failed. Aborted...";
return -1;
}
qDebug() << "raw2png: Saving full RAW image to "
<< fullOutput.fileName() << " size ("
<< image.width() << "x" << image.height()
<< ")";
image.save(fullFilePath, "PNG");
return 0;
}
diff --git a/core/utilities/assistants/common/dmetainfoiface.cpp b/core/utilities/assistants/common/dmetainfoiface.cpp
index 265f2e7406..b5baac30d1 100644
--- a/core/utilities/assistants/common/dmetainfoiface.cpp
+++ b/core/utilities/assistants/common/dmetainfoiface.cpp
@@ -1,176 +1,176 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-05-06
* Description : interface to item information for shared tools
* based on DMetadata.
*
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "dmetainfoiface.h"
// Qt includes
#include <QFileInfo>
// Local includes
#include "dmetadata.h"
#include "infocontainer.h"
#include "template.h"
#include "dfileselector.h"
namespace Digikam
{
class Q_DECL_HIDDEN DMetaInfoIface::Private
{
public:
explicit Private()
{
dirSelector = 0;
}
DFileSelector* dirSelector;
QList<QUrl> urls;
};
DMetaInfoIface::DMetaInfoIface(QObject* const parent, const QList<QUrl>& lst)
: DInfoInterface(parent),
d(new Private)
{
d->urls = lst;
}
DMetaInfoIface::~DMetaInfoIface()
{
delete d;
}
QList<QUrl> DMetaInfoIface::currentAlbumItems() const
{
return d->urls;
}
QList<QUrl> DMetaInfoIface::currentSelectedItems() const
{
- // No multiple items selection is avaialble in DMeta.
+ // No multiple items selection is available in DMeta.
return currentAlbumItems();
}
QList<QUrl> DMetaInfoIface::allAlbumItems() const
{
- // No album management avaialble in DMeta.
+ // No album management available in DMeta.
return currentAlbumItems();
}
DMetaInfoIface::DInfoMap DMetaInfoIface::itemInfo(const QUrl& url) const
{
DInfoMap map;
if (d->urls.contains(url))
{
DMetadata meta(url.toLocalFile());
QString def = QLatin1String("x-default");
QFileInfo info(url.toLocalFile());
map.insert(QLatin1String("name"), info.fileName());
map.insert(QLatin1String("title"), meta.getImageTitles()[def].caption);
map.insert(QLatin1String("comment"), meta.getImageComments()[def].caption);
map.insert(QLatin1String("orientation"), (int)meta.getImageOrientation());
map.insert(QLatin1String("datetime"), meta.getImageDateTime());
map.insert(QLatin1String("rating"), meta.getImageRating());
map.insert(QLatin1String("colorlabel"), meta.getImageColorLabel());
map.insert(QLatin1String("picklabel"), meta.getImagePickLabel());
map.insert(QLatin1String("filesize"), (qlonglong)info.size());
map.insert(QLatin1String("dimensions"), meta.getImageDimensions());
// Get digiKam Tags Path list of picture from database.
// Ex.: "City/Paris/Monuments/Notre Dame"
QStringList tagsPath;
meta.getImageTagsPath(tagsPath);
map.insert(QLatin1String("tagspath"), tagsPath);
// Get digiKam Tags name (keywords) list of picture from database.
// Ex.: "Notre Dame"
QStringList keywords = meta.getMetadataField(MetadataInfo::Keywords).toStringList();
map.insert(QLatin1String("keywords"), keywords);
// Get GPS location of picture from database.
double lat = 0.0;
double lng = 0.0;
double alt = 0.0;
if (meta.getGPSInfo(lat, lng, alt))
{
map.insert(QLatin1String("latitude"), lat);
map.insert(QLatin1String("longitude"), lng);
map.insert(QLatin1String("altitude"), alt);
}
// Get Copyright information of picture from database.
Template temp;
meta.getCopyrightInformation(temp);
map.insert(QLatin1String("creators"), temp.authors());
map.insert(QLatin1String("credit"), temp.credit());
map.insert(QLatin1String("rights"), temp.copyright()[def]);
map.insert(QLatin1String("source"), temp.source());
PhotoInfoContainer photoInfo = meta.getPhotographInformation();
map.insert(QLatin1String("exposuretime"), photoInfo.exposureTime);
map.insert(QLatin1String("sensitivity"), photoInfo.sensitivity);
map.insert(QLatin1String("aperture"), photoInfo.aperture);
map.insert(QLatin1String("focallength"), photoInfo.focalLength);
}
return map;
}
bool DMetaInfoIface::supportAlbums() const
{
return false;
}
QWidget* DMetaInfoIface::uploadWidget(QWidget* const parent) const
{
if (!d->dirSelector)
{
d->dirSelector = new DFileSelector(parent);
d->dirSelector->setFileDlgMode(QFileDialog::Directory);
d->dirSelector->setFileDlgOptions(QFileDialog::ShowDirsOnly);
d->dirSelector->setFileDlgTitle(i18n("Destination Folder"));
d->dirSelector->lineEdit()->setPlaceholderText(i18n("Output Destination Path"));
connect(d->dirSelector, SIGNAL(signalUrlSelected(QUrl)),
this, SIGNAL(signalUploadUrlChanged()));
}
QFileInfo info(!d->urls.isEmpty() ? d->urls[0].toLocalFile() : QString());
d->dirSelector->setFileDlgPath(info.absolutePath());
return d->dirSelector;
}
QUrl DMetaInfoIface::uploadUrl() const
{
return QUrl::fromLocalFile(d->dirSelector->fileDlgPath());
}
} // namespace Digikam
diff --git a/core/utilities/assistants/expoblending/manager/expoblendingactions.h b/core/utilities/assistants/expoblending/manager/expoblendingactions.h
index 03c8d4b738..b2ccdd2b33 100644
--- a/core/utilities/assistants/expoblending/manager/expoblendingactions.h
+++ b/core/utilities/assistants/expoblending/manager/expoblendingactions.h
@@ -1,109 +1,109 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-11-13
* Description : a tool to blend bracketed images.
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2015 by Benjamin Girault, <benjamin dot girault at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_EXPO_BLENDING_ACTIONS_H
#define DIGIKAM_EXPO_BLENDING_ACTIONS_H
// Qt includes
#include <QString>
#include <QImage>
#include <QMetaType>
#include <QMap>
// Local includes
#include "enfusesettings.h"
namespace Digikam
{
enum ExpoBlendingAction
{
EXPOBLENDING_NONE = 0,
EXPOBLENDING_IDENTIFY,
EXPOBLENDING_PREPROCESSING,
EXPOBLENDING_ENFUSEPREVIEW,
EXPOBLENDING_ENFUSEFINAL,
EXPOBLENDING_LOAD
};
class ExpoBlendingItemPreprocessedUrls
{
public:
ExpoBlendingItemPreprocessedUrls()
{
};
ExpoBlendingItemPreprocessedUrls(const QUrl& preprocessed, const QUrl& preview)
: preprocessedUrl(preprocessed),
previewUrl(preview)
{
};
virtual ~ExpoBlendingItemPreprocessedUrls()
{
};
QUrl preprocessedUrl; // Can be original file or aligned version, depending of user choice.
- QUrl previewUrl; // The JPEG preview version, accordingly of preprocessedUrl constent.
+ QUrl previewUrl; // The JPEG preview version, accordingly of preprocessedUrl constant.
};
typedef QMap<QUrl, ExpoBlendingItemPreprocessedUrls> ExpoBlendingItemUrlsMap; // Map between original Url and processed temp Urls.
class ExpoBlendingActionData
{
public:
explicit ExpoBlendingActionData()
: starting(false),
success(false),
action(EXPOBLENDING_NONE)
{
}
bool starting;
bool success;
QString message;
QImage image;
QList<QUrl> inUrls;
QList<QUrl> outUrls;
EnfuseSettings enfuseSettings;
ExpoBlendingItemUrlsMap preProcessedUrlsMap;
ExpoBlendingAction action;
};
} // namespace Digikam
Q_DECLARE_METATYPE(Digikam::ExpoBlendingActionData)
Q_DECLARE_METATYPE(Digikam::ExpoBlendingItemPreprocessedUrls)
#endif // DIGIKAM_EXPO_BLENDING_ACTIONS_H
diff --git a/core/utilities/assistants/expoblending/manager/expoblendingthread.cpp b/core/utilities/assistants/expoblending/manager/expoblendingthread.cpp
index e2eec3fa75..06f3eb6c0c 100644
--- a/core/utilities/assistants/expoblending/manager/expoblendingthread.cpp
+++ b/core/utilities/assistants/expoblending/manager/expoblendingthread.cpp
@@ -1,1043 +1,1043 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-11-13
* Description : a tool to blend bracketed images.
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2009-2011 by Johannes Wienke <languitar at semipol dot de>
* Copyright (C) 2012-2015 by Benjamin Girault <benjamin dot girault at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "expoblendingthread.h"
// C++ includes
#include <cmath>
// Under Win32, log2f is not defined...
#ifdef Q_OS_WIN32
# define log2f(x) (logf(x)*1.4426950408889634f)
#endif
#ifdef Q_OS_FREEBSD
#include <osreldate.h>
# if __FreeBSD_version < 802502
# define log2f(x) (logf(x)*1.4426950408889634f)
# endif
#endif
// Qt includes
#include <QPair>
#include <QMutex>
#include <QMutexLocker>
#include <QWaitCondition>
#include <QDateTime>
#include <QFileInfo>
#include <QPointer>
#include <QFuture>
#include <QtConcurrent> // krazy:exclude=includes
#include <QTemporaryDir>
#include <QProcess>
// KDE includes
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
// Local includes
#include "digikam_version.h"
#include "digikam_debug.h"
#include "digikam_globals.h"
#include "drawdecoder.h"
#include "metaengine.h"
#include "dimg.h"
#include "dimgloaderobserver.h"
#include "drawdecoderwidget.h"
#include "drawdecoding.h"
namespace Digikam
{
class RawObserver;
class Q_DECL_HIDDEN ExpoBlendingThread::Private
{
public:
explicit Private()
: cancel(false),
align(false),
enfuseVersion4x(true),
rawObserver(0)
{
}
struct Task
{
bool align;
QList<QUrl> urls;
QUrl outputUrl;
QString binaryPath;
ExpoBlendingAction action;
EnfuseSettings enfuseSettings;
};
volatile bool cancel;
bool align;
bool enfuseVersion4x;
QMutex mutex;
QMutex lock;
QWaitCondition condVar;
QList<Task*> todo;
QSharedPointer<QTemporaryDir> preprocessingTmpDir;
QSharedPointer<QProcess> enfuseProcess;
QSharedPointer<QProcess> alignProcess;
RawObserver* rawObserver;
/**
* List of results files produced by enfuse that may need cleaning.
* Only access this through the provided mutex.
*/
QList<QUrl> enfuseTmpUrls;
QMutex enfuseTmpUrlsMutex;
// Preprocessing
QList<QUrl> mixedUrls; // Original non-RAW + Raw converted urls to align.
ExpoBlendingItemUrlsMap preProcessedUrlsMap;
MetaEngine meta;
};
class Q_DECL_HIDDEN RawObserver : public DImgLoaderObserver
{
public:
explicit RawObserver(ExpoBlendingThread::Private* const priv)
: DImgLoaderObserver(),
d(priv)
{
}
~RawObserver()
{
}
bool continueQuery(const DImg* const)
{
return (!d->cancel);
}
private:
ExpoBlendingThread::Private* const d;
};
ExpoBlendingThread::ExpoBlendingThread(QObject* const parent)
: QThread(parent),
d(new Private)
{
d->rawObserver = new RawObserver(d);
qRegisterMetaType<ExpoBlendingActionData>();
}
ExpoBlendingThread::~ExpoBlendingThread()
{
qCDebug(DIGIKAM_GENERAL_LOG) << "ExpoBlendingThread shutting down."
<< "Canceling all actions and waiting for them";
// cancel the thread
cancel();
// wait for the thread to finish
wait();
qCDebug(DIGIKAM_GENERAL_LOG) << "Thread finished";
cleanUpResultFiles();
delete d;
}
void ExpoBlendingThread::setEnfuseVersion(const double version)
{
d->enfuseVersion4x = (version >= 4.0);
}
void ExpoBlendingThread::cleanUpResultFiles()
{
// Cleanup all tmp files created by Enfuse process.
QMutexLocker(&d->enfuseTmpUrlsMutex);
foreach(const QUrl& url, d->enfuseTmpUrls)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Removing temp file " << url.toLocalFile();
QFile(url.toLocalFile()).remove();
}
d->enfuseTmpUrls.clear();
}
void ExpoBlendingThread::setPreProcessingSettings(bool align)
{
d->align = align;
}
void ExpoBlendingThread::identifyFiles(const QList<QUrl>& urlList)
{
foreach(const QUrl& url, urlList)
{
Private::Task* const t = new Private::Task;
t->action = EXPOBLENDING_IDENTIFY;
t->urls.append(url);
QMutexLocker lock(&d->mutex);
d->todo << t;
d->condVar.wakeAll();
}
}
void ExpoBlendingThread::loadProcessed(const QUrl& url)
{
Private::Task* const t = new Private::Task;
t->action = EXPOBLENDING_LOAD;
t->urls.append(url);
QMutexLocker lock(&d->mutex);
d->todo << t;
d->condVar.wakeAll();
}
void ExpoBlendingThread::preProcessFiles(const QList<QUrl>& urlList, const QString& alignPath)
{
Private::Task* const t = new Private::Task;
t->action = EXPOBLENDING_PREPROCESSING;
t->urls = urlList;
t->align = d->align;
t->binaryPath = alignPath;
QMutexLocker lock(&d->mutex);
d->todo << t;
d->condVar.wakeAll();
}
void ExpoBlendingThread::enfusePreview(const QList<QUrl>& alignedUrls, const QUrl& outputUrl,
const EnfuseSettings& settings, const QString& enfusePath)
{
Private::Task* const t = new Private::Task;
t->action = EXPOBLENDING_ENFUSEPREVIEW;
t->urls = alignedUrls;
t->outputUrl = outputUrl;
t->enfuseSettings = settings;
t->binaryPath = enfusePath;
QMutexLocker lock(&d->mutex);
d->todo << t;
d->condVar.wakeAll();
}
void ExpoBlendingThread::enfuseFinal(const QList<QUrl>& alignedUrls, const QUrl& outputUrl,
const EnfuseSettings& settings, const QString& enfusePath)
{
Private::Task* const t = new Private::Task;
t->action = EXPOBLENDING_ENFUSEFINAL;
t->urls = alignedUrls;
t->outputUrl = outputUrl;
t->enfuseSettings = settings;
t->binaryPath = enfusePath;
QMutexLocker lock(&d->mutex);
d->todo << t;
d->condVar.wakeAll();
}
void ExpoBlendingThread::cancel()
{
QMutexLocker lock(&d->mutex);
d->todo.clear();
d->cancel = true;
if (d->enfuseProcess)
d->enfuseProcess->kill();
if (d->alignProcess)
d->alignProcess->kill();
d->condVar.wakeAll();
}
void ExpoBlendingThread::run()
{
d->cancel = false;
while (!d->cancel)
{
Private::Task* t = 0;
{
QMutexLocker lock(&d->mutex);
if (!d->todo.isEmpty())
t = d->todo.takeFirst();
else
d->condVar.wait(&d->mutex);
}
if (t)
{
switch (t->action)
{
case EXPOBLENDING_IDENTIFY:
{
// Identify Exposure.
QString avLum;
if (!t->urls.isEmpty())
{
float val = getAverageSceneLuminance(t->urls[0]);
if (val != -1)
avLum.setNum(log2f(val), 'g', 2);
}
ExpoBlendingActionData ad;
ad.action = t->action;
ad.inUrls = t->urls;
ad.message = avLum.isEmpty() ? i18nc("average scene luminance value unknown", "unknown") : avLum;
ad.success = avLum.isEmpty();
emit finished(ad);
break;
}
case EXPOBLENDING_PREPROCESSING:
{
ExpoBlendingActionData ad1;
ad1.action = EXPOBLENDING_PREPROCESSING;
ad1.inUrls = t->urls;
ad1.starting = true;
emit starting(ad1);
QString errors;
bool result = startPreProcessing(t->urls, t->align, t->binaryPath, errors);
ExpoBlendingActionData ad2;
ad2.action = EXPOBLENDING_PREPROCESSING;
ad2.inUrls = t->urls;
ad2.preProcessedUrlsMap = d->preProcessedUrlsMap;
ad2.success = result;
ad2.message = errors;
emit finished(ad2);
break;
}
case EXPOBLENDING_LOAD:
{
ExpoBlendingActionData ad1;
ad1.action = EXPOBLENDING_LOAD;
ad1.inUrls = t->urls;
ad1.starting = true;
emit starting(ad1);
QImage image;
bool result = image.load(t->urls[0].toLocalFile());
// rotate image
if (result)
{
if (d->meta.load(t->urls[0].toLocalFile()))
d->meta.rotateExifQImage(image, d->meta.getImageOrientation());
}
ExpoBlendingActionData ad2;
ad2.action = EXPOBLENDING_LOAD;
ad2.inUrls = t->urls;
ad2.success = result;
ad2.image = image;
emit finished(ad2);
break;
}
case EXPOBLENDING_ENFUSEPREVIEW:
{
ExpoBlendingActionData ad1;
ad1.action = EXPOBLENDING_ENFUSEPREVIEW;
ad1.inUrls = t->urls;
ad1.starting = true;
ad1.enfuseSettings = t->enfuseSettings;
emit starting(ad1);
QString errors;
QUrl destUrl = t->outputUrl;
EnfuseSettings settings = t->enfuseSettings;
settings.outputFormat = DSaveSettingsWidget::OUTPUT_JPEG; // JPEG for preview: fast and small.
bool result = startEnfuse(t->urls, destUrl, settings, t->binaryPath, errors);
qCDebug(DIGIKAM_GENERAL_LOG) << "Preview result was: " << result;
// preserve exif information for auto rotation
if (result)
{
if (d->meta.load(t->urls[0].toLocalFile()))
{
MetaEngine::ImageOrientation orientation = d->meta.getImageOrientation();
if (d->meta.load(destUrl.toLocalFile()))
{
d->meta.setImageOrientation(orientation);
d->meta.applyChanges();
}
}
}
// To be cleaned in destructor.
QMutexLocker(&d->enfuseTmpUrlsMutex);
d->enfuseTmpUrls << destUrl;
ExpoBlendingActionData ad2;
ad2.action = EXPOBLENDING_ENFUSEPREVIEW;
ad2.inUrls = t->urls;
ad2.outUrls = QList<QUrl>() << destUrl;
ad2.success = result;
ad2.message = errors;
ad2.enfuseSettings = t->enfuseSettings;
emit finished(ad2);
break;
}
case EXPOBLENDING_ENFUSEFINAL:
{
ExpoBlendingActionData ad1;
ad1.action = EXPOBLENDING_ENFUSEFINAL;
ad1.inUrls = t->urls;
ad1.starting = true;
ad1.enfuseSettings = t->enfuseSettings;
emit starting(ad1);
QString errors;
QUrl destUrl = t->outputUrl;
bool result = startEnfuse(t->urls, destUrl, t->enfuseSettings, t->binaryPath, errors);
// We will take first image metadata from stack to restore Exif, Iptc, and Xmp.
if (d->meta.load(t->urls[0].toLocalFile()))
{
result = result & d->meta.setXmpTagString("Xmp.digiKam.EnfuseInputFiles",
t->enfuseSettings.inputImagesList());
result = result & d->meta.setXmpTagString("Xmp.digiKam.EnfuseSettings",
t->enfuseSettings.asCommentString().
replace(QLatin1Char('\n'),
QLatin1String(" ; ")));
d->meta.setImageDateTime(QDateTime::currentDateTime());
if (t->enfuseSettings.outputFormat != DSaveSettingsWidget::OUTPUT_JPEG)
{
QImage img;
if (img.load(destUrl.toLocalFile()))
d->meta.setImagePreview(img.scaled(1280, 1024, Qt::KeepAspectRatio));
}
d->meta.save(destUrl.toLocalFile());
}
// To be cleaned in destructor.
QMutexLocker(&d->enfuseTmpUrlsMutex);
d->enfuseTmpUrls << destUrl;
ExpoBlendingActionData ad2;
ad2.action = EXPOBLENDING_ENFUSEFINAL;
ad2.inUrls = t->urls;
ad2.outUrls = QList<QUrl>() << destUrl;
ad2.success = result;
ad2.message = errors;
ad2.enfuseSettings = t->enfuseSettings;
emit finished(ad2);
break;
}
default:
{
qCritical(DIGIKAM_GENERAL_LOG) << "Unknown action specified" << endl;
break;
}
}
}
delete t;
}
}
void ExpoBlendingThread::preProcessingMultithreaded(const QUrl& url, volatile bool& error)
{
if (error)
{
return;
}
// check if we have to RAW file -> use preview image then
if (DRawDecoder::isRawFile(url))
{
QUrl preprocessedUrl, previewUrl;
if (!convertRaw(url, preprocessedUrl))
{
error = true;
return;
}
if (!computePreview(preprocessedUrl, previewUrl))
{
error = true;
return;
}
d->lock.lock();
d->mixedUrls.append(preprocessedUrl);
// In case of alignment is not performed.
d->preProcessedUrlsMap.insert(url, ExpoBlendingItemPreprocessedUrls(preprocessedUrl, previewUrl));
d->lock.unlock();
}
else
{
// NOTE: in this case, preprocessed Url is original file Url.
QUrl previewUrl;
if (!computePreview(url, previewUrl))
{
error = true;
return;
}
d->lock.lock();
d->mixedUrls.append(url);
// In case of alignment is not performed.
d->preProcessedUrlsMap.insert(url, ExpoBlendingItemPreprocessedUrls(url, previewUrl));
d->lock.unlock();
}
}
bool ExpoBlendingThread::startPreProcessing(const QList<QUrl>& inUrls,
bool align,
const QString& alignPath, QString& errors)
{
QString prefix = QDir::tempPath() + QLatin1Char('/') +
QLatin1String("digiKam-expoblending-tmp-XXXXXX");
d->preprocessingTmpDir = QSharedPointer<QTemporaryDir>(new QTemporaryDir(prefix));
qCDebug(DIGIKAM_GENERAL_LOG) << "Temp dir : " << d->preprocessingTmpDir->path();
// Parallelized pre-process RAW files if necessary.
d->mixedUrls.clear();
d->preProcessedUrlsMap.clear();
volatile bool error = false;
QList <QFuture<void> > tasks;
for (int i = 0 ; i < inUrls.size() ; ++i)
{
QUrl url = inUrls.at(i);
tasks.append(QtConcurrent::run(this,
&ExpoBlendingThread::preProcessingMultithreaded,
url,
error
));
}
for (QFuture<void>& t: tasks)
{
t.waitForFinished();
}
if (error)
{
return false;
}
if (align)
{
// Re-align images
d->alignProcess.reset(new QProcess());
d->alignProcess->setWorkingDirectory(d->preprocessingTmpDir->path());
d->alignProcess->setProcessChannelMode(QProcess::MergedChannels);
d->alignProcess->setProcessEnvironment(adjustedEnvironmentForAppImage());
QStringList args;
args << QLatin1String("-v");
args << QLatin1String("-a");
args << QLatin1String("aligned");
foreach(const QUrl& url, d->mixedUrls)
{
args << url.toLocalFile();
}
d->alignProcess->setProgram(alignPath);
d->alignProcess->setArguments(args);
qCDebug(DIGIKAM_GENERAL_LOG) << "Align command line: " << d->alignProcess->program();
d->alignProcess->start();
if (!d->alignProcess->waitForFinished(-1))
{
errors = getProcessError(*(d->alignProcess));
qCDebug(DIGIKAM_GENERAL_LOG) << "align_image_stack error: " << errors;
return false;
}
uint i = 0;
QString temp;
d->preProcessedUrlsMap.clear();
foreach (const QUrl& url, inUrls)
{
QUrl previewUrl;
QUrl alignedUrl = QUrl::fromLocalFile(d->preprocessingTmpDir->path() +
QLatin1Char('/') +
QLatin1String("aligned") +
QString::number(i).rightJustified(4, QLatin1Char('0')) +
QLatin1String(".tif"));
if (!computePreview(alignedUrl, previewUrl))
{
return false;
}
d->preProcessedUrlsMap.insert(url, ExpoBlendingItemPreprocessedUrls(alignedUrl, previewUrl));
i++;
}
foreach(const QUrl& inputUrl, d->preProcessedUrlsMap.keys())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Pre-processed output urls map: "
<< inputUrl << "=>"
<< d->preProcessedUrlsMap[inputUrl].preprocessedUrl << ","
<< d->preProcessedUrlsMap[inputUrl].previewUrl << ";";
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Align exit status : " << d->alignProcess->exitStatus();
qCDebug(DIGIKAM_GENERAL_LOG) << "Align exit code : " << d->alignProcess->exitCode();
if (d->alignProcess->exitStatus() != QProcess::NormalExit)
{
return false;
}
if (d->alignProcess->exitCode() == 0)
{
// Process finished successfully !
return true;
}
errors = getProcessError(*(d->alignProcess));
return false;
}
else
{
foreach(const QUrl& inputUrl, d->preProcessedUrlsMap.keys())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Pre-processed output urls map: "
<< inputUrl << "=>"
<< d->preProcessedUrlsMap[inputUrl].preprocessedUrl << ","
<< d->preProcessedUrlsMap[inputUrl].previewUrl << ";";
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Alignment not performed.";
return true;
}
}
bool ExpoBlendingThread::computePreview(const QUrl& inUrl, QUrl& outUrl)
{
outUrl = QUrl::fromLocalFile(d->preprocessingTmpDir->path() +
QLatin1Char('/') +
QLatin1Char('.') +
inUrl.fileName().replace(QLatin1Char('.'), QLatin1Char('_')) +
QLatin1String("-preview.jpg"));
DImg img;
if (img.load(inUrl.toLocalFile()))
{
DImg preview = img.smoothScale(1280, 1024, Qt::KeepAspectRatio);
bool saved = preview.save(outUrl.toLocalFile(), QLatin1String("JPG"));
// save exif information also to preview image for auto rotation
if (saved)
{
if (d->meta.load(inUrl.toLocalFile()))
{
MetaEngine::ImageOrientation orientation = d->meta.getImageOrientation();
if (d->meta.load(outUrl.toLocalFile()))
{
d->meta.setImageOrientation(orientation);
d->meta.applyChanges();
}
}
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Preview Image url: " << outUrl << ", saved: " << saved;
return saved;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Input image not loaded:" << inUrl;
return false;
}
bool ExpoBlendingThread::convertRaw(const QUrl& inUrl, QUrl& outUrl)
{
DImg img;
DRawDecoding settings;
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(QLatin1String("ImageViewer Settings"));
DRawDecoderWidget::readSettings(settings.rawPrm, group);
if (img.load(inUrl.toLocalFile(), d->rawObserver, settings))
{
if (d->meta.load(inUrl.toLocalFile()))
{
d->meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion());
d->meta.setImageDimensions(img.size());
d->meta.setExifTagString("Exif.Image.DocumentName", inUrl.fileName());
d->meta.setXmpTagString("Xmp.tiff.Make", d->meta.getExifTagString("Exif.Image.Make"));
d->meta.setXmpTagString("Xmp.tiff.Model", d->meta.getExifTagString("Exif.Image.Model"));
d->meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL);
QFileInfo fi(inUrl.toLocalFile());
outUrl = QUrl::fromLocalFile(d->preprocessingTmpDir->path() +
QLatin1Char('/') +
QLatin1Char('.') +
fi.completeBaseName().replace(QLatin1Char('.'), QLatin1Char('_')) +
QLatin1String(".tif"));
if (!img.save(outUrl.toLocalFile(), QLatin1String("TIF")))
{
return false;
}
d->meta.save(outUrl.toLocalFile());
}
}
else
{
return false;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Convert RAW output url: " << outUrl;
return true;
}
bool ExpoBlendingThread::startEnfuse(const QList<QUrl>& inUrls, QUrl& outUrl,
const EnfuseSettings& settings,
const QString& enfusePath, QString& errors)
{
QString comp;
QString ext = DSaveSettingsWidget::extensionForFormat(settings.outputFormat);
if (ext == QLatin1String(".tif"))
{
comp = QLatin1String("--compression=DEFLATE");
}
outUrl.setPath(outUrl.adjusted(QUrl::RemoveFilename).path() + QLatin1String(".digiKam-expoblending-tmp-") +
QString::number(QDateTime::currentDateTime().toTime_t()) + ext);
d->enfuseProcess.reset(new QProcess());
d->enfuseProcess->setWorkingDirectory(d->preprocessingTmpDir->path());
d->enfuseProcess->setProcessChannelMode(QProcess::MergedChannels);
d->enfuseProcess->setProcessEnvironment(adjustedEnvironmentForAppImage());
QStringList args;
if (!settings.autoLevels)
{
args << QLatin1String("-l");
args << QString::number(settings.levels);
}
if (settings.ciecam02)
{
args << QLatin1String("-c");
}
if (!comp.isEmpty())
{
args << comp;
}
if (settings.hardMask)
{
if (d->enfuseVersion4x)
args << QLatin1String("--hard-mask");
else
args << QLatin1String("--HardMask");
}
if (d->enfuseVersion4x)
{
args << QString::fromUtf8("--exposure-weight=%1").arg(settings.exposure);
args << QString::fromUtf8("--saturation-weight=%1").arg(settings.saturation);
args << QString::fromUtf8("--contrast-weight=%1").arg(settings.contrast);
}
else
{
args << QString::fromUtf8("--wExposure=%1").arg(settings.exposure);
args << QString::fromUtf8("--wSaturation=%1").arg(settings.saturation);
args << QString::fromUtf8("--wContrast=%1").arg(settings.contrast);
}
args << QLatin1String("-v");
args << QLatin1String("-o");
args << outUrl.toLocalFile();
foreach (const QUrl& url, inUrls)
{
args << url.toLocalFile();
}
d->enfuseProcess->setProgram(enfusePath);
d->enfuseProcess->setArguments(args);
qCDebug(DIGIKAM_GENERAL_LOG) << "Enfuse command line: " << d->enfuseProcess->program();
d->enfuseProcess->start();
if (!d->enfuseProcess->waitForFinished(-1))
{
errors = getProcessError(*(d->enfuseProcess));
return false;
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Enfuse output url: " << outUrl;
qCDebug(DIGIKAM_GENERAL_LOG) << "Enfuse exit status: " << d->enfuseProcess->exitStatus();
qCDebug(DIGIKAM_GENERAL_LOG) << "Enfuse exit code: " << d->enfuseProcess->exitCode();
if (d->enfuseProcess->exitStatus() != QProcess::NormalExit)
{
return false;
}
if (d->enfuseProcess->exitCode() == 0)
{
// Process finished successfully !
return true;
}
errors = getProcessError(*(d->enfuseProcess));
return false;
}
QString ExpoBlendingThread::getProcessError(QProcess& proc) const
{
QString std = QString::fromLocal8Bit(proc.readAll());
return (i18n("Cannot run %1:\n\n %2", proc.program(), std));
}
/**
* This function obtains the "average scene luminance" (cd/m^2) from an image file.
* "average scene luminance" is the L (aka B) value mentioned in [1]
* You have to take a log2f of the returned value to get an EV value.
*
* We are using K=12.07488f and the exif-implied value of N=1/3.125 (see [1]).
* K=12.07488f is the 1.0592f * 11.4f value in pfscalibration's pfshdrcalibrate.cpp file.
* Based on [3] we can say that the value can also be 12.5 or even 14.
* Another reference for APEX is [4] where N is 0.3, closer to the APEX specification of 2^(-7/4)=0.2973.
*
* [1] http://en.wikipedia.org/wiki/APEX_system
* [2] http://en.wikipedia.org/wiki/Exposure_value
* [3] http://en.wikipedia.org/wiki/Light_meter
* [4] http://doug.kerr.home.att.net/pumpkin/#APEX
*
* This function tries first to obtain the shutter speed from either of
- * two exif tags (there is no standard between camera manifacturers):
+ * two exif tags (there is no standard between camera manufacturers):
* ExposureTime or ShutterSpeedValue.
* Same thing for f-number: it can be found in FNumber or in ApertureValue.
*
* F-number and shutter speed are mandatory in exif data for EV calculation, iso is not.
*/
float ExpoBlendingThread::getAverageSceneLuminance(const QUrl& url)
{
if (!d->meta.load(url.toLocalFile()))
return -1;
if (!d->meta.hasExif())
return -1;
long num = 1, den = 1;
// default not valid values
float expo = -1.0;
float iso = -1.0;
float fnum = -1.0;
QVariant rationals;
if (d->meta.getExifTagRational("Exif.Photo.ExposureTime", num, den))
{
if (den)
expo = (float)(num) / (float)(den);
}
else if (getXmpRational("Xmp.exif.ExposureTime", num, den, &d->meta))
{
if (den)
expo = (float)(num) / (float)(den);
}
else if (d->meta.getExifTagRational("Exif.Photo.ShutterSpeedValue", num, den))
{
long nmr = 1, div = 1;
double tmp = 0.0;
if (den)
tmp = exp(log(2.0) * (float)(num) / (float)(den));
if (tmp > 1.0)
{
div = (long)(tmp + 0.5);
}
else
{
nmr = (long)(1 / tmp + 0.5);
}
if (div)
expo = (float)(nmr) / (float)(div);
}
else if (getXmpRational("Xmp.exif.ShutterSpeedValue", num, den, &d->meta))
{
long nmr = 1, div = 1;
double tmp = 0.0;
if (den)
tmp = exp(log(2.0) * (float)(num) / (float)(den));
if (tmp > 1.0)
{
div = (long)(tmp + 0.5);
}
else
{
nmr = (long)(1 / tmp + 0.5);
}
if (div)
expo = (float)(nmr) / (float)(div);
}
qCDebug(DIGIKAM_GENERAL_LOG) << url.fileName() << " : expo = " << expo;
if (d->meta.getExifTagRational("Exif.Photo.FNumber", num, den))
{
if (den)
fnum = (float)(num) / (float)(den);
}
else if (getXmpRational("Xmp.exif.FNumber", num, den, &d->meta))
{
if (den)
fnum = (float)(num) / (float)(den);
}
else if (d->meta.getExifTagRational("Exif.Photo.ApertureValue", num, den))
{
if (den)
fnum = (float)(exp(log(2.0) * (float)(num) / (float)(den) / 2.0));
}
else if (getXmpRational("Xmp.exif.ApertureValue", num, den, &d->meta))
{
if (den)
fnum = (float)(exp(log(2.0) * (float)(num) / (float)(den) / 2.0));
}
qCDebug(DIGIKAM_GENERAL_LOG) << url.fileName() << " : fnum = " << fnum;
// Some cameras/lens DO print the fnum but with value 0, and this is not allowed for ev computation purposes.
if (fnum == 0.0)
return -1.0;
// If iso is found use that value, otherwise assume a value of iso=100. (again, some cameras do not print iso in exif).
if (d->meta.getExifTagRational("Exif.Photo.ISOSpeedRatings", num, den))
{
if (den)
iso = (float)(num) / (float)(den);
}
else if (getXmpRational("Xmp.exif.ISOSpeedRatings", num, den, &d->meta))
{
if (den)
iso = (float)(num) / (float)(den);
}
else
{
iso = 100.0;
}
qCDebug(DIGIKAM_GENERAL_LOG) << url.fileName() << " : iso = " << iso;
// At this point the three variables have to be != -1
if (expo != -1.0 && iso != -1.0 && fnum != -1.0)
{
float asl = (expo * iso) / (fnum * fnum * 12.07488f);
qCDebug(DIGIKAM_GENERAL_LOG) << url.fileName() << " : ASL ==> " << asl;
return asl;
}
return -1.0;
}
bool ExpoBlendingThread::getXmpRational(const char* xmpTagName, long& num, long& den, MetaEngine* const meta)
{
QVariant rationals = meta->getXmpTagVariant(xmpTagName);
if (!rationals.isNull())
{
QVariantList list = rationals.toList();
if (list.size() == 2)
{
num = list[0].toInt();
den = list[1].toInt();
return true;
}
}
return false;
}
} // namespace Digikam
diff --git a/core/utilities/assistants/expoblending/wizard/expoblendingwizard.cpp b/core/utilities/assistants/expoblending/wizard/expoblendingwizard.cpp
index 279ac60b18..92cbac0012 100644
--- a/core/utilities/assistants/expoblending/wizard/expoblendingwizard.cpp
+++ b/core/utilities/assistants/expoblending/wizard/expoblendingwizard.cpp
@@ -1,171 +1,171 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-11-13
* Description : a tool to blend bracketed images.
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2012-2015 by Benjamin Girault <benjamin dot girault at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "expoblendingwizard.h"
// Qt includes
#include <QDesktopWidget>
#include <QApplication>
#include <QMenu>
// KDE includes
#include <klocalizedstring.h>
-// Locale incudes
+// Locale includes
#include "expoblendingintropage.h"
#include "expoblendingitemspage.h"
#include "expoblendinglastpage.h"
#include "expoblendingmanager.h"
#include "expoblendingpreprocesspage.h"
namespace Digikam
{
class Q_DECL_HIDDEN ExpoBlendingWizard::Private
{
public:
explicit Private()
: mngr(0),
introPage(0),
itemsPage(0),
preProcessingPage(0),
lastPage(0),
preProcessed(false)
{
}
ExpoBlendingManager* mngr;
ExpoBlendingIntroPage* introPage;
ItemsPage* itemsPage;
ExpoBlendingPreProcessPage* preProcessingPage;
ExpoBlendingLastPage* lastPage;
bool preProcessed;
};
ExpoBlendingWizard::ExpoBlendingWizard(ExpoBlendingManager* const mngr, QWidget* const parent)
: DWizardDlg(parent, QLatin1String("ExpoBlending Dialog")),
d(new Private)
{
setModal(false);
setWindowTitle(i18nc("@title:window", "Stacked Images Tool"));
d->mngr = mngr;
d->introPage = new ExpoBlendingIntroPage(d->mngr, this);
d->itemsPage = new ItemsPage(d->mngr, this);
d->preProcessingPage = new ExpoBlendingPreProcessPage(d->mngr, this);
d->lastPage = new ExpoBlendingLastPage(d->mngr, this);
// ---------------------------------------------------------------
connect(d->introPage, SIGNAL(signalExpoBlendingIntroPageIsValid(bool)),
this, SLOT(slotExpoBlendingIntroPageIsValid(bool)));
connect(d->itemsPage, SIGNAL(signalItemsPageIsValid(bool)),
this, SLOT(slotItemsPageIsValid(bool)));
connect(d->preProcessingPage, SIGNAL(signalPreProcessed(ExpoBlendingItemUrlsMap)),
this, SLOT(slotPreProcessed(ExpoBlendingItemUrlsMap)));
connect(this, SIGNAL(currentIdChanged(int)),
this, SLOT(slotCurrentIdChanged(int)));
d->introPage->setComplete(d->introPage->binariesFound());
}
ExpoBlendingWizard::~ExpoBlendingWizard()
{
delete d;
}
ExpoBlendingManager* ExpoBlendingWizard::manager() const
{
return d->mngr;
}
QList<QUrl> ExpoBlendingWizard::itemUrls() const
{
return d->itemsPage->itemUrls();
}
bool ExpoBlendingWizard::validateCurrentPage()
{
if (currentPage() == d->itemsPage)
{
d->mngr->setItemsList(d->itemsPage->itemUrls());
}
else if (currentPage() == d->preProcessingPage && !d->preProcessed)
{
// Do not give access to Next button during alignment process.
d->preProcessingPage->setComplete(false);
d->preProcessingPage->process();
d->preProcessed = true;
// Next is handled with signals/slots
return false;
}
return true;
}
void ExpoBlendingWizard::slotCurrentIdChanged(int id)
{
if (page(id) != d->lastPage && d->preProcessed)
{
d->preProcessed = false;
d->preProcessingPage->cancel();
d->preProcessingPage->setComplete(true);
}
}
void ExpoBlendingWizard::slotExpoBlendingIntroPageIsValid(bool binariesFound)
{
d->introPage->setComplete(binariesFound);
}
void ExpoBlendingWizard::slotPreProcessed(const ExpoBlendingItemUrlsMap& map)
{
if (map.isEmpty())
{
// pre-processing failed.
d->preProcessingPage->setComplete(false);
d->preProcessed = false;
}
else
{
// pre-processing Done.
d->mngr->setPreProcessedMap(map);
next();
}
}
void ExpoBlendingWizard::slotItemsPageIsValid(bool valid)
{
d->itemsPage->setComplete(valid);
}
} // namespace Digikam
diff --git a/core/utilities/assistants/firstrun/firstrundlg.cpp b/core/utilities/assistants/firstrun/firstrundlg.cpp
index f1fe9ad07f..4916866f16 100644
--- a/core/utilities/assistants/firstrun/firstrundlg.cpp
+++ b/core/utilities/assistants/firstrun/firstrundlg.cpp
@@ -1,195 +1,195 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-28-04
* Description : first run assistant dialog
*
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "firstrundlg.h"
// Qt includes
#include <QPushButton>
// KDE includes
#include <kdelibs4migration.h>
-// Local incudes
+// Local includes
#include "dxmlguiwindow.h"
#include "welcomepage.h"
#include "migratefromdigikam4page.h"
#include "collectionpage.h"
#include "databasepage.h"
#include "rawpage.h"
#include "metadatapage.h"
#include "previewpage.h"
#include "openfilepage.h"
#include "tooltipspage.h"
#include "startscanpage.h"
namespace Digikam
{
class Q_DECL_HIDDEN FirstRunDlg::Private
{
public:
explicit Private()
: welcomePage(0),
migrateFromDigikam4Page(0),
collectionPage(0),
databasePage(0),
rawPage(0),
metadataPage(0),
previewPage(0),
openFilePage(0),
tooltipsPage(0),
startScanPage(0)
{
}
WelcomePage* welcomePage;
MigrateFromDigikam4Page* migrateFromDigikam4Page;
CollectionPage* collectionPage;
DatabasePage* databasePage;
RawPage* rawPage;
MetadataPage* metadataPage;
PreviewPage* previewPage;
OpenFilePage* openFilePage;
TooltipsPage* tooltipsPage;
StartScanPage* startScanPage;
};
FirstRunDlg::FirstRunDlg(QWidget* const parent)
: QWizard(parent),
d(new Private)
{
setWizardStyle(QWizard::ClassicStyle);
setButtonLayout(QList<QWizard::WizardButton>() << QWizard::HelpButton
<< QWizard::BackButton
<< QWizard::CancelButton
<< QWizard::NextButton
<< QWizard::FinishButton);
bool migrateAvailable;
#ifdef Q_OS_LINUX
::Kdelibs4Migration migration;
// If there's a digikamrc file in $KDEHOME/share/config,
// then we create the migration page in the wizard
migrateAvailable = !migration.locateLocal("config", QLatin1String("digikamrc")).isEmpty();
#else
migrateAvailable = false;
#endif
d->welcomePage = new WelcomePage(this); // First assistant page
if (migrateAvailable)
d->migrateFromDigikam4Page = new MigrateFromDigikam4Page(this);
d->collectionPage = new CollectionPage(this);
d->databasePage = new DatabasePage(this);
d->rawPage = new RawPage(this);
d->metadataPage = new MetadataPage(this);
d->previewPage = new PreviewPage(this);
d->openFilePage = new OpenFilePage(this);
d->tooltipsPage = new TooltipsPage(this);
// NOTE: Added here new assistant pages...
d->startScanPage = new StartScanPage(this); // Last assistant page
resize(600, 600);
connect(button(QWizard::FinishButton), SIGNAL(clicked()),
this, SLOT(slotFinishPressed()));
connect(this, SIGNAL(helpRequested()),
this, SLOT(slotHelp()));
}
FirstRunDlg::~FirstRunDlg()
{
delete d;
}
void FirstRunDlg::slotHelp()
{
DXmlGuiWindow::openHandbook();
}
QString FirstRunDlg::firstAlbumPath() const
{
return d->collectionPage->firstAlbumPath();
}
DbEngineParameters FirstRunDlg::getDbEngineParameters() const
{
return d->databasePage->getDbEngineParameters();
}
bool FirstRunDlg::validateCurrentPage()
{
if (currentPage() == d->collectionPage)
{
if (!d->collectionPage->checkSettings())
{
return false;
}
else
{
d->databasePage->setDatabasePath(firstAlbumPath());
}
}
if (currentPage() == d->databasePage)
{
if (!d->databasePage->checkSettings())
{
return false;
}
}
return true;
}
void FirstRunDlg::slotFinishPressed()
{
if (d->migrateFromDigikam4Page && d->migrateFromDigikam4Page->isMigrationChecked())
{
// The user choose to do a migration from digikam4
d->migrateFromDigikam4Page->doMigration();
}
else
{
// Save settings to rc files.
d->collectionPage->saveSettings();
d->databasePage->saveSettings();
d->rawPage->saveSettings();
d->metadataPage->saveSettings();
d->previewPage->saveSettings();
d->openFilePage->saveSettings();
d->tooltipsPage->saveSettings();
}
}
} // namespace Digikam
diff --git a/core/utilities/assistants/htmlgallery/generator/gallerytheme.cpp b/core/utilities/assistants/htmlgallery/generator/gallerytheme.cpp
index 6a5e251dbb..3f871aa6f5 100644
--- a/core/utilities/assistants/htmlgallery/generator/gallerytheme.cpp
+++ b/core/utilities/assistants/htmlgallery/generator/gallerytheme.cpp
@@ -1,293 +1,293 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-04-04
* Description : a tool to generate HTML image galleries
*
* Copyright (C) 2006-2010 by Aurelien Gateau <aurelien dot gateau at free dot fr>
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "gallerytheme.h"
// Qt includes
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QUrl>
#include <QDir>
// KDE includes
#include <kconfiggroup.h>
#include <kdesktopfile.h>
// Local includes
#include "digikam_debug.h"
#include "colorthemeparameter.h"
#include "intthemeparameter.h"
#include "listthemeparameter.h"
#include "stringthemeparameter.h"
namespace Digikam
{
static const char* AUTHOR_GROUP = "X-HTMLGallery Author";
static const char* PARAMETER_GROUP_PREFIX = "X-HTMLGallery Parameter ";
static const char* PARAMETER_TYPE_KEY = "Type";
static const char* PREVIEW_GROUP = "X-HTMLGallery Preview";
static const char* OPTIONS_GROUP = "X-HTMLGallery Options";
static const char* STRING_PARAMETER_TYPE = "string";
static const char* LIST_PARAMETER_TYPE = "list";
static const char* COLOR_PARAMETER_TYPE = "color";
static const char* INT_PARAMETER_TYPE = "int";
static GalleryTheme::List sList;
class Q_DECL_HIDDEN GalleryTheme::Private
{
public:
explicit Private()
: desktopFile(0)
{
}
KDesktopFile* desktopFile;
QUrl url;
ParameterList parameterList;
public:
/**
* Return the list of parameters defined in the desktop file. We need to
- * parse the file ourself to preserve parameter order.
+ * parse the file ourselves to preserve parameter order.
*/
QStringList readParameterNameList(const QString& desktopFileName)
{
QStringList list;
QFile file(desktopFileName);
if (!file.open(QIODevice::ReadOnly))
{
return QStringList();
}
QTextStream stream(&file);
stream.setCodec("UTF-8");
QString prefix = QLatin1String("[") + QLatin1String(PARAMETER_GROUP_PREFIX);
while (!stream.atEnd())
{
QString line = stream.readLine();
line = line.trimmed();
if (!line.startsWith(prefix))
{
continue;
}
// Remove opening bracket and group prefix
line = line.mid(prefix.length());
// Remove closing bracket
line.truncate(line.length() - 1);
list.append(line);
}
return list;
}
void init(const QString& desktopFileName)
{
delete desktopFile;
desktopFile = new KDesktopFile(desktopFileName);
url = QUrl::fromLocalFile(desktopFileName);
QStringList parameterNameList = readParameterNameList(desktopFileName);
readParameters(parameterNameList);
}
void readParameters(const QStringList& list)
{
QStringList::ConstIterator it = list.constBegin();
QStringList::ConstIterator end = list.constEnd();
for (; it != end ; ++it)
{
QString groupName = QLatin1String(PARAMETER_GROUP_PREFIX) + *it;
QByteArray internalName = it->toUtf8();
KConfigGroup group = desktopFile->group(groupName);
QString type = group.readEntry(PARAMETER_TYPE_KEY);
AbstractThemeParameter* parameter = 0;
if (type == QLatin1String(STRING_PARAMETER_TYPE))
{
parameter = new StringThemeParameter();
}
else if (type == QLatin1String(LIST_PARAMETER_TYPE))
{
parameter = new ListThemeParameter();
}
else if (type == QLatin1String(COLOR_PARAMETER_TYPE))
{
parameter = new ColorThemeParameter();
}
else if (type == QLatin1String(INT_PARAMETER_TYPE))
{
parameter = new IntThemeParameter();
}
else
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Parameter '" << internalName
<< "' has unknown type '" << type
<< "'. Falling back to string type\n";
parameter = new StringThemeParameter();
}
parameter->init(internalName, &group);
parameterList << parameter;
}
}
};
GalleryTheme::GalleryTheme()
: d(new Private)
{
}
GalleryTheme::~GalleryTheme()
{
delete d->desktopFile;
delete d;
}
const GalleryTheme::List& GalleryTheme::getList()
{
if (sList.isEmpty())
{
QStringList list;
QStringList internalNameList;
const QStringList filter = QStringList() << QLatin1String("*.desktop");
const QStringList themesDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation,
QLatin1String("digikam/themes"),
QStandardPaths::LocateDirectory);
foreach(const QString& themeDir, themesDirs)
{
foreach(const QFileInfo& themeInfo, QDir(themeDir).entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot))
{
foreach(const QFileInfo& deskFile, QDir(themeInfo.absoluteFilePath()).entryInfoList(filter))
{
list << deskFile.absoluteFilePath();
}
}
}
QStringList::ConstIterator it = list.constBegin();
QStringList::ConstIterator end = list.constEnd();
for (; it != end ; ++it)
{
GalleryTheme* const theme = new GalleryTheme;
theme->d->init(*it);
QString internalName = theme->internalName();
if (!internalNameList.contains(internalName))
{
sList << GalleryTheme::Ptr(theme);
internalNameList << internalName;
}
}
}
return sList;
}
GalleryTheme::Ptr GalleryTheme::findByInternalName(const QString& internalName)
{
const GalleryTheme::List& lst = getList();
GalleryTheme::List::ConstIterator it = lst.constBegin();
GalleryTheme::List::ConstIterator end = lst.constEnd();
for (; it != end ; ++it)
{
GalleryTheme::Ptr theme = *it;
if (theme->internalName() == internalName)
{
return theme;
}
}
return GalleryTheme::Ptr(0);
}
QString GalleryTheme::internalName() const
{
return d->url.fileName();
}
QString GalleryTheme::name() const
{
return d->desktopFile->readName();
}
QString GalleryTheme::comment() const
{
return d->desktopFile->readComment();
}
QString GalleryTheme::directory() const
{
return d->url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile();
}
QString GalleryTheme::authorName() const
{
return d->desktopFile->group(AUTHOR_GROUP).readEntry("Name");
}
QString GalleryTheme::authorUrl() const
{
return d->desktopFile->group(AUTHOR_GROUP).readEntry("Url");
}
QString GalleryTheme::previewName() const
{
return d->desktopFile->group(PREVIEW_GROUP).readEntry("Name");
}
QString GalleryTheme::previewUrl() const
{
return d->desktopFile->group(PREVIEW_GROUP).readEntry("Url");
}
bool GalleryTheme::allowNonsquareThumbnails() const
{
return d->desktopFile->group(OPTIONS_GROUP).readEntry("Allow-non-square-thumbnails", false);
}
GalleryTheme::ParameterList GalleryTheme::parameterList() const
{
return d->parameterList;
}
} // namespace Digikam
diff --git a/core/utilities/assistants/panorama/manager/panoactions.h b/core/utilities/assistants/panorama/manager/panoactions.h
index 3b403dbefd..f1b7a5f2a9 100644
--- a/core/utilities/assistants/panorama/manager/panoactions.h
+++ b/core/utilities/assistants/panorama/manager/panoactions.h
@@ -1,117 +1,117 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-05-23
* Description : a tool to create panorama by fusion of several images.
*
* Copyright (C) 2011-2016 by Benjamin Girault <benjamin dot girault at gmail dot com>
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_PANO_ACTIONS_H
#define DIGIKAM_PANO_ACTIONS_H
// Qt includes
#include <QString>
#include <QImage>
#include <QMetaType>
#include <QMap>
#include <QUrl>
namespace Digikam
{
enum PanoAction
{
PANO_NONE = 0, // 0
PANO_PREPROCESS_INPUT, // 1
PANO_CREATEPTO, // 2
PANO_CPFIND, // 3
PANO_CPCLEAN, // 4
PANO_OPTIMIZE, // 5
PANO_AUTOCROP, // 6
PANO_CREATEPREVIEWPTO, // 7
PANO_CREATEMK, // 8
PANO_CREATEMKPREVIEW, // 9
PANO_CREATEFINALPTO, // 10
PANO_NONAFILE, // 11
PANO_NONAFILEPREVIEW, // 12
PANO_STITCH, // 13
PANO_STITCHPREVIEW, // 14
PANO_HUGINEXECUTOR, // 15
PANO_HUGINEXECUTORPREVIEW, // 16
PANO_COPY // 17
};
typedef enum
{
JPEG,
TIFF,
HDR
}
PanoramaFileType;
struct PanoramaPreprocessedUrls
{
PanoramaPreprocessedUrls()
{
}
PanoramaPreprocessedUrls(const QUrl& preprocessed, const QUrl& preview)
: preprocessedUrl(preprocessed),
previewUrl(preview)
{
}
virtual ~PanoramaPreprocessedUrls()
{
}
QUrl preprocessedUrl; // Can be an original file or a converted version, depending on the original file type
- QUrl previewUrl; // The JPEG preview version, accordingly of preprocessedUrl constent.
+ QUrl previewUrl; // The JPEG preview version, accordingly of preprocessedUrl constant.
};
typedef QMap<QUrl, PanoramaPreprocessedUrls> PanoramaItemUrlsMap; // Map between original Url and processed temp Urls.
// ----------------------------------------------------------------------------------------------------------
struct PanoActionData
{
PanoActionData()
: starting(false),
success(false),
id(0),
action(PANO_NONE)
{
}
bool starting;
bool success;
QString message; // Usually, an error message
int id;
PanoAction action;
};
} // namespace Digikam
Q_DECLARE_METATYPE(Digikam::PanoActionData)
Q_DECLARE_METATYPE(Digikam::PanoramaPreprocessedUrls)
#endif // DIGIKAM_PANO_ACTIONS_H
diff --git a/core/utilities/assistants/panorama/tasks/createpreviewtask.cpp b/core/utilities/assistants/panorama/tasks/createpreviewtask.cpp
index 417ce3b113..2faf0613db 100644
--- a/core/utilities/assistants/panorama/tasks/createpreviewtask.cpp
+++ b/core/utilities/assistants/panorama/tasks/createpreviewtask.cpp
@@ -1,120 +1,120 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-03-15
* Description : a tool to create panorama by fusion of several images.
*
* Copyright (C) 2012-2016 by Benjamin Girault <benjamin dot girault at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "createpreviewtask.h"
// Qt includes
#include <QFile>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
namespace Digikam
{
CreatePreviewTask::CreatePreviewTask(const QString& workDirPath, QSharedPointer<const PTOType> inputPTO,
QUrl& previewPtoUrl, const PanoramaItemUrlsMap& preProcessedUrlsMap)
: PanoTask(PANO_CREATEMKPREVIEW, workDirPath),
previewPtoUrl(previewPtoUrl),
ptoData(inputPTO),
preProcessedUrlsMap(preProcessedUrlsMap)
{
}
CreatePreviewTask::~CreatePreviewTask()
{
}
void CreatePreviewTask::run(ThreadWeaver::JobPointer, ThreadWeaver::Thread*)
{
PTOType data(*ptoData);
if (data.images.size() != preProcessedUrlsMap.size())
{
errString = i18n("Project file parsing failed.");
qCDebug(DIGIKAM_GENERAL_LOG) << "Missing parsing data!";
successFlag = false;
return;
}
m_meta.load(preProcessedUrlsMap.begin().value().preprocessedUrl.toLocalFile());
double wIn = (double)m_meta.getPixelSize().width();
m_meta.load(preProcessedUrlsMap.begin().value().previewUrl.toLocalFile());
double wOut = (double)m_meta.getPixelSize().width();
double scalingFactor = wOut / wIn;
data.project.fileFormat.fileType = PTOType::Project::FileFormat::JPEG;
data.project.fileFormat.quality = 90;
data.project.size.setHeight(data.project.size.height() * scalingFactor);
data.project.size.setWidth(data.project.size.width() * scalingFactor);
data.project.crop = QRect();
for (auto& image : data.images)
{
QUrl imgUrl = QUrl::fromLocalFile(image.fileName);
PanoramaItemUrlsMap::const_iterator it;
const PanoramaItemUrlsMap* const ppum = &preProcessedUrlsMap;
for (it = ppum->constBegin(); it != ppum->constEnd() && it.value().preprocessedUrl.toLocalFile() != imgUrl.toLocalFile(); ++it);
if (it == ppum->constEnd())
{
errString = i18n("Unknown input file in the project file: <filename>%1</filename>", image.fileName);
qCDebug(DIGIKAM_GENERAL_LOG) << "Unknown input File in the PTO: " << image.fileName;
qCDebug(DIGIKAM_GENERAL_LOG) << "IMG: " << imgUrl.toLocalFile();
successFlag = false;
return;
}
image.fileName = it.value().previewUrl.toLocalFile();
m_meta.load(image.fileName);
image.size = m_meta.getPixelSize();
image.optimizationParameters.clear();
}
- // Remove unncessary stuff
+ // Remove unnecessary stuff
data.controlPoints.clear();
// Add two commented line for a JPEG output
data.lastComments.clear();
data.lastComments << QLatin1String("#hugin_outputImageType jpg");
data.lastComments << QLatin1String("#hugin_outputJPEGQuality 90");
previewPtoUrl = tmpDir;
previewPtoUrl.setPath(previewPtoUrl.path() + QLatin1String("preview.pto"));
data.createFile(previewPtoUrl.toLocalFile());
qCDebug(DIGIKAM_GENERAL_LOG) << "Preview PTO File created: " << previewPtoUrl.fileName();
successFlag = true;
}
} // namespace Digikam
diff --git a/core/utilities/assistants/panorama/wizard/panointropage.cpp b/core/utilities/assistants/panorama/wizard/panointropage.cpp
index 1527527c74..2753fd8d2f 100644
--- a/core/utilities/assistants/panorama/wizard/panointropage.cpp
+++ b/core/utilities/assistants/panorama/wizard/panointropage.cpp
@@ -1,301 +1,301 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-05-23
* Description : a tool to create panorama by fusion of several images.
* Acknowledge : based on the expoblending tool
*
* Copyright (C) 2011-2016 by Benjamin Girault <benjamin dot girault at gmail dot com>
* Copyright (C) 2009-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "panointropage.h"
// Qt includes
#include <QLabel>
#include <QPixmap>
#include <QCheckBox>
#include <QRadioButton>
#include <QGroupBox>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QStandardPaths>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "dbinarysearch.h"
#include "autooptimiserbinary.h"
#include "cpcleanbinary.h"
#include "cpfindbinary.h"
#include "enblendbinary.h"
#include "makebinary.h"
#include "nonabinary.h"
#include "panomodifybinary.h"
#include "pto2mkbinary.h"
#include "huginexecutorbinary.h"
#include "dlayoutbox.h"
namespace Digikam
{
class Q_DECL_HIDDEN PanoIntroPage::Private
{
public:
explicit Private(PanoManager* const m)
: mngr(m),
// addGPlusMetadataCheckBox(0),
// TODO HDR
// hdrCheckBox(0),
formatGroupBox(0),
settingsGroupBox(0),
jpegRadioButton(0),
tiffRadioButton(0),
hdrRadioButton(0),
binariesWidget(0)
{
}
PanoManager* mngr;
// QCheckBox* addGPlusMetadataCheckBox;
// TODO HDR
// QCheckBox* hdrCheckBox;
QGroupBox* formatGroupBox;
QGroupBox* settingsGroupBox;
QRadioButton* jpegRadioButton;
QRadioButton* tiffRadioButton;
QRadioButton* hdrRadioButton;
DBinarySearch* binariesWidget;
};
PanoIntroPage::PanoIntroPage(PanoManager* const mngr, QWizard* const dlg)
: DWizardPage(dlg, i18nc("@title:window", "<b>Welcome to Panorama Tool</b>")),
d(new Private(mngr))
{
DVBox* const vbox = new DVBox(this);
QLabel* const title = new QLabel(vbox);
title->setWordWrap(true);
title->setOpenExternalLinks(true);
title->setText(i18n("<qt>"
"<p><h1><b>Welcome to Panorama Tool</b></h1></p>"
"<p>This tool stitches several images together to create a panorama, making the "
"seam between images not visible.</p>"
"<p>This assistant will help you to configure how to import images before "
"stitching them into a panorama.</p>"
"<p>Images must be taken from the same point of view.</p>"
"<p>For more information, please take a look at "
"<a href='http://hugin.sourceforge.net/tutorials/overview/en.shtml'>this page</a></p>"
"</qt>"));
QGroupBox* const binaryBox = new QGroupBox(vbox);
QGridLayout* const binaryLayout = new QGridLayout;
binaryBox->setLayout(binaryLayout);
binaryBox->setTitle(i18nc("@title:group", "Panorama Binaries"));
d->binariesWidget = new DBinarySearch(binaryBox);
d->binariesWidget->addBinary(d->mngr->autoOptimiserBinary());
d->binariesWidget->addBinary(d->mngr->cpCleanBinary());
d->binariesWidget->addBinary(d->mngr->cpFindBinary());
d->binariesWidget->addBinary(d->mngr->enblendBinary());
d->binariesWidget->addBinary(d->mngr->makeBinary());
d->binariesWidget->addBinary(d->mngr->nonaBinary());
d->binariesWidget->addBinary(d->mngr->panoModifyBinary());
d->mngr->checkForHugin2015();
if (d->mngr->hugin2015())
{
d->binariesWidget->addBinary(d->mngr->huginExecutorBinary());
}
else
{
d->binariesWidget->addBinary(d->mngr->pto2MkBinary());
}
d->mngr->checkBinaries();
#ifdef Q_OS_OSX
// Hugin bundle PKG install
d->binariesWidget->addDirectory(QLatin1String("/Applications/Hugin/HuginTools"));
d->binariesWidget->addDirectory(QLatin1String("/Applications/Hugin/Hugin.app/Contents/MacOS"));
d->binariesWidget->addDirectory(QLatin1String("/Applications/Hugin/tools_mac"));
// Std Macports install
d->binariesWidget->addDirectory(QLatin1String("/opt/local/bin"));
// digiKam Bundle PKG install
d->binariesWidget->addDirectory(QLatin1String("/opt/digikam/bin"));
#endif
#ifdef Q_OS_WIN
d->binariesWidget->addDirectory(QLatin1String("C:/Program Files/Hugin/bin"));
d->binariesWidget->addDirectory(QLatin1String("C:/Program Files (x86)/Hugin/bin"));
d->binariesWidget->addDirectory(QLatin1String("C:/Program Files/GnuWin32/bin"));
d->binariesWidget->addDirectory(QLatin1String("C:/Program Files (x86)/GnuWin32/bin"));
#endif
/*
QVBoxLayout* const settingsVBox = new QVBoxLayout();
d->settingsGroupBox = new QGroupBox(i18nc("@title:group", "Panorama Settings"), this);
d->settingsGroupBox->setLayout(settingsVBox);
d->addGPlusMetadataCheckBox = new QCheckBox(i18nc("@option:check", "Add Photosphere Metadata"), d->settingsGroupBox);
d->addGPlusMetadataCheckBox->setToolTip(i18nc("@info:tooltip", "Add Exif metadata to the output panorama image for Google+ 3D viewer"));
d->addGPlusMetadataCheckBox->setWhatsThis(i18nc("@info:whatsthis", "<b>Add Photosphere Metadata</b>: Enabling this allows the program to add "
"metadata to the output image such that when uploaded to Google+, the "
"Google+ 3D viewer is activated and the panorama can be seen in 3D. Note "
- "that this feature is most insteresting for large panoramas."));
+ "that this feature is most interesting for large panoramas."));
settingsVBox->addWidget(d->addGPlusMetadataCheckBox);
vbox->addWidget(d->settingsGroupBox);
*/
QVBoxLayout* const formatVBox = new QVBoxLayout();
d->formatGroupBox = new QGroupBox(i18nc("@title:group", "File Format"), vbox);
d->formatGroupBox->setLayout(formatVBox);
QButtonGroup* const group = new QButtonGroup();
d->jpegRadioButton = new QRadioButton(i18nc("@option:radio", "JPEG output"), d->formatGroupBox);
// The following comment is to get the next string extracted for translation
// xgettext: no-c-format
d->jpegRadioButton->setToolTip(i18nc("@info:tooltip", "Selects a JPEG output with 90% compression rate "
"(lossy compression, smaller size)."));
d->jpegRadioButton->setWhatsThis(i18nc("@info:whatsthis", "<b>JPEG output</b>: Using JPEG output, the panorama file will be smaller "
"at the cost of information loss during compression. This is the easiest "
"way to share the result, or print it online or in a shop."));
formatVBox->addWidget(d->jpegRadioButton);
group->addButton(d->jpegRadioButton);
d->tiffRadioButton = new QRadioButton(i18nc("@option:radio", "TIFF output"), d->formatGroupBox);
d->tiffRadioButton->setToolTip(i18nc("@info:tooltip", "Selects a TIFF output compressed using the LZW algorithm "
"(lossless compression, bigger size)."));
d->tiffRadioButton->setWhatsThis(i18nc("@info:whatsthis", "<b>TIFF output</b>: Using TIFF output, you get the same color depth than "
"your original photos using RAW images at the cost of a bigger panorama file."));
formatVBox->addWidget(d->tiffRadioButton);
group->addButton(d->tiffRadioButton);
// TODO HDR
/*
d->hdrRadioButton = new QRadioButton(i18nc("@option:radio", "HDR output"), d->formatGroupBox);
d->hdrRadioButton->setToolTip(i18nc("@info:tooltip", "Selects an High Dynamic Range (HDR) image, that can be processed further "
"with a dedicated software."));
d->hdrRadioButton->setWhatsThis(i18nc("@info:whatsthis", "<b>HDR output</b>: Output in High Dynamic Range, meaning that every piece of "
"information contained in the original photos are preserved. Note that you "
"need another software to process the resulting panorama, like "
"<a href=\"http://qtpfsgui.sourceforge.net/\">Luminance HDR</a>"));
formatVBox->addWidget(d->hdrRadioButton);
group->addButton(d->hdrRadioButton);
*/
switch (d->mngr->format())
{
case JPEG:
d->jpegRadioButton->setChecked(true);
break;
case TIFF:
d->tiffRadioButton->setChecked(true);
break;
case HDR:
// TODO HDR
// d->hdrRadioButton->setChecked(true);
break;
}
setPageWidget(vbox);
QPixmap leftPix(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("digikam/data/assistant-tripod.png")));
setLeftBottomPix(leftPix.scaledToWidth(128, Qt::SmoothTransformation));
/*
connect(d->addGPlusMetadataCheckBox, SIGNAL(stateChanged(int)),
this, SLOT(slotToggleGPano(int)));
d->addGPlusMetadataCheckBox->setChecked(d->mngr->gPano());
*/
slotToggleGPano(0); // Disabled for the moment
connect(group, SIGNAL(buttonClicked(QAbstractButton*)),
this, SLOT(slotChangeFileFormat(QAbstractButton*)));
connect(d->binariesWidget, SIGNAL(signalBinariesFound(bool)),
this, SLOT(slotBinariesChanged(bool)));
// TODO HDR
// d->hdrCheckBox->setChecked(d->mngr->hdr());
}
PanoIntroPage::~PanoIntroPage()
{
delete d;
}
bool PanoIntroPage::binariesFound()
{
return d->binariesWidget->allBinariesFound();
}
void PanoIntroPage::slotToggleGPano(int state)
{
d->mngr->setGPano(state);
}
void PanoIntroPage::slotChangeFileFormat(QAbstractButton* button)
{
if (button == d->jpegRadioButton)
d->mngr->setFileFormatJPEG();
else if (button == d->tiffRadioButton)
d->mngr->setFileFormatTIFF();
else if (button == d->hdrRadioButton)
d->mngr->setFileFormatHDR();
}
void PanoIntroPage::slotBinariesChanged(bool found)
{
setComplete(found);
emit completeChanged();
}
// TODO HDR
/*
void PanoIntroPage::slotShowFileFormat(int state)
{
d->mngr->setHDR(state);
if (state)
{
d->formatGroupBox->setEnabled(false);
}
else
{
d->formatGroupBox->setEnabled(true);
}
}
*/
void PanoIntroPage::initializePage()
{
setComplete(d->binariesWidget->allBinariesFound());
emit completeChanged();
}
} // namespace Digikam
diff --git a/core/utilities/assistants/printcreator/tools/templateicon.h b/core/utilities/assistants/printcreator/tools/templateicon.h
index adb6c5ef45..e77cae3ae9 100644
--- a/core/utilities/assistants/printcreator/tools/templateicon.h
+++ b/core/utilities/assistants/printcreator/tools/templateicon.h
@@ -1,90 +1,90 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-06-26
* Description : a tool to print images
*
* Copyright (C) 2008 by Andreas Trink <atrink at nociaro dot org>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef TEMPLATE_ICON_H
#define TEMPLATE_ICON_H
-// Qt incudes
+// Qt includes
#include <QPainter>
#include <QIcon>
#include <QColor>
#include <QSize>
namespace Digikam
{
class TemplateIcon
{
public:
/**
* Constructor: The height of the icon is <iconHeight>. The width is computed
* during invocation of method 'begin()' according to the paper-size.
*/
explicit TemplateIcon(int iconHeight, const QSize& templateSize);
~TemplateIcon();
/**
* Begin painting the icon
*/
void begin();
/**
* End painting the icon
*/
void end();
/**
* Returns a pointer to the icon.
*/
QIcon& getIcon() const;
/**
* Returns the size of the icon.
*/
QSize& getSize();
/**
* Returns the painter.
*/
QPainter& getPainter() const;
/**
* Draw a filled rectangle with color <color> at position <x>/<y> (relative
* to template-origin) and width <w> and height <h>.
*/
void fillRect(int x, int y, int w, int h, const QColor& color);
private:
TemplateIcon(const TemplateIcon&); // Disable
class Private;
Private* const d;
};
} // namespace Digikam
#endif // TEMPLATE_ICON_H
diff --git a/core/utilities/assistants/printcreator/wizard/advprintwizard.h b/core/utilities/assistants/printcreator/wizard/advprintwizard.h
index 2eed6ad704..ce918ab801 100644
--- a/core/utilities/assistants/printcreator/wizard/advprintwizard.h
+++ b/core/utilities/assistants/printcreator/wizard/advprintwizard.h
@@ -1,83 +1,83 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-01-11
* Description : a tool to print images
*
* Copyright (C) 2008-2012 by Angelo Naselli <anaselli at linux dot it>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_ADV_PRINT_WIZARD_H
#define DIGIKAM_ADV_PRINT_WIZARD_H
-// Qt incudes
+// Qt includes
#include <QImage>
// Local includes
#include "advprintsettings.h"
#include "dimageslist.h"
#include "dinfointerface.h"
#include "dwizarddlg.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT AdvPrintWizard : public DWizardDlg
{
Q_OBJECT
public:
explicit AdvPrintWizard(QWidget* const, DInfoInterface* const iface = 0);
~AdvPrintWizard();
void setItemsList(const QList<QUrl>& fileList = QList<QUrl>());
QList<QUrl> itemsList() const;
DInfoInterface* iface() const;
AdvPrintSettings* settings() const;
/** Update the pages to be printed and preview first/last pages.
*/
void previewPhotos();
void updateCropFrame(AdvPrintPhoto* const, int);
int nextId() const override;
static int normalizedInt(double n);
private:
bool eventFilter(QObject*, QEvent*) Q_DECL_OVERRIDE;
private Q_SLOTS:
void slotPreview(const QImage&);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_ADV_PRINT_WIZARD_H
diff --git a/core/utilities/assistants/sendbymail/manager/mailprocess.cpp b/core/utilities/assistants/sendbymail/manager/mailprocess.cpp
index 5bf1d6cec5..746f67b9d8 100644
--- a/core/utilities/assistants/sendbymail/manager/mailprocess.cpp
+++ b/core/utilities/assistants/sendbymail/manager/mailprocess.cpp
@@ -1,608 +1,608 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-02-25
* Description : a tool to e-mailing images
*
* Copyright (C) 2004-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2010 by Andi Clemens <andi dot clemens at googlemail dot com>
* Copyright (C) 2006 by Tom Albers <tomalbers at kde dot nl>
* Copyright (C) 2006 by Michael Hoechstetter <michael dot hoechstetter at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "mailprocess.h"
// Qt includes
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QPointer>
#include <QStringList>
#include <QTextCodec>
#include <QTextStream>
#include <QApplication>
#include <QMessageBox>
#include <QTemporaryDir>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "digikam_globals.h"
#include "imageresizethread.h"
namespace Digikam
{
class Q_DECL_HIDDEN MailProcess::Private
{
public:
explicit Private()
: cancel(false),
settings(0),
iface(0),
threadImgResize(0)
{
}
bool cancel;
QList<QUrl> attachementFiles;
QList<QUrl> failedResizedImages;
MailSettings* settings;
DInfoInterface* iface;
ImageResizeThread* threadImgResize;
};
MailProcess::MailProcess(MailSettings* const settings,
DInfoInterface* const iface,
QObject* const parent)
: QObject(parent),
d(new Private)
{
d->settings = settings;
d->iface = iface;
d->threadImgResize = new ImageResizeThread(this);
connect(d->threadImgResize, SIGNAL(startingResize(QUrl)),
this, SLOT(slotStartingResize(QUrl)));
connect(d->threadImgResize, SIGNAL(finishedResize(QUrl,QUrl,int)),
this, SLOT(slotFinishedResize(QUrl,QUrl,int)));
connect(d->threadImgResize, SIGNAL(failedResize(QUrl,QString,int)),
this, SLOT(slotFailedResize(QUrl,QString,int)));
connect(d->threadImgResize, SIGNAL(finished()),
this, SLOT(slotCompleteResize()));
}
MailProcess::~MailProcess()
{
delete d;
}
void MailProcess::firstStage()
{
d->cancel = false;
if (!d->threadImgResize->isRunning())
{
d->threadImgResize->cancel();
d->threadImgResize->wait();
}
QTemporaryDir tempPath;
tempPath.setAutoRemove(false);
if (!tempPath.isValid())
{
emit signalMessage(i18n("Cannot create a temporary directory"), true);
slotCancel();
emit signalDone(false);
return;
}
d->settings->tempPath = tempPath.path() + QLatin1Char('/');
d->attachementFiles.clear();
d->failedResizedImages.clear();
if (d->settings->imagesChangeProp)
{
// Resize all images if necessary in a separate threadImgResize.
// Attachments list is updated by slotFinishedResize().
d->threadImgResize->resize(d->settings);
d->threadImgResize->start();
}
else
{
// Add all original files to the attachments list.
for (QMap<QUrl, QUrl>::const_iterator it = d->settings->itemsList.constBegin() ;
it != d->settings->itemsList.constEnd() ; ++it)
{
d->attachementFiles.append(it.key());
d->settings->setMailUrl(it.key(), it.key());
}
emit signalProgress(50);
secondStage();
}
}
void MailProcess::slotCancel()
{
d->cancel = true;
if (!d->threadImgResize->isRunning())
{
d->threadImgResize->cancel();
d->threadImgResize->wait();
}
emit signalProgress(0);
slotCleanUp();
}
void MailProcess::slotStartingResize(const QUrl& orgUrl)
{
if (d->cancel) return;
QString text = i18n("Resizing %1", orgUrl.fileName());
emit signalMessage(text, false);
}
void MailProcess::slotFinishedResize(const QUrl& orgUrl, const QUrl& emailUrl, int percent)
{
if (d->cancel) return;
emit signalProgress((int)(80.0*(percent/100.0)));
qCDebug(DIGIKAM_GENERAL_LOG) << emailUrl;
d->attachementFiles.append(emailUrl);
d->settings->setMailUrl(orgUrl, emailUrl);
QString text = i18n("%1 resized successfully", orgUrl.fileName());
emit signalMessage(text, false);
}
void MailProcess::slotFailedResize(const QUrl& orgUrl, const QString& error, int percent)
{
if (d->cancel) return;
emit signalProgress((int)(80.0*(percent/100.0)));
QString text = i18n("Failed to resize %1: %2", orgUrl.fileName(), error);
emit signalMessage(text, true);
d->failedResizedImages.append(orgUrl);
}
void MailProcess::slotCompleteResize()
{
if (d->cancel) return;
if (!showFailedResizedImages())
{
slotCancel();
return;
}
secondStage();
}
void MailProcess::secondStage()
{
if (d->cancel) return;
// If the initial list of files contained only unsupported file formats,
// and the user chose not to attach them without resizing, then there are
// no files approved for sending.
if (d->attachementFiles.isEmpty())
{
emit signalMessage(i18n("There are no files to send"), false);
emit signalProgress(0);
return;
}
buildPropertiesFile();
emit signalProgress(90);
invokeMailAgent();
emit signalProgress(100);
}
void MailProcess::buildPropertiesFile()
{
if (d->cancel) return;
if (d->iface && d->settings->addFileProperties)
{
emit signalMessage(i18n("Build images properties file"), false);
QString propertiesText;
for (QMap<QUrl, QUrl>::const_iterator it = d->settings->itemsList.constBegin() ;
it != d->settings->itemsList.constEnd() ; ++it)
{
DItemInfo info(d->iface->itemInfo(it.key()));
QString comments = info.comment();
QString tags = info.keywords().join(QLatin1String(", "));
QString rating = QString::number(info.rating());
QString orgFile = it.key().fileName();
QString emailFile = it.value().fileName();
if (comments.isEmpty())
comments = i18n("no caption");
if (tags.isEmpty())
tags = i18n("no keywords");
propertiesText.append(i18n("file \"%1\":\nOriginal images: %2\n", emailFile, orgFile));
propertiesText.append(i18n("Comments: %1\n", comments));
propertiesText.append(i18n("Tags: %1\n", tags));
propertiesText.append(i18n("Rating: %1\n", rating));
propertiesText.append(QLatin1Char('\n'));
}
QFile propertiesFile(d->settings->tempPath + i18n("properties.txt"));
QTextStream stream(&propertiesFile);
stream.setCodec(QTextCodec::codecForName("UTF-8"));
stream.setAutoDetectUnicode(true);
if (!propertiesFile.open(QIODevice::WriteOnly))
{
emit signalMessage(i18n("Image properties file cannot be opened"), true);
qCDebug(DIGIKAM_GENERAL_LOG) << "File open error:" << propertiesFile.fileName();
return;
}
stream << propertiesText << QLatin1Char('\n');
propertiesFile.close();
d->attachementFiles << QUrl::fromLocalFile(propertiesFile.fileName());
qCDebug(DIGIKAM_GENERAL_LOG) << "Image properties file done" << propertiesFile.fileName();
emit signalMessage(i18n("Image properties file done"), false);
}
}
bool MailProcess::showFailedResizedImages() const
{
bool ret = true;
if (!d->failedResizedImages.isEmpty())
{
QStringList list;
for (QList<QUrl>::const_iterator it = d->failedResizedImages.constBegin() ;
it != d->failedResizedImages.constEnd() ; ++it)
{
list.append((*it).fileName());
}
QPointer<QMessageBox> mbox = new QMessageBox(QApplication::activeWindow());
mbox->setIcon(QMessageBox::Warning);
mbox->setWindowTitle(i18n("Processing Failed"));
mbox->setText(i18n("Some images cannot be resized.\n"
"Do you want them to be added as attachments without resizing?"));
mbox->setStandardButtons(QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel));
mbox->setDefaultButton(QMessageBox::No);
mbox->setDetailedText(list.join(QLatin1Char('\n')));
int valRet = mbox->exec();
switch (valRet)
{
case QMessageBox::Yes:
{
// Added source image files instead resized images...
for (QList<QUrl>::const_iterator it = d->failedResizedImages.constBegin() ;
it != d->failedResizedImages.constEnd() ; ++it)
{
d->attachementFiles.append(*it);
d->settings->setMailUrl(*it, *it);
}
break;
}
case QMessageBox::No:
{
// Do nothing...
break;
}
case QMessageBox::Cancel:
{
// Stop process...
ret = false;
break;
}
}
delete mbox;
}
return ret;
}
QList<QUrl> MailProcess::divideEmails()
{
qint64 myListSize = 0;
- QList<QUrl> processedNow; // List witch can be processed now.
+ QList<QUrl> processedNow; // List which can be processed now.
QList<QUrl> todoAttachement; // Still todo list
qCDebug(DIGIKAM_GENERAL_LOG) << "Attachment limit: " << d->settings->attachementLimit();
for (QList<QUrl>::const_iterator it = d->attachementFiles.constBegin() ;
it != d->attachementFiles.constEnd() ; ++it)
{
QFile file((*it).toLocalFile());
qCDebug(DIGIKAM_GENERAL_LOG) << "File: " << file.fileName() << " Size: " << file.size();
if ((myListSize + file.size()) <= d->settings->attachementLimit())
{
myListSize += file.size();
processedNow.append(*it);
qCDebug(DIGIKAM_GENERAL_LOG) << "Current list size: " << myListSize;
}
else
{
if ((file.size()) >= d->settings->attachementLimit())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "File \"" << file.fileName()
<< "\" is out of attachment limit!";
QString mess = i18n("The file \"%1\" is too big to be sent, "
"please reduce its size or change your settings",
file.fileName());
emit signalMessage(mess, true);
}
else
{
todoAttachement.append(*it);
}
}
}
d->attachementFiles = todoAttachement;
return processedNow;
}
bool MailProcess::invokeMailAgent()
{
if (d->cancel) return false;
bool agentInvoked = false;
QList<QUrl> fileList;
do
{
fileList = divideEmails();
if (!fileList.isEmpty())
{
QString prog = QDir::toNativeSeparators(d->settings->binPaths[d->settings->mailProgram]);
if (prog.isEmpty())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Mail binary path is empty."
<< "Cannot start Mail client program!";
return false;
}
switch ((int)d->settings->mailProgram)
{
case MailSettings::BALSA:
{
QStringList args;
args.append(QLatin1String("-m"));
args.append(QLatin1String("mailto:"));
for (QList<QUrl>::ConstIterator it = fileList.constBegin() ; it != fileList.constEnd() ; ++it)
{
args.append(QLatin1String("-a"));
args.append(QDir::toNativeSeparators((*it).toLocalFile()));
}
QProcess process;
process.setProcessEnvironment(adjustedEnvironmentForAppImage());
if (!process.startDetached(prog, args))
{
invokeMailAgentError(prog, args);
}
else
{
invokeMailAgentDone(prog, args);
agentInvoked = true;
}
break;
}
case MailSettings::CLAWSMAIL:
case MailSettings::SYLPHEED:
{
QStringList args;
args.append(QLatin1String("--compose"));
args.append(QLatin1String("--attach"));
for (QList<QUrl>::ConstIterator it = fileList.constBegin() ;
it != fileList.constEnd() ; ++it)
{
args.append(QDir::toNativeSeparators((*it).toLocalFile()));
}
QProcess process;
process.setProcessEnvironment(adjustedEnvironmentForAppImage());
if (!process.startDetached(prog, args))
{
invokeMailAgentError(prog, args);
}
else
{
invokeMailAgentDone(prog, args);
agentInvoked = true;
}
break;
}
case MailSettings::EVOLUTION:
{
QStringList args;
QString tmp = QLatin1String("mailto:?subject=");
for (QList<QUrl>::ConstIterator it = fileList.constBegin() ; it != fileList.constEnd() ; ++it)
{
tmp.append(QLatin1String("&attach="));
tmp.append(QDir::toNativeSeparators((*it).toLocalFile()));
}
args.append(tmp);
QProcess process;
process.setProcessEnvironment(adjustedEnvironmentForAppImage());
if (!process.startDetached(prog, args))
{
invokeMailAgentError(prog, args);
}
else
{
invokeMailAgentDone(prog, args);
agentInvoked = true;
}
break;
}
case MailSettings::KMAIL:
{
QStringList args;
for (QList<QUrl>::ConstIterator it = fileList.constBegin() ; it != fileList.constEnd() ; ++it)
{
args.append(QLatin1String("--attach"));
args.append(QDir::toNativeSeparators((*it).toLocalFile()));
}
QProcess process;
process.setProcessEnvironment(adjustedEnvironmentForAppImage());
if (!process.startDetached(prog, args))
{
invokeMailAgentError(prog, args);
}
else
{
invokeMailAgentDone(prog, args);
agentInvoked = true;
}
break;
}
// More info about command lines options with Mozilla & co:
// http://www.mozilla.org/docs/command-line-args.html#Syntax_Rules
case MailSettings::NETSCAPE:
case MailSettings::THUNDERBIRD:
{
QStringList args;
args.append(QLatin1String("-compose"));
QString tmp = QLatin1String("attachment='");
for (QList<QUrl>::ConstIterator it = fileList.constBegin() ; it != fileList.constEnd() ; ++it)
{
tmp.append(QLatin1String("file://"));
tmp.append(QDir::toNativeSeparators((*it).toLocalFile()));
tmp.append(QLatin1String(","));
}
tmp.remove(tmp.length()-1, 1);
tmp.append(QLatin1String("'"));
args.append(tmp);
QProcess process;
process.setProcessEnvironment(adjustedEnvironmentForAppImage());
if (!process.startDetached(prog, args))
{
invokeMailAgentError(prog, args);
}
else
{
invokeMailAgentDone(prog, args);
agentInvoked = true;
}
break;
}
}
}
}
while (!fileList.isEmpty());
return agentInvoked;
}
void MailProcess::invokeMailAgentError(const QString& prog, const QStringList& args)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Command Line: " << prog << args;
QString text = i18n("Failed to start \"%1\" program. Check your system.", prog);
emit signalMessage(text, true);
slotCleanUp();
emit signalDone(false);
}
void MailProcess::invokeMailAgentDone(const QString& prog, const QStringList& args)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Command Line: " << prog << args;
QString text = i18n("Starting \"%1\" program...", prog);
emit signalMessage(text, false);
emit signalMessage(i18n("After having sent your images by email..."), false);
emit signalMessage(i18n("Press 'Finish' button to clean up temporary files"), false);
emit signalDone(true);
}
void MailProcess::slotCleanUp()
{
if (QDir().exists(d->settings->tempPath))
{
QDir(d->settings->tempPath).removeRecursively();
}
}
} // namespace Digikam
diff --git a/core/utilities/assistants/sendbymail/manager/mailsettings.h b/core/utilities/assistants/sendbymail/manager/mailsettings.h
index 6f24b1d806..a01052f615 100644
--- a/core/utilities/assistants/sendbymail/manager/mailsettings.h
+++ b/core/utilities/assistants/sendbymail/manager/mailsettings.h
@@ -1,122 +1,122 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-11-07
* Description : mail settings container.
*
* Copyright (C) 2007-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2010 by Andi Clemens <andi dot clemens at googlemail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MAIL_SETTINGS_H
#define DIGIKAM_MAIL_SETTINGS_H
// Qt includes
#include <QtGlobal>
#include <QList>
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QMap>
class KConfigGroup;
namespace Digikam
{
class MailSettings
{
public:
// Images selection mode
enum Selection
{
IMAGES = 0,
ALBUMS
};
enum MailClient
{
BALSA = 0,
CLAWSMAIL,
EVOLUTION,
KMAIL,
NETSCAPE, // Messenger (https://en.wikipedia.org/wiki/Netscape_Messenger_9)
SYLPHEED,
THUNDERBIRD
};
enum ImageFormat
{
JPEG = 0,
PNG
};
public:
explicit MailSettings();
~MailSettings();
// Read and write settings in config file between sessions.
void readSettings(KConfigGroup& group);
void writeSettings(KConfigGroup& group);
QString format() const;
- /** Return the attachement limit in bytes
+ /** Return the attachment limit in bytes
*/
qint64 attachementLimit() const;
void setMailUrl(const QUrl& orgUrl, const QUrl& emailUrl);
QUrl mailUrl(const QUrl& orgUrl) const;
// Helper methods to fill combobox from GUI.
static QMap<MailClient, QString> mailClientNames();
static QMap<ImageFormat, QString> imageFormatNames();
public:
Selection selMode; // Items selection mode
QList<QUrl> inputImages; // Selected items to send.
bool addFileProperties;
bool imagesChangeProp;
bool removeMetadata;
int imageCompression;
qint64 attLimitInMbytes;
QString tempPath;
MailClient mailProgram;
int imageSize;
ImageFormat imageFormat;
QMap<QUrl, QUrl> itemsList; // Map of original item and attached item (can be resized).
QMap<MailClient, QString> binPaths; // Map of paths for all mail clients.
};
} // namespace Digikam
#endif // DIGIKAM_MAIL_SETTINGS_H
diff --git a/core/utilities/assistants/webservices/common/manager/wsauthentication.cpp b/core/utilities/assistants/webservices/common/manager/wsauthentication.cpp
index 068a637f65..b931d7f02e 100644
--- a/core/utilities/assistants/webservices/common/manager/wsauthentication.cpp
+++ b/core/utilities/assistants/webservices/common/manager/wsauthentication.cpp
@@ -1,438 +1,438 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2018-07-03
* Description : Web Service authentication container.
*
* Copyright (C) 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "wsauthentication.h"
// Qt includes
#include <QApplication>
#include <QMap>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "digikam_version.h"
#include "dmetadata.h"
#include "previewloadthread.h"
#include "wstoolutils.h"
#include "wstalker.h"
#include "dbtalker.h"
#include "fbtalker.h"
#include "fbnewalbumdlg.h"
#include "flickrtalker.h"
#include "gptalker.h"
#include "gdtalker.h"
#include "imgurtalker.h"
#include "smugtalker.h"
namespace Digikam
{
class Q_DECL_HIDDEN WSAuthentication::Private
{
public:
explicit Private()
: wizard(0),
iface(0),
talker(0),
ws(WSSettings::WebService::FLICKR),
albumDlg(0),
imagesCount(0),
imagesTotal(0)
{
}
WSWizard* wizard;
DInfoInterface* iface;
WSTalker* talker;
WSSettings::WebService ws;
QString serviceName;
WSNewAlbumDialog* albumDlg;
QString currentAlbumId;
WSAlbum baseAlbum;
QStringList tmpPath;
QString tmpDir;
unsigned int imagesCount;
unsigned int imagesTotal;
QMap<QString, QString> imagesCaption;
QList<QUrl> transferQueue;
};
WSAuthentication::WSAuthentication(QWidget* const parent, DInfoInterface* const iface)
: d(new Private())
{
d->wizard = dynamic_cast<WSWizard*>(parent);
if (d->wizard)
{
d->iface = d->wizard->iface();
}
else
{
d->iface = iface;
}
/* --------------------
* Temporary path to store images before uploading
*/
d->tmpPath.clear();
d->tmpDir = WSToolUtils::makeTemporaryDir(d->serviceName.toUtf8()).absolutePath() + QLatin1Char('/');
}
WSAuthentication::~WSAuthentication()
{
slotCancel();
delete d;
}
void WSAuthentication::createTalker(WSSettings::WebService ws, const QString& serviceName)
{
d->ws = ws;
d->serviceName = serviceName;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "create " << serviceName << "talker";
switch (ws)
{
case WSSettings::WebService::FLICKR:
//d->talker = new FlickrTalker(d->wizard, serviceName, d->iface);
break;
case WSSettings::WebService::DROPBOX:
//d->talker = new DBTalker(d->wizard);
break;
case WSSettings::WebService::IMGUR:
//d->talker = new ImgurTalker(d->wizard);
break;
case WSSettings::WebService::FACEBOOK:
d->albumDlg = new FbNewAlbumDlg(d->wizard, d->serviceName);
d->talker = new FbTalker(d->wizard, d->albumDlg);
break;
case WSSettings::WebService::SMUGMUG:
//d->talker = new SmugTalker(d->iface, d->wizard);
break;
case WSSettings::WebService::GDRIVE:
//d->talker = new GDTalker(d->wizard);
break;
case WSSettings::WebService::GPHOTO:
//d->talker = new GPTalker(d->wizard);
break;
}
connect(d->talker, SIGNAL(signalOpenBrowser(QUrl)),
this, SIGNAL(signalOpenBrowser(QUrl)));
connect(d->talker, SIGNAL(signalCloseBrowser()),
this, SIGNAL(signalCloseBrowser()));
connect(d->talker, SIGNAL(signalAuthenticationComplete(bool)),
this, SIGNAL(signalAuthenticationComplete(bool)));
connect(this, SIGNAL(signalResponseTokenReceived(QMap<QString,QString>)),
d->talker, SLOT(slotResponseTokenReceived(QMap<QString,QString>)));
connect(d->talker, SIGNAL(signalCreateAlbumDone(int,QString,QString)),
this, SIGNAL(signalCreateAlbumDone(int,QString,QString)));
connect(d->talker, SIGNAL(signalListAlbumsDone(int,QString,QList<WSAlbum>)),
this, SLOT(slotListAlbumsDone(int,QString,QList<WSAlbum>)));
connect(d->talker, SIGNAL(signalAddPhotoDone(int,QString)),
this, SLOT(slotAddPhotoDone(int,QString)));
}
void WSAuthentication::cancelTalker()
{
if (d->talker)
{
d->talker->cancel();
}
}
QString WSAuthentication::webserviceName()
{
return d->serviceName;
}
void WSAuthentication::authenticate()
{
d->talker->authenticate();
}
void WSAuthentication::reauthenticate()
{
d->talker->reauthenticate();
}
bool WSAuthentication::authenticated() const
{
return d->talker->linked();
}
void WSAuthentication::parseTreeFromListAlbums(const QList <WSAlbum>& albumsList,
QMap<QString, AlbumSimplified>& albumTree,
QStringList& rootAlbums)
{
foreach (const WSAlbum& album, albumsList)
{
if (albumTree.contains(album.id))
{
albumTree[album.id].title = album.title;
albumTree[album.id].uploadable = album.uploadable;
}
else
{
AlbumSimplified item(album.title, album.uploadable);
albumTree[album.id] = item;
}
if (album.isRoot)
{
rootAlbums << album.id;
}
else
{
if (albumTree.contains(album.parentID))
{
albumTree[album.parentID].childrenIDs << album.id;
}
else
{
AlbumSimplified parentAlbum;
parentAlbum.childrenIDs << album.id;
albumTree[album.parentID] = parentAlbum;
}
}
}
}
QString WSAuthentication::getImageCaption(const QString& fileName)
{
DItemInfo info(d->iface->itemInfo(QUrl::fromLocalFile(fileName)));
// If webservice doesn't support image titles, include it in descriptions if needed.
QStringList descriptions = QStringList() << info.title() << info.comment();
descriptions.removeAll(QLatin1String(""));
return descriptions.join(QLatin1String("\n\n"));
}
void WSAuthentication::prepareForUpload()
{
d->transferQueue = d->wizard->settings()->inputImages;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "prepareForUpload invoked";
if (d->transferQueue.isEmpty())
{
emit(QLatin1String("transferQueue is empty"), true);
return;
}
d->currentAlbumId = d->wizard->settings()->currentAlbumId;
d->imagesTotal = d->transferQueue.count();
d->imagesCount = 0;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "upload request got album id from widget: " << d->currentAlbumId;
if (d->wizard->settings()->imagesChangeProp)
{
foreach(const QUrl& imgUrl, d->transferQueue)
{
QString imgPath = imgUrl.toLocalFile();
QImage image = PreviewLoadThread::loadHighQualitySynchronously(imgPath).copyQImage();
if (image.isNull())
{
image.load(imgPath);
}
if (image.isNull())
{
emit d->talker->signalAddPhotoDone(666, i18n("Cannot open image at %1\n", imgPath));
return;
}
// get temporary file name
d->tmpPath << d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + d->wizard->settings()->format();
// rescale image if requested
int maxDim = d->wizard->settings()->imageSize;
if (image.width() > maxDim || image.height() > maxDim)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Resizing to " << maxDim;
image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio,
Qt::SmoothTransformation);
}
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Saving to temp file: " << d->tmpPath.last();
image.save(d->tmpPath.last(), "JPEG", d->wizard->settings()->imageCompression);
// copy meta data to temporary image and get caption for image
DMetadata meta;
QString caption = QLatin1String("");
if (meta.load(imgPath))
{
meta.setImageDimensions(image.size());
meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL);
meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion());
meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY);
meta.save(d->tmpPath.last());
caption = getImageCaption(imgPath);
}
d->imagesCaption[imgPath] = caption;
}
}
}
unsigned int WSAuthentication::numberItemsUpload()
{
return d->imagesTotal;
}
void WSAuthentication::uploadNextPhoto()
{
if (d->transferQueue.isEmpty())
{
emit signalDone();
return;
}
/*
- * This comparaison is a little bit complicated and may seem unnecessary, but it will be useful later
+ * This comparison is a little bit complicated and may seem unnecessary, but it will be useful later
* when we will be able to choose to change or not image properties for EACH image.
*/
QString imgPath = d->transferQueue.first().toLocalFile();
QString tmpPath = d->tmpDir + QFileInfo(imgPath).baseName().trimmed() + d->wizard->settings()->format();
if (!d->tmpPath.isEmpty() && tmpPath == d->tmpPath.first())
{
d->talker->addPhoto(tmpPath, d->currentAlbumId, d->imagesCaption[imgPath]);
d->tmpPath.removeFirst();
}
else
{
d->talker->addPhoto(imgPath, d->currentAlbumId, d->imagesCaption[imgPath]);
}
}
void WSAuthentication::startTransfer()
{
uploadNextPhoto();
}
void WSAuthentication::slotCancel()
{
// First we cancel talker
cancelTalker();
// Then the folder containing all temporary photos to upload will be removed after all.
QDir tmpDir(d->tmpDir);
if (tmpDir.exists())
{
tmpDir.removeRecursively();
}
emit signalProgress(0);
}
void WSAuthentication::slotNewAlbumRequest()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Slot create New Album";
if (d->albumDlg->exec() == QDialog::Accepted)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Calling New Album method";
d->talker->createNewAlbum();
}
}
void WSAuthentication::slotListAlbumsRequest()
{
d->talker->listAlbums();
}
void WSAuthentication::slotListAlbumsDone(int errCode, const QString& errMsg, const QList<WSAlbum>& albumsList)
{
QString albumDebug = QLatin1String("");
foreach (const WSAlbum &album, albumsList)
{
albumDebug.append(QString::fromLatin1("%1: %2\n").arg(album.id).arg(album.title));
}
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Received albums (errCode = " << errCode << ", errMsg = "
<< errMsg << "): " << albumDebug;
if (errCode != 0)
{
QMessageBox::critical(QApplication::activeWindow(),
i18n("%1 Call Failed", d->serviceName),
i18n("Code: %1. %2", errCode, errMsg));
return;
}
QMap<QString, AlbumSimplified> albumTree;
QStringList rootAlbums;
parseTreeFromListAlbums(albumsList, albumTree, rootAlbums);
emit signalListAlbumsDone(albumTree, rootAlbums, QLatin1String(""));
}
void WSAuthentication::slotAddPhotoDone(int errCode, const QString& errMsg)
{
if (errCode == 0)
{
emit signalMessage(QDir::toNativeSeparators(d->transferQueue.first().toLocalFile()), false);
d->transferQueue.removeFirst();
d->imagesCount++;
emit signalProgress(d->imagesCount);
}
else
{
if (QMessageBox::question(d->wizard, i18n("Uploading Failed"),
i18n("Failed to upload photo: %1\n"
"Do you want to continue?", errMsg))
!= QMessageBox::Yes)
{
d->transferQueue.clear();
return;
}
}
uploadNextPhoto();
}
} // namespace Digikam
diff --git a/core/utilities/assistants/webservices/common/manager/wstalker.h b/core/utilities/assistants/webservices/common/manager/wstalker.h
index 8549611e25..0f7e9850db 100644
--- a/core/utilities/assistants/webservices/common/manager/wstalker.h
+++ b/core/utilities/assistants/webservices/common/manager/wstalker.h
@@ -1,230 +1,230 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2018-07-08
* Description : Base class for web service talkers.
*
* Copyright (C) 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_WS_TALKER_H
#define DIGIKAM_WS_TALKER_H
// Qt includes
#include <QtGlobal>
#include <QList>
#include <QPair>
#include <QString>
#include <QUrl>
#include <QWidget>
#include <QSettings>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QMap>
// Local includes
#include "o0settingsstore.h"
#include "wsitem.h"
#include "wswizard.h"
namespace Digikam
{
class WSTalker : public QObject
{
Q_OBJECT
public:
enum State
{
DEFAULT = 0,
GETUSER,
LISTALBUMS,
CREATEALBUM,
ADDPHOTO
};
public:
explicit WSTalker(QWidget* const parent);
~WSTalker();
/*
* Get ID of an existent user account saved when he logged in before,
* knowing user name.
*/
QString getUserID(const QString& userName);
/*
* Link user account (login).
*/
virtual void link();
/*
* Unlink user account (logout).
*/
virtual void unlink();
/*
* Return true if account is linked.
*/
virtual bool linked() const;
/*
* This method load account that user chooses to login. If it exists and doesn't expire yet,
* then obtain the saved token and pass the authentication process. Otherwise, relogin.
*/
virtual void authenticate();
/*
* Force user to login on web service login page.
*/
void reauthenticate();
/*
* Abort any network request realizing at the moment.
*/
void cancel();
protected:
/*
* Return a map of all information stored in the previous login of userName account.
*/
QMap<QString, QVariant> getUserAccountInfo(const QString& userName);
/*
- * Save all necessary information of user account to disk. That information wil be retrieved
+ * Save all necessary information of user account to disk. That information will be retrieved
* by getUserAccountInfo(userName) when needed.
*/
void saveUserAccount(const QString& userName,
const QString& userID,
long long int expire,
const QString& accessToken,
const QString& refreshToken = QString());
/*
* Remove all information of user account that was stored by saveUserAccount(...)
* TODO: this method should be called when user uninstalls digiKam.
*/
void removeUserAccount(const QString& userName);
/*
* Save as removeUserAccount(userName), but for all accounts.
*/
void removeAllAccounts();
/*
* A wrapper method of getUserAccountInfo(userName), but perform further verification
* on account's validation and further operation in case that account is expired.
*/
bool loadUserAccount(const QString& userName);
/*
* This method can be (and must be) reimplemented in derived class. Indeed, it will hard code
* (at runtime) O2's settings (i.e accessToken, refreshToken, expired date and value of linked state).
* It forces O2 to link to another account according to user's selection. Otherwise, O2 will
* "remember" account from previous login and always link to that account, if an obligated reauthenticate
* (unlink and then link) is not realized.
*/
virtual void resetTalker(const QString& expire, const QString& accessToken, const QString& refreshToken);
/*
* Sort list of albums by ascending order of titles.
*/
virtual void sortAlbumsList(QList<WSAlbum>& albumsList);
/*
* These methods are reimplemented in derived class and used to parse response of network requests
* for user's information or APIs of web service. They will be called asynchronously when responses
* for net request are received.
*/
virtual void parseResponseGetLoggedInUser(const QByteArray& data);
virtual void parseResponseListAlbums(const QByteArray& data);
virtual void parseResponseCreateAlbum(const QByteArray& data);
virtual void parseResponseAddPhoto(const QByteArray& data);
/*
* This method is called when authentication is complete. It should be reimplemented in derived class
* and call saveUserAccount(...) inside. Here, we implement a minimised version so that derived class
* can call it if needed.
*/
virtual void authenticationDone(int errCode, const QString& errMsg);
public:
/*
* These methods are reimplemented in derived class, and will be used to make network requests
* for user's information or APIs of web service.
*/
virtual void getLoggedInUser();
virtual void listAlbums(long long userID = 0);
virtual void createNewAlbum();
virtual void addPhoto(const QString& imgPath, const QString& albumID, const QString& caption);
Q_SIGNALS:
void signalBusy(bool val);
void signalOpenBrowser(const QUrl& url);
void signalCloseBrowser();
void signalAuthenticationComplete(bool);
void signalCreateAlbumDone(int errCode, const QString& errMsg, const QString& newAlbumId);
void signalListAlbumsDone(int errCode, const QString& errMsg, const QList <WSAlbum>& albumsList);
void signalAddPhotoDone(int errCode, const QString& errMsg);
protected Q_SLOTS:
/*
* Slots for signals from O2 authentication flow
*/
void slotFinished(QNetworkReply* reply);
void slotOpenBrowser(const QUrl& url);
void slotCloseBrowser();
virtual void slotLinkingFailed();
virtual void slotLinkingSucceeded();
/*
* This is a particular slot, only used in case that digiKam will intercept O2 authentication flow,
* catch all navigation from web service, and the final url whose fragment contains accessToken
* and other necessary information. digiKam then parses the response to get accessToken and join back to
* O2's authentication flow by calling this method.
*
* Facebook is a web service where this approach is used, because the callback url is not http://127.0.0.1/
*/
virtual void slotResponseTokenReceived(const QMap<QString, QString>& rep);
protected:
QNetworkAccessManager* m_netMngr;
QNetworkReply* m_reply;
State m_state;
QByteArray m_buffer;
QSettings* m_settings;
O0SettingsStore* m_store;
QString m_userName;
WSWizard* m_wizard;
};
} // namespace Digikam
#endif // DIGIKAM_WS_TALKER_H
diff --git a/core/utilities/assistants/webservices/common/o2/src/o0simplecrypt.h b/core/utilities/assistants/webservices/common/o2/src/o0simplecrypt.h
index 3e3363c59d..19b96fe19b 100644
--- a/core/utilities/assistants/webservices/common/o2/src/o0simplecrypt.h
+++ b/core/utilities/assistants/webservices/common/o2/src/o0simplecrypt.h
@@ -1,227 +1,227 @@
/*
Copyright (c) 2011, Andre Somers
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Rathenau Instituut, Andre Somers nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SIMPLECRYPT_H
#define SIMPLECRYPT_H
#include <QString>
#include <QVector>
#include <QFlags>
#include "o0baseauth.h"
/**
@short Simple encryption and decryption of strings and byte arrays
This class provides a simple implementation of encryption and decryption
of strings and byte arrays.
@warning The encryption provided by this class is NOT strong encryption. It may
help to shield things from curious eyes, but it will NOT stand up to someone
determined to break the encryption. Don't say you were not warned.
The class uses a 64 bit key. Simply create an instance of the class, set the key,
and use the encryptToString() method to calculate an encrypted version of the input string.
To decrypt that string again, use an instance of SimpleCrypt initialized with
the same key, and call the decryptToString() method with the encrypted string. If the key
matches, the decrypted version of the string will be returned again.
If you do not provide a key, or if something else is wrong, the encryption and
decryption function will return an empty string or will return a string containing nonsense.
- lastError() will return a value indicating if the method was succesful, and if not, why not.
+ lastError() will return a value indicating if the method was successful, and if not, why not.
SimpleCrypt is prepared for the case that the encryption and decryption
algorithm is changed in a later version, by prepending a version identifier to the cypertext.
*/
class O0_EXPORT O0SimpleCrypt
{
public:
/**
CompressionMode describes if compression will be applied to the data to be
encrypted.
*/
enum CompressionMode {
CompressionAuto, /*!< Only apply compression if that results in a shorter plaintext. */
CompressionAlways, /*!< Always apply compression. Note that for short inputs, a compression may result in longer data */
CompressionNever /*!< Never apply compression. */
};
/**
IntegrityProtectionMode describes measures taken to make it possible to detect problems with the data
or wrong decryption keys.
Measures involve adding a checksum or a cryptograhpic hash to the data to be encrypted. This
increases the length of the resulting cypertext, but makes it possible to check if the plaintext
appears to be valid after decryption.
*/
enum IntegrityProtectionMode {
- ProtectionNone, /*!< The integerity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
+ ProtectionNone, /*!< The integrity of the encrypted data is not protected. It is not really possible to detect a wrong key, for instance. */
ProtectionChecksum,/*!< A simple checksum is used to verify that the data is in order. If not, an empty string is returned. */
ProtectionHash /*!< A cryptographic hash is used to verify the integrity of the data. This method produces a much stronger, but longer check */
};
/**
- Error describes the type of error that occured.
+ Error describes the type of error that occurred.
*/
enum Error {
ErrorNoError, /*!< No error occurred. */
ErrorNoKeySet, /*!< No key was set. You can not encrypt or decrypt without a valid key. */
ErrorUnknownVersion, /*!< The version of this data is unknown, or the data is otherwise not valid. */
ErrorIntegrityFailed /*!< The integrity check of the data failed. Perhaps the wrong key was used. */
};
/**
Constructor.
Constructs a SimpleCrypt instance without a valid key set on it.
*/
O0SimpleCrypt();
/**
Constructor.
Constructs a SimpleCrypt instance and initializes it with the given @arg key.
*/
explicit O0SimpleCrypt(quint64 key);
/**
(Re-) initializes the key with the given @arg key.
*/
void setKey(quint64 key);
/**
Returns true if SimpleCrypt has been initialized with a key.
*/
bool hasKey() const {return !m_keyParts.isEmpty();}
/**
Sets the compression mode to use when encrypting data. The default mode is Auto.
Note that decryption is not influenced by this mode, as the decryption recognizes
what mode was used when encrypting.
*/
void setCompressionMode(CompressionMode mode) {m_compressionMode = mode;}
/**
Returns the CompressionMode that is currently in use.
*/
CompressionMode compressionMode() const {return m_compressionMode;}
/**
Sets the integrity mode to use when encrypting data. The default mode is Checksum.
Note that decryption is not influenced by this mode, as the decryption recognizes
what mode was used when encrypting.
*/
void setIntegrityProtectionMode(IntegrityProtectionMode mode) {m_protectionMode = mode;}
/**
Returns the IntegrityProtectionMode that is currently in use.
*/
IntegrityProtectionMode integrityProtectionMode() const {return m_protectionMode;}
/**
Returns the last error that occurred.
*/
Error lastError() const {return m_lastError;}
/**
Encrypts the @arg plaintext string with the key the class was initialized with, and returns
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
actual result of the string, so it can be stored easily in a text format.
*/
QString encryptToString(const QString& plaintext) ;
/**
Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns
a cyphertext the result. The result is a base64 encoded version of the binary array that is the
actual result of the encryption, so it can be stored easily in a text format.
*/
QString encryptToString(QByteArray plaintext) ;
/**
Encrypts the @arg plaintext string with the key the class was initialized with, and returns
a binary cyphertext in a QByteArray the result.
- This method returns a byte array, that is useable for storing a binary format. If you need
+ This method returns a byte array, that is usable for storing a binary format. If you need
a string you can store in a text file, use encryptToString() instead.
*/
QByteArray encryptToByteArray(const QString& plaintext) ;
/**
Encrypts the @arg plaintext QByteArray with the key the class was initialized with, and returns
a binary cyphertext in a QByteArray the result.
- This method returns a byte array, that is useable for storing a binary format. If you need
+ This method returns a byte array, that is usable for storing a binary format. If you need
a string you can store in a text file, use encryptToString() instead.
*/
QByteArray encryptToByteArray(QByteArray plaintext) ;
/**
Decrypts a cyphertext string encrypted with this class with the set key back to the
plain text version.
- If an error occured, such as non-matching keys between encryption and decryption,
+ If an error occurred, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QString decryptToString(const QString& cyphertext) ;
/**
Decrypts a cyphertext string encrypted with this class with the set key back to the
plain text version.
- If an error occured, such as non-matching keys between encryption and decryption,
+ If an error occurred, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QByteArray decryptToByteArray(const QString& cyphertext) ;
/**
Decrypts a cyphertext binary encrypted with this class with the set key back to the
plain text version.
- If an error occured, such as non-matching keys between encryption and decryption,
+ If an error occurred, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QString decryptToString(QByteArray cypher) ;
/**
Decrypts a cyphertext binary encrypted with this class with the set key back to the
plain text version.
- If an error occured, such as non-matching keys between encryption and decryption,
+ If an error occurred, such as non-matching keys between encryption and decryption,
an empty string or a string containing nonsense may be returned.
*/
QByteArray decryptToByteArray(QByteArray cypher) ;
//enum to describe options that have been used for the encryption. Currently only one, but
//that only leaves room for future extensions like adding a cryptographic hash...
enum CryptoFlag{CryptoFlagNone = 0,
CryptoFlagCompression = 0x01,
CryptoFlagChecksum = 0x02,
CryptoFlagHash = 0x04
};
Q_DECLARE_FLAGS(CryptoFlags, CryptoFlag)
private:
void splitKey();
quint64 m_key;
QVector<char> m_keyParts;
CompressionMode m_compressionMode;
IntegrityProtectionMode m_protectionMode;
Error m_lastError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(O0SimpleCrypt::CryptoFlags)
#endif // SimpleCrypt_H
diff --git a/core/utilities/assistants/webservices/common/o2/src/o2.cpp b/core/utilities/assistants/webservices/common/o2/src/o2.cpp
index 760d3dd311..6a50a31b15 100644
--- a/core/utilities/assistants/webservices/common/o2/src/o2.cpp
+++ b/core/utilities/assistants/webservices/common/o2/src/o2.cpp
@@ -1,512 +1,512 @@
#include <QList>
#include <QPair>
#include <QDebug>
#include <QTcpServer>
#include <QMap>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>
#include <QDateTime>
#include <QCryptographicHash>
#include <QTimer>
#include <QVariantMap>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#include <QJsonDocument>
#include <QJsonObject>
#else
#include <QScriptEngine>
#include <QScriptValueIterator>
#endif
#include "o2.h"
#include "o2replyserver.h"
#include "o0globals.h"
#include "o0settingsstore.h"
/// Parse JSON data into a QVariantMap
static QVariantMap parseTokenResponse(const QByteArray &data) {
#if QT_VERSION >= 0x050000
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError) {
qWarning() << "parseTokenResponse: Failed to parse token response due to err:" << err.errorString();
return QVariantMap();
}
if (!doc.isObject()) {
qWarning() << "parseTokenResponse: Token response is not an object";
return QVariantMap();
}
return doc.object().toVariantMap();
#else
QScriptEngine engine;
QScriptValue value = engine.evaluate("(" + QString(data) + ")");
QScriptValueIterator it(value);
QVariantMap map;
while (it.hasNext()) {
it.next();
map.insert(it.name(), it.value().toVariant());
}
return map;
#endif
}
/// Add query parameters to a query
static void addQueryParametersToUrl(QUrl &url, QList<QPair<QString, QString> > parameters) {
#if QT_VERSION < 0x050000
url.setQueryItems(parameters);
#else
QUrlQuery query(url);
query.setQueryItems(parameters);
url.setQuery(query);
#endif
}
O2::O2(QObject *parent, QNetworkAccessManager *manager, O0AbstractStore *store): O0BaseAuth(parent, store) {
manager_ = manager ? manager : new QNetworkAccessManager(this);
grantFlow_ = GrantFlowAuthorizationCode;
localhostPolicy_ = QString(O2_CALLBACK_URL);
qRegisterMetaType<QNetworkReply::NetworkError>("QNetworkReply::NetworkError");
}
O2::GrantFlow O2::grantFlow() {
return grantFlow_;
}
void O2::setGrantFlow(O2::GrantFlow value) {
grantFlow_ = value;
Q_EMIT grantFlowChanged();
}
QString O2::username() {
return username_;
}
void O2::setUsername(const QString &value) {
username_ = value;
Q_EMIT usernameChanged();
}
QString O2::password() {
return password_;
}
void O2::setPassword(const QString &value) {
password_ = value;
Q_EMIT passwordChanged();
}
QString O2::scope() {
return scope_;
}
void O2::setScope(const QString &value) {
scope_ = value;
Q_EMIT scopeChanged();
}
QString O2::requestUrl() {
return requestUrl_.toString();
}
void O2::setRequestUrl(const QString &value) {
requestUrl_ = value;
Q_EMIT requestUrlChanged();
}
QVariantMap O2::extraRequestParams()
{
return extraReqParams_;
}
void O2::setExtraRequestParams(const QVariantMap &value)
{
extraReqParams_ = value;
Q_EMIT extraRequestParamsChanged();
}
QString O2::tokenUrl() {
return tokenUrl_.toString();
}
void O2::setTokenUrl(const QString &value) {
tokenUrl_= value;
Q_EMIT tokenUrlChanged();
}
QString O2::refreshTokenUrl() {
return refreshTokenUrl_.toString();
}
void O2::setRefreshTokenUrl(const QString &value) {
refreshTokenUrl_ = value;
Q_EMIT refreshTokenUrlChanged();
}
void O2::link() {
qDebug() << "O2::link";
// Create the reply server if it doesn't exist
// and we don't use an external web interceptor
if(!useExternalWebInterceptor_) {
if(replyServer_ == NULL) {
replyServer_ = new O2ReplyServer(this);
connect(replyServer_, SIGNAL(verificationReceived(QMap<QString,QString>)), this, SLOT(onVerificationReceived(QMap<QString,QString>)));
connect(replyServer_, SIGNAL(serverClosed(bool)), this, SLOT(serverHasClosed(bool)));
}
}
if (linked()) {
qDebug() << "O2::link: Linked already";
Q_EMIT linkingSucceeded();
return;
}
setLinked(false);
setToken("");
setTokenSecret("");
setExtraTokens(QVariantMap());
setRefreshToken(QString());
setExpires(0);
if (grantFlow_ == GrantFlowAuthorizationCode || grantFlow_ == GrantFlowImplicit) {
if (useExternalWebInterceptor_) {
// Save redirect URI, as we have to reuse it when requesting the access token
redirectUri_ = localhostPolicy_.arg(localPort());
} else {
// Start listening to authentication replies
if (!replyServer_->isListening()) {
if (replyServer_->listen(QHostAddress::Any, localPort_)) {
qDebug() << "O2::link: Reply server listening on port" << localPort();
} else {
qWarning() << "O2::link: Reply server failed to start listening on port" << localPort();
Q_EMIT linkingFailed();
return;
}
}
// Save redirect URI, as we have to reuse it when requesting the access token
redirectUri_ = localhostPolicy_.arg(replyServer_->serverPort());
}
- // Assemble intial authentication URL
+ // Assemble initial authentication URL
QList<QPair<QString, QString> > parameters;
parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE),
(grantFlow_ == GrantFlowAuthorizationCode)? QString(O2_OAUTH2_GRANT_TYPE_CODE): QString(O2_OAUTH2_GRANT_TYPE_TOKEN)));
parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_));
if ( !redirectUri_.isEmpty() )
parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_));
if ( !scope_.isEmpty() )
parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_.replace( " ", "+" )));
if ( !apiKey_.isEmpty() )
parameters.append(qMakePair(QString(O2_OAUTH2_API_KEY), apiKey_));
foreach (QString key, extraRequestParams().keys()) {
parameters.append(qMakePair(key, extraRequestParams().value(key).toString()));
}
// Show authentication URL with a web browser
QUrl url(requestUrl_);
addQueryParametersToUrl(url, parameters);
qDebug() << "O2::link: Emit openBrowser" << url.toString();
Q_EMIT openBrowser(url);
} else if (grantFlow_ == GrantFlowResourceOwnerPasswordCredentials) {
QList<O0RequestParameter> parameters;
parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_ID, clientId_.toUtf8()));
if ( !clientSecret_.isEmpty() )
parameters.append(O0RequestParameter(O2_OAUTH2_CLIENT_SECRET, clientSecret_.toUtf8()));
parameters.append(O0RequestParameter(O2_OAUTH2_USERNAME, username_.toUtf8()));
parameters.append(O0RequestParameter(O2_OAUTH2_PASSWORD, password_.toUtf8()));
parameters.append(O0RequestParameter(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_GRANT_TYPE_PASSWORD));
parameters.append(O0RequestParameter(O2_OAUTH2_SCOPE, scope_.toUtf8()));
if ( !apiKey_.isEmpty() )
parameters.append(O0RequestParameter(O2_OAUTH2_API_KEY, apiKey_.toUtf8()));
foreach (QString key, extraRequestParams().keys()) {
parameters.append(O0RequestParameter(key.toUtf8(), extraRequestParams().value(key).toByteArray()));
}
QByteArray payload = O0BaseAuth::createQueryParameters(parameters);
qDebug() << "O2::link: Sending token request for resource owner flow";
QUrl url(tokenUrl_);
QNetworkRequest tokenRequest(url);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply *tokenReply = manager_->post(tokenRequest, payload);
connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
}
}
void O2::unlink() {
qDebug() << "O2::unlink";
setLinked(false);
setToken(QString());
setRefreshToken(QString());
setExpires(0);
setExtraTokens(QVariantMap());
Q_EMIT linkingSucceeded();
}
void O2::onVerificationReceived(const QMap<QString, QString> response) {
qDebug() << "O2::onVerificationReceived:" << response;
qDebug() << "O2::onVerificationReceived: Emitting closeBrowser()";
Q_EMIT closeBrowser();
if (response.contains("error")) {
qWarning() << "O2::onVerificationReceived: Verification failed:" << response;
Q_EMIT linkingFailed();
return;
}
if (grantFlow_ == GrantFlowAuthorizationCode) {
// Save access code
setCode(response.value(QString(O2_OAUTH2_GRANT_TYPE_CODE)));
// Exchange access code for access/refresh tokens
QString query;
if(!apiKey_.isEmpty())
query = QString("?" + QString(O2_OAUTH2_API_KEY) + "=" + apiKey_);
QNetworkRequest tokenRequest(QUrl(tokenUrl_.toString() + query));
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
tokenRequest.setRawHeader("Accept", O2_MIME_TYPE_JSON);
QMap<QString, QString> parameters;
parameters.insert(O2_OAUTH2_GRANT_TYPE_CODE, code());
parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_);
parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE);
QByteArray data = buildRequestBody(parameters);
qDebug() << QString("O2::onVerificationReceived: Exchange access code data:\n%1").arg(QString(data));
QNetworkReply *tokenReply = manager_->post(tokenRequest, data);
timedReplies_.add(tokenReply);
connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
} else if (grantFlow_ == GrantFlowImplicit) {
// Check for mandatory tokens
if (response.contains(O2_OAUTH2_ACCESS_TOKEN)) {
qDebug() << "O2::onVerificationReceived: Access token returned for implicit flow";
setToken(response.value(O2_OAUTH2_ACCESS_TOKEN));
if (response.contains(O2_OAUTH2_EXPIRES_IN)) {
bool ok = false;
int expiresIn = response.value(O2_OAUTH2_EXPIRES_IN).toInt(&ok);
if (ok) {
qDebug() << "O2::onVerificationReceived: Token expires in" << expiresIn << "seconds";
setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn));
}
}
setLinked(true);
Q_EMIT linkingSucceeded();
} else {
qWarning() << "O2::onVerificationReceived: Access token missing from response for implicit flow";
Q_EMIT linkingFailed();
}
} else {
setToken(response.value(O2_OAUTH2_ACCESS_TOKEN));
setRefreshToken(response.value(O2_OAUTH2_REFRESH_TOKEN));
}
}
QString O2::code() {
QString key = QString(O2_KEY_CODE).arg(clientId_);
return store_->value(key);
}
void O2::setCode(const QString &c) {
QString key = QString(O2_KEY_CODE).arg(clientId_);
store_->setValue(key, c);
}
void O2::onTokenReplyFinished() {
qDebug() << "O2::onTokenReplyFinished";
QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
if (!tokenReply)
{
qDebug() << "O2::onTokenReplyFinished: reply is null";
return;
}
if (tokenReply->error() == QNetworkReply::NoError) {
QByteArray replyData = tokenReply->readAll();
// Dump replyData
// SENSITIVE DATA in RelWithDebInfo or Debug builds
//qDebug() << "O2::onTokenReplyFinished: replyData\n";
//qDebug() << QString( replyData );
QVariantMap tokens = parseTokenResponse(replyData);
// Dump tokens
qDebug() << "O2::onTokenReplyFinished: Tokens returned:\n";
foreach (QString key, tokens.keys()) {
// SENSITIVE DATA in RelWithDebInfo or Debug builds, so it is truncated first
qDebug() << key << ": "<< tokens.value( key ).toString().left( 3 ) << "...";
}
// Check for mandatory tokens
if (tokens.contains(O2_OAUTH2_ACCESS_TOKEN)) {
qDebug() << "O2::onTokenReplyFinished: Access token returned";
setToken(tokens.take(O2_OAUTH2_ACCESS_TOKEN).toString());
bool ok = false;
int expiresIn = tokens.take(O2_OAUTH2_EXPIRES_IN).toInt(&ok);
if (ok) {
qDebug() << "O2::onTokenReplyFinished: Token expires in" << expiresIn << "seconds";
setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + expiresIn));
}
setRefreshToken(tokens.take(O2_OAUTH2_REFRESH_TOKEN).toString());
setExtraTokens(tokens);
timedReplies_.remove(tokenReply);
setLinked(true);
Q_EMIT linkingSucceeded();
} else {
qWarning() << "O2::onTokenReplyFinished: Access token missing from response";
Q_EMIT linkingFailed();
}
}
tokenReply->deleteLater();
}
void O2::onTokenReplyError(QNetworkReply::NetworkError error) {
QNetworkReply *tokenReply = qobject_cast<QNetworkReply *>(sender());
qWarning() << "O2::onTokenReplyError: " << error << ": " << tokenReply->errorString();
qDebug() << "O2::onTokenReplyError: " << tokenReply->readAll();
setToken(QString());
setRefreshToken(QString());
timedReplies_.remove(tokenReply);
Q_EMIT linkingFailed();
}
QByteArray O2::buildRequestBody(const QMap<QString, QString> &parameters) {
QByteArray body;
bool first = true;
foreach (QString key, parameters.keys()) {
if (first) {
first = false;
} else {
body.append("&");
}
QString value = parameters.value(key);
body.append(QUrl::toPercentEncoding(key) + QString("=").toUtf8() + QUrl::toPercentEncoding(value));
}
return body;
}
int O2::expires() {
QString key = QString(O2_KEY_EXPIRES).arg(clientId_);
return store_->value(key).toInt();
}
void O2::setExpires(int v) {
QString key = QString(O2_KEY_EXPIRES).arg(clientId_);
store_->setValue(key, QString::number(v));
}
QString O2::refreshToken() {
QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_);
return store_->value(key);
}
void O2::setRefreshToken(const QString &v) {
qDebug() << "O2::setRefreshToken" << v.left(4) << "...";
QString key = QString(O2_KEY_REFRESH_TOKEN).arg(clientId_);
store_->setValue(key, v);
}
void O2::refresh() {
qDebug() << "O2::refresh: Token: ..." << refreshToken().right(7);
if (refreshToken().isEmpty()) {
qWarning() << "O2::refresh: No refresh token";
onRefreshError(QNetworkReply::AuthenticationRequiredError);
return;
}
if (refreshTokenUrl_.isEmpty()) {
qWarning() << "O2::refresh: Refresh token URL not set";
onRefreshError(QNetworkReply::AuthenticationRequiredError);
return;
}
QNetworkRequest refreshRequest(refreshTokenUrl_);
refreshRequest.setHeader(QNetworkRequest::ContentTypeHeader, O2_MIME_TYPE_XFORM);
QMap<QString, QString> parameters;
parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
parameters.insert(O2_OAUTH2_REFRESH_TOKEN, refreshToken());
parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_OAUTH2_REFRESH_TOKEN);
QByteArray data = buildRequestBody(parameters);
QNetworkReply *refreshReply = manager_->post(refreshRequest, data);
timedReplies_.add(refreshReply);
connect(refreshReply, SIGNAL(finished()), this, SLOT(onRefreshFinished()), Qt::QueuedConnection);
connect(refreshReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onRefreshError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
}
void O2::onRefreshFinished() {
QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
if (refreshReply->error() == QNetworkReply::NoError) {
QByteArray reply = refreshReply->readAll();
QVariantMap tokens = parseTokenResponse(reply);
setToken(tokens.value(O2_OAUTH2_ACCESS_TOKEN).toString());
setExpires((int)(QDateTime::currentMSecsSinceEpoch() / 1000 + tokens.value(O2_OAUTH2_EXPIRES_IN).toInt()));
QString refreshToken = tokens.value(O2_OAUTH2_REFRESH_TOKEN).toString();
if(!refreshToken.isEmpty()) {
setRefreshToken(refreshToken);
}
else {
qDebug() << "No new refresh token. Keep the old one.";
}
timedReplies_.remove(refreshReply);
setLinked(true);
Q_EMIT linkingSucceeded();
Q_EMIT refreshFinished(QNetworkReply::NoError);
qDebug() << " New token expires in" << expires() << "seconds";
} else {
qDebug() << "O2::onRefreshFinished: Error" << (int)refreshReply->error() << refreshReply->errorString();
}
refreshReply->deleteLater();
}
void O2::onRefreshError(QNetworkReply::NetworkError error) {
QNetworkReply *refreshReply = qobject_cast<QNetworkReply *>(sender());
qWarning() << "O2::onRefreshError: " << error;
unlink();
timedReplies_.remove(refreshReply);
Q_EMIT refreshFinished(error);
}
void O2::serverHasClosed(bool paramsfound)
{
if ( !paramsfound ) {
// server has probably timed out after receiving first response
Q_EMIT linkingFailed();
}
}
QString O2::localhostPolicy() const {
return localhostPolicy_;
}
void O2::setLocalhostPolicy(const QString &value) {
localhostPolicy_ = value;
}
QString O2::apiKey() {
return apiKey_;
}
void O2::setApiKey(const QString &value) {
apiKey_ = value;
}
bool O2::ignoreSslErrors() {
return timedReplies_.ignoreSslErrors();
}
void O2::setIgnoreSslErrors(bool ignoreSslErrors) {
timedReplies_.setIgnoreSslErrors(ignoreSslErrors);
}
diff --git a/core/utilities/assistants/webservices/common/o2/src/o2replyserver.cpp b/core/utilities/assistants/webservices/common/o2/src/o2replyserver.cpp
index 647f4f1a57..ef09a028b8 100644
--- a/core/utilities/assistants/webservices/common/o2/src/o2replyserver.cpp
+++ b/core/utilities/assistants/webservices/common/o2/src/o2replyserver.cpp
@@ -1,168 +1,168 @@
#include <QTcpServer>
#include <QTcpSocket>
#include <QByteArray>
#include <QString>
#include <QMap>
#include <QPair>
#include <QTimer>
#include <QStringList>
#include <QUrl>
#include <QDebug>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif
#include "o2replyserver.h"
O2ReplyServer::O2ReplyServer(QObject *parent): QTcpServer(parent),
timeout_(15), maxtries_(3), tries_(0) {
qDebug() << "O2ReplyServer: Starting";
connect(this, SIGNAL(newConnection()), this, SLOT(onIncomingConnection()));
replyContent_ = "<HTML></HTML>";
}
void O2ReplyServer::onIncomingConnection() {
qDebug() << "O2ReplyServer::onIncomingConnection: Receiving...";
QTcpSocket *socket = nextPendingConnection();
connect(socket, SIGNAL(readyRead()), this, SLOT(onBytesReady()), Qt::UniqueConnection);
connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
- // Wait for a bit *after* first response, then close server if no useable data has arrived
+ // Wait for a bit *after* first response, then close server if no usable data has arrived
// Helps with implicit flow, where a URL fragment may need processed by local user-agent and
// sent as secondary query string callback, or additional requests make it through first,
// like for favicons, etc., before such secondary callbacks are fired
QTimer *timer = new QTimer(socket);
timer->setObjectName("timeoutTimer");
connect(timer, SIGNAL(timeout()), this, SLOT(closeServer()));
timer->setSingleShot(true);
timer->setInterval(timeout() * 1000);
connect(socket, SIGNAL(readyRead()), timer, SLOT(start()));
}
void O2ReplyServer::onBytesReady() {
if (!isListening()) {
// server has been closed, stop processing queued connections
return;
}
qDebug() << "O2ReplyServer::onBytesReady: Processing request";
// NOTE: on first call, the timeout timer is started
QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
if (!socket) {
qWarning() << "O2ReplyServer::onBytesReady: No socket available";
return;
}
QByteArray reply;
reply.append("HTTP/1.0 200 OK \r\n");
reply.append("Content-Type: text/html; charset=\"utf-8\"\r\n");
reply.append(QString("Content-Length: %1\r\n\r\n").arg(replyContent_.size()).toLatin1());
reply.append(replyContent_);
socket->write(reply);
qDebug() << "O2ReplyServer::onBytesReady: Sent reply";
QByteArray data = socket->readAll();
QMap<QString, QString> queryParams = parseQueryParams(&data);
if (queryParams.isEmpty()) {
if (tries_ < maxtries_ ) {
qDebug() << "O2ReplyServer::onBytesReady: No query params found, waiting for more callbacks";
++tries_;
return;
} else {
tries_ = 0;
qWarning() << "O2ReplyServer::onBytesReady: No query params found, maximum callbacks received";
closeServer(socket, false);
return;
}
}
qDebug() << "O2ReplyServer::onBytesReady: Query params found, closing server";
closeServer(socket, true);
Q_EMIT verificationReceived(queryParams);
}
QMap<QString, QString> O2ReplyServer::parseQueryParams(QByteArray *data) {
qDebug() << "O2ReplyServer::parseQueryParams";
//qDebug() << QString("O2ReplyServer::parseQueryParams data:\n%1").arg(QString(*data));
QString splitGetLine = QString(*data).split("\r\n").first();
splitGetLine.remove("GET ");
splitGetLine.remove("HTTP/1.1");
splitGetLine.remove("\r\n");
splitGetLine.prepend("http://localhost");
QUrl getTokenUrl(splitGetLine);
QList< QPair<QString, QString> > tokens;
#if QT_VERSION < 0x050000
tokens = getTokenUrl.queryItems();
#else
QUrlQuery query(getTokenUrl);
tokens = query.queryItems();
#endif
QMultiMap<QString, QString> queryParams;
QPair<QString, QString> tokenPair;
foreach (tokenPair, tokens) {
// FIXME: We are decoding key and value again. This helps with Google OAuth, but is it mandated by the standard?
QString key = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.first.trimmed().toLatin1()));
QString value = QUrl::fromPercentEncoding(QByteArray().append(tokenPair.second.trimmed().toLatin1()));
queryParams.insert(key, value);
}
return queryParams;
}
void O2ReplyServer::closeServer(QTcpSocket *socket, bool hasparameters)
{
if (!isListening()) {
return;
}
qDebug() << "O2ReplyServer::closeServer: Initiating";
int port = serverPort();
if (!socket && sender()) {
QTimer *timer = qobject_cast<QTimer*>(sender());
if (timer) {
qWarning() << "O2ReplyServer::closeServer: Closing due to timeout";
timer->stop();
socket = qobject_cast<QTcpSocket *>(timer->parent());
timer->deleteLater();
}
}
if (socket) {
QTimer *timer = socket->findChild<QTimer*>("timeoutTimer");
if (timer) {
qDebug() << "O2ReplyServer::closeServer: Stopping socket's timeout timer";
timer->stop();
}
socket->disconnectFromHost();
}
close();
qDebug() << "O2ReplyServer::closeServer: Closed, no longer listening on port" << port;
Q_EMIT serverClosed(hasparameters);
}
QByteArray O2ReplyServer::replyContent() {
return replyContent_;
}
void O2ReplyServer::setReplyContent(const QByteArray &value) {
replyContent_ = value;
}
int O2ReplyServer::timeout()
{
return timeout_;
}
void O2ReplyServer::setTimeout(int timeout)
{
timeout_ = timeout;
}
int O2ReplyServer::callbackTries()
{
return maxtries_;
}
void O2ReplyServer::setCallbackTries(int maxtries)
{
maxtries_ = maxtries;
}
diff --git a/core/utilities/assistants/webservices/common/o2/src/o2skydrive.cpp b/core/utilities/assistants/webservices/common/o2/src/o2skydrive.cpp
index a458c78bdd..863dc59bac 100644
--- a/core/utilities/assistants/webservices/common/o2/src/o2skydrive.cpp
+++ b/core/utilities/assistants/webservices/common/o2/src/o2skydrive.cpp
@@ -1,125 +1,125 @@
#include <QDebug>
#include <QDateTime>
#include <QMap>
#include <QString>
#include <QStringList>
#if QT_VERSION >= 0x050000
#include <QUrlQuery>
#endif
#include "o2skydrive.h"
#include "o0globals.h"
O2Skydrive::O2Skydrive(QObject *parent): O2(parent) {
setRequestUrl("https://login.live.com/oauth20_authorize.srf");
setTokenUrl("https://login.live.com/oauth20_token.srf");
setRefreshTokenUrl("https://login.live.com/oauth20_token.srf");
}
void O2Skydrive::link() {
qDebug() << "O2Skydrive::link";
if (linked()) {
qDebug() << "O2kydrive::link: Linked already";
return;
}
setLinked(false);
setToken("");
setTokenSecret("");
setExtraTokens(QVariantMap());
setRefreshToken(QString());
setExpires(0);
redirectUri_ = QString("https://login.live.com/oauth20_desktop.srf");
- // Assemble intial authentication URL
+ // Assemble initial authentication URL
QList<QPair<QString, QString> > parameters;
parameters.append(qMakePair(QString(O2_OAUTH2_RESPONSE_TYPE), (grantFlow_ == GrantFlowAuthorizationCode) ? QString(O2_OAUTH2_GRANT_TYPE_CODE) : QString(O2_OAUTH2_GRANT_TYPE_TOKEN)));
parameters.append(qMakePair(QString(O2_OAUTH2_CLIENT_ID), clientId_));
parameters.append(qMakePair(QString(O2_OAUTH2_REDIRECT_URI), redirectUri_));
parameters.append(qMakePair(QString(O2_OAUTH2_SCOPE), scope_));
// Show authentication URL with a web browser
QUrl url(requestUrl_);
#if QT_VERSION < 0x050000
url.setQueryItems(parameters);
#else
QUrlQuery query(url);
query.setQueryItems(parameters);
url.setQuery(query);
#endif
Q_EMIT openBrowser(url);
}
void O2Skydrive::redirected(const QUrl &url) {
qDebug() << "O2Skydrive::redirected" << url;
Q_EMIT closeBrowser();
if (grantFlow_ == GrantFlowAuthorizationCode) {
// Get access code
QString urlCode;
#if QT_VERSION < 0x050000
urlCode = url.queryItemValue(O2_OAUTH2_GRANT_TYPE_CODE);
#else
QUrlQuery query(url);
urlCode = query.queryItemValue(O2_OAUTH2_GRANT_TYPE_CODE);
#endif
if (urlCode.isEmpty()) {
qDebug() << "O2Skydrive::redirected: Code not received";
Q_EMIT linkingFailed();
return;
}
setCode(urlCode);
// Exchange access code for access/refresh tokens
QNetworkRequest tokenRequest(tokenUrl_);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QMap<QString, QString> parameters;
parameters.insert(O2_OAUTH2_GRANT_TYPE_CODE, code());
parameters.insert(O2_OAUTH2_CLIENT_ID, clientId_);
parameters.insert(O2_OAUTH2_CLIENT_SECRET, clientSecret_);
parameters.insert(O2_OAUTH2_REDIRECT_URI, redirectUri_);
parameters.insert(O2_OAUTH2_GRANT_TYPE, O2_AUTHORIZATION_CODE);
QByteArray data = buildRequestBody(parameters);
QNetworkReply *tokenReply = manager_->post(tokenRequest, data);
timedReplies_.add(tokenReply);
connect(tokenReply, SIGNAL(finished()), this, SLOT(onTokenReplyFinished()), Qt::QueuedConnection);
connect(tokenReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(onTokenReplyError(QNetworkReply::NetworkError)), Qt::QueuedConnection);
} else {
// Get access token
QString urlToken = "";
QString urlRefreshToken = "";
int urlExpiresIn = 0;
QStringList parts = url.toString().split("#");
if (parts.length() > 1) {
foreach (QString item, parts[1].split("&")) {
int index = item.indexOf("=");
if (index == -1) {
continue;
}
QString key = item.left(index);
QString value = item.mid(index + 1);
qDebug() << "O2Skydrive::redirected: Got" << key;
if (key == O2_OAUTH2_ACCESS_TOKEN) {
urlToken = value;
} else if (key == O2_OAUTH2_EXPIRES_IN) {
urlExpiresIn = value.toInt();
} else if (key == O2_OAUTH2_REFRESH_TOKEN) {
urlRefreshToken = value;
}
}
}
setToken(urlToken);
setRefreshToken(urlRefreshToken);
setExpires(QDateTime::currentMSecsSinceEpoch() / 1000 + urlExpiresIn);
if (urlToken.isEmpty()) {
Q_EMIT linkingFailed();
} else {
setLinked(true);
Q_EMIT linkingSucceeded();
}
}
}
diff --git a/core/utilities/assistants/webservices/common/wizard/wsauthenticationpage.cpp b/core/utilities/assistants/webservices/common/wizard/wsauthenticationpage.cpp
index 3ed60979cb..cc925305a1 100644
--- a/core/utilities/assistants/webservices/common/wizard/wsauthenticationpage.cpp
+++ b/core/utilities/assistants/webservices/common/wizard/wsauthenticationpage.cpp
@@ -1,351 +1,351 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2018-07-02
* Description : embedded web browser for web service authentication
*
* Copyright (C) 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "wsauthenticationpage.h"
// Qt includes
#include <QFile>
#include <QFileInfo>
#include <QTimer>
#include <QWidget>
#include <QApplication>
#include <QStandardPaths>
#include <QString>
#include <QLabel>
#include <QStringList>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "digikam_version.h"
#include "dlayoutbox.h"
#include "wswizard.h"
#include "wssettings.h"
namespace Digikam
{
#ifdef HAVE_QWEBENGINE
WSAuthenticationPage::WSAuthenticationPage(QObject* const parent, QWebEngineProfile* profile, const QString& callbackUrl)
: QWebEnginePage(profile, parent),
m_callbackUrl(callbackUrl)
{
}
#else
WSAuthenticationPage::WSAuthenticationPage(QObject* const parent, const QString& callbackUrl)
: QWebPage(parent),
m_callbackUrl(callbackUrl)
{
connect(mainFrame(), SIGNAL(urlChanged(QUrl)),
this, SLOT(slotUrlChanged(QUrl)));
}
#endif // #ifdef HAVE_QWEBENGINE
WSAuthenticationPage::~WSAuthenticationPage()
{
}
void WSAuthenticationPage::setCallbackUrl(const QString& url)
{
m_callbackUrl = url;
}
#ifdef HAVE_QWEBENGINE
bool WSAuthenticationPage::acceptNavigationRequest(const QUrl& url, QWebEnginePage::NavigationType /*type*/, bool /*isMainFrame*/)
#else
bool WSAuthenticationPage::slotUrlChanged(const QUrl& url)
#endif // #ifdef HAVE_QWEBENGINE
{
QString urlString = url.toString();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "urlString: " << urlString;
/*
* Condition to verify that the url loaded on page is the one containing access token
*/
if (m_callbackUrl.length() > 0 &&
urlString.length() >= m_callbackUrl.length() &&
urlString.left(m_callbackUrl.length()) == m_callbackUrl)
{
emit callbackCatched(urlString);
return false;
}
return true;
}
// ----------------------------------------------------------------------------
#ifdef HAVE_QWEBENGINE
WSAuthenticationPageView::WSAuthenticationPageView(QWidget* const parent,
WSAuthentication* const wsAuth,
const QString& callbackUrl)
: QWebEngineView(parent),
m_WSAuthentication(wsAuth)
#else
WSAuthenticationPageView::WSAuthenticationPageView(QWidget* const parent,
WSAuthentication* const wsAuth,
const QString& callbackUrl)
: QWebView(parent),
m_WSAuthentication(wsAuth)
#endif // #ifdef HAVE_QWEBENGINE
{
adjustSize();
setMinimumSize(QSize(850,800));
#ifdef HAVE_QWEBENGINE
WSAuthenticationPage* const wpage = new WSAuthenticationPage(this, new QWebEngineProfile, callbackUrl);
#else
WSAuthenticationPage* const wpage = new WSAuthenticationPage(this, callbackUrl);
#endif // #ifdef HAVE_QWEBENGINE
setPage(wpage);
connect(wpage, SIGNAL(callbackCatched(QString)),
this, SLOT(slotCallbackCatched(QString)));
connect(m_WSAuthentication, SIGNAL(signalOpenBrowser(QUrl)),
this, SLOT(slotOpenBrowser(QUrl)));
connect(m_WSAuthentication, SIGNAL(signalCloseBrowser()),
this, SLOT(slotCloseBrowser()));
/*
* Here we hide the web browser immediately after creation.
- * If user has to login, we will show the browser again. Ohterwise,
+ * If user has to login, we will show the browser again. Otherwise,
* we will keep it hiding to improve page's looking.
*/
hide();
}
WSAuthenticationPageView::~WSAuthenticationPageView()
{
}
bool WSAuthenticationPageView::authenticationComplete() const
{
return m_WSAuthentication->authenticated();
}
QMap<QString, QString> WSAuthenticationPageView::parseUrlFragment(const QString& urlFragment)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseUrlFragment: " << urlFragment;
QMap<QString, QString> result;
QStringList listArgs = urlFragment.split(QLatin1Char('&'));
foreach (const QString& arg, listArgs)
{
QStringList pair = arg.split(QLatin1Char('='));
result.insert(pair.first(), pair.last());
}
return result;
}
void WSAuthenticationPageView::slotOpenBrowser(const QUrl& url)
{
WSAuthenticationPage* const page = dynamic_cast<WSAuthenticationPage*>(this->page());
#ifdef HAVE_QWEBENGINE
page->setUrl(url);
#else
page->mainFrame()->setUrl(url);
#endif
/*
* Here we show the web browser again after creation, because when this slot is triggered,
* user has to login. Therefore the login page has to be shown.
*/
show();
}
void WSAuthenticationPageView::slotCloseBrowser()
{
close();
}
void WSAuthenticationPageView::slotCallbackCatched(const QString& callbackUrl)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "slotCallbackCatched url: " << callbackUrl;
QUrl url(callbackUrl);
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url fragment: " << url.fragment();
QMap<QString, QString> res = parseUrlFragment(url.fragment());
emit m_WSAuthentication->signalResponseTokenReceived(res);
}
// ----------------------------------------------------------------------------
class Q_DECL_HIDDEN WSAuthenticationWizard::Private
{
public:
explicit Private(QWizard* const dialog, const QString& callback)
: wizard(0),
iface(0),
wsAuth(0),
wsAuthView(0),
vbox(0),
text(0),
callbackUrl(callback)
{
wizard = dynamic_cast<WSWizard*>(dialog);
if (wizard)
{
iface = wizard->iface();
if (wizard->settings()->webService == WSSettings::WebService::FACEBOOK)
{
callbackUrl = QLatin1String("https://www.facebook.com/connect/login_success.html");
}
}
}
WSWizard* wizard;
DInfoInterface* iface;
WSAuthentication* wsAuth;
WSAuthenticationPageView* wsAuthView;
DVBox* vbox;
QLabel* text;
QString callbackUrl;
};
WSAuthenticationWizard::WSAuthenticationWizard(QWizard* const dialog,
const QString& title,
const QString& callback)
: DWizardPage(dialog, title),
d(new Private(dialog, callback))
{
d->wsAuth = d->wizard->wsAuth();
connect(d->wsAuth, SIGNAL(signalAuthenticationComplete(bool)),
this, SLOT(slotAuthenticationComplete(bool)));
d->vbox = new DVBox(this);
d->text = new QLabel(d->vbox);
d->text->setWordWrap(true);
d->text->setOpenExternalLinks(true);
d->vbox->setStretchFactor(d->text, 1);
d->vbox->setStretchFactor(d->wsAuthView, 4);
setPageWidget(d->vbox);
}
WSAuthenticationWizard::~WSAuthenticationWizard()
{
delete d;
}
bool WSAuthenticationWizard::isComplete() const
{
return d->wsAuthView->authenticationComplete();
}
void WSAuthenticationWizard::initializePage()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "initPage WSAuthenticationWizard";
/*
* Init WebView of WSAuthenticationWizard every time initializePage is called.
*
* This guarantees an appropriate authentication page, even when user goes back to
* intro page and choose a different account or different web service.
*/
d->text->hide();
d->wsAuthView = new WSAuthenticationPageView(d->vbox, d->wsAuth, d->callbackUrl);
QMap<WSSettings::WebService, QString> wsNames = WSSettings::webServiceNames();
WSSettings::WebService ws = d->wizard->settings()->webService;
d->wsAuth->createTalker(ws, wsNames[ws]);
d->wsAuth->authenticate();
}
bool WSAuthenticationWizard::validatePage()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "validatePage";
return true;
}
void WSAuthenticationWizard::cleanupPage()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "cleanupPage WSAuthenticationWizard";
d->wsAuth->cancelTalker();
delete d->wsAuthView;
d->wsAuthView = 0;
}
void WSAuthenticationWizard::slotAuthenticationComplete(bool isLinked)
{
d->text->show();
if (isLinked)
{
d->text->setText(i18n("<qt>"
"<p><h1><b>Authentication done!</b></h1></p>"
"<p><h3>Account linking succeeded!</h3></p>"
"</qt>"));
}
else
{
d->text->setText(i18n("<qt>"
"<p><h1><b>Authentication done!</b></h1></p>"
"<p><h3>Account linking failed!</h3></p>"
"</qt>"));
}
emit completeChanged();
}
} // namespace Digikam
diff --git a/core/utilities/assistants/webservices/common/wizard/wswizard.h b/core/utilities/assistants/webservices/common/wizard/wswizard.h
index b71bdff247..6ef551f1e2 100644
--- a/core/utilities/assistants/webservices/common/wizard/wswizard.h
+++ b/core/utilities/assistants/webservices/common/wizard/wswizard.h
@@ -1,85 +1,85 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-06-27
* Description : a tool to export items to web services.
*
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_WS_WIZARD_H
#define DIGIKAM_WS_WIZARD_H
// Qt includes
#include <QList>
#include <QUrl>
#include <QSettings>
// Local includes
#include "dwizarddlg.h"
#include "dinfointerface.h"
#include "digikam_export.h"
#include "o0settingsstore.h"
#include "wssettings.h"
#include "wsauthentication.h"
namespace Digikam
{
class DIGIKAM_EXPORT WSWizard : public DWizardDlg
{
Q_OBJECT
public:
explicit WSWizard(QWidget* const parent, DInfoInterface* const iface = 0);
~WSWizard();
bool validateCurrentPage() override;
int nextId() const override;
DInfoInterface* iface() const;
WSSettings* settings() const;
/*
- * Instance of WSAuthentication (which wraps instance of WSTalker) and correspondant QSettings
+ * Instance of WSAuthentication (which wraps instance of WSTalker) and correspondent QSettings
* are initialized only once in WSWizard.
*
* These 2 methods below are getters, used in other pages of wizard so as to facilitate
* access to WSAuthentication instance and its settings.
*/
WSAuthentication* wsAuth() const;
QSettings* oauthSettings() const;
O0SettingsStore* oauthSettingsStore() const;
void setItemsList(const QList<QUrl>& urls);
public Q_SLOTS:
void slotBusy(bool val);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_WS_WIZARD_H
diff --git a/core/utilities/assistants/webservices/flickr/flickrlist.cpp b/core/utilities/assistants/webservices/flickr/flickrlist.cpp
index 2a46d848a7..e1ac5863ac 100644
--- a/core/utilities/assistants/webservices/flickr/flickrlist.cpp
+++ b/core/utilities/assistants/webservices/flickr/flickrlist.cpp
@@ -1,662 +1,662 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-05-22
* Description : Flickr/23HQ file list view and items.
*
* Copyright (C) 2009 by Pieter Edelman <pieter dot edelman at gmx dot net>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "flickrlist.h"
// Qt includes
#include <QApplication>
#include <QComboBox>
#include <QPainter>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "wscomboboxdelegate.h"
namespace Digikam
{
class Q_DECL_HIDDEN FlickrList::Private
{
public:
explicit Private()
{
isPublic = Qt::Unchecked;
isFamily = Qt::Unchecked;
isFriends = Qt::Unchecked;
safetyLevel = FlickrList::SAFE;
contentType = FlickrList::PHOTO;
is23 = false;
userIsEditing = false;
}
Qt::CheckState isPublic;
Qt::CheckState isFamily;
Qt::CheckState isFriends;
FlickrList::SafetyLevel safetyLevel;
FlickrList::ContentType contentType;
// Used to separate the ImagesList::itemChanged signals that were caused
// programmatically from those caused by the user.
bool userIsEditing;
bool is23;
};
FlickrList::FlickrList(QWidget* const parent, bool is_23)
: DImagesList(parent),
d(new Private)
{
d->is23 = is_23;
// Catch a click on the items.
connect(listView(), SIGNAL(itemClicked(QTreeWidgetItem*,int)),
this, SLOT(slotItemClicked(QTreeWidgetItem*,int)));
// Catch it if the items change.
connect(listView(), SIGNAL(itemChanged(QTreeWidgetItem*,int)),
this, SLOT(slotItemChanged(QTreeWidgetItem*,int)));
}
FlickrList::~FlickrList()
{
delete d;
}
void FlickrList::setPublic(Qt::CheckState isPublic)
{
/* Change the general public flag for photos in the list. */
d->isPublic = isPublic;
setPermissionState(PUBLIC, isPublic);
}
void FlickrList::setFamily(Qt::CheckState isFamily)
{
/* Change the general family flag for photos in the list. */
d->isFamily = isFamily;
setPermissionState(FAMILY, d->isFamily);
}
void FlickrList::setFriends(Qt::CheckState isFriends)
{
/* Change the general friends flag for photos in the list. */
d->isFriends = isFriends;
setPermissionState(FRIENDS, d->isFriends);
}
void FlickrList::setSafetyLevels(SafetyLevel safetyLevel)
{
/* Change the general safety level for photos in the list. */
d->safetyLevel = safetyLevel;
if (safetyLevel != MIXEDLEVELS)
{
for (int i = 0; i < listView()->topLevelItemCount(); ++i)
{
FlickrListViewItem* const lvItem = dynamic_cast<FlickrListViewItem*>(listView()->topLevelItem(i));
if (lvItem)
lvItem->setSafetyLevel(d->safetyLevel);
}
}
}
void FlickrList::setContentTypes(ContentType contentType)
{
/* Change the general content type for photos in the list. */
d->contentType = contentType;
if (contentType != MIXEDTYPES)
{
for (int i = 0; i < listView()->topLevelItemCount(); ++i)
{
FlickrListViewItem* const lvItem = dynamic_cast<FlickrListViewItem*>(listView()->topLevelItem(i));
if (lvItem)
lvItem->setContentType(d->contentType);
}
}
}
void FlickrList::setPermissionState(FieldType type, Qt::CheckState state)
{
/* When the state of one of the three permission levels changes, distribute
* the global change to each individual photo. */
if (state != Qt::PartiallyChecked)
{
for (int i = 0; i < listView()->topLevelItemCount(); ++i)
{
FlickrListViewItem* const lvItem = dynamic_cast<FlickrListViewItem*>(listView()->topLevelItem(i));
if (lvItem)
{
if (type == PUBLIC)
{
lvItem->setPublic(state);
}
else if (type == FAMILY)
{
lvItem->setFamily(state);
}
else if (type == FRIENDS)
{
lvItem->setFriends(state);
}
}
}
}
}
void FlickrList::slotItemClicked(QTreeWidgetItem* item, int column)
{
// If a click occurs from one of the three permission checkbox columns,
// it means something has changed in the permissions.
if ((column == PUBLIC) || (column == FAMILY) || (column == FRIENDS))
{
singlePermissionChanged(item, column);
}
// If a click occurs in the Safety Level or Content Type column, it means
// that editing should start on these items.
else if ((column == static_cast<int>(FlickrList::SAFETYLEVEL)) || (column == static_cast<int>(FlickrList::CONTENTTYPE)))
{
d->userIsEditing = true;
ComboBoxDelegate* const cbDelegate = dynamic_cast<ComboBoxDelegate*>(listView()->itemDelegateForColumn(column));
if (cbDelegate)
{
cbDelegate->startEditing(item, column);
}
}
}
void FlickrList::slotItemChanged(QTreeWidgetItem* item, int column)
{
// If an item in the Safety Level or Content Type column changes, it should
// be distributed further.
if ((column == SAFETYLEVEL) || (column == CONTENTTYPE))
{
singleComboBoxChanged(item, column);
}
}
void FlickrList::singlePermissionChanged(QTreeWidgetItem* item, int column)
{
/* Callback for when the user clicks a checkbox in one of the permission
* columns. */
if ((column == PUBLIC) || (column == FAMILY) || (column == FRIENDS))
{
// Call the toggled() method of the item on which the selection
// occurred.
FlickrListViewItem* const lvItem = dynamic_cast<FlickrListViewItem*>(item);
if (lvItem)
{
lvItem->toggled();
// Count the number of set checkboxes for the selected column.
int numChecked = 0;
for (int i = 0; i < listView()->topLevelItemCount(); ++i)
{
FlickrListViewItem* const titem = dynamic_cast<FlickrListViewItem*>(listView()->topLevelItem(i));
if (titem)
{
if (((column == PUBLIC) && (titem->isPublic())) ||
((column == FAMILY) && (titem->isFamily())) ||
((column == FRIENDS) && (titem->isFriends())))
{
numChecked += 1;
}
}
}
// Determine the new state.
Qt::CheckState state = Qt::PartiallyChecked;
if (numChecked == 0)
{
state = Qt::Unchecked;
}
else if (numChecked == listView()->topLevelItemCount())
{
state = Qt::Checked;
}
// If needed, signal the change.
if ((column == PUBLIC) && (state != d->isPublic))
{
setPublic(state);
emit signalPermissionChanged(PUBLIC, state);
}
if ((column == FAMILY) && (state != d->isFamily))
{
setFamily(state);
emit signalPermissionChanged(FAMILY, state);
}
if ((column == FRIENDS) && (state != d->isFriends))
{
setFriends(state);
emit signalPermissionChanged(FRIENDS, state);
}
}
}
}
void FlickrList::singleComboBoxChanged(QTreeWidgetItem* item, int column)
{
/* Callback for when one of the comboboxes for Safety Level or Content
* Type changes. */
// Make sure to only process changes from user editing, because this
// function also responds to programmatic changes, which it causes itself
// again.
if (((column == SAFETYLEVEL) || (column == CONTENTTYPE)) && d->userIsEditing)
{
// The user has stopped editing.
d->userIsEditing = false;
// Convert the value from the model to the setting for the
// FlickrListViewItem.
FlickrListViewItem* const lvItem = dynamic_cast<FlickrListViewItem*>(item);
if (lvItem)
{
int data = lvItem->data(column, Qt::DisplayRole).toInt();
if (column == SAFETYLEVEL)
{
lvItem->setSafetyLevel(static_cast<SafetyLevel>(data));
}
else if (column == CONTENTTYPE)
{
lvItem->setContentType(static_cast<ContentType>(data));
}
// Determine how much photos are set to different Safety Levels/Content
// Types.
QMap<int, int> nums = QMap<int, int>();
for (int i = 0; i < listView()->topLevelItemCount(); ++i)
{
FlickrListViewItem* const titem = dynamic_cast<FlickrListViewItem*>(listView()->topLevelItem(i));
if (titem)
{
if (column == SAFETYLEVEL)
{
nums[lvItem->safetyLevel()]++;
}
else if (column == CONTENTTYPE)
{
nums[lvItem->contentType()]++;
}
}
}
// If there's only one Safety Level or Content Type, make everything
// uniform
if (nums.count() == 1)
{
QMapIterator<int, int> i(nums);
i.next();
if (column == SAFETYLEVEL)
{
SafetyLevel safetyLevel = static_cast<SafetyLevel>(i.key());
setSafetyLevels(safetyLevel);
emit signalSafetyLevelChanged(safetyLevel);
}
else if (column == CONTENTTYPE)
{
ContentType contentType = static_cast<ContentType>(i.key());
setContentTypes(contentType);
emit signalContentTypeChanged(contentType);
}
}
// If there are different Safety Levels/Content Types among the photos,
// signal that.
else
{
if (column == SAFETYLEVEL)
{
setSafetyLevels(MIXEDLEVELS);
emit signalSafetyLevelChanged(MIXEDLEVELS);
}
else if (column == CONTENTTYPE)
{
setContentTypes(MIXEDTYPES);
emit signalContentTypeChanged(MIXEDTYPES);
}
}
}
}
}
void FlickrList::slotAddImages(const QList<QUrl>& list)
{
/* Replaces the ImagesList::slotAddImages method, so that
* FlickrListViewItems can be added instead of ImagesListViewItems */
// Figure out which permissions should be used. If permissions are set to
// intermediate, default to the most public option.
bool isPublic, isFamily, isFriends;
(d->isPublic == Qt::PartiallyChecked) ? isPublic = true : isPublic = d->isPublic;
(d->isFamily == Qt::PartiallyChecked) ? isFamily = true : isFamily = d->isFamily;
(d->isFriends == Qt::PartiallyChecked) ? isFriends = true : isFriends = d->isFriends;
// Figure out safety level and content type. If these are intermediate, use
// the Flickr defaults.
SafetyLevel safetyLevel;
ContentType contentType;
(d->safetyLevel == MIXEDLEVELS) ? safetyLevel = SAFE : safetyLevel = d->safetyLevel;
(d->contentType == MIXEDTYPES) ? contentType = PHOTO : contentType = d->contentType;
// Figure out which of the supplied URL's should actually be added and which
// of them already exist.
bool found;
QList<QUrl> added_urls;
QList<QUrl>::const_iterator it;
for (it = list.constBegin(); it != list.constEnd(); ++it)
{
QUrl imageUrl = *it;
found = false;
for (int i = 0; i < listView()->topLevelItemCount(); ++i)
{
FlickrListViewItem* const currItem = dynamic_cast<FlickrListViewItem*>(listView()->topLevelItem(i));
if (currItem && currItem->url() == imageUrl)
{
found = true;
break;
}
}
if (!found)
{
- qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Insterting new item " << imageUrl.fileName();
+ qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Inserting new item " << imageUrl.fileName();
new FlickrListViewItem(listView(), imageUrl, d->is23,
isPublic, isFamily, isFriends,
safetyLevel, contentType);
added_urls.append(imageUrl);
}
}
// Duplicate the signalImageListChanged of the ImageWindow, to enable the
// upload button again.
emit signalImageListChanged();
}
// ------------------------------------------------------------------------------------------------
class Q_DECL_HIDDEN FlickrListViewItem::Private
{
public:
explicit Private()
{
is23 = false;
isPublic = true;
isFamily = true;
isFriends = true;
safetyLevel = FlickrList::SAFE;
contentType = FlickrList::PHOTO;
tagLineEdit = 0;
}
bool is23;
bool isPublic;
bool isFamily;
bool isFriends;
FlickrList::SafetyLevel safetyLevel;
FlickrList::ContentType contentType;
/** LineEdit used for extra tags per image.
*/
QLineEdit* tagLineEdit;
};
FlickrListViewItem::FlickrListViewItem(DImagesListView* const view,
const QUrl& url,
bool is23 = false,
bool accessPublic = true,
bool accessFamily = true,
bool accessFriends = true,
FlickrList::SafetyLevel safetyLevel = FlickrList::SAFE,
FlickrList::ContentType contentType = FlickrList::PHOTO)
: DImagesListViewItem(view, url),
d(new Private)
{
d->is23 = is23;
/* Initialize the FlickrListViewItem with the ImagesListView and a QUrl
* object pointing to the location on disk.
* If the photo is meant for 23HQ, the service_23 flag should be set to
* true.
* The access_public, access_family and access_friends flags determine if
* the public, family and friends permissions of this particular photo. */
// Set the flags for checkboxes to appear
setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsSelectable | Qt::ItemIsEnabled);
// Set the text and checkbox for the public column.
setCheckState(static_cast<DImagesListView::ColumnType>(FlickrList::PUBLIC), accessPublic ? Qt::Checked : Qt::Unchecked);
// Set the tooltips to guide the user to the mass settings options.
setToolTip(static_cast<DImagesListView::ColumnType>(FlickrList::PUBLIC),
i18n("Check if photo should be publicly visible or use Upload "
"Options tab to specify this for all images"));
setToolTip(static_cast<DImagesListView::ColumnType>(FlickrList::FAMILY),
i18n("Check if photo should be visible to family or use Upload "
"Options tab to specify this for all images"));
setToolTip(static_cast<DImagesListView::ColumnType>(FlickrList::FRIENDS),
i18n("Check if photo should be visible to friends or use "
"Upload Options tab to specify this for all images"));
setToolTip(static_cast<DImagesListView::ColumnType>(FlickrList::SAFETYLEVEL),
i18n("Indicate the safety level for the photo or use Upload "
"Options tab to specify this for all images"));
setToolTip(static_cast<DImagesListView::ColumnType>(FlickrList::CONTENTTYPE),
i18n("Indicate what kind of image this is or use Upload "
"Options tab to specify this for all images"));
// Set the other checkboxes.
setFamily(accessFamily);
setFriends(accessFriends);
setPublic(accessPublic);
setSafetyLevel(safetyLevel);
setContentType(contentType);
// Extra per image tags handling.
setToolTip(static_cast<DImagesListView::ColumnType>(
FlickrList::TAGS),
i18n("Add extra tags per image or use Upload Options tab to "
"add tags for all images"));
//d->tagLineEdit = new QLineEdit(view);
//d->tagLineEdit->setToolTip(i18n("Enter extra tags, separated by commas."));
//view->setItemWidget(this, static_cast<DImagesListView::ColumnType>(
// FlickrList::TAGS), d->tagLineEdit);
updateItemWidgets();
}
FlickrListViewItem::~FlickrListViewItem()
{
delete d;
}
void FlickrListViewItem::updateItemWidgets()
{
d->tagLineEdit = new QLineEdit(view());
d->tagLineEdit->setToolTip(i18n("Enter extra tags, separated by commas."));
view()->setItemWidget(this, static_cast<DImagesListView::ColumnType>(
FlickrList::TAGS), d->tagLineEdit);
}
QStringList FlickrListViewItem::extraTags() const
{
return d->tagLineEdit->text().split(QLatin1Char(','), QString::SkipEmptyParts);
}
void FlickrListViewItem::toggled()
{
// The d->isFamily and d->isFriends states should be set first, so that the
// setPublic method has the proper values to work with.
if (!d->is23)
{
if (data(FlickrList::FAMILY, Qt::CheckStateRole) != QVariant())
{
setFamily(checkState(static_cast<DImagesListView::ColumnType>(FlickrList::FAMILY)));
}
if (data(FlickrList::FRIENDS, Qt::CheckStateRole) != QVariant())
{
setFriends(checkState(static_cast<DImagesListView::ColumnType>(FlickrList::FRIENDS)));
}
}
setPublic(checkState(static_cast<DImagesListView::ColumnType>(FlickrList::PUBLIC)));
}
void FlickrListViewItem::setPublic(bool status)
{
/* Set the public status of the entry. If public is true, hide the
* family and friends checkboxes, otherwise, make them appear. */
// Set the status.
d->isPublic = status;
// Toggle the family and friends checkboxes, if applicable.
if (!d->is23)
{
if (d->isPublic)
{
// Hide the checkboxes by feeding them a bogus QVariant for the
// CheckStateRole. This might seem like a hack, but it's described in
// the Qt FAQ at
// http://www.qtsoftware.com/developer/faqs/faq.2007-04-23.8353273326.
setData(static_cast<DImagesListView::ColumnType>(FlickrList::FAMILY), Qt::CheckStateRole, QVariant());
setData(static_cast<DImagesListView::ColumnType>(FlickrList::FRIENDS), Qt::CheckStateRole, QVariant());
}
else
{
// Show the checkboxes.
setCheckState(static_cast<DImagesListView::ColumnType>(FlickrList::FAMILY), d->isFamily ? Qt::Checked : Qt::Unchecked);
setCheckState(static_cast<DImagesListView::ColumnType>(FlickrList::FRIENDS), d->isFriends ? Qt::Checked : Qt::Unchecked);
}
}
// Toggle the public checkboxes
if (d->isPublic)
{
setCheckState(FlickrList::PUBLIC, Qt::Checked);
}
else
{
setCheckState(FlickrList::PUBLIC, Qt::Unchecked);
}
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Public status set to" << d->isPublic;
}
void FlickrListViewItem::setFamily(bool status)
{
/* Set the family status. */
d->isFamily = status;
if ((!d->is23) && (data(FlickrList::FAMILY, Qt::CheckStateRole) != QVariant()))
{
setCheckState(FlickrList::FAMILY, d->isFamily ? Qt::Checked : Qt::Unchecked);
}
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Family status set to" << d->isFamily;
}
void FlickrListViewItem::setFriends(bool status)
{
/* Set the family status. */
d->isFriends = status;
if ((!d->is23) && (data(FlickrList::FRIENDS, Qt::CheckStateRole) != QVariant()))
{
setCheckState(FlickrList::FRIENDS, d->isFriends ? Qt::Checked : Qt::Unchecked);
}
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Friends status set to" << d->isFriends;
}
void FlickrListViewItem::setSafetyLevel(FlickrList::SafetyLevel safetyLevel)
{
d->safetyLevel = safetyLevel;
setData(FlickrList::SAFETYLEVEL, Qt::DisplayRole, QVariant(safetyLevel));
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Safety level set to" << safetyLevel;
}
void FlickrListViewItem::setContentType(FlickrList::ContentType contentType)
{
d->contentType = contentType;
setData(FlickrList::CONTENTTYPE, Qt::DisplayRole, QVariant(contentType));
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Content type set to" << contentType;
}
bool FlickrListViewItem::isPublic() const
{
/* Return whether the photo is public. */
return d->isPublic;
}
bool FlickrListViewItem::isFamily() const
{
/* Return whether the photo is accessible for family. */
return d->isFamily;
}
bool FlickrListViewItem::isFriends() const
{
/* Return whether the photo is accessible for friends. */
return d->isFriends;
}
FlickrList::SafetyLevel FlickrListViewItem::safetyLevel() const
{
return d->safetyLevel;
}
FlickrList::ContentType FlickrListViewItem::contentType() const
{
return d->contentType;
}
} // namespace Digikam
diff --git a/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp b/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp
index ff4cbe5d34..ad72bc68f0 100644
--- a/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp
+++ b/core/utilities/assistants/webservices/google/gphoto/gptalker.cpp
@@ -1,935 +1,935 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-16-07
* Description : a tool to export items to Google web services
*
* Copyright (C) 2007-2008 by Vardhman Jain <vardhman at gmail dot com>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2009 by Luka Renko <lure at kubuntu dot org>
* Copyright (C) 2018 by Thanh Trung Dinh <dinhthanhtrung1996 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "gptalker.h"
// Qt includes
#include <QMimeDatabase>
#include <QByteArray>
#include <QDomDocument>
#include <QDomElement>
#include <QDomNode>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <QFile>
#include <QFileInfo>
#include <QImage>
#include <QStringList>
#include <QUrl>
#include <QtAlgorithms>
#include <QApplication>
#include <QDir>
#include <QMessageBox>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "wstoolutils.h"
#include "digikam_version.h"
#include "gswindow.h"
#include "gpmpform.h"
#include "digikam_debug.h"
#include "previewloadthread.h"
#include "dmetadata.h"
#define NB_MAX_ITEM_UPLOAD 50
namespace Digikam
{
static bool gphotoLessThan(const GSFolder& p1, const GSFolder& p2)
{
return (p1.title.toLower() < p2.title.toLower());
}
class Q_DECL_HIDDEN GPTalker::Private
{
public:
enum State
{
GP_LOGOUT = -1,
GP_LISTALBUMS = 0,
GP_GETUSER,
GP_LISTPHOTOS,
GP_ADDPHOTO,
GP_UPDATEPHOTO,
GP_UPLOADPHOTO,
GP_GETPHOTO,
GP_CREATEALBUM
};
public:
explicit Private()
{
state = GP_LOGOUT;
netMngr = 0;
userInfoUrl = QLatin1String("https://www.googleapis.com/plus/v1/people/me");
apiVersion = QLatin1String("v1");
apiUrl = QString::fromLatin1("https://photoslibrary.googleapis.com/%1/%2").arg(apiVersion);
albumIdToUpload = QLatin1String("-1");
previousImageId = QLatin1String("-1");
}
public:
QString userInfoUrl;
QString apiUrl;
QString apiVersion;
State state;
QString albumIdToUpload;
QString previousImageId;
QStringList uploadTokenList;
QNetworkAccessManager* netMngr;
};
GPTalker::GPTalker(QWidget* const parent)
: GSTalkerBase(parent,
(QStringList() << QLatin1String("https://www.googleapis.com/auth/plus.login") // to get user login (temporary until gphoto supports it officially)
<< QLatin1String("https://www.googleapis.com/auth/photoslibrary") // to add and download photo in the library
<< QLatin1String("https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata") // to download photo created by digiKam on GPhoto
<< QLatin1String(" https://www.googleapis.com/auth/photoslibrary.sharing")), // for shared albums
QLatin1String("GooglePhotos")),
d(new Private)
{
m_reply = 0;
d->netMngr = new QNetworkAccessManager(this);
connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
this, SLOT(slotFinished(QNetworkReply*)));
connect(this, SIGNAL(signalError(QString)),
this, SLOT(slotError(QString)));
connect(this, SIGNAL(signalReadyToUpload()),
this, SLOT(slotUploadPhoto()));
}
GPTalker::~GPTalker()
{
if (m_reply)
{
m_reply->abort();
}
WSToolUtils::removeTemporaryDir("google");
delete d;
}
QStringList GPTalker::getUploadTokenList()
{
return d->uploadTokenList;
}
/**
* (Trung): Comments below are not valid anymore with google photos api
* Google Photo's Album listing request/response
* First a request is sent to the url below and then we might(?) get a redirect URL
* We then need to send the GET request to the Redirect url.
* This uses the authenticated album list fetching to get all the albums included the unlisted-albums
* which is not returned for an unauthorised request as done without the Authorization header.
*/
void GPTalker::listAlbums()
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list albums";
QUrl url(d->apiUrl.arg("albums"));
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url for list albums " << url;
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
m_reply = d->netMngr->get(netRequest);
d->state = Private::GP_LISTALBUMS;
m_buffer.resize(0);
emit signalBusy(true);
}
/**
* We get user profile from Google Plus API
* This is a temporary solution until Google Photo support API for user profile
*/
void GPTalker::getLoggedInUser()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "getLoggedInUser";
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
QUrl url(d->userInfoUrl);
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "url for list albums " << url;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "m_accessToken " << m_accessToken;
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
m_reply = d->netMngr->get(netRequest);
d->state = Private::GP_GETUSER;
m_buffer.resize(0);
emit signalBusy(true);
}
void GPTalker::listPhotos(const QString& albumId, const QString& /*imgmax*/)
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
QUrl url(d->apiUrl.arg("mediaItems:search"));
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toUtf8());
QByteArray data;
data += "{\"pageSize\": \"100\",";
data += "\"albumId\":\"";
data += albumId.toUtf8();
data += "\"}";
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "data to list photos : " << QString(data);
m_reply = d->netMngr->post(netRequest, data);
d->state = Private::GP_LISTPHOTOS;
m_buffer.resize(0);
emit signalBusy(true);
}
void GPTalker::createAlbum(const GSFolder& album)
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
// Create body in json
QByteArray data;
data += "{\"album\":";
data += "{\"title\":\"";
data += album.title.toLatin1();
data += "\"}}";
qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString(data);
QUrl url(d->apiUrl.arg("albums"));
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
m_reply = d->netMngr->post(netRequest, data);
d->state = Private::GP_CREATEALBUM;
m_buffer.resize(0);
emit signalBusy(true);
}
/**
* First a request is sent to the url below and then we will get an upload token
- * Upload token then will be sent with url in GPTlaker::uploadPhoto to create real photos on user accont
+ * Upload token then will be sent with url in GPTlaker::uploadPhoto to create real photos on user account
*/
bool GPTalker::addPhoto(const QString& photoPath,
GSPhoto& /*info*/,
const QString& albumId,
bool rescale,
int maxDim,
int imageQuality)
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
QUrl url(d->apiUrl.arg("uploads"));
// Save album ID to upload
d->albumIdToUpload = albumId;
// GPMPForm form;
QString path = photoPath;
QMimeDatabase mimeDB;
if (mimeDB.mimeTypeForFile(path).name().startsWith(QLatin1String("image/")))
{
QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage();
if (image.isNull())
{
image.load(photoPath);
}
if (image.isNull())
{
return false;
}
path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(photoPath)
.baseName().trimmed() + QLatin1String(".jpg"));
int imgQualityToApply = 100;
if (rescale)
{
if (image.width() > maxDim || image.height() > maxDim)
image = image.scaled(maxDim, maxDim, Qt::KeepAspectRatio, Qt::SmoothTransformation);
imgQualityToApply = imageQuality;
}
image.save(path, "JPEG", imgQualityToApply);
DMetadata meta;
if (meta.load(photoPath))
{
meta.setImageDimensions(image.size());
meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL);
meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion());
meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY);
meta.save(path);
}
}
// Create the body for temporary upload
QFile imageFile(path);
if (!imageFile.open(QIODevice::ReadOnly))
{
return false;
}
QByteArray data = imageFile.readAll();
imageFile.close();
QString imageName = QUrl::fromLocalFile(path).fileName().toLatin1();
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/octet-stream"));
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
netRequest.setRawHeader("X-Goog-Upload-File-Name", imageName.toLatin1());
qCDebug(DIGIKAM_WEBSERVICES_LOG) << imageName;
m_reply = d->netMngr->post(netRequest, data);
d->state = Private::GP_ADDPHOTO;
m_buffer.resize(0);
emit signalBusy(true);
return true;
}
bool GPTalker::updatePhoto(const QString& photoPath, GSPhoto& info/*, const QString& albumId*/,
bool rescale, int maxDim, int imageQuality)
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
emit signalBusy(true);
GPMPForm form;
QString path = photoPath;
QMimeDatabase mimeDB;
if (mimeDB.mimeTypeForFile(path).name().startsWith(QLatin1String("image/")))
{
QImage image = PreviewLoadThread::loadHighQualitySynchronously(photoPath).copyQImage();
if (image.isNull())
{
image.load(photoPath);
}
if (image.isNull())
{
emit signalBusy(false);
return false;
}
path = WSToolUtils::makeTemporaryDir("google").filePath(QFileInfo(photoPath)
.baseName().trimmed() + QLatin1String(".jpg"));
int imgQualityToApply = 100;
if (rescale)
{
if (image.width() > maxDim || image.height() > maxDim)
image = image.scaled(maxDim,maxDim, Qt::KeepAspectRatio,Qt::SmoothTransformation);
imgQualityToApply = imageQuality;
}
image.save(path, "JPEG", imgQualityToApply);
DMetadata meta;
if (meta.load(photoPath))
{
meta.setImageDimensions(image.size());
meta.setImageOrientation(MetaEngine::ORIENTATION_NORMAL);
meta.setImageProgramId(QLatin1String("digiKam"), digiKamVersion());
meta.setMetadataWritingMode((int)DMetadata::WRITETOIMAGEONLY);
meta.save(path);
}
}
//Create the Body in atom-xml
QDomDocument docMeta;
QDomProcessingInstruction instr = docMeta.createProcessingInstruction(
QLatin1String("xml"),
QLatin1String("version='1.0' encoding='UTF-8'"));
docMeta.appendChild(instr);
QDomElement entryElem = docMeta.createElement(QLatin1String("entry"));
docMeta.appendChild(entryElem);
entryElem.setAttribute(
QLatin1String("xmlns"),
QLatin1String("http://www.w3.org/2005/Atom"));
QDomElement titleElem = docMeta.createElement(QLatin1String("title"));
entryElem.appendChild(titleElem);
QDomText titleText = docMeta.createTextNode(QFileInfo(path).fileName());
titleElem.appendChild(titleText);
QDomElement summaryElem = docMeta.createElement(QLatin1String("summary"));
entryElem.appendChild(summaryElem);
QDomText summaryText = docMeta.createTextNode(info.description);
summaryElem.appendChild(summaryText);
QDomElement categoryElem = docMeta.createElement(QLatin1String("category"));
entryElem.appendChild(categoryElem);
categoryElem.setAttribute(
QLatin1String("scheme"),
QLatin1String("http://schemas.google.com/g/2005#kind"));
categoryElem.setAttribute(
QLatin1String("term"),
QLatin1String("http://schemas.google.com/photos/2007#photo"));
QDomElement mediaGroupElem = docMeta.createElementNS(
QLatin1String("http://search.yahoo.com/mrss/"),
QLatin1String("media:group"));
entryElem.appendChild(mediaGroupElem);
QDomElement mediaKeywordsElem = docMeta.createElementNS(
QLatin1String("http://search.yahoo.com/mrss/"),
QLatin1String("media:keywords"));
mediaGroupElem.appendChild(mediaKeywordsElem);
QDomText mediaKeywordsText = docMeta.createTextNode(info.tags.join(QLatin1Char(',')));
mediaKeywordsElem.appendChild(mediaKeywordsText);
if (!info.gpsLat.isEmpty() && !info.gpsLon.isEmpty())
{
QDomElement whereElem = docMeta.createElementNS(
QLatin1String("http://www.georss.org/georss"),
QLatin1String("georss:where"));
entryElem.appendChild(whereElem);
QDomElement pointElem = docMeta.createElementNS(
QLatin1String("http://www.opengis.net/gml"),
QLatin1String("gml:Point"));
whereElem.appendChild(pointElem);
QDomElement gpsElem = docMeta.createElementNS(
QLatin1String("http://www.opengis.net/gml"),
QLatin1String("gml:pos"));
pointElem.appendChild(gpsElem);
QDomText gpsVal = docMeta.createTextNode(info.gpsLat + QLatin1Char(' ') + info.gpsLon);
gpsElem.appendChild(gpsVal);
}
form.addPair(QLatin1String("descr"), docMeta.toString(), QLatin1String("application/atom+xml"));
if (!form.addFile(QLatin1String("photo"), path))
{
emit signalBusy(false);
return false;
}
form.finish();
QNetworkRequest netRequest(info.editUrl);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, form.contentType());
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1() + "\nIf-Match: *");
m_reply = d->netMngr->put(netRequest, form.formData());
d->state = Private::GP_UPDATEPHOTO;
m_buffer.resize(0);
return true;
}
void GPTalker::getPhoto(const QString& imgPath)
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
emit signalBusy(true);
QUrl url(imgPath);
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "link to get photo " << url.url();
m_reply = d->netMngr->get(QNetworkRequest(url));
d->state = Private::GP_GETPHOTO;
m_buffer.resize(0);
}
void GPTalker::cancel()
{
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
emit signalBusy(false);
}
void GPTalker::slotError(const QString & error)
{
QString transError;
int errorNo = 0;
if (!error.isEmpty())
errorNo = error.toInt();
switch (errorNo)
{
case 2:
transError=i18n("No photo specified");
break;
case 3:
transError=i18n("General upload failure");
break;
case 4:
transError=i18n("File-size was zero");
break;
case 5:
transError=i18n("File-type was not recognized");
break;
case 6:
transError=i18n("User exceeded upload limit");
break;
case 96:
transError=i18n("Invalid signature");
break;
case 97:
transError=i18n("Missing signature");
break;
case 98:
transError=i18n("Login failed / Invalid auth token");
break;
case 100:
transError=i18n("Invalid API Key");
break;
case 105:
transError=i18n("Service currently unavailable");
break;
case 108:
transError=i18n("Invalid Frob");
break;
case 111:
transError=i18n("Format \"xxx\" not found");
break;
case 112:
transError=i18n("Method \"xxx\" not found");
break;
case 114:
transError=i18n("Invalid SOAP envelope");
break;
case 115:
transError=i18n("Invalid XML-RPC Method Call");
break;
case 116:
transError=i18n("The POST method is now required for all setters.");
break;
default:
transError=i18n("Unknown error");
};
QMessageBox::critical(QApplication::activeWindow(), i18nc("@title:window", "Error"),
i18n("Error occurred: %1\nUnable to proceed further.",transError + error));
}
void GPTalker::slotFinished(QNetworkReply* reply)
{
emit signalBusy(false);
if (reply != m_reply)
{
return;
}
m_reply = 0;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "reply error : " << reply->error() << " - " << reply->errorString();
if (reply->error() != QNetworkReply::NoError)
{
if (d->state == Private::GP_ADDPHOTO)
{
emit signalAddPhotoDone(reply->error(), reply->errorString());
}
else
{
QMessageBox::critical(QApplication::activeWindow(),
i18n("Error"), reply->errorString());
}
reply->deleteLater();
return;
}
m_buffer.append(reply->readAll());
switch (d->state)
{
case (Private::GP_LOGOUT):
break;
case (Private::GP_GETUSER):
parseResponseGetLoggedInUser(m_buffer);
break;
case (Private::GP_CREATEALBUM):
parseResponseCreateAlbum(m_buffer);
break;
case (Private::GP_LISTALBUMS):
parseResponseListAlbums(m_buffer);
break;
case (Private::GP_LISTPHOTOS):
parseResponseListPhotos(m_buffer);
break;
case (Private::GP_ADDPHOTO):
parseResponseAddPhoto(m_buffer);
break;
case (Private::GP_UPDATEPHOTO):
emit signalAddPhotoDone(1, QLatin1String(""));
break;
case (Private::GP_UPLOADPHOTO):
parseResponseUploadPhoto(m_buffer);
break;
case (Private::GP_GETPHOTO):
qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString(m_buffer);
// all we get is data of the image
emit signalGetPhotoDone(1, QString(), m_buffer);
break;
}
reply->deleteLater();
}
void GPTalker::slotUploadPhoto()
{
/* Keep track of number of items will be uploaded, because
* Google Photo API upload maximum NB_MAX_ITEM_UPLOAD items in at a time
*/
int nbItemsUpload = 0;
if (m_reply)
{
m_reply->abort();
m_reply = 0;
}
QUrl url(d->apiUrl.arg("mediaItems:batchCreate"));
QByteArray data;
data += '{';
if (d->albumIdToUpload != QLatin1String("-1"))
{
data += "\"albumId\": \"";
data += d->albumIdToUpload.toLatin1();
data += "\",";
}
data += "\"newMediaItems\": [";
if (d->uploadTokenList.isEmpty())
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "token list is empty";
}
while (!d->uploadTokenList.isEmpty() && nbItemsUpload < NB_MAX_ITEM_UPLOAD)
{
const QString& uploadToken = d->uploadTokenList.takeFirst();
data += "{\"description\": \"\",";
data += "\"simpleMediaItem\": {";
data += "\"uploadToken\": \"";
data += uploadToken;
data += "\"}}";
if (d->uploadTokenList.length() > 0)
{
data += ',';
}
nbItemsUpload ++;
}
if (d->previousImageId == QLatin1String("-1"))
{
data += ']';
}
else
{
data += "],\"albumPosition\": {";
data += "\"position\": \"AFTER_MEDIA_ITEM\",";
data += "\"relativeMediaItemId\": \"";
data += d->previousImageId.toLatin1();
data += "\"}\r\n";
}
data += "}\r\n";
qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString(data);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json"));
netRequest.setRawHeader("Authorization", m_bearerAccessToken.toLatin1());
m_reply = d->netMngr->post(netRequest, data);
d->state = Private::GP_UPLOADPHOTO;
m_buffer.resize(0);
emit signalBusy(true);
}
void GPTalker::parseResponseListAlbums(const QByteArray& data)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListAlbums";
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError)
{
emit signalBusy(false);
emit signalListAlbumsDone(0, QString::fromLatin1("Code: %1 - %2").arg(err.error)
.arg(err.errorString()),
QList<GSFolder>());
return;
}
QJsonObject jsonObject = doc.object();
QJsonArray jsonArray = jsonObject[QLatin1String("albums")].toArray();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "json array " << doc;
QList<GSFolder> albumList;
/**
* Google-photos allows user to post photos on their main page (not in any albums)
* so this folder is created for that purpose
*/
GSFolder mainPage;
albumList.append(mainPage);
foreach (const QJsonValue& value, jsonArray)
{
QJsonObject obj = value.toObject();
GSFolder album;
album.id = obj[QLatin1String("id")].toString();
album.title = obj[QLatin1String("title")].toString();
album.url = obj[QLatin1String("productUrl")].toString();
albumList.append(album);
}
std::sort(albumList.begin(), albumList.end(), gphotoLessThan);
emit signalListAlbumsDone(1, QLatin1String(""), albumList);
}
void GPTalker::parseResponseListPhotos(const QByteArray& data)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseListPhotos";
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError)
{
emit signalBusy(false);
emit signalListPhotosDone(0, i18n("Failed to fetch photo-set list"), QList<GSPhoto>());
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "error code: " << err.error << ", msg: " << err.errorString();
return;
}
QJsonObject jsonObject = doc.object();
QJsonArray jsonArray = jsonObject[QLatin1String("mediaItems")].toArray();
QList<GSPhoto> photoList;
foreach (const QJsonValue& value, jsonArray)
{
QJsonObject obj = value.toObject();
GSPhoto photo;
photo.baseUrl = obj[QLatin1String("baseUrl")].toString();
photo.description = obj[QLatin1String("description")].toString();
photo.id = obj[QLatin1String("id")].toString();
photo.mimeType = obj[QLatin1String("mimeType")].toString();
photo.location = obj[QLatin1String("Location")].toString(); // Not yet available in v1 but will be in the future
QJsonObject metadata = obj[QLatin1String("mediaMetadata")].toObject();
photo.creationTime = metadata[QLatin1String("creationTime")].toString();
photo.width = metadata[QLatin1String("width")].toString();
photo.height = metadata[QLatin1String("height")].toString();
photo.originalURL = QUrl(photo.baseUrl + QString::fromLatin1("=w%1-h%2").arg(photo.width)
.arg(photo.height));
qCDebug(DIGIKAM_WEBSERVICES_LOG) << photo.originalURL.url();
photoList.append(photo);
}
emit signalListPhotosDone(1, QLatin1String(""), photoList);
}
void GPTalker::parseResponseCreateAlbum(const QByteArray& data)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseCreateAlbums";
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError)
{
emit signalBusy(false);
emit signalCreateAlbumDone(0, QString::fromLatin1("Code: %1 - %2").arg(err.error)
.arg(err.errorString()),
QString());
return;
}
QJsonObject jsonObject = doc.object();
QString albumId = jsonObject[QLatin1String("id")].toString();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "album Id " << doc;
emit signalCreateAlbumDone(1, QLatin1String(""), albumId);
}
void GPTalker::parseResponseAddPhoto(const QByteArray& data)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseAddPhoto";
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "response " << QString(data);
d->uploadTokenList << QString(data);
emit signalAddPhotoDone(1, QLatin1String(""));
}
void GPTalker::parseResponseGetLoggedInUser(const QByteArray& data)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseGetLoggedInUser";
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
if (err.error != QJsonParseError::NoError)
{
emit signalBusy(false);
return;
}
QJsonObject jsonObject = doc.object();
QString userName = jsonObject[QLatin1String("displayName")].toString();
emit signalSetUserName(userName);
listAlbums();
}
//TODO: Parse and return photoID
void GPTalker::parseResponseUploadPhoto(const QByteArray& data)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "parseResponseUploadPhoto";
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(data, &err);
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "doc " << doc;
if (err.error != QJsonParseError::NoError)
{
emit signalBusy(false);
emit signalUploadPhotoDone(0, err.errorString(), QStringList());
return;
}
QJsonObject jsonObject = doc.object();
QJsonArray jsonArray = jsonObject[QLatin1String("newMediaItemResults")].toArray();
QStringList listPhotoId;
foreach (const QJsonValue& value, jsonArray)
{
QJsonObject obj = value.toObject();
QJsonObject mediaItem = obj[QLatin1String("mediaItem")].toObject();
listPhotoId << mediaItem[QLatin1String("id")].toString();
}
d->previousImageId = listPhotoId.last();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "list photo Id " << listPhotoId.join(", ");
emit signalBusy(false);
emit signalUploadPhotoDone(1, QLatin1String(""), listPhotoId);
}
} // namespace Digikam
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_generalinfo.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_generalinfo.h
index c4dd7e6f83..a233029b23 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_generalinfo.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_generalinfo.h
@@ -1,361 +1,361 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Manuel Campomanes <campomanes dot manuel at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_GENERALINFO_H
#define DIGIKAM_MEDIAWIKI_GENERALINFO_H
// Qt includes
#include <QDateTime>
#include <QString>
#include <QUrl>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief A general info.
*/
class DIGIKAM_EXPORT Generalinfo
{
public:
/**
* @brief Constructs a general info.
*/
Generalinfo();
/**
* @brief Constructs a generalinfo from an other generalinfo.
* @param other an other generalinfo
*/
Generalinfo(const Generalinfo& other);
/**
* @brief Destructs a general info.
*/
~Generalinfo();
/**
- * @brief Assingning an image from an other image.
+ * @brief Assigning an image from an other image.
* @param other an other image
*/
Generalinfo& operator=(const Generalinfo& other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const Generalinfo& other) const;
/**
* @brief Get the name of the main page.
* @return the name of the main page
*/
QString mainPage() const;
/**
* @brief Set the name of the main page.
* @param mainPage the name of the main page
*/
void setMainPage(const QString& mainPage);
/**
* @brief Get the url of the page.
* @return the url of the page
*/
QUrl url() const;
/**
* @brief Set the url of the page.
* @param url the url of the page
*/
void setUrl(const QUrl& url);
/**
* @brief Get the name of the web site.
* @return the name of the web site
*/
QString siteName() const;
/**
* @brief Set the name of the web site.
* @param siteName the name of the web site
*/
void setSiteName(const QString& siteName);
/**
* @brief Get the generator.
* @return the generator
*/
QString generator() const;
/**
* @brief Set the generator.
* @param generator
*/
void setGenerator(const QString& generator);
/**
* @brief Get the PHP version.
* @return the PHP version
*/
QString phpVersion() const;
/**
* @brief Set the PHP version.
* @param phpVersion the PHP version
*/
void setPhpVersion(const QString& phpVersion);
/**
* @brief Get the PHP API name.
* @return the PHP API name
*/
QString phpApi() const;
/**
* @brief Set the PHP API name.
* @param phpApi the PHP API name
*/
void setPhpApi(const QString& phpApi);
/**
* @brief Get the type of the database.
* @return the type of the database
*/
QString dataBaseType() const;
/**
* @brief Set the type of the database.
* @param dataBaseType the type of the database
*/
void setDataBaseType(const QString& dataBaseType);
/**
* @brief Get the version of the database.
* @return the version of the database
*/
QString dataBaseVersion() const;
/**
* @brief Set the version of the database.
* @param dataBaseVersion the version of the database
*/
void setDataBaseVersion(const QString& dataBaseVersion);
/**
* @brief Get the rev number.
* @return the rev number
*/
QString rev() const;
/**
* @brief Set the rev number.
* @param rev the rev number
*/
void setRev(const QString& rev);
/**
* @brief Get the case.
* @return the case
*/
QString cas() const;
/**
* @brief Set the case.
* @param cas the case
*/
void setCas(const QString& cas);
/**
* @brief Get the license.
* @return the license
*/
QString license() const;
/**
* @brief Set the license.
* @param license the license
*/
void setLicense(const QString& license);
/**
* @brief Get the language.
* @return the language
*/
QString language() const;
/**
* @brief Set the language.
* @param language
*/
void setLanguage(const QString& language);
/**
* @brief Get the fallBack8bitEncoding.
* @return the fallBack8bitEncoding
*/
QString fallBack8bitEncoding() const;
/**
* @brief Set the fallBack8bitEncoding.
* @param fallBack8bitEncoding
*/
void setFallBack8bitEncoding(const QString& fallBack8bitEncoding);
/**
* @brief Get the writeApi.
* @return the writeApi
*/
QString writeApi() const;
/**
* @brief Set the writeApi.
* @param writeApi
*/
void setWriteApi(const QString& writeApi);
/**
* @brief Get the timeZone.
* @return the timeZone
*/
QString timeZone() const;
/**
* @brief Set the timeZone.
* @param timeZone
*/
void setTimeZone(const QString& timeZone);
/**
* @brief Get the timeOffset.
* @return the timeOffset
*/
QString timeOffset() const;
/**
* @brief Set the timeOffset.
* @param timeOffset
*/
void setTimeOffset(const QString& timeOffset);
/**
* @brief Get the path of the article.
* @return the path of the article
*/
QString articlePath() const;
/**
* @brief Set the path of the article.
* @param articlePath the path of the article
*/
void setArticlePath(const QString& articlePath);
/**
* @brief Get the path of the script.
* @return the path of the script
*/
QString scriptPath() const;
/**
* @brief Set the path of the script.
* @param scriptPath the path of the script
*/
void setScriptPath(const QString& scriptPath);
/**
* @brief Get the path of the script file.
* @return the path of the script file
*/
QString script() const;
/**
* @brief Set the path of the script file.
* @param script the path of the script file
*/
void setScript(const QString& script);
/**
* @brief Get the path of the variant article.
* @return the path of the variant article
*/
QString variantArticlePath() const;
/**
* @brief Set the path of the variant article.
* @param variantArticlePath the path of the variant article
*/
void setVariantArticlePath(const QString& variantArticlePath);
/**
* @brief Get the url of the server.
* @return the url of the server
*/
QUrl serverUrl() const;
/**
* @brief Set the url of the server.
* @param serverUrl the url of the server
*/
void setServerUrl(const QUrl& serverUrl);
/**
* @brief Get the id of the wiki.
* @return the id of the wiki
*/
QString wikiId() const;
/**
* @brief Set the id of the wiki.
* @param wikiId the id of the wiki
*/
void setWikiId(const QString& wikiId);
/**
* @brief Get the time.
* @return the time
*/
QDateTime time() const;
/**
* @brief Set the time.
* @param time
*/
void setTime(const QDateTime& time);
private:
class Private;
Private* const d;
};
} // namespace MediaWiki
#endif // DIGIKAM_MEDIAWIKI_GENERALINFO_H
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_image.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_image.h
index 20756c449f..384c045fd2 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_image.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_image.h
@@ -1,107 +1,107 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Ludovic Delfau <ludovicdelfau at gmail dot com>
* Copyright (C) 2011 by Paolo de Vathaire <paolo dot devathaire at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_IMAGE_H
#define DIGIKAM_MEDIAWIKI_IMAGE_H
// Qt includes
#include <QString>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief A image.
*/
class DIGIKAM_EXPORT Image
{
public:
/**
* @brief Constructs a image.
*/
Image();
/**
* @brief Constructs an image from an other image.
* @param other an other image
*/
Image(const Image& other);
/**
* @brief Destructs an image.
*/
~Image();
/**
- * @brief Assingning an image from an other image.
+ * @brief Assigning an image from an other image.
* @param other an other image
*/
Image& operator=(Image other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const Image& other) const;
/**
* @brief Returns the namespace id of the image.
* @return the namespace id of the image
*/
qint64 namespaceId() const;
/**
* @brief Set the namespace id.
* @param namespaceId the namespace id of the image
*/
void setNamespaceId(qint64 namespaceId);
/**
* @brief Returns the title of the image.
* @return the title of the image
*/
QString title() const;
/**
* @brief Set the title.
* @param title the title of the image
*/
void setTitle(const QString& title);
private:
class Private;
Private* const d;
};
} // namespace MediaWiki
#endif // DIGIKAM_MEDIAWIKI_IMAGE_H
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_imageinfo.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_imageinfo.h
index a21450f994..286d83ccb3 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_imageinfo.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_imageinfo.h
@@ -1,260 +1,260 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Ludovic Delfau <ludovicdelfau at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_IMAGEINFO_H
#define DIGIKAM_MEDIAWIKI_IMAGEINFO_H
// Qt includes
#include <QDateTime>
#include <QHash>
#include <QString>
#include <QUrl>
#include <QVariant>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief An image info.
*/
class DIGIKAM_EXPORT Imageinfo
{
public:
/**
* @brief Constructs an image info.
*/
Imageinfo();
/**
* @brief Constructs an image info from an other image info.
* @param other an other image info
*/
Imageinfo(const Imageinfo& other);
/**
* @brief Destructs an image info.
*/
~Imageinfo();
/**
- * @brief Assingning an image info from an other image info.
+ * @brief Assigning an image info from an other image info.
* @param other an other image info
*/
Imageinfo& operator=(Imageinfo other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const Imageinfo& other) const;
/**
* @brief Get the time and date of the revision.
* @return the time and date of the revision
*/
QDateTime timestamp() const;
/**
* @brief Set the time and date of the revision.
* @param timestamp the time and date of the revision
*/
void setTimestamp(const QDateTime& timestamp);
/**
* @brief Get the user who made the revision.
* @return the user who made the revision
*/
QString user() const;
/**
* @brief Set the user who made the revision.
* @param user the user who made the revision
*/
void setUser(const QString& user);
/**
* @brief Get the edit comment.
* @return the edit comment
*/
QString comment() const;
/**
* @brief Set the edit comment.
* @param comment the edit comment
*/
void setComment(const QString& comment);
/**
* @brief Get the URL of the image.
* @return the URL of the image
*/
QUrl url() const;
/**
* @brief Set the URL of the image.
* @param url the URL of the image
*/
void setUrl(const QUrl& url);
/**
* @brief Get the description URL of the image.
* @return the description URL of the image
*/
QUrl descriptionUrl() const;
/**
* @brief Set the description URL of the image.
* @param descriptionUrl the description URL of the image
*/
void setDescriptionUrl(const QUrl& descriptionUrl);
/**
* @brief Get the thumb URL of the image.
* @return the thumb URL of the image
*/
QUrl thumbUrl() const;
/**
* @brief Get the thumb URL of the image.
* @param thumbUrl the thumb URL of the image
*/
void setThumbUrl(const QUrl& thumbUrl);
/**
* @brief Get the thumb width of the image.
* @return the thumb width of the image
*/
qint64 thumbWidth() const;
/**
* @brief Set the thumb width of the image.
* @param thumbWidth the thumb width of the image
*/
void setThumbWidth(qint64 thumbWidth);
/**
* @brief Get the thumb height of the image.
* @return the thumb height of the image
*/
qint64 thumbHeight() const;
/**
* @brief Set the thumb height of the image.
* @param thumbHeight the thumb height of the image
*/
void setThumbHeight(qint64 thumbHeight);
/**
* @brief Get the image's size in bytes.
* @return the image's size in bytes
*/
qint64 size() const;
/**
* @brief Set the image's size in bytes.
* @param size the image's size in bytes
*/
void setSize(qint64 size);
/**
* @brief Get the image's width.
* @return the image's width
*/
qint64 width() const;
/**
* @brief Set the image's width.
* @param width the image's width
*/
void setWidth(qint64 width);
/**
* @brief Get the image's height.
* @return the image's height
*/
qint64 height() const;
/**
* @brief Set the image's height.
* @param height the image's height
*/
void setHeight(qint64 height);
/**
* @brief Get the image's SHA-1 hash.
* @return the image's SHA-1 hash
*/
QString sha1() const;
/**
* @brief Set the image's SHA-1 hash.
* @param sha1 the image's SHA-1 hash
*/
void setSha1(const QString& sha1);
/**
* @brief Get the image's MIME type.
* @return the image's MIME type
*/
QString mime() const;
/**
* @brief Set the image's MIME type.
* @param mime the image's MIME type
*/
void setMime(const QString& mime);
/**
* @brief Get image metadata.
* @return image metadata
*/
const QHash<QString, QVariant>& metadata() const;
/**
* @brief Get image metadata.
* @return image metadata
*/
QHash<QString, QVariant>& metadata();
/**
* @brief Set image metadata.
* @param metadata image metadata
*/
void setMetadata(const QHash<QString, QVariant>& metadata);
private:
class Private;
Private* const d;
};
} // namespace MediaWiki
#endif // DIGIKAM_MEDIAWIKI_IMAGEINFO_H
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_page.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_page.h
index 77ed18a721..3fea9f2d6e 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_page.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_page.h
@@ -1,252 +1,252 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Joris Munoz <munozjoris at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_PAGE_H
#define DIGIKAM_MEDIAWIKI_PAGE_H
// Qt includes
#include <QDateTime>
#include <QUrl>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief An image info.
*/
class DIGIKAM_EXPORT Page
{
public:
/**
* @brief Constructs a page.
*/
Page();
/**
* @brief Constructs a page from an other page.
* @param other an other page
*/
Page(const Page& other);
/**
* @brief Destructs a page.
*/
~Page();
/**
- * @brief Assingning a page from an other page.
+ * @brief Assigning a page from an other page.
* @param other an other page
*/
Page& operator=(Page other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const Page& other) const;
/**
* @brief Set the pageId of the page.
* @param id the page id of the page
*/
void setPageId(unsigned int id);
/**
* @brief Return the page id of the page.
* @return the page id of the page
*/
unsigned int pageId() const;
/**
* @brief Set the title of the page.
* @param title the title of the page
*/
void setTitle(const QString& title);
/**
* @brief Return the title of the page.
* @return the title of the page
*/
QString pageTitle() const;
/**
* @brief Set the namespace of the page.
* @param ns the namespace of the page
*/
void setNs(unsigned int ns) const;
/**
* @brief Return the namespace of the page.
* @return the namespace of the page
*/
unsigned int pageNs() const;
/**
* @brief Set the last revision id of the page.
* @param lastRevId the last revision id of the page
*/
void setLastRevId(unsigned int lastRevId) const;
/**
* @brief Return the last revision id of the page.
* @return the last revision id of the page
*/
unsigned int pageLastRevId() const;
/**
* @brief Set the number of views of the page.
* @param counter the number of views of the page
*/
void setCounter(unsigned int counter) const;
/**
* @brief Return the number of views of the page.
* @return the number of views of the page
*/
unsigned int pageCounter() const;
/**
* @brief Set the page size.
* @param length the page size
*/
void setLength(unsigned int length) const;
/**
* @brief Return the page size.
* @return the page size
*/
unsigned int pageLength() const;
/**
* @brief Set the page token.
* @param editToken the page token
*/
void setEditToken(const QString& editToken);
/**
* @brief Return the page token.
* @return the page token
*/
QString pageEditToken() const;
/**
* @brief Set the page ID of the talk page for each non-talk page.
* @param talkid the page ID of the talk page for each non-talk page
*/
void setTalkid(unsigned int talkid) const;
/**
* @brief Return the page ID of the talk page for each non-talk page.
* @return the page ID of the talk page for each non-talk page
*/
unsigned int pageTalkid() const;
/**
* @brief Set the full url of the page.
* @param fullurl the full url of the page
*/
void setFullurl(const QUrl& fullurl);
/**
* @brief Return the full url of the page.
* @return the full url of the page
*/
QUrl pageFullurl() const;
/**
* @brief Set the edit url of the page.
* @param editurl the edit url of the page
*/
void setEditurl(const QUrl& editurl);
/**
* @brief Return the edit url of the page.
* @return the edit url of the page
*/
QUrl pageEditurl() const;
/**
* @brief Set the readability of the page.
* @param readable the readability of the page
*/
void setReadable(const QString& readable);
/**
* @brief Return the readability of the page.
* @return the readability of the page
*/
QString pageReadable() const;
/**
* @brief Set the text returned by EditFormPreloadText.
* @param preload the text returned by EditFormPreloadText
*/
void setPreload(const QString& preload);
/**
* @brief Return the text returned by EditFormPreloadText.
* @return the text returned by EditFormPreloadText
*/
QString pagePreload() const;
/**
* @brief Set the last touched timestamp.
* @param touched the last touched timestamp
*/
void setTouched(const QDateTime& touched);
/**
* @brief Return the last touched timestamp.
* @return the last touched timestamp
*/
QDateTime pageTouched() const;
/**
* @brief Set the timestamp when you obtained the edit token.
* @param starttimestamp the timestamp when you obtained the edit token
*/
void setStarttimestamp(const QDateTime& starttimestamp);
/**
* @brief Return the timestamp when you obtained the edit token.
* @return the timestamp when you obtained the edit token
*/
QDateTime pageStarttimestamp() const;
private:
class PagePrivate;
PagePrivate* const d;
};
} // namespace MediaWiki
#endif // PAGE_H
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_protection.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_protection.h
index 9b75efb1cd..16a125ce14 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_protection.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_protection.h
@@ -1,135 +1,135 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Vincent Garcia <xavier dot vincent dot garcia at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_PROTECTION_H
#define DIGIKAM_MEDIAWIKI_PROTECTION_H
// Qt includes
#include <QString>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief Protection info job.
*
* Represent protection parameters in a page.
*/
class DIGIKAM_EXPORT Protection
{
public:
/**
* @brief Constructs a protection.
*
* You can set parameters of the protection after.
*/
Protection();
/**
* @brief Constructs an protection from an other protection.
* @param other an other protection
*/
Protection(const Protection& other);
/**
* @brief Destructs a protection.
*/
~Protection();
/**
- * @brief Assingning an protection from an other protection.
+ * @brief Assigning an protection from an other protection.
* @param other an other protection
*/
Protection& operator=(Protection other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const Protection& other) const;
/**
* @brief Set the protection type.
* @param type the protection type
*/
void setType(const QString& type);
/**
* @brief Get the protection type.
* @return the protection type
*/
QString type() const;
/**
* @brief Set the page protection level.
* @param level the page protection level
*/
void setLevel(const QString& level);
/**
* @brief Get the page protection level.
* @return the page protection level
*/
QString level() const;
/**
* @brief Set the expiry date.
* @param expiry the expiry date
*/
void setExpiry(const QString& expiry);
/**
² * @brief Get the expiry date.
* @return the expiry date
*/
QString expiry() const;
/**
* @brief Set the source.
* @param source the source
*/
void setSource(const QString& source);
/**
* @brief Get the source.
* @return the source
*/
QString source() const;
private:
class ProtectionPrivate;
ProtectionPrivate* const d;
};
} // namespace MediaWiki
#endif // DIGIKAM_MEDIAWIKI_PROTECTION_H
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_revision.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_revision.h
index 6e942f712c..82d8b09c86 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_revision.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_revision.h
@@ -1,202 +1,202 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Robin Bussenot <bussenot dot robin at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_REVISION_H
#define DIGIKAM_MEDIAWIKI_REVISION_H
// Qt includes
#include <QDateTime>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief An image info.
*/
class DIGIKAM_EXPORT Revision
{
public:
/**
* @brief Constructs a revision.
*/
Revision();
/**
* @brief Destructs a revision.
*/
~Revision();
/**
* @brief Constructs a revision from an other revision.
* @param other an other revision
*/
Revision(const Revision& other);
/**
- * @brief Assingning a revision from an other revision.
+ * @brief Assigning a revision from an other revision.
* @param other an other revision
*/
Revision& operator=(Revision other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const Revision& other) const;
/**
* @brief Set the revision ID.
* @param revisionId the revision ID
**/
void setRevisionId(int revisionId);
/**
* @brief Get the revision ID.
* @return the revision ID
*/
int revisionId() const;
/**
* @brief Set the parent ID.
* @param parentId the parent ID
*/
void setParentId(int parentId);
/**
* @brief Get the parent ID.
* @return the parent ID
*/
int parentId() const;
/**
* @brief Set the size of the revision text in bytes.
* @param size the size of the revision text in bytes
*/
void setSize(int size);
/**
* @brief Get the size of the revision text in bytes.
* @return the size of the revision text in bytes
*/
int size() const;
/**
- * @brief Set true if the revsion is minor.
- * @param minor true if the revsion is minor
+ * @brief Set true if the revision is minor.
+ * @param minor true if the revision is minor
*/
void setMinorRevision(bool minorRevision);
/**
- * @brief Get true if the revsion is minor.
- * @return true if the revsion is minor
+ * @brief Get true if the revision is minor.
+ * @return true if the revision is minor
*/
bool minorRevision() const;
/**
* @brief Get the date and time of the revision.
* @return the date and time of the revision
*/
QDateTime timestamp() const;
/**
* @brief Set the date and time of the revision
* @param timestamp the date and time of the revision
*/
void setTimestamp(const QDateTime& timestamp);
/**
* @brief Get the user who made the revision.
* @return the user who made the revision
*/
QString user() const;
/**
* @brief Set the user who made the revision.
* @param user the user who made the revision
*/
void setUser(const QString& user);
/**
* @brief The revision content.
*/
QString content() const;
/**
* @brief Set the revision content.
* @param content the revision content
*/
void setContent(const QString& content);
/**
* @brief Get the edit comment.
* @return the edit comment
*/
QString comment() const;
/**
* @brief Set the edit comment.
* @param comment the edit comment
*/
void setComment(const QString& comment);
/**
* @brief Set the parse tree of the revision content.
* @param parseTree the parse tree of the revision content
*/
void setParseTree(const QString& parseTree);
/**
* @brief Get the parse tree of the revision content.
* @return the parse tree of the revision content
*/
QString parseTree() const;
/**
* @brief Set the rollback token.
* @param rollback the rollback token
*/
void setRollback(const QString& rollback);
/**
* @brief Get the rollback token.
* @return the rollback token
*/
QString rollback() const;
private:
class RevisionPrivate;
RevisionPrivate* const d;
};
} // namespace MediaWiki
#endif // DIGIKAM_MEDIAWIKI_REVISION_H
diff --git a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_usergroup.h b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_usergroup.h
index acde90cfb4..0a34de4123 100644
--- a/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_usergroup.h
+++ b/core/utilities/assistants/webservices/mediawiki/iface/mediawiki_usergroup.h
@@ -1,126 +1,126 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-03-22
* Description : a Iface C++ interface
*
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2011 by Remi Benoit <r3m1 dot benoit at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_USERGROUP_H
#define DIGIKAM_MEDIAWIKI_USERGROUP_H
// Qt includes
#include <QString>
#include <QList>
// Local includes
#include "digikam_export.h"
namespace MediaWiki
{
/**
* @brief A user group.
*/
class DIGIKAM_EXPORT UserGroup
{
public:
/**
* Constructs a user group.
*/
UserGroup();
/**
* @brief Constructs a user group from an other user group.
* @param other an other user group
*/
UserGroup(const UserGroup& other);
/**
* @brief Destructs a user group.
*/
~UserGroup();
/**
- * @brief Assingning a user group from an other user group.
+ * @brief Assigning a user group from an other user group.
* @param other an other user group
*/
UserGroup& operator=(UserGroup other);
/**
* @brief Returns true if this instance and other are equal, else false.
* @param other instance to compare
* @return true if there are equal, else false
*/
bool operator==(const UserGroup& other) const;
/**
* @brief Returns the name of the user group.
* @return the name of the user group
*/
QString name() const;
/**
* @brief Set the name of the user group.
* @param name the name of the user group
*/
void setName(const QString& name);
/**
* @brief Returns rights of the user group.
* @return rights of the user group
*/
const QList<QString>& rights() const;
/**
* @brief Returns rights of the user group.
* @return rights of the user group
*/
QList<QString>& rights();
/**
* @brief Set rights of the user group.
* @param rights rights of the user group
*/
void setRights(const QList<QString>& rights);
/**
* @brief Returns the numbers of users in the user group.
* @return the numbers of users in the user group
*/
qint64 number() const;
/**
* @brief Set the number of users in the user group.
* @param number the number of users in the user group
*/
void setNumber(qint64 number) ;
private:
class UserGroupPrivate;
UserGroupPrivate* const d;
};
} // namespace MediaWiki
#endif // DIGIKAM_MEDIAWIKI_USERGROUP_H
diff --git a/core/utilities/assistants/webservices/mediawiki/mediawikitalker.h b/core/utilities/assistants/webservices/mediawiki/mediawikitalker.h
index 3ac4750616..bb964f5643 100644
--- a/core/utilities/assistants/webservices/mediawiki/mediawikitalker.h
+++ b/core/utilities/assistants/webservices/mediawiki/mediawikitalker.h
@@ -1,87 +1,87 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2011-02-11
* Description : a tool to export images to WikiMedia web service
*
* Copyright (C) 2011 by Alexandre Mendes <alex dot mendes1988 at gmail dot com>
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_MEDIAWIKI_TALKER_H
#define DIGIKAM_MEDIAWIKI_TALKER_H
// Qt includes
#include <QString>
#include <QList>
#include <QMap>
#include <QUrl>
// KDE includes
#include <kjob.h>
-// Local incudes
+// Local includes
#include "dinfointerface.h"
namespace MediaWiki
{
class Iface;
}
using namespace MediaWiki;
namespace Digikam
{
class MediaWikiTalker : public KJob
{
Q_OBJECT
public:
explicit MediaWikiTalker(DInfoInterface* const iface, Iface* const MediaWiki, QObject* const parent=0);
~MediaWikiTalker();
public:
QString buildWikiText(const QMap<QString, QString>& info) const;
void setImageMap(const QMap <QString, QMap <QString, QString> >& imageDesc);
void start() Q_DECL_OVERRIDE;
Q_SIGNALS:
void signalUploadProgress(int percent);
void signalEndUpload();
public Q_SLOTS:
void slotBegin();
void slotUploadHandle(KJob* j = 0);
void slotUploadProgress(KJob* job, unsigned long percent);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_MEDIAWIKI_TALKER_H
diff --git a/core/utilities/assistants/webservices/yandexfotki/yftalker.cpp b/core/utilities/assistants/webservices/yandexfotki/yftalker.cpp
index eeacd124f5..88b0ec83fd 100644
--- a/core/utilities/assistants/webservices/yandexfotki/yftalker.cpp
+++ b/core/utilities/assistants/webservices/yandexfotki/yftalker.cpp
@@ -1,1178 +1,1178 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-11-14
* Description : a tool to export items to YandexFotki web service
*
* Copyright (C) 2010 by Roman Tsisyk <roman at tsisyk dot com>
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "yftalker.h"
// Qt includes
#include <QTextDocument>
#include <QByteArray>
#include <QDomDocument>
#include <QDomNode>
#include <QFile>
#include <QFileInfo>
#include <QPointer>
#include <QNetworkReply>
#include <QNetworkAccessManager>
// Local includes
#include "digikam_debug.h"
#include "digikam_version.h"
#include "yfauth.h"
#include "yfalbum.h"
namespace Digikam
{
class Q_DECL_HIDDEN YFTalker::Private
{
public:
explicit Private()
{
state = STATE_UNAUTHENTICATED;
lastPhoto = 0;
netMngr = 0;
reply = 0;
}
// API-related fields
QString sessionKey;
QString sessionId;
QString token;
QString login;
QString password;
QString apiAlbumsUrl;
QString apiPhotosUrl;
QString apiTagsUrl;
// FSM data
State state;
// temporary data
YFPhoto* lastPhoto;
QString lastPhotosUrl;
// for albums pagination
//in listAlbums()
QList<YandexFotkiAlbum> albums;
QString albumsNextUrl;
QList<YFPhoto> photos;
QString photosNextUrl;
QNetworkAccessManager* netMngr;
QNetworkReply* reply;
// Data buffer
QByteArray buffer;
// constants
- // use QString insted of QUrl, we need .arg
+ // use QString instead of QUrl, we need .arg
static const QString SESSION_URL;
static const QString TOKEN_URL;
static const QString SERVICE_URL;
static const QString AUTH_REALM;
static const QString ACCESS_STRINGS[];
};
/*
* static API constants
*/
const QString YFTalker::Private::SESSION_URL = QLatin1String("http://auth.mobile.yandex.ru/yamrsa/key/");
const QString YFTalker::Private::AUTH_REALM = QLatin1String("fotki.yandex.ru");
const QString YFTalker::Private::TOKEN_URL = QLatin1String("http://auth.mobile.yandex.ru/yamrsa/token/");
const QString YFTalker::Private::SERVICE_URL = QLatin1String("http://api-fotki.yandex.ru/api/users/%1/");
const QString YFTalker::Private::ACCESS_STRINGS[] =
{
QLatin1String("public"),
QLatin1String("friends"),
QLatin1String("private")
};
const QString YFTalker::USERPAGE_URL = QLatin1String("http://fotki.yandex.ru/users/%1/");
const QString YFTalker::USERPAGE_DEFAULT_URL = QLatin1String("http://fotki.yandex.ru/");
// ------------------------------------------------------------
YFTalker::YFTalker(QObject* const parent)
: QObject(parent),
d(new Private)
{
d->netMngr = new QNetworkAccessManager(this);
connect(d->netMngr, SIGNAL(finished(QNetworkReply*)),
this, SLOT(slotFinished(QNetworkReply*)));
}
YFTalker::~YFTalker()
{
reset();
delete d;
}
YFTalker::State YFTalker::state() const
{
return d->state;
}
const QString& YFTalker::sessionKey() const
{
return d->sessionKey;
}
const QString& YFTalker::sessionId() const
{
return d->sessionId;
}
const QString& YFTalker::token() const
{
return d->token;
}
const QString& YFTalker::login() const
{
return d->login;
}
void YFTalker::setLogin(const QString& login)
{
d->login = login;
}
const QString& YFTalker::password() const
{
return d->password;
}
void YFTalker::setPassword(const QString& password)
{
d->password = password;
}
bool YFTalker::isAuthenticated() const
{
return (d->state & STATE_AUTHENTICATED) != 0;
}
bool YFTalker::isErrorState() const
{
return (d->state & STATE_ERROR) != 0;
}
const QList<YandexFotkiAlbum>& YFTalker::albums() const
{
return d->albums;
}
const QList<YFPhoto>& YFTalker::photos() const
{
return d->photos;
}
void YFTalker::getService()
{
d->state = STATE_GETSERVICE;
QUrl url(d->SERVICE_URL.arg(d->login));
d->reply = d->netMngr->get(QNetworkRequest(url));
d->buffer.resize(0);
}
/*
void YFTalker::checkToken()
{
- // try to get somthing with our token, if it is invalid catch 401
+ // try to get something with our token, if it is invalid catch 401
d->state = STATE_CHECKTOKEN;
QUrl url(d->apiAlbumsUrl);
QNetworkRequest netRequest(url);
netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"")
.arg(AUTH_REALM).arg(d->token).toLatin1());
d->reply = d->netMngr->get(netRequest);
// Error: STATE_CHECKTOKEN_INVALID
// Function: slotParseResponseCheckToken()
d->buffer.resize(0);
}
*/
void YFTalker::getSession()
{
if (d->state != STATE_GETSERVICE_DONE)
return;
d->state = STATE_GETSESSION;
QUrl url(d->SESSION_URL);
d->reply = d->netMngr->get(QNetworkRequest(url));
d->buffer.resize(0);
}
void YFTalker::getToken()
{
if (d->state != STATE_GETSESSION_DONE)
return;
const QString credentials = YFAuth::makeCredentials(d->sessionKey,
d->login, d->password);
// prepare params
QStringList paramList;
paramList.append(QLatin1String("request_id=") + d->sessionId);
paramList.append(QLatin1String("credentials=") + QString::fromUtf8(QUrl::toPercentEncoding(credentials)));
QString params = paramList.join(QLatin1Char('&'));
d->state = STATE_GETTOKEN;
QUrl url(d->TOKEN_URL);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/x-www-form-urlencoded"));
d->reply = d->netMngr->post(netRequest, params.toUtf8());
d->buffer.resize(0);
}
void YFTalker::listAlbums()
{
if (isErrorState() || !isAuthenticated())
return;
d->albumsNextUrl = d->apiAlbumsUrl;
d->albums.clear();
listAlbumsNext();
}
void YFTalker::listAlbumsNext()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "listAlbumsNext";
d->state = STATE_LISTALBUMS;
QUrl url(d->albumsNextUrl);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=feed"));
netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"")
.arg(d->AUTH_REALM).arg(d->token).toLatin1());
d->reply = d->netMngr->get(netRequest);
d->buffer.resize(0);
}
void YFTalker::listPhotos(const YandexFotkiAlbum& album)
{
if (isErrorState() || !isAuthenticated())
return;
d->photosNextUrl = album.m_apiPhotosUrl;
d->photos.clear();
listPhotosNext();
}
// protected member
void YFTalker::listPhotosNext()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "listPhotosNext";
d->state = STATE_LISTPHOTOS;
QUrl url(d->photosNextUrl);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=feed"));
netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"")
.arg(d->AUTH_REALM).arg(d->token).toLatin1());
d->reply = d->netMngr->get(netRequest);
d->buffer.resize(0);
}
void YFTalker::updatePhoto(YFPhoto& photo, const YandexFotkiAlbum& album)
{
if (isErrorState() || !isAuthenticated())
return;
// sanity check
if (photo.title().isEmpty())
{
photo.setTitle(QFileInfo(photo.localUrl()).baseName().trimmed());
}
// move photo to another album (if changed)
photo.m_apiAlbumUrl = album.m_apiSelfUrl;
// FIXME: hack
d->lastPhotosUrl = album.m_apiPhotosUrl;
if (!photo.remoteUrl().isNull())
{
// TODO: updating image file haven't yet supported by API
// so, just update info
return updatePhotoInfo(photo);
}
else
{
// for new images also upload file
updatePhotoFile(photo);
}
}
void YFTalker::updatePhotoFile(YFPhoto& photo)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "updatePhotoFile" << photo;
QFile imageFile(photo.localUrl());
if (!imageFile.open(QIODevice::ReadOnly))
{
setErrorState(STATE_UPDATEPHOTO_FILE_ERROR);
return;
}
d->state = STATE_UPDATEPHOTO_FILE;
d->lastPhoto = &photo;
QUrl url(d->lastPhotosUrl);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("image/jpeg"));
netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"")
.arg(d->AUTH_REALM).arg(d->token).toLatin1());
netRequest.setRawHeader("Slug", QUrl::toPercentEncoding(photo.title()) + ".jpg");
d->reply = d->netMngr->post(netRequest, imageFile.readAll());
d->buffer.resize(0);
}
void YFTalker::updatePhotoInfo(YFPhoto& photo)
{
QDomDocument doc;
QDomProcessingInstruction instr = doc.createProcessingInstruction(
QLatin1String("xml"),
QLatin1String("version='1.0' encoding='UTF-8'"));
doc.appendChild(instr);
QDomElement entryElem = doc.createElement(QLatin1String("entry"));
entryElem.setAttribute(QLatin1String("xmlns"), QLatin1String("http://www.w3.org/2005/Atom"));
entryElem.setAttribute(QLatin1String("xmlns:f"), QLatin1String("yandex:fotki"));
doc.appendChild(entryElem);
QDomElement urn = doc.createElement(QLatin1String("urn"));
urn.appendChild(doc.createTextNode(photo.urn()));
entryElem.appendChild(urn);
QDomElement title = doc.createElement(QLatin1String("title"));
title.appendChild(doc.createTextNode(photo.title()));
entryElem.appendChild(title);
QDomElement linkAlbum = doc.createElement(QLatin1String("link"));
linkAlbum.setAttribute(QLatin1String("href"), photo.m_apiAlbumUrl);
linkAlbum.setAttribute(QLatin1String("rel"), QLatin1String("album"));
entryElem.appendChild(linkAlbum);
QDomElement summary = doc.createElement(QLatin1String("summary"));
summary.appendChild(doc.createTextNode(photo.summary()));
entryElem.appendChild(summary);
QDomElement adult = doc.createElement(QLatin1String("f:xxx"));
adult.setAttribute(QLatin1String("value"), photo.isAdult() ? QLatin1String("true") : QLatin1String("false"));
entryElem.appendChild(adult);
QDomElement hideOriginal = doc.createElement(QLatin1String("f:hide_original"));
hideOriginal.setAttribute(QLatin1String("value"),
photo.isHideOriginal() ? QLatin1String("true") : QLatin1String("false"));
entryElem.appendChild(hideOriginal);
QDomElement disableComments = doc.createElement(QLatin1String("f:disable_comments"));
disableComments.setAttribute(QLatin1String("value"),
photo.isDisableComments() ? QLatin1String("true") : QLatin1String("false"));
entryElem.appendChild(disableComments);
QDomElement access = doc.createElement(QLatin1String("f:access"));
access.setAttribute(QLatin1String("value"), d->ACCESS_STRINGS[photo.access()]);
entryElem.appendChild(access);
// FIXME: undocumented API
foreach(const QString& t, photo.tags)
{
QDomElement tag = doc.createElement(QLatin1String("category"));
tag.setAttribute(QLatin1String("scheme"), d->apiTagsUrl);
tag.setAttribute(QLatin1String("term"), t);
entryElem.appendChild(tag);
}
QByteArray buffer = doc.toString(1).toUtf8(); // with idents
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Prepared data: " << buffer;
d->lastPhoto = &photo;
d->state = STATE_UPDATEPHOTO_INFO;
QUrl url(photo.m_apiEditUrl);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader,
QLatin1String("application/atom+xml; charset=utf-8; type=entry"));
netRequest.setRawHeader("Authorization",
QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"")
.arg(d->AUTH_REALM).arg(d->token).toLatin1());
d->reply = d->netMngr->put(netRequest, buffer);
d->buffer.resize(0);
}
void YFTalker::updateAlbum(YandexFotkiAlbum& album)
{
if (isErrorState() || !isAuthenticated())
return;
if (album.urn().isEmpty())
{
// new album
return updateAlbumCreate(album);
}
else
{
qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Updating albums is not yet supported";
}
}
void YFTalker::updateAlbumCreate(YandexFotkiAlbum& album)
{
QDomDocument doc;
QDomProcessingInstruction instr = doc.createProcessingInstruction(
QLatin1String("xml"),
QLatin1String("version='1.0' encoding='UTF-8'"));
doc.appendChild(instr);
QDomElement entryElem = doc.createElement(QLatin1String("entry"));
entryElem.setAttribute(QLatin1String("xmlns"), QLatin1String("http://www.w3.org/2005/Atom"));
entryElem.setAttribute(QLatin1String("xmlns:f"), QLatin1String("yandex:fotki"));
doc.appendChild(entryElem);
QDomElement title = doc.createElement(QLatin1String("title"));
title.appendChild(doc.createTextNode(album.title()));
entryElem.appendChild(title);
QDomElement summary = doc.createElement(QLatin1String("summary"));
summary.appendChild(doc.createTextNode(album.summary()));
entryElem.appendChild(summary);
QDomElement password = doc.createElement(QLatin1String("f:password"));
password.appendChild(doc.createTextNode(album.m_password));
entryElem.appendChild(password);
const QByteArray postData = doc.toString(1).toUtf8(); // with idents
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Prepared data: " << postData;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Url" << d->apiAlbumsUrl;
d->state = STATE_UPDATEALBUM;
QUrl url(d->apiAlbumsUrl);
QNetworkRequest netRequest(url);
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/atom+xml; charset=utf-8; type=entry"));
netRequest.setRawHeader("Authorization", QString::fromLatin1("FimpToken realm=\"%1\", token=\"%2\"")
.arg(d->AUTH_REALM).arg(d->token).toLatin1());
d->reply = d->netMngr->post(netRequest, postData);
d->buffer.resize(0);
}
void YFTalker::reset()
{
if (d->reply)
{
d->reply->abort();
d->reply = 0;
}
d->token.clear();
d->state = STATE_UNAUTHENTICATED;
}
void YFTalker::cancel()
{
if (d->reply)
{
d->reply->abort();
d->reply = 0;
}
if (isAuthenticated())
{
d->state = STATE_AUTHENTICATED;
}
else
{
d->token.clear();
d->state = STATE_UNAUTHENTICATED;
}
}
void YFTalker::setErrorState(State state)
{
d->state = state;
emit signalError();
}
void YFTalker::slotFinished(QNetworkReply* reply)
{
if (reply != d->reply)
{
return;
}
d->reply = 0;
if (reply->error() != QNetworkReply::NoError)
{
int code = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Transfer Error" << code << reply->errorString();
if (code == 401 || code == 403 || code == 404) // auth required, 404 user not found
{
setErrorState(STATE_INVALID_CREDENTIALS);
}
else if (d->state == STATE_GETSERVICE)
{
setErrorState(STATE_GETSERVICE_ERROR);
}
else if (d->state == STATE_GETSESSION)
{
setErrorState(STATE_GETSESSION_ERROR);
}
else if (d->state == STATE_GETTOKEN)
{
setErrorState(STATE_GETTOKEN_ERROR);
}
else if (d->state == STATE_LISTALBUMS)
{
setErrorState(STATE_LISTALBUMS_ERROR);
}
else if (d->state == STATE_LISTPHOTOS)
{
setErrorState(STATE_LISTPHOTOS_ERROR);
}
else if (d->state == STATE_UPDATEPHOTO_FILE)
{
setErrorState(STATE_UPDATEPHOTO_FILE_ERROR);
}
else if (d->state == STATE_UPDATEPHOTO_INFO)
{
setErrorState(STATE_UPDATEPHOTO_INFO_ERROR);
}
else if (d->state == STATE_UPDATEALBUM)
{
setErrorState(STATE_UPDATEALBUM_ERROR);
}
reply->deleteLater();
return;
}
d->buffer.append(reply->readAll());
switch(d->state)
{
case (STATE_GETSERVICE):
slotParseResponseGetService();
break;
case (STATE_GETSESSION):
slotParseResponseGetSession();
break;
case (STATE_GETTOKEN):
slotParseResponseGetToken();
break;
case (STATE_LISTALBUMS):
slotParseResponseListAlbums();
break;
case (STATE_LISTPHOTOS):
slotParseResponseListPhotos();
break;
case (STATE_UPDATEPHOTO_FILE):
slotParseResponseUpdatePhotoFile();
break;
case (STATE_UPDATEPHOTO_INFO):
slotParseResponseUpdatePhotoInfo();
break;
case (STATE_UPDATEALBUM):
slotParseResponseUpdateAlbum();
break;
default:
break;
}
reply->deleteLater();
}
void YFTalker::slotParseResponseGetService()
{
QDomDocument doc(QLatin1String("service"));
if (!doc.setContent(d->buffer))
{
qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error" << d->buffer;
return setErrorState(STATE_GETSERVICE_ERROR);
}
const QDomElement rootElem = doc.documentElement();
QDomElement workspaceElem = rootElem.firstChildElement(QLatin1String("app:workspace"));
// FIXME: workaround for Yandex xml namespaces bugs
QString prefix = QLatin1String("app:");
if (workspaceElem.isNull())
{
workspaceElem = rootElem.firstChildElement(QLatin1String("workspace"));
prefix = QString();
qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Service document without namespaces found";
}
if (workspaceElem.isNull())
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data: workspace element";
return setErrorState(STATE_GETSERVICE_ERROR);
}
QString apiAlbumsUrl;
QString apiPhotosUrl;
QString apiTagsUrl;
QDomElement collectionElem = workspaceElem.firstChildElement(prefix + QLatin1String("collection"));
for ( ; !collectionElem.isNull() ;
collectionElem = collectionElem.nextSiblingElement(prefix + QLatin1String("collection")))
{
const QDomElement acceptElem = collectionElem.firstChildElement(prefix + QLatin1String("accept"));
if (acceptElem.isNull()) // invalid section, ignore
{
continue;
}
// FIXME: id attribute is undocumented
if (collectionElem.attribute(QLatin1String("id")) == QLatin1String("album-list"))
{
apiAlbumsUrl = collectionElem.attribute(QLatin1String("href"));
}
else if (collectionElem.attribute(QLatin1String("id")) == QLatin1String("photo-list"))
{
apiPhotosUrl = collectionElem.attribute(QLatin1String("href"));
}
else if (collectionElem.attribute(QLatin1String("id")) == QLatin1String("tag-list"))
{
apiTagsUrl = collectionElem.attribute(QLatin1String("href"));
}
// else skip unknown section
}
if (apiAlbumsUrl.isNull() || apiPhotosUrl.isNull())
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data: service URLs";
return setErrorState(STATE_GETSERVICE_ERROR);
}
d->apiAlbumsUrl = apiAlbumsUrl;
d->apiPhotosUrl = apiPhotosUrl;
d->apiTagsUrl = apiTagsUrl;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "ServiceUrls:";
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Albums" << d->apiAlbumsUrl;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Photos" << d->apiPhotosUrl;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Tags" << d->apiTagsUrl;
d->state = STATE_GETSERVICE_DONE;
emit signalGetServiceDone();
}
/*
void YFTalker::slotParseResponseCheckToken()
{
// token still valid, skip getSession and getToken
d->state = STATE_GETTOKEN_DONE;
emit signalGetTokenDone();
}
*/
void YFTalker::slotParseResponseGetSession()
{
QDomDocument doc(QLatin1String("session"));
if (!doc.setContent(d->buffer))
{
return setErrorState(STATE_GETSESSION_ERROR);
}
const QDomElement rootElem = doc.documentElement();
const QDomElement keyElem = rootElem.firstChildElement(QLatin1String("key"));
const QDomElement requestIdElem = rootElem.firstChildElement(QLatin1String("request_id"));
if (keyElem.isNull() || keyElem.nodeType() != QDomNode::ElementNode ||
requestIdElem.isNull() || requestIdElem.nodeType() != QDomNode::ElementNode)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML" << d->buffer;
return setErrorState(STATE_GETSESSION_ERROR);
}
d->sessionKey = keyElem.text();
d->sessionId = requestIdElem.text();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Session started" << d->sessionKey << d->sessionId;
d->state = STATE_GETSESSION_DONE;
emit signalGetSessionDone();
}
void YFTalker::slotParseResponseGetToken()
{
QDomDocument doc(QLatin1String("response"));
if (!doc.setContent(d->buffer))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error" << d->buffer;
return setErrorState(STATE_GETTOKEN_ERROR);
}
const QDomElement rootElem = doc.documentElement();
const QDomElement tokenElem = rootElem.firstChildElement(QLatin1String("token"));
if (tokenElem.isNull() || tokenElem.nodeType() != QDomNode::ElementNode)
{
const QDomElement errorElem = rootElem.firstChildElement(QLatin1String("error"));
if (errorElem.isNull() || errorElem.nodeType() != QDomNode::ElementNode)
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Auth unknown error";
return setErrorState(STATE_GETTOKEN_ERROR);
}
/*
// checked by HTTP error code in prepareJobResult
const QString errorCode = errorElem.attribute("code", "0");
qCDebug(DIGIKAM_WEBSERVICES_LOG) << QString("Auth error: %1, code=%2").arg(errorElem.text()).arg(errorCode);
if (errorCode == "2")
{
// Invalid credentials
return setErrorState(STATE_GETTOKEN_INVALID_CREDENTIALS);
}
*/
return;
}
d->token = tokenElem.text();
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Token got" << d->token;
d->state = STATE_GETTOKEN_DONE;
emit signalGetTokenDone();
}
void YFTalker::slotParseResponseListAlbums()
{
QDomDocument doc(QLatin1String("feed"));
if (!doc.setContent(d->buffer))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error";
return setErrorState(STATE_LISTALBUMS_ERROR);
}
bool errorOccurred = false;
const QDomElement rootElem = doc.documentElement();
// find next page link
d->albumsNextUrl.clear();
QDomElement linkElem = rootElem.firstChildElement(QLatin1String("link"));
for ( ; !linkElem.isNull() ;
linkElem = linkElem.nextSiblingElement(QLatin1String("link")))
{
if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("next") &&
!linkElem.attribute(QLatin1String("href")).isNull())
{
d->albumsNextUrl = linkElem.attribute(QLatin1String("href"));
break;
}
}
QDomElement entryElem = rootElem.firstChildElement(QLatin1String("entry"));
for ( ; !entryElem.isNull() ;
entryElem = entryElem.nextSiblingElement(QLatin1String("entry")))
{
const QDomElement urn = entryElem.firstChildElement(QLatin1String("id"));
const QDomElement author = entryElem.firstChildElement(QLatin1String("author"));
const QDomElement title = entryElem.firstChildElement(QLatin1String("title"));
const QDomElement summary = entryElem.firstChildElement(QLatin1String("summary"));
const QDomElement published = entryElem.firstChildElement(QLatin1String("published"));
const QDomElement edited = entryElem.firstChildElement(QLatin1String("app:edited"));
const QDomElement updated = entryElem.firstChildElement(QLatin1String("updated"));
const QDomElement prot = entryElem.firstChildElement(QLatin1String("protected"));
QDomElement linkSelf;
QDomElement linkEdit;
QDomElement linkPhotos;
QDomElement linkElem = entryElem.firstChildElement(QLatin1String("link"));
for ( ; !linkElem.isNull() ;
linkElem = linkElem.nextSiblingElement(QLatin1String("link")))
{
if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("self"))
linkSelf = linkElem;
else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("edit"))
linkEdit = linkElem;
else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("photos"))
linkPhotos = linkElem;
// else skip <link>
}
if (urn.isNull() || title.isNull() ||
linkSelf.isNull() || linkEdit.isNull() || linkPhotos.isNull())
{
errorOccurred = true;
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data: invalid entry on line" << entryElem.lineNumber();
- // simple skip this record, no addtional messages to user
+ // simple skip this record, no additional messages to user
continue;
}
QString password;
if (!prot.isNull() && prot.attribute(QLatin1String("value"), QLatin1String("false")) == QLatin1String("true"))
{
password = QLatin1String(""); // set not null value
}
d->albums.append(YandexFotkiAlbum(
urn.text(),
author.text(),
title.text(),
summary.text(),
linkEdit.attribute(QLatin1String("href")),
linkSelf.attribute(QLatin1String("href")),
linkPhotos.attribute(QLatin1String("href")),
QDateTime::fromString(published.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")),
QDateTime::fromString(edited.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")),
QDateTime::fromString(updated.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ")),
password
));
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Found album:" << d->albums.last();
}
// TODO: pagination like listPhotos
// if an error has occurred and we didn't find anything => notify user
if (errorOccurred && d->albums.isEmpty())
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "No result and errors have occurred";
return setErrorState(STATE_LISTALBUMS_ERROR);
}
// we have next page
if (!d->albumsNextUrl.isNull())
{
return listAlbumsNext();
}
else
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "List albums done: " << d->albums.size();
d->state = STATE_LISTALBUMS_DONE;
emit signalListAlbumsDone(d->albums);
}
}
bool YFTalker::slotParsePhotoXml(const QDomElement& entryElem, YFPhoto& photo)
{
const QDomElement urn = entryElem.firstChildElement(QLatin1String("id"));
const QDomElement author = entryElem.firstChildElement(QLatin1String("author"));
const QDomElement title = entryElem.firstChildElement(QLatin1String("title"));
const QDomElement summary = entryElem.firstChildElement(QLatin1String("summary"));
const QDomElement published = entryElem.firstChildElement(QLatin1String("published"));
const QDomElement edited = entryElem.firstChildElement(QLatin1String("app:edited"));
const QDomElement updated = entryElem.firstChildElement(QLatin1String("updated"));
const QDomElement created = entryElem.firstChildElement(QLatin1String("f:created"));
const QDomElement accessAttr = entryElem.firstChildElement(QLatin1String("f:access"));
const QDomElement hideOriginal = entryElem.firstChildElement(QLatin1String("f:hide_original"));
const QDomElement disableComments = entryElem.firstChildElement(QLatin1String("f:disable_comments"));
const QDomElement adult = entryElem.firstChildElement(QLatin1String("f:xxx"));
const QDomElement content = entryElem.firstChildElement(QLatin1String("content"));
QDomElement linkSelf;
QDomElement linkEdit;
QDomElement linkMedia;
QDomElement linkAlbum;
QDomElement linkElem = entryElem.firstChildElement(QLatin1String("link"));
for ( ; !linkElem.isNull() ;
linkElem = linkElem.nextSiblingElement(QLatin1String("link")))
{
if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("self"))
linkSelf = linkElem;
else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("edit"))
linkEdit = linkElem;
else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("edit-media"))
linkMedia = linkElem;
else if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("album"))
linkAlbum = linkElem;
// else skip <link>
}
// XML sanity checks
if (urn.isNull() || title.isNull() ||
linkSelf.isNull() || linkEdit.isNull() ||
linkMedia.isNull() || linkAlbum.isNull() ||
!content.hasAttribute(QLatin1String("src")) ||
!accessAttr.hasAttribute(QLatin1String("value")))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML data, error on line" << entryElem.lineNumber();
- // simple skip this record, no addtional messages to user
+ // simple skip this record, no additional messages to user
return false;
}
const QString accessString = accessAttr.attribute(QLatin1String("value"));
YFPhoto::Access access;
if (accessString == d->ACCESS_STRINGS[YFPhoto::ACCESS_PRIVATE])
access = YFPhoto::ACCESS_PRIVATE;
else if (accessString == d->ACCESS_STRINGS[YFPhoto::ACCESS_FRIENDS])
access = YFPhoto::ACCESS_FRIENDS;
else if (accessString == d->ACCESS_STRINGS[YFPhoto::ACCESS_PUBLIC])
access = YFPhoto::ACCESS_PUBLIC;
else
{
qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Unknown photo access level: " << accessString;
access = YFPhoto::ACCESS_PUBLIC;
}
photo.m_urn = urn.text();
photo.m_author = author.text();
photo.setTitle(title.text());
photo.setSummary(summary.text());
photo.m_apiEditUrl = linkEdit.attribute(QLatin1String("href"));
photo.m_apiSelfUrl = linkSelf.attribute(QLatin1String("href"));
photo.m_apiMediaUrl = linkMedia.attribute(QLatin1String("href"));
photo.m_apiAlbumUrl = linkAlbum.attribute(QLatin1String("href"));
photo.m_publishedDate = QDateTime::fromString(published.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ"));
photo.m_editedDate = QDateTime::fromString(edited.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ"));
photo.m_updatedDate = QDateTime::fromString(updated.text(), QLatin1String("yyyy-MM-ddTHH:mm:ssZ"));
photo.m_createdDate = QDateTime::fromString(created.text(), QLatin1String("yyyy-MM-ddTHH:mm:ss"));
photo.setAccess(access);
photo.setHideOriginal(hideOriginal.attribute(
QLatin1String("value"), QLatin1String("false")) == QLatin1String("true"));
photo.setDisableComments(disableComments.attribute(
QLatin1String("value"), QLatin1String("false")) == QLatin1String("true"));
photo.setAdult(adult.attribute(
QLatin1String("value"), QLatin1String("false")) == QLatin1String("true"));
photo.m_remoteUrl = content.attribute(QLatin1String("src"));
/*
* FIXME: tags part of the API is not documented by Yandex
*/
// reload all tags from the response
photo.tags.clear();
QDomElement category = entryElem.firstChildElement(QLatin1String("category"));
for ( ; !category.isNull() ;
category = category.nextSiblingElement(QLatin1String("category")))
{
if (category.hasAttribute(QLatin1String("term")) &&
category.hasAttribute(QLatin1String("scheme")) &&
// FIXME: I have no idea how to make its better, usable API is needed
category.attribute(QLatin1String("scheme")) == d->apiTagsUrl)
{
photo.tags.append(category.attribute(QLatin1String("term")));
}
}
return true;
}
void YFTalker::slotParseResponseListPhotos()
{
QDomDocument doc(QLatin1String("feed"));
if (!doc.setContent(d->buffer))
{
qCCritical(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML, parse error: " << d->buffer;
return setErrorState(STATE_LISTPHOTOS_ERROR);
}
int initialSize = d->photos.size();
bool errorOccurred = false;
const QDomElement rootElem = doc.documentElement();
// find next page link
d->photosNextUrl.clear();
QDomElement linkElem = rootElem.firstChildElement(QLatin1String("link"));
for ( ; !linkElem.isNull() ;
linkElem = linkElem.nextSiblingElement(QLatin1String("link")))
{
if (linkElem.attribute(QLatin1String("rel")) == QLatin1String("next") &&
!linkElem.attribute(QLatin1String("href")).isNull())
{
d->photosNextUrl = linkElem.attribute(QLatin1String("href"));
break;
}
}
QDomElement entryElem = rootElem.firstChildElement(QLatin1String("entry"));
for ( ; !entryElem.isNull() ;
entryElem = entryElem.nextSiblingElement(QLatin1String("entry")))
{
YFPhoto photo;
if (slotParsePhotoXml(entryElem, photo))
{
d->photos.append(photo);
}
else
{
- // set error mark and conintinue
+ // set error mark and continue
errorOccurred = true;
}
}
// if an error has occurred and we didn't find anything => notify user
if (errorOccurred && initialSize == d->photos.size())
{
qCCritical(DIGIKAM_WEBSERVICES_LOG) << "No photos found, some XML errors have occurred";
return setErrorState(STATE_LISTPHOTOS_ERROR);
}
// we have next page
if (!d->photosNextUrl.isNull())
{
return listPhotosNext();
}
else
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "List photos done: " << d->photos.size();
d->state = STATE_LISTPHOTOS_DONE;
emit signalListPhotosDone(d->photos);
}
}
void YFTalker::slotParseResponseUpdatePhotoFile()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Uploaded photo document" << d->buffer;
QDomDocument doc(QLatin1String("entry"));
if (!doc.setContent(d->buffer))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML, parse error" << d->buffer;
return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR);
}
YFPhoto& photo = *d->lastPhoto;
YFPhoto tmpPhoto;
const QDomElement entryElem = doc.documentElement();
if (!slotParsePhotoXml(entryElem, tmpPhoto))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML, entry not found" << d->buffer;
return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR);
}
photo.m_urn = tmpPhoto.m_urn;
photo.m_apiEditUrl = tmpPhoto.m_apiEditUrl;
photo.m_apiSelfUrl = tmpPhoto.m_apiSelfUrl;
photo.m_apiMediaUrl = tmpPhoto.m_apiMediaUrl;
photo.m_remoteUrl = tmpPhoto.m_remoteUrl;
photo.m_remoteUrl = tmpPhoto.m_remoteUrl;
photo.m_author = tmpPhoto.m_author;
// update info
updatePhotoInfo(photo);
}
void YFTalker::slotParseResponseUpdatePhotoInfo()
{
YFPhoto& photo = *d->lastPhoto;
/*
// reload all information
QDomDocument doc("entry");
if (!doc.setContent(d->buffer))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Invalid XML: parse error" << d->buffer;
return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR);
}
const QDomElement entryElem = doc.documentElement();
if (!slotParsePhotoXml(entryElem, photo))
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Can't reload photo after uploading";
return setErrorState(STATE_UPDATEPHOTO_INFO_ERROR);
}
*/
d->state = STATE_UPDATEPHOTO_DONE;
d->lastPhoto = 0;
emit signalUpdatePhotoDone(photo);
}
void YFTalker::slotParseResponseUpdateAlbum()
{
qCDebug(DIGIKAM_WEBSERVICES_LOG) << "Updated album" << d->buffer;
d->state = STATE_UPDATEALBUM_DONE;
d->lastPhoto = 0;
emit signalUpdateAlbumDone();
}
} // namespace Digikam
diff --git a/core/utilities/extrasupport/addressbook/akonadiiface.cpp b/core/utilities/extrasupport/addressbook/akonadiiface.cpp
index cea50329eb..ceee24fab0 100644
--- a/core/utilities/extrasupport/addressbook/akonadiiface.cpp
+++ b/core/utilities/extrasupport/addressbook/akonadiiface.cpp
@@ -1,143 +1,143 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-02-15
* Description : Plasma Address Book contacts interface
*
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "akonadiiface.h"
// Qt includes
#include <QApplication>
#include <QAction>
#include <QString>
#include <QMenu>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
#if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wundef"
#endif
#include <kjob.h>
#include <AkonadiCore/Item>
#include <Akonadi/Contact/ContactSearchJob>
#include <KContacts/Addressee>
#if defined(Q_OS_DARWIN) && defined(Q_CC_CLANG)
# pragma clang diagnostic pop
#endif
// Local includes
#include "digikam_debug.h"
namespace Digikam
{
// TODO: Port from KABC::AddressBook to libakonadi-kontact. For instance using Akonadi::ContactSearchJob.
// See http://techbase.kde.org/Development/AkonadiPorting/AddressBook
AkonadiIface::AkonadiIface(QMenu* const parent)
: QObject(parent)
{
m_parent = parent;
m_ABCmenu = 0;
m_ABCmenu = new QMenu(m_parent);
QAction* const abcAction = m_ABCmenu->menuAction();
abcAction->setIcon(QIcon::fromTheme(QLatin1String("tag-addressbook")));
abcAction->setText(i18n("Create Tag From Address Book"));
m_parent->addMenu(m_ABCmenu);
QAction* const nothingFound = m_ABCmenu->addAction(i18n("No address book entries found"));
nothingFound->setEnabled(false);
Akonadi::ContactSearchJob* const job = new Akonadi::ContactSearchJob();
job->setQuery(Akonadi::ContactSearchJob::ContactUid, QLatin1String(""));
connect(job, SIGNAL(result(KJob*)),
this, SLOT(slotABCSearchResult(KJob*)));
}
AkonadiIface::~AkonadiIface()
{
delete m_ABCmenu;
}
void AkonadiIface::slotABCSearchResult(KJob* job)
{
if (job->error())
{
- qCDebug(DIGIKAM_GENERAL_LOG) << "Akonadi search was not succesfull";
+ qCDebug(DIGIKAM_GENERAL_LOG) << "Akonadi search was not successful";
return;
}
Akonadi::ContactSearchJob* const searchJob = qobject_cast<Akonadi::ContactSearchJob*>(job);
const KContacts::Addressee::List contacts = searchJob->contacts();
if (contacts.isEmpty())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "No contacts in Akonadi";
return;
}
QStringList names;
foreach(const KContacts::Addressee& addr, contacts)
{
if (!addr.realName().isNull())
{
names.append(addr.realName());
}
}
names.removeDuplicates();
names.sort();
if (names.isEmpty())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "No names in the address book";
return;
}
m_ABCmenu->clear();
foreach(const QString& name, names)
{
m_ABCmenu->addAction(name);
}
connect(m_ABCmenu, SIGNAL(triggered(QAction*)),
this, SLOT(slotABCMenuTriggered(QAction*)));
}
void AkonadiIface::slotABCMenuTriggered(QAction* action)
{
QString name = action->iconText();
emit signalContactTriggered(name);
}
} // namespace Digikam
diff --git a/core/utilities/facemanagement/faceworkers.cpp b/core/utilities/facemanagement/faceworkers.cpp
index 38c734a7b8..aa1b15d4c9 100644
--- a/core/utilities/facemanagement/faceworkers.cpp
+++ b/core/utilities/facemanagement/faceworkers.cpp
@@ -1,370 +1,370 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-09-03
* Description : Integrated, multithread face detection / recognition
*
* Copyright (C) 2010-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "faceworkers.h"
// Qt includes
#include <QMetaObject>
#include <QMutexLocker>
// KDE includes
#include <klocalizedstring.h>
#include <ksharedconfig.h>
#include <kconfiggroup.h>
// Local includes
#include "digikam_debug.h"
#include "loadingdescription.h"
#include "metadatasettings.h"
#include "tagscache.h"
#include "threadmanager.h"
#include "facebenchmarkers.h"
namespace Digikam
{
DetectionWorker::DetectionWorker(FacePipeline::Private* const d)
: d(d)
{
}
void DetectionWorker::process(FacePipelineExtendedPackage::Ptr package)
{
QImage detectionImage = scaleForDetection(package->image);
package->detectedFaces = detector.detectFaces(detectionImage, package->image.originalSize());
qCDebug(DIGIKAM_GENERAL_LOG) << "Found" << package->detectedFaces.size() << "faces in"
<< package->info.name() << package->image.size()
<< package->image.originalSize();
package->processFlags |= FacePipelinePackage::ProcessedByDetector;
emit processed(package);
}
QImage DetectionWorker::scaleForDetection(const DImg& image) const
{
int recommendedSize = detector.recommendedImageSize(image.size());
if (qMax(image.width(), image.height()) > (uint)recommendedSize)
{
return image.smoothScale(recommendedSize, recommendedSize, Qt::KeepAspectRatio).copyQImage();
}
return image.copyQImage();
}
void DetectionWorker::setAccuracy(double accuracy)
{
QVariantMap params;
params[QLatin1String("accuracy")] = accuracy;
params[QLatin1String("specificity")] = 0.8; //TODO: add UI for sensitivity - specificity
detector.setParameters(params);
}
// ----------------------------------------------------------------------------------------
RecognitionWorker::RecognitionWorker(FacePipeline::Private* const d)
: imageRetriever(d),
d(d)
{
}
void RecognitionWorker::activeFaceRecognizer(RecognitionDatabase::RecognizeAlgorithm algorithmType)
{
database.activeFaceRecognizer(algorithmType);
}
void RecognitionWorker::process(FacePipelineExtendedPackage::Ptr package)
{
FaceUtils utils;
QList<QImage> images;
if (package->processFlags & FacePipelinePackage::ProcessedByDetector)
{
// assume we have an image
images = imageRetriever.getDetails(package->image, package->detectedFaces);
}
else if (!package->databaseFaces.isEmpty())
{
images = imageRetriever.getThumbnails(package->filePath, package->databaseFaces.toFaceTagsIfaceList());
}
package->recognitionResults = database.recognizeFaces(images);
package->processFlags |= FacePipelinePackage::ProcessedByRecognizer;
emit processed(package);
}
void RecognitionWorker::setThreshold(double threshold)
{
database.setParameter(QLatin1String("threshold"), threshold);
}
void RecognitionWorker::aboutToDeactivate()
{
imageRetriever.cancel();
}
// ----------------------------------------------------------------------------------------
class Q_DECL_HIDDEN MapListTrainingDataProvider : public TrainingDataProvider
{
public:
MapListTrainingDataProvider()
{
}
ImageListProvider* newImages(const Identity& identity)
{
if (imagesToTrain.contains(identity.id()))
{
QListImageListProvider& provider = imagesToTrain[identity.id()];
provider.reset();
return &provider;
}
return &empty;
}
ImageListProvider* images(const Identity&)
{
// Not implemented. Would be needed if we use a backend with a "holistic" approach that needs all images to train.
return &empty;
}
public:
EmptyImageListProvider empty;
QMap<int, QListImageListProvider> imagesToTrain;
};
// ----------------------------------------------------------------------------------------
Trainer::Trainer(FacePipeline::Private* const d)
: imageRetriever(d),
d(d)
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(QLatin1String("Face Detection Dialog"));
RecognitionDatabase::RecognizeAlgorithm algo =
(RecognitionDatabase::RecognizeAlgorithm)group.readEntry(QLatin1String("Recognize Algorithm"),
(int)RecognitionDatabase::RecognizeAlgorithm::LBP);
database.activeFaceRecognizer(algo);
}
void Trainer::process(FacePipelineExtendedPackage::Ptr package)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "Trainer: processing one package";
// Get a list of faces with type FaceForTraining (probably type is ConfirmedFace)
QList<FaceTagsIface> toTrain;
QList<int> identities;
QList<Identity> identitySet;
FaceUtils utils;
foreach (const FacePipelineFaceTagsIface& face, package->databaseFaces)
{
if (face.roles & FacePipelineFaceTagsIface::ForTraining)
{
FaceTagsIface dbFace = face;
dbFace.setType(FaceTagsIface::FaceForTraining);
toTrain << dbFace;
Identity identity = utils.identityForTag(dbFace.tagId(), database);
identities << identity.id();
if (!identitySet.contains(identity))
{
identitySet << identity;
}
}
}
if (!toTrain.isEmpty())
{
QList<QImage> images;
if (package->image.isNull())
{
images = imageRetriever.getThumbnails(package->filePath, toTrain);
}
else
{
images = imageRetriever.getDetails(package->image, toTrain);
}
MapListTrainingDataProvider provider;
// Group images by identity
for (int i = 0 ; i < toTrain.size() ; ++i)
{
provider.imagesToTrain[identities[i]].list << images[i];
}
database.train(identitySet, &provider, QLatin1String("digikam"));
}
utils.removeFaces(toTrain);
package->databaseFaces.replaceRole(FacePipelineFaceTagsIface::ForTraining, FacePipelineFaceTagsIface::Trained);
package->processFlags |= FacePipelinePackage::ProcessedByTrainer;
emit processed(package);
}
void Trainer::aboutToDeactivate()
{
imageRetriever.cancel();
}
// ----------------------------------------------------------------------------------------
DatabaseWriter::DatabaseWriter(FacePipeline::WriteMode mode, FacePipeline::Private* const d)
: mode(mode),
thumbnailLoadThread(d->createThumbnailLoadThread()),
d(d)
{
}
void DatabaseWriter::process(FacePipelineExtendedPackage::Ptr package)
{
if (package->databaseFaces.isEmpty())
{
// Detection / Recognition
FaceUtils utils;
- // OverwriteUnconfirmed means that a new scan discared unconfirmed results of previous scans
+ // OverwriteUnconfirmed means that a new scan discarded unconfirmed results of previous scans
// (assuming at least equal or new, better methodology is in use compared to the previous scan)
if (mode == FacePipeline::OverwriteUnconfirmed && (package->processFlags & FacePipelinePackage::ProcessedByDetector))
{
QList<FaceTagsIface> oldEntries = utils.unconfirmedFaceTagsIfaces(package->info.id());
qCDebug(DIGIKAM_GENERAL_LOG) << "Removing old entries" << oldEntries;
utils.removeFaces(oldEntries);
}
// mark the whole image as scanned-for-faces
utils.markAsScanned(package->info);
if (!package->info.isNull() && !package->detectedFaces.isEmpty())
{
package->databaseFaces = utils.writeUnconfirmedResults(package->info.id(),
package->detectedFaces,
package->recognitionResults,
package->image.originalSize());
package->databaseFaces.setRole(FacePipelineFaceTagsIface::DetectedFromImage);
if (!package->image.isNull())
{
utils.storeThumbnails(thumbnailLoadThread, package->filePath,
package->databaseFaces.toFaceTagsIfaceList(), package->image);
}
}
}
else if (package->processFlags & FacePipelinePackage::ProcessedByRecognizer)
{
FaceUtils utils;
for (int i = 0 ; i < package->databaseFaces.size() ; ++i)
{
if (package->databaseFaces[i].roles & FacePipelineFaceTagsIface::ForRecognition)
{
// Allow to overwrite existing recognition with new, possibly valid, "not recognized" status
int tagId = FaceTags::unknownPersonTagId();
- // NOTE: See bug #338485 : check if index is not outside of containe size.
+ // NOTE: See bug #338485 : check if index is not outside of container size.
if (i < package->recognitionResults.size() &&
!package->recognitionResults[i].isNull())
{
// Only perform this call if recognition as results, to prevent crash in QMap. See bug #335624
tagId = FaceTags::getOrCreateTagForIdentity(package->recognitionResults[i].attributesMap());
}
package->databaseFaces[i] = FacePipelineFaceTagsIface(utils.changeSuggestedName(package->databaseFaces[i], tagId));
package->databaseFaces[i].roles &= ~FacePipelineFaceTagsIface::ForRecognition;
}
}
}
else
{
// Editing database entries
FaceUtils utils;
FacePipelineFaceTagsIfaceList add;
FacePipelineFaceTagsIfaceList::iterator it;
for (it = package->databaseFaces.begin() ; it != package->databaseFaces.end() ; ++it)
{
if (it->roles & FacePipelineFaceTagsIface::ForConfirmation)
{
FacePipelineFaceTagsIface confirmed = FacePipelineFaceTagsIface(utils.confirmName(*it, it->assignedTagId, it->assignedRegion));
confirmed.roles |= FacePipelineFaceTagsIface::Confirmed | FacePipelineFaceTagsIface::ForTraining;
add << confirmed;
}
else if (it->roles & FacePipelineFaceTagsIface::ForEditing)
{
if (it->isNull())
{
// add Manually
FaceTagsIface newFace = utils.unconfirmedEntry(package->info.id(), it->assignedTagId, it->assignedRegion);
utils.addManually(newFace);
add << FacePipelineFaceTagsIface(newFace);
}
else if (it->assignedRegion.isValid())
{
add << FacePipelineFaceTagsIface(utils.changeRegion(*it, it->assignedRegion));
// not implemented: changing tag id
}
else
{
utils.removeFace(*it);
}
it->roles &= ~FacePipelineFaceTagsIface::ForEditing;
it->roles |= FacePipelineFaceTagsIface::Edited;
}
// Training is done by trainer
}
if (!package->image.isNull())
{
utils.storeThumbnails(thumbnailLoadThread, package->filePath, add.toFaceTagsIfaceList(), package->image);
}
package->databaseFaces << add;
}
package->processFlags |= FacePipelinePackage::WrittenToDatabase;
emit processed(package);
}
} // namespace Digikam
diff --git a/core/utilities/fuzzysearch/fuzzysearchview.h b/core/utilities/fuzzysearch/fuzzysearchview.h
index fc4336496e..8c46466816 100644
--- a/core/utilities/fuzzysearch/fuzzysearchview.h
+++ b/core/utilities/fuzzysearch/fuzzysearchview.h
@@ -1,135 +1,135 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-05-19
* Description : Fuzzy search sidebar tab contents.
*
* Copyright (C) 2016-2018 by Mario Frank <mario dot frank at uni minus potsdam dot de>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2008-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_FUZZY_SEARCH_VIEW_H
#define DIGIKAM_FUZZY_SEARCH_VIEW_H
// Qt includes
#include <QScrollArea>
-// Local includs
+// Local includes
#include "statesavingobject.h"
class QDragEnterEvent;
class QDropEvent;
class QPixmap;
namespace Digikam
{
class Album;
class FuzzySearchFolderView;
class ImageInfo;
class LoadingDescription;
class SAlbum;
class PAlbum;
class TAlbum;
class SearchModel;
class SearchModificationHelper;
class SearchTextBar;
class FuzzySearchView : public QScrollArea, public StateSavingObject
{
Q_OBJECT
public:
explicit FuzzySearchView(SearchModel* const searchModel,
SearchModificationHelper* const searchModificationHelper,
QWidget* const parent = 0);
virtual ~FuzzySearchView();
SAlbum* currentAlbum() const;
void setCurrentAlbum(SAlbum* const album);
void setActive(bool val);
void setImageInfo(const ImageInfo& info);
void newDuplicatesSearch(PAlbum* const album);
void newDuplicatesSearch(const QList<PAlbum*>& albums);
void newDuplicatesSearch(const QList<TAlbum*>& albums);
virtual void setConfigGroup(const KConfigGroup& group);
void doLoadState();
void doSaveState();
protected:
void dragEnterEvent(QDragEnterEvent* e);
void dropEvent(QDropEvent* e);
private Q_SLOTS:
void slotTabChanged(int);
void slotHSChanged(int h, int s);
void slotVChanged(int v);
void slotPenColorChanged(const QColor&);
void slotClearSketch();
void slotSaveSketchSAlbum();
void slotCheckNameEditSketchConditions();
void slotAlbumSelected(Album* album);
void slotSaveImageSAlbum();
void slotCheckNameEditImageConditions();
void slotThumbnailLoaded(const LoadingDescription&, const QPixmap&);
void slotDirtySketch();
void slotTimerSketchDone();
void slotUndoRedoStateChanged(bool, bool);
void slotMinLevelImageChanged(int);
void slotMaxLevelImageChanged(int);
void slotFuzzyAlbumsChanged();
void slotTimerImageDone();
void slotApplicationSettingsChanged();
private:
void setCurrentImage(qlonglong imageid);
void setCurrentImage(const ImageInfo& info);
void createNewFuzzySearchAlbumFromSketch(const QString& name, bool force = false);
void createNewFuzzySearchAlbumFromImage(const QString& name, bool force = false);
void setColor(QColor c);
QWidget* setupFindSimilarPanel() const;
QWidget* setupSketchPanel() const;
void setupConnections();
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_FUZZY_SEARCH_VIEW_H
diff --git a/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp b/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp
index 7dfc59d47a..8b529959c6 100644
--- a/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp
+++ b/core/utilities/geolocation/editor/bookmark/bookmarksmngr.cpp
@@ -1,848 +1,848 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-05-15
* Description : low level manager for GPS bookmarks
*
* Copyright (C) 2017-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "bookmarksmngr.h"
// Qt includes
#include <QBuffer>
#include <QFile>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QIcon>
#include <QHeaderView>
#include <QMessageBox>
#include <QToolButton>
#include <QDebug>
#include <QApplication>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include <dfiledialog.h>
#include "bookmarknode.h"
#include "digikam_debug.h"
namespace Digikam
{
RemoveBookmarksCommand::RemoveBookmarksCommand(BookmarksManager* const mngr,
BookmarkNode* const parent,
int row)
: QUndoCommand(i18n("Remove Bookmark")),
m_row(row),
m_bookmarkManager(mngr),
m_node(parent->children().value(row)),
m_parent(parent),
m_done(false)
{
}
RemoveBookmarksCommand::~RemoveBookmarksCommand()
{
if (m_done && !m_node->parent())
{
delete m_node;
}
}
void RemoveBookmarksCommand::undo()
{
m_parent->add(m_node, m_row);
emit m_bookmarkManager->entryAdded(m_node);
m_done = false;
}
void RemoveBookmarksCommand::redo()
{
m_parent->remove(m_node);
emit m_bookmarkManager->entryRemoved(m_parent, m_row, m_node);
m_done = true;
}
// --------------------------------------------------------------
InsertBookmarksCommand::InsertBookmarksCommand(BookmarksManager* const mngr,
BookmarkNode* const parent,
BookmarkNode* const node,
int row)
: RemoveBookmarksCommand(mngr, parent, row)
{
setText(i18n("Insert Bookmark"));
m_node = node;
}
void InsertBookmarksCommand::undo()
{
RemoveBookmarksCommand::redo();
}
void InsertBookmarksCommand::redo()
{
RemoveBookmarksCommand::undo();
}
// --------------------------------------------------------------
class Q_DECL_HIDDEN ChangeBookmarkCommand::Private
{
public:
explicit Private()
: manager(0),
type(Url),
node(0)
{
}
BookmarksManager* manager;
BookmarkData type;
QString oldValue;
QString newValue;
BookmarkNode* node;
};
ChangeBookmarkCommand::ChangeBookmarkCommand(BookmarksManager* const mngr,
BookmarkNode* const node,
const QString& newValue,
BookmarkData type)
: QUndoCommand(),
d(new Private)
{
d->manager = mngr;
d->type = type;
d->newValue = newValue;
d->node = node;
switch (d->type)
{
case Title:
d->oldValue = d->node->title;
setText(i18n("Title Change"));
break;
case Desc:
d->oldValue = d->node->desc;
setText(i18n("Comment Change"));
break;
default: // Url
d->oldValue = d->node->url;
setText(i18n("Address Change"));
break;
}
}
ChangeBookmarkCommand::~ChangeBookmarkCommand()
{
delete d;
}
void ChangeBookmarkCommand::undo()
{
switch (d->type)
{
case Title:
d->node->title = d->oldValue;
break;
case Desc:
d->node->desc = d->oldValue;
break;
default: // Url
d->node->url = d->oldValue;
break;
}
emit d->manager->entryChanged(d->node);
}
void ChangeBookmarkCommand::redo()
{
switch (d->type)
{
case Title:
d->node->title = d->newValue;
break;
case Desc:
d->node->desc = d->newValue;
break;
default: // Url
d->node->url = d->newValue;
break;
}
emit d->manager->entryChanged(d->node);
}
// --------------------------------------------------------------
class Q_DECL_HIDDEN BookmarksModel::Private
{
public:
explicit Private()
: manager(0),
endMacro(false)
{
}
BookmarksManager* manager;
bool endMacro;
};
BookmarksModel::BookmarksModel(BookmarksManager* const mngr, QObject* const parent)
: QAbstractItemModel(parent),
d(new Private)
{
d->manager = mngr;
connect(d->manager, SIGNAL(entryAdded(BookmarkNode*)),
this, SLOT(entryAdded(BookmarkNode*)));
connect(d->manager, SIGNAL(entryRemoved(BookmarkNode*,int,BookmarkNode*)),
this, SLOT(entryRemoved(BookmarkNode*,int,BookmarkNode*)));
connect(d->manager, SIGNAL(entryChanged(BookmarkNode*)),
this, SLOT(entryChanged(BookmarkNode*)));
}
BookmarksModel::~BookmarksModel()
{
delete d;
}
BookmarksManager* BookmarksModel::bookmarksManager() const
{
return d->manager;
}
QModelIndex BookmarksModel::index(BookmarkNode* node) const
{
BookmarkNode* const parent = node->parent();
if (!parent)
return QModelIndex();
return createIndex(parent->children().indexOf(node), 0, node);
}
void BookmarksModel::entryAdded(BookmarkNode* item)
{
Q_ASSERT(item && item->parent());
int row = item->parent()->children().indexOf(item);
BookmarkNode* const parent = item->parent();
- // item was already added so remove beore beginInsertRows is called
+ // item was already added so remove before beginInsertRows is called
parent->remove(item);
beginInsertRows(index(parent), row, row);
parent->add(item, row);
endInsertRows();
}
void BookmarksModel::entryRemoved(BookmarkNode* parent, int row, BookmarkNode* item)
{
// item was already removed, re-add so beginRemoveRows works
parent->add(item, row);
beginRemoveRows(index(parent), row, row);
parent->remove(item);
endRemoveRows();
}
void BookmarksModel::entryChanged(BookmarkNode* item)
{
QModelIndex idx = index(item);
emit dataChanged(idx, idx);
}
bool BookmarksModel::removeRows(int row, int count, const QModelIndex& parent)
{
if (row < 0 || count <= 0 || (row + count) > rowCount(parent))
return false;
BookmarkNode* const bookmarkNode = node(parent);
for (int i = (row + count - 1) ; i >= row ; i--)
{
BookmarkNode* const node = bookmarkNode->children().at(i);
d->manager->removeBookmark(node);
}
if (d->endMacro)
{
d->manager->undoRedoStack()->endMacro();
d->endMacro = false;
}
return true;
}
QVariant BookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case 0:
return i18n("Title");
case 1:
return i18n("Comment");
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QVariant BookmarksModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid() || index.model() != this)
return QVariant();
const BookmarkNode* const bookmarkNode = node(index);
switch (role)
{
case Qt::EditRole:
case Qt::DisplayRole:
if (bookmarkNode->type() == BookmarkNode::Separator)
{
switch (index.column())
{
case 0:
return QString(50, 0xB7);
case 1:
return QString();
}
}
switch (index.column())
{
case 0:
return bookmarkNode->title;
case 1:
return bookmarkNode->desc;
}
break;
case BookmarksModel::UrlRole:
return QUrl(bookmarkNode->url);
break;
case BookmarksModel::UrlStringRole:
return bookmarkNode->url;
break;
case BookmarksModel::DateAddedRole:
return bookmarkNode->dateAdded;
break;
case BookmarksModel::TypeRole:
return bookmarkNode->type();
break;
case BookmarksModel::SeparatorRole:
return (bookmarkNode->type() == BookmarkNode::Separator);
break;
case Qt::DecorationRole:
if (index.column() == 0)
{
if (bookmarkNode->type() == BookmarkNode::Bookmark)
{
return QIcon::fromTheme(QLatin1String("globe"));
}
else
{
return QIcon::fromTheme(QLatin1String("folder"));
}
}
}
return QVariant();
}
int BookmarksModel::columnCount(const QModelIndex& parent) const
{
return (parent.column() > 0) ? 0 : 2;
}
int BookmarksModel::rowCount(const QModelIndex& parent) const
{
if (parent.column() > 0)
return 0;
if (!parent.isValid())
return d->manager->bookmarks()->children().count();
const BookmarkNode* const item = static_cast<BookmarkNode*>(parent.internalPointer());
return item->children().count();
}
QModelIndex BookmarksModel::index(int row, int column, const QModelIndex& parent) const
{
if (row < 0 || column < 0 || row >= rowCount(parent) || column >= columnCount(parent))
return QModelIndex();
// get the parent node
BookmarkNode* const parentNode = node(parent);
return createIndex(row, column, parentNode->children().at(row));
}
QModelIndex BookmarksModel::parent(const QModelIndex& index) const
{
if (!index.isValid())
return QModelIndex();
BookmarkNode* const itemNode = node(index);
BookmarkNode* const parentNode = (itemNode ? itemNode->parent() : 0);
if (!parentNode || parentNode == d->manager->bookmarks())
return QModelIndex();
// get the parent's row
BookmarkNode* const grandParentNode = parentNode->parent();
int parentRow = grandParentNode->children().indexOf(parentNode);
Q_ASSERT(parentRow >= 0);
return createIndex(parentRow, 0, parentNode);
}
bool BookmarksModel::hasChildren(const QModelIndex& parent) const
{
if (!parent.isValid())
return true;
const BookmarkNode* const parentNode = node(parent);
return (parentNode->type() == BookmarkNode::Folder ||
parentNode->type() == BookmarkNode::RootFolder);
}
Qt::ItemFlags BookmarksModel::flags(const QModelIndex& index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
BookmarkNode* const bookmarkNode = node(index);
if (bookmarkNode->type() != BookmarkNode::RootFolder)
flags |= Qt::ItemIsDragEnabled;
if (bookmarkNode->type() != BookmarkNode::Separator &&
bookmarkNode->type() != BookmarkNode::RootFolder)
{
flags |= Qt::ItemIsEditable;
}
if (hasChildren(index))
flags |= Qt::ItemIsDropEnabled;
return flags;
}
Qt::DropActions BookmarksModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}
QStringList BookmarksModel::mimeTypes() const
{
QStringList types;
types << QLatin1String("application/bookmarks.xbel");
return types;
}
QMimeData* BookmarksModel::mimeData(const QModelIndexList& indexes) const
{
QMimeData* const mimeData = new QMimeData();
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
foreach (QModelIndex index, indexes)
{
if (index.column() != 0 || !index.isValid())
continue;
QByteArray encodedData;
QBuffer buffer(&encodedData);
buffer.open(QBuffer::ReadWrite);
XbelWriter writer;
const BookmarkNode* const parentNode = node(index);
writer.write(&buffer, parentNode);
stream << encodedData;
}
mimeData->setData(QLatin1String("application/bookmarks.xbel"), data);
return mimeData;
}
bool BookmarksModel::dropMimeData(const QMimeData* data,
Qt::DropAction action,
int row, int column,
const QModelIndex& parent)
{
if (action == Qt::IgnoreAction)
return true;
if (!data->hasFormat(QLatin1String("application/bookmarks.xbel")) || column > 0)
return false;
QByteArray ba = data->data(QLatin1String("application/bookmarks.xbel"));
QDataStream stream(&ba, QIODevice::ReadOnly);
if (stream.atEnd())
return false;
QUndoStack* const undoStack = d->manager->undoRedoStack();
undoStack->beginMacro(QLatin1String("Move Bookmarks"));
while (!stream.atEnd())
{
QByteArray encodedData;
stream >> encodedData;
QBuffer buffer(&encodedData);
buffer.open(QBuffer::ReadOnly);
XbelReader reader;
BookmarkNode* const rootNode = reader.read(&buffer);
QList<BookmarkNode*> children = rootNode->children();
for (int i = 0 ; i < children.count() ; i++)
{
BookmarkNode* const bookmarkNode = children.at(i);
rootNode->remove(bookmarkNode);
row = qMax(0, row);
BookmarkNode* const parentNode = node(parent);
d->manager->addBookmark(parentNode, bookmarkNode, row);
d->endMacro = true;
}
delete rootNode;
}
return true;
}
bool BookmarksModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!index.isValid() || (flags(index) & Qt::ItemIsEditable) == 0)
return false;
BookmarkNode* const item = node(index);
switch (role)
{
case Qt::EditRole:
case Qt::DisplayRole:
if (index.column() == 0)
{
d->manager->setTitle(item, value.toString());
break;
}
if (index.column() == 1)
{
d->manager->setComment(item, value.toString());
break;
}
return false;
case BookmarksModel::UrlRole:
d->manager->setUrl(item, value.toUrl().toString());
break;
case BookmarksModel::UrlStringRole:
d->manager->setUrl(item, value.toString());
break;
default:
return false;
}
return true;
}
BookmarkNode* BookmarksModel::node(const QModelIndex& index) const
{
BookmarkNode* const itemNode = static_cast<BookmarkNode*>(index.internalPointer());
if (!itemNode)
return d->manager->bookmarks();
return itemNode;
}
// --------------------------------------------------------------
AddBookmarkProxyModel::AddBookmarkProxyModel(QObject* const parent)
: QSortFilterProxyModel(parent)
{
}
int AddBookmarkProxyModel::columnCount(const QModelIndex& parent) const
{
return qMin(1, QSortFilterProxyModel::columnCount(parent));
}
bool AddBookmarkProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const
{
QModelIndex idx = sourceModel()->index(srow, 0, sparent);
return sourceModel()->hasChildren(idx);
}
// --------------------------------------------------------------
TreeProxyModel::TreeProxyModel(QObject* const parent)
: QSortFilterProxyModel(parent)
{
setFilterCaseSensitivity(Qt::CaseInsensitive);
}
int TreeProxyModel::columnCount(const QModelIndex&) const
{
// 1th column : Title
// 2th column : Comment
return 2;
}
bool TreeProxyModel::filterAcceptsRow(int srow, const QModelIndex& sparent) const
{
QModelIndex index = sourceModel()->index(srow, 0, sparent);
if (!index.isValid())
{
return false;
}
if (index.data().toString().contains(filterRegExp()))
{
return true;
}
for (int i = 0 ; i < sourceModel()->rowCount(index) ; i++)
{
if (filterAcceptsRow(i, index))
{
return true;
}
}
return false;
}
void TreeProxyModel::emitResult(bool v)
{
emit signalFilterAccepts(v);
}
// --------------------------------------------------------------
class Q_DECL_HIDDEN BookmarksManager::Private
{
public:
explicit Private()
: loaded(false),
bookmarkRootNode(0),
bookmarkModel(0)
{
}
bool loaded;
BookmarkNode* bookmarkRootNode;
BookmarksModel* bookmarkModel;
QUndoStack commands;
QString bookmarksFile;
};
BookmarksManager::BookmarksManager(const QString& bookmarksFile, QObject* const parent)
: QObject(parent),
d(new Private)
{
d->bookmarksFile = bookmarksFile;
load();
}
BookmarksManager::~BookmarksManager()
{
delete d;
}
void BookmarksManager::changeExpanded()
{
}
void BookmarksManager::load()
{
if (d->loaded)
return;
qCDebug(DIGIKAM_GEOIFACE_LOG) << "Loading GPS bookmarks from" << d->bookmarksFile;
d->loaded = true;
XbelReader reader;
d->bookmarkRootNode = reader.read(d->bookmarksFile);
if (reader.error() != QXmlStreamReader::NoError)
{
QMessageBox::warning(0, i18n("Loading Bookmark"),
i18n("Error when loading bookmarks on line %1, column %2:\n%3",
reader.lineNumber(),
reader.columnNumber(),
reader.errorString()));
}
}
void BookmarksManager::save()
{
if (!d->loaded)
return;
qCDebug(DIGIKAM_GEOIFACE_LOG) << "Saving GPS bookmarks to" << d->bookmarksFile;
XbelWriter writer;
if (!writer.write(d->bookmarksFile, d->bookmarkRootNode))
{
qCWarning(DIGIKAM_GEOIFACE_LOG) << "BookmarkManager: error saving to" << d->bookmarksFile;
}
}
void BookmarksManager::addBookmark(BookmarkNode* const parent, BookmarkNode* const node, int row)
{
if (!d->loaded)
return;
Q_ASSERT(parent);
InsertBookmarksCommand* const command = new InsertBookmarksCommand(this, parent, node, row);
d->commands.push(command);
}
void BookmarksManager::removeBookmark(BookmarkNode* const node)
{
if (!d->loaded)
return;
Q_ASSERT(node);
BookmarkNode* const parent = node->parent();
int row = parent->children().indexOf(node);
RemoveBookmarksCommand* const command = new RemoveBookmarksCommand(this, parent, row);
d->commands.push(command);
}
void BookmarksManager::setTitle(BookmarkNode* const node, const QString& newTitle)
{
if (!d->loaded)
return;
Q_ASSERT(node);
ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newTitle,
ChangeBookmarkCommand::Title);
d->commands.push(command);
}
void BookmarksManager::setUrl(BookmarkNode* const node, const QString& newUrl)
{
if (!d->loaded)
return;
Q_ASSERT(node);
ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newUrl,
ChangeBookmarkCommand::Url);
d->commands.push(command);
}
void BookmarksManager::setComment(BookmarkNode* const node, const QString& newDesc)
{
if (!d->loaded)
return;
Q_ASSERT(node);
ChangeBookmarkCommand* const command = new ChangeBookmarkCommand(this, node, newDesc,
ChangeBookmarkCommand::Desc);
d->commands.push(command);
}
BookmarkNode* BookmarksManager::bookmarks()
{
if (!d->loaded)
load();
return d->bookmarkRootNode;
}
BookmarksModel* BookmarksManager::bookmarksModel()
{
if (!d->bookmarkModel)
d->bookmarkModel = new BookmarksModel(this, this);
return d->bookmarkModel;
}
QUndoStack* BookmarksManager::undoRedoStack() const
{
return &d->commands;
}
void BookmarksManager::importBookmarks()
{
QString fileName = DFileDialog::getOpenFileName(0, i18n("Open File"),
QString(),
i18n("XBEL (*.xbel *.xml)"));
if (fileName.isEmpty())
return;
XbelReader reader;
BookmarkNode* const importRootNode = reader.read(fileName);
if (reader.error() != QXmlStreamReader::NoError)
{
QMessageBox::warning(0, i18n("Loading Bookmark"),
i18n("Error when loading bookmarks on line %1, column %2:\n%3",
reader.lineNumber(),
reader.columnNumber(),
reader.errorString()));
}
importRootNode->setType(BookmarkNode::Folder);
importRootNode->title = i18n("Imported %1", QDate::currentDate().toString(Qt::SystemLocaleShortDate));
addBookmark(bookmarks(), importRootNode);
}
void BookmarksManager::exportBookmarks()
{
QString fileName = DFileDialog::getSaveFileName(0, i18n("Save File"),
i18n("%1 Bookmarks.xbel", QCoreApplication::applicationName()),
i18n("XBEL (*.xbel *.xml)"));
if (fileName.isEmpty())
return;
XbelWriter writer;
if (!writer.write(fileName, d->bookmarkRootNode))
QMessageBox::critical(0, i18n("Export error"), i18n("error saving bookmarks"));
}
} // namespace Digikam
diff --git a/core/utilities/geolocation/editor/items/gpsimageitem.cpp b/core/utilities/geolocation/editor/items/gpsimageitem.cpp
index a561d4edf3..ec6d654e6c 100644
--- a/core/utilities/geolocation/editor/items/gpsimageitem.cpp
+++ b/core/utilities/geolocation/editor/items/gpsimageitem.cpp
@@ -1,969 +1,969 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2010-03-21
* Description : An item to hold information about an image.
*
* Copyright (C) 2010-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2010-2014 by Michael G. Hansen <mike at mghansen dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "gpsimageitem.h"
// Qt includes
#include <QBrush>
#include <QFileInfo>
#include <QScopedPointer>
#include <QLocale>
// KDE includes
#include <klocalizedstring.h>
// local includes
#include "gpsimagemodel.h"
#include "metadatasettings.h"
namespace Digikam
{
bool setExifXmpTagDataVariant(DMetadata* const meta, const char* const exifTagName,
const char* const xmpTagName, const QVariant& value)
{
bool success = meta->setExifTagVariant(exifTagName, value);
if (success)
{
/** @todo Here we save all data types as XMP Strings. Is that okay or do we have to store them as some other type?
*/
switch (value.type())
{
case QVariant::Int:
case QVariant::UInt:
case QVariant::Bool:
case QVariant::LongLong:
case QVariant::ULongLong:
success = meta->setXmpTagString(xmpTagName, QString::number(value.toInt()));
break;
case QVariant::Double:
{
long num, den;
meta->convertToRationalSmallDenominator(value.toDouble(), &num, &den);
success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den));
break;
}
case QVariant::List:
{
long num = 0, den = 1;
QList<QVariant> list = value.toList();
if (list.size() >= 1)
num = list[0].toInt();
if (list.size() >= 2)
den = list[1].toInt();
success = meta->setXmpTagString(xmpTagName, QString::fromLatin1("%1/%2").arg(num).arg(den));
break;
}
case QVariant::Date:
case QVariant::DateTime:
{
QDateTime dateTime = value.toDateTime();
if(!dateTime.isValid())
{
success = false;
break;
}
success = meta->setXmpTagString(xmpTagName, dateTime.toString(QLatin1String("yyyy:MM:dd hh:mm:ss")));
break;
}
case QVariant::String:
case QVariant::Char:
success = meta->setXmpTagString(xmpTagName, value.toString());
break;
case QVariant::ByteArray:
/// @todo I don't know a straightforward way to convert a byte array to XMP
success = false;
break;
default:
success = false;
break;
}
}
return success;
}
GPSImageItem::GPSImageItem(const QUrl& url)
: m_model(0),
m_url(url),
m_dateTime(),
m_dirty(false),
m_gpsData(),
m_savedState(),
m_tagListDirty(false),
m_tagList(),
m_savedTagList(),
m_writeXmpTags(true)
{
}
GPSImageItem::~GPSImageItem()
{
}
DMetadata* GPSImageItem::getMetadataForFile() const
{
QScopedPointer<DMetadata> meta(new DMetadata);
if (!meta->load(m_url.toLocalFile()))
{
// It is possible that no sidecar file has yet been created.
// If writing to sidecar file is activated, we ignore the loading error of the metadata.
if (MetadataSettings::instance()->settings().metadataWritingMode == DMetadata::WRITETOIMAGEONLY)
{
return 0;
}
}
return meta.take();
}
int getWarningLevelFromGPSDataContainer(const GPSDataContainer& data)
{
if (data.hasDop())
{
const int dopValue = data.getDop();
if (dopValue < 2)
return 1;
if (dopValue < 4)
return 2;
if (dopValue < 10)
return 3;
return 4;
}
else if (data.hasFixType())
{
if (data.getFixType() < 3)
return 4;
}
else if (data.hasNSatellites())
{
if (data.getNSatellites() < 4)
return 4;
}
// no warning level
return -1;
}
bool GPSImageItem::loadImageData()
{
QScopedPointer<DMetadata> meta(getMetadataForFile());
if (meta && !m_dateTime.isValid())
{
m_dateTime = meta->getImageDateTime();
}
if (!m_dateTime.isValid())
{
// Get date from filesystem.
QFileInfo info(m_url.toLocalFile());
QDateTime ctime = info.created();
QDateTime mtime = info.lastModified();
if (ctime.isNull() || mtime.isNull())
{
m_dateTime = qMax(ctime, mtime);
}
else
{
m_dateTime = qMin(ctime, mtime);
}
}
if (!meta)
return false;
// The way we read the coordinates here is problematic
// if the coordinates were in the file initially, but
// the user deleted them in the database. Then we still load
// them from the file. On the other hand, we can not clear
// the coordinates, because then we would loose them if
// they are only stored in the database.
// m_gpsData.clear();
if (!m_gpsData.hasCoordinates())
{
// could not load the coordinates from the interface,
// read them directly from the file
double lat, lng;
bool haveCoordinates = meta->getGPSLatitudeNumber(&lat) && meta->getGPSLongitudeNumber(&lng);
if (haveCoordinates)
{
GeoCoordinates coordinates(lat, lng);
double alt;
if (meta->getGPSAltitude(&alt))
{
coordinates.setAlt(alt);
}
m_gpsData.setCoordinates(coordinates);
}
}
/** @todo It seems that exiv2 provides EXIF entries if XMP sidecar entries exist,
* therefore no need to read XMP as well?
*/
// read the remaining GPS information from the file:
const QByteArray speedRef = meta->getExifTagData("Exif.GPSInfo.GPSSpeedRef");
bool success = !speedRef.isEmpty();
long num, den;
success &= meta->getExifTagRational("Exif.GPSInfo.GPSSpeed", num, den);
if (success)
{
// be relaxed about 0/0
if ((num == 0.0) && (den == 0.0))
den = 1.0;
const qreal speedInRef = qreal(num)/qreal(den);
qreal FactorToMetersPerSecond;
if (speedRef.startsWith('K'))
{
// km/h = 1000 * 3600
FactorToMetersPerSecond = 1.0/3.6;
}
else if (speedRef.startsWith('M'))
{
// TODO: someone please check that this is the 'right' mile
// miles/hour = 1609.344 meters / hour = 1609.344 meters / 3600 seconds
FactorToMetersPerSecond = 1.0 / (1609.344 / 3600.0);
}
else if (speedRef.startsWith('N'))
{
// speed is in knots.
- // knot = one nautic mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds
+ // knot = one nautical mile / hour = 1852 meters / hour = 1852 meters / 3600 seconds
FactorToMetersPerSecond = 1.0 / (1852.0 / 3600.0);
}
else
{
success = false;
}
if (success)
{
const qreal speedInMetersPerSecond = speedInRef * FactorToMetersPerSecond;
m_gpsData.setSpeed(speedInMetersPerSecond);
}
}
// number of satellites
const QString gpsSatellitesString = meta->getExifTagString("Exif.GPSInfo.GPSSatellites");
bool satellitesOkay = !gpsSatellitesString.isEmpty();
if (satellitesOkay)
{
/**
* @todo Here we only accept a single integer denoting the number of satellites used
* but not detailed information about all satellites.
*/
const int nSatellites = gpsSatellitesString.toInt(&satellitesOkay);
if (satellitesOkay)
{
m_gpsData.setNSatellites(nSatellites);
}
}
// fix type / measure mode
const QByteArray gpsMeasureModeByteArray = meta->getExifTagData("Exif.GPSInfo.GPSMeasureMode");
bool measureModeOkay = !gpsMeasureModeByteArray.isEmpty();
if (measureModeOkay)
{
const int measureMode = gpsMeasureModeByteArray.toInt(&measureModeOkay);
if (measureModeOkay)
{
if ((measureMode == 2) || (measureMode == 3))
{
m_gpsData.setFixType(measureMode);
}
}
}
// read the DOP value:
success = meta->getExifTagRational("Exif.GPSInfo.GPSDOP", num, den);
if (success)
{
// be relaxed about 0/0
if ((num == 0.0) && (den == 0.0))
den = 1.0;
const qreal dop = qreal(num)/qreal(den);
m_gpsData.setDop(dop);
}
// mark us as not-dirty, because the data was just loaded:
m_dirty = false;
m_savedState = m_gpsData;
emitDataChanged();
return true;
}
QVariant GPSImageItem::data(const int column, const int role) const
{
if ((column == ColumnFilename) && (role == Qt::DisplayRole))
{
return m_url.fileName();
}
else if ((column == ColumnDateTime) && (role == Qt::DisplayRole))
{
if (m_dateTime.isValid())
{
return QLocale().toString(m_dateTime, QLocale::ShortFormat);
}
return i18n("Not available");
}
else if (role == RoleCoordinates)
{
return QVariant::fromValue(m_gpsData.getCoordinates());
}
else if ((column == ColumnLatitude) && (role == Qt::DisplayRole))
{
if (!m_gpsData.getCoordinates().hasLatitude())
return QString();
return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lat(), 7);
}
else if ((column == ColumnLongitude) && (role == Qt::DisplayRole))
{
if (!m_gpsData.getCoordinates().hasLongitude())
return QString();
return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().lon(), 7);
}
else if ((column == ColumnAltitude) && (role == Qt::DisplayRole))
{
if (!m_gpsData.getCoordinates().hasAltitude())
return QString();
return QString::fromLatin1("%1").arg(m_gpsData.getCoordinates().alt(), 7);
}
else if (column == ColumnAccuracy)
{
if (role == Qt::DisplayRole)
{
if (m_gpsData.hasDop())
{
return i18n("DOP: %1", m_gpsData.getDop());
}
if (m_gpsData.hasFixType())
{
return i18n("Fix: %1d", m_gpsData.getFixType());
}
if (m_gpsData.hasNSatellites())
{
return i18n("#Sat: %1", m_gpsData.getNSatellites());
}
}
else if (role == Qt::BackgroundRole)
{
const int warningLevel = getWarningLevelFromGPSDataContainer(m_gpsData);
switch (warningLevel)
{
case 1:
return QBrush(Qt::green);
case 2:
return QBrush(Qt::yellow);
case 3:
// orange
return QBrush(QColor(0xff, 0x80, 0x00));
case 4:
return QBrush(Qt::red);
default:
break;
}
}
}
else if ((column == ColumnDOP) && (role == Qt::DisplayRole))
{
if (!m_gpsData.hasDop())
return QString();
return QString::number(m_gpsData.getDop());
}
else if ((column == ColumnFixType) && (role == Qt::DisplayRole))
{
if (!m_gpsData.hasFixType())
return QString();
return i18n("%1d", m_gpsData.getFixType());
}
else if ((column == ColumnNSatellites) && (role == Qt::DisplayRole))
{
if (!m_gpsData.hasNSatellites())
return QString();
return QString::number(m_gpsData.getNSatellites());
}
else if ((column == ColumnSpeed) && (role == Qt::DisplayRole))
{
if (!m_gpsData.hasSpeed())
return QString();
return QString::number(m_gpsData.getSpeed());
}
else if ((column == ColumnStatus) && (role == Qt::DisplayRole))
{
if (m_dirty || m_tagListDirty)
{
return i18n("Modified");
}
return QString();
}
else if ((column == ColumnTags) && (role == Qt::DisplayRole))
{
if (!m_tagList.isEmpty())
{
QString myTagsList;
for (int i = 0 ; i < m_tagList.count() ; ++i)
{
QString myTag;
for (int j = 0 ; j < m_tagList[i].count() ; ++j)
{
myTag.append(QLatin1Char('/') + m_tagList[i].at(j).tagName);
if (j == 0)
myTag.remove(0, 1);
}
if (!myTagsList.isEmpty())
myTagsList.append(QLatin1String(", "));
myTagsList.append(myTag);
}
return myTagsList;
}
return QString();
}
return QVariant();
}
void GPSImageItem::setCoordinates(const GeoCoordinates& newCoordinates)
{
m_gpsData.setCoordinates(newCoordinates);
m_dirty = true;
emitDataChanged();
}
void GPSImageItem::setModel(GPSImageModel* const model)
{
m_model = model;
}
void GPSImageItem::emitDataChanged()
{
if (m_model)
{
m_model->itemChanged(this);
}
}
void GPSImageItem::setHeaderData(GPSImageModel* const model)
{
model->setColumnCount(ColumnGPSImageItemCount);
model->setHeaderData(ColumnThumbnail, Qt::Horizontal, i18n("Thumbnail"), Qt::DisplayRole);
model->setHeaderData(ColumnFilename, Qt::Horizontal, i18n("Filename"), Qt::DisplayRole);
model->setHeaderData(ColumnDateTime, Qt::Horizontal, i18n("Date and time"), Qt::DisplayRole);
model->setHeaderData(ColumnLatitude, Qt::Horizontal, i18n("Latitude"), Qt::DisplayRole);
model->setHeaderData(ColumnLongitude, Qt::Horizontal, i18n("Longitude"), Qt::DisplayRole);
model->setHeaderData(ColumnAltitude, Qt::Horizontal, i18n("Altitude"), Qt::DisplayRole);
model->setHeaderData(ColumnAccuracy, Qt::Horizontal, i18n("Accuracy"), Qt::DisplayRole);
model->setHeaderData(ColumnDOP, Qt::Horizontal, i18n("DOP"), Qt::DisplayRole);
model->setHeaderData(ColumnFixType, Qt::Horizontal, i18n("Fix type"), Qt::DisplayRole);
model->setHeaderData(ColumnNSatellites, Qt::Horizontal, i18n("# satellites"), Qt::DisplayRole);
model->setHeaderData(ColumnSpeed, Qt::Horizontal, i18n("Speed"), Qt::DisplayRole);
model->setHeaderData(ColumnStatus, Qt::Horizontal, i18n("Status"), Qt::DisplayRole);
model->setHeaderData(ColumnTags, Qt::Horizontal, i18n("Tags"), Qt::DisplayRole);
}
bool GPSImageItem::lessThan(const GPSImageItem* const otherItem, const int column) const
{
switch (column)
{
case ColumnThumbnail:
return false;
case ColumnFilename:
return m_url < otherItem->m_url;
case ColumnDateTime:
return m_dateTime < otherItem->m_dateTime;
case ColumnAltitude:
{
if (!m_gpsData.hasAltitude())
return false;
if (!otherItem->m_gpsData.hasAltitude())
return true;
return m_gpsData.getCoordinates().alt() < otherItem->m_gpsData.getCoordinates().alt();
}
case ColumnNSatellites:
{
if (!m_gpsData.hasNSatellites())
return false;
if (!otherItem->m_gpsData.hasNSatellites())
return true;
return m_gpsData.getNSatellites() < otherItem->m_gpsData.getNSatellites();
}
case ColumnAccuracy:
{
const int myWarning = getWarningLevelFromGPSDataContainer(m_gpsData);
const int otherWarning = getWarningLevelFromGPSDataContainer(otherItem->m_gpsData);
if (myWarning < 0)
return false;
if (otherWarning < 0)
return true;
if (myWarning != otherWarning)
return myWarning < otherWarning;
// TODO: this may not be the best way to sort images with equal warning levels
// but it works for now
if (m_gpsData.hasDop() != otherItem->m_gpsData.hasDop())
return !m_gpsData.hasDop();
if (m_gpsData.hasDop() && otherItem->m_gpsData.hasDop())
{
return m_gpsData.getDop() < otherItem->m_gpsData.getDop();
}
if (m_gpsData.hasFixType() != otherItem->m_gpsData.hasFixType())
return m_gpsData.hasFixType();
if (m_gpsData.hasFixType() && otherItem->m_gpsData.hasFixType())
{
return m_gpsData.getFixType() > otherItem->m_gpsData.getFixType();
}
if (m_gpsData.hasNSatellites() != otherItem->m_gpsData.hasNSatellites())
return m_gpsData.hasNSatellites();
if (m_gpsData.hasNSatellites() && otherItem->m_gpsData.hasNSatellites())
{
return m_gpsData.getNSatellites() > otherItem->m_gpsData.getNSatellites();
}
return false;
}
case ColumnDOP:
{
if (!m_gpsData.hasDop())
return false;
if (!otherItem->m_gpsData.hasDop())
return true;
return m_gpsData.getDop() < otherItem->m_gpsData.getDop();
}
case ColumnFixType:
{
if (!m_gpsData.hasFixType())
return false;
if (!otherItem->m_gpsData.hasFixType())
return true;
return m_gpsData.getFixType() < otherItem->m_gpsData.getFixType();
}
case ColumnSpeed:
{
if (!m_gpsData.hasSpeed())
return false;
if (!otherItem->m_gpsData.hasSpeed())
return true;
return m_gpsData.getSpeed() < otherItem->m_gpsData.getSpeed();
}
case ColumnLatitude:
{
if (!m_gpsData.hasCoordinates())
return false;
if (!otherItem->m_gpsData.hasCoordinates())
return true;
return m_gpsData.getCoordinates().lat() < otherItem->m_gpsData.getCoordinates().lat();
}
case ColumnLongitude:
{
if (!m_gpsData.hasCoordinates())
return false;
if (!otherItem->m_gpsData.hasCoordinates())
return true;
return m_gpsData.getCoordinates().lon() < otherItem->m_gpsData.getCoordinates().lon();
}
case ColumnStatus:
{
return m_dirty && !otherItem->m_dirty;
}
default:
return false;
}
}
SaveProperties GPSImageItem::saveProperties() const
{
SaveProperties p;
// do we have gps information?
if (m_gpsData.hasCoordinates())
{
p.shouldWriteCoordinates = true;
p.latitude = m_gpsData.getCoordinates().lat();
p.longitude = m_gpsData.getCoordinates().lon();
if (m_gpsData.hasAltitude())
{
p.shouldWriteAltitude = true;
p.altitude = m_gpsData.getCoordinates().alt();
}
else
{
p.shouldRemoveAltitude = true;
}
}
else
{
p.shouldRemoveCoordinates = true;
}
return p;
}
QString GPSImageItem::saveChanges()
{
SaveProperties p = saveProperties();
QString returnString;
// first try to write the information to the image file
bool success = false;
QScopedPointer<DMetadata> meta(getMetadataForFile());
if (!meta)
{
// TODO: more verbosity!
returnString = i18n("Failed to open file.");
}
else
{
if (p.shouldWriteCoordinates)
{
if (p.shouldWriteAltitude)
{
success = meta->setGPSInfo(p.altitude, p.latitude, p.longitude);
}
else
{
success = meta->setGPSInfo(const_cast<double*>(static_cast<double*>(0)),
p.latitude, p.longitude);
}
// write all other GPS information here too
if (success && m_gpsData.hasSpeed())
{
success = setExifXmpTagDataVariant(meta.data(),
"Exif.GPSInfo.GPSSpeedRef",
"Xmp.exif.GPSSpeedRef",
QVariant(QLatin1String("K")));
if (success)
{
const qreal speedInMetersPerSecond = m_gpsData.getSpeed();
// km/h = 0.001 * m / ( s * 1/(60*60) ) = 3.6 * m/s
const qreal speedInKilometersPerHour = 3.6 * speedInMetersPerSecond;
success = setExifXmpTagDataVariant(meta.data(), "Exif.GPSInfo.GPSSpeed", "Xmp.exif.GPSSpeed", QVariant(speedInKilometersPerHour));
}
}
if (success && m_gpsData.hasNSatellites())
{
/**
* @todo According to the EXIF 2.2 spec, GPSSatellites is a free form field which can either hold only the
* number of satellites or more details about each satellite used. For now, we just write
* the number of satellites. Are we using the correct format for the number of satellites here?
*/
success = setExifXmpTagDataVariant(meta.data(),
"Exif.GPSInfo.GPSSatellites", "Xmp.exif.GPSSatellites",
QVariant(QString::number(m_gpsData.getNSatellites())));
}
if (success && m_gpsData.hasFixType())
{
success = setExifXmpTagDataVariant(meta.data(),
"Exif.GPSInfo.GPSMeasureMode", "Xmp.exif.GPSMeasureMode",
QVariant(QString::number(m_gpsData.getFixType())));
}
// write DOP
if (success && m_gpsData.hasDop())
{
success = setExifXmpTagDataVariant(meta.data(),
"Exif.GPSInfo.GPSDOP",
"Xmp.exif.GPSDOP",
QVariant(m_gpsData.getDop()));
}
if (!success)
{
returnString = i18n("Failed to add GPS info to image.");
}
}
if (p.shouldRemoveCoordinates)
{
// TODO: remove only the altitude if requested
success = meta->removeGPSInfo();
if (!success)
{
returnString = i18n("Failed to remove GPS info from image");
}
}
if (!m_tagList.isEmpty() && m_writeXmpTags)
{
QStringList tagSeq;
for (int i = 0 ; i < m_tagList.count() ; ++i)
{
QList<TagData> currentTagList = m_tagList[i];
QString tag;
for (int j = 0 ; j < currentTagList.count() ; ++j)
{
tag.append(QLatin1Char('/') + currentTagList[j].tagName);
}
tag.remove(0, 1);
tagSeq.append(tag);
}
bool success = meta->setXmpTagStringSeq("Xmp.digiKam.TagsList", tagSeq);
if (!success)
{
returnString = i18n("Failed to save tags to file.");
}
success = meta->setXmpTagStringSeq("Xmp.dc.subject", tagSeq);
if (!success)
{
returnString = i18n("Failed to save tags to file.");
}
}
}
if (success)
{
success = meta->save(m_url.toLocalFile());
if (!success)
{
returnString = i18n("Unable to save changes to file");
}
else
{
m_dirty = false;
m_savedState = m_gpsData;
m_tagListDirty = false;
m_savedTagList = m_tagList;
}
}
if (returnString.isEmpty())
{
// mark all changes as not dirty and tell the model:
emitDataChanged();
}
return returnString;
}
/**
* @brief Restore the gps data to @p container. Sets m_dirty to false if container equals savedState.
*/
void GPSImageItem::restoreGPSData(const GPSDataContainer& container)
{
m_dirty = !(container == m_savedState);
m_gpsData = container;
emitDataChanged();
}
void GPSImageItem::restoreRGTagList(const QList<QList<TagData> >& tagList)
{
//TODO: override == operator
if (tagList.count() != m_savedTagList.count())
{
m_tagListDirty = true;
}
else
{
for (int i = 0 ; i < tagList.count() ; ++i)
{
bool foundNotEqual = false;
if (tagList[i].count() != m_savedTagList[i].count())
{
m_tagListDirty = true;
break;
}
for (int j = 0 ; j < tagList[i].count() ; ++j)
{
if (tagList[i].at(j).tagName != m_savedTagList[i].at(j).tagName)
{
foundNotEqual = true;
break;
}
}
if (foundNotEqual)
{
m_tagListDirty = true;
break;
}
}
}
m_tagList = tagList;
emitDataChanged();
}
bool GPSImageItem::isDirty() const
{
return m_dirty;
}
QUrl GPSImageItem::url() const
{
return m_url;
}
QDateTime GPSImageItem::dateTime() const
{
return m_dateTime;
}
GeoCoordinates GPSImageItem::coordinates() const
{
return m_gpsData.getCoordinates();
}
GPSDataContainer GPSImageItem::gpsData() const
{
return m_gpsData;
}
void GPSImageItem::setGPSData(const GPSDataContainer& container)
{
m_gpsData = container;
m_dirty = true;
emitDataChanged();
}
void GPSImageItem::setTagList(const QList<QList<TagData> >& externalTagList)
{
m_tagList = externalTagList;
m_tagListDirty = true;
emitDataChanged();
}
bool GPSImageItem::isTagListDirty() const
{
return m_tagListDirty;
}
QList<QList<TagData> > GPSImageItem::getTagList() const
{
return m_tagList;
}
} // namespace Digikam
diff --git a/core/utilities/geolocation/editor/kmlexport/kmlexport.cpp b/core/utilities/geolocation/editor/kmlexport/kmlexport.cpp
index 8669173cb7..49bef8e90e 100644
--- a/core/utilities/geolocation/editor/kmlexport/kmlexport.cpp
+++ b/core/utilities/geolocation/editor/kmlexport/kmlexport.cpp
@@ -1,621 +1,621 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-05-16
* Description : a tool to export GPS data to KML file.
*
* Copyright (C) 2006-2007 by Stephane Pontier <shadow dot walker at free dot fr>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "kmlexport.h"
// Qt includes
#include <QImageReader>
#include <QPainter>
#include <QRegExp>
#include <QTextStream>
#include <QStandardPaths>
#include <QApplication>
#include <QIODevice>
#include <QDir>
// KDE includes
#include <ksharedconfig.h>
#include <kconfiggroup.h>
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "dmessagebox.h"
namespace Digikam
{
KmlExport::KmlExport(DInfoInterface* const iface)
{
m_localTarget = true;
m_optimize_googlemap = false;
m_GPXtracks = false;
m_iconSize = 33;
m_googlemapSize = 32;
m_size = 320;
m_altitudeMode = 0;
m_TimeZone = 12;
m_LineWidth = 4;
m_GPXOpacity = 64;
m_GPXAltitudeMode = 0;
m_kmlDocument = 0;
m_iface = iface;
}
KmlExport::~KmlExport()
{
}
void KmlExport::setUrls(const QList<QUrl>& urls)
{
m_urls = urls;
}
QString KmlExport::webifyFileName(const QString& fileName) const
{
QString webFileName = fileName.toLower();
// Remove potentially troublesome chars
webFileName = webFileName.replace(QRegExp(QLatin1String("[^-0-9a-z]+")), QLatin1String("_"));
return webFileName;
}
QImage KmlExport::generateSquareThumbnail(const QImage& fullImage, int size) const
{
QImage image = fullImage.scaled(size, size, Qt::KeepAspectRatioByExpanding);
if (image.width() == size && image.height() == size)
{
return image;
}
QPixmap croppedPix(size, size);
QPainter painter(&croppedPix);
int sx = 0, sy = 0;
if (image.width()>size)
{
sx = (image.width() - size)/2;
}
else
{
sy = (image.height() - size)/2;
}
painter.drawImage(0, 0, image, sx, sy, size, size);
painter.end();
return croppedPix.toImage();
}
QImage KmlExport::generateBorderedThumbnail(const QImage& fullImage, int size) const
{
int image_border = 3;
// getting an image minus the border
QImage image = fullImage.scaled(size -(2*image_border), size - (2*image_border), Qt::KeepAspectRatioByExpanding);
QPixmap croppedPix(image.width() + (2*image_border), image.height() + (2*image_border));
QPainter painter(&croppedPix);
QColor BrushColor(255,255,255);
painter.fillRect(0,0,image.width() + (2*image_border),image.height() + (2*image_border),BrushColor);
/*! @todo add a corner to the thumbnail and a hotspot to the kml element */
painter.drawImage(image_border, image_border, image);
painter.end();
return croppedPix.toImage();
}
void KmlExport::generateImagesthumb(const QUrl& imageURL, QDomElement& kmlAlbum)
{
DItemInfo info(m_iface->itemInfo(imageURL));
// Load image
QString path = imageURL.toLocalFile();
QFile imageFile(path);
if (!imageFile.open(QIODevice::ReadOnly))
{
logError(i18n("Could not read image '%1'", path));
return;
}
QImageReader reader(&imageFile);
QString imageFormat = QString::fromUtf8(reader.format());
if (imageFormat.isEmpty())
{
logError(i18n("Format of image '%1' is unknown", path));
return;
}
imageFile.close();
if (!imageFile.open(QIODevice::ReadOnly))
{
logError(i18n("Could not read image '%1'", path));
return;
}
QByteArray imageData = imageFile.readAll();
imageFile.close();
QImage image;
if (!image.loadFromData(imageData))
{
logError(i18n("Error loading image '%1'", path));
return;
}
// Process images
if (info.orientation() != DMetadata::ORIENTATION_UNSPECIFIED)
{
m_meta.rotateExifQImage(image, (MetaEngine::ImageOrientation)info.orientation());
}
image = image.scaled(m_size, m_size, Qt::KeepAspectRatioByExpanding);
QImage icon;
if (m_optimize_googlemap)
{
icon = generateSquareThumbnail(image,m_googlemapSize);
}
else
{
// icon = image.smoothScale(m_iconSize, m_iconSize, QImage::ScaleMax);
icon = generateBorderedThumbnail(image, m_iconSize);
}
// Save images
/** @todo remove the extension of the file
* it's appear with digikam but not with gwenview
* which already seems to strip the extension
*/
QString baseFileName = webifyFileName(info.name());
//baseFileName = mUniqueNameHelper.makeNameUnique(baseFileName);
QString fullFileName;
fullFileName = baseFileName + QLatin1Char('.') + imageFormat.toLower();
QString destPath = m_imageDir.filePath(fullFileName);
if (!image.save(destPath, imageFormat.toLatin1().constData(), 85))
{
// if not able to save the image, it's pointless to create a placemark
logError(i18n("Could not save image '%1' to '%2'", path, destPath));
}
else
{
logInfo(i18n("Creation of picture '%1'", fullFileName));
double alt = 0.0, lat = 0.0, lng = 0.0;
if (info.hasGeolocationInfo())
{
lat = info.latitude();
lng = info.longitude();
alt = info.altitude();
}
else if (m_meta.load(imageURL.toLocalFile()))
{
m_meta.getGPSInfo(alt, lat, lng);
}
QDomElement kmlPlacemark = addKmlElement(kmlAlbum, QLatin1String("Placemark"));
addKmlTextElement(kmlPlacemark, QLatin1String("name"), fullFileName);
// location and altitude
QDomElement kmlGeometry = addKmlElement(kmlPlacemark, QLatin1String("Point"));
if (alt)
{
addKmlTextElement(kmlGeometry, QLatin1String("coordinates"), QString::fromUtf8("%1,%2,%3 ")
.arg(lng, 0, 'f', 8)
.arg(lat, 0, 'f', 8)
.arg(alt, 0, 'f', 8));
}
else
{
addKmlTextElement(kmlGeometry, QLatin1String("coordinates"), QString::fromUtf8("%1,%2 ")
.arg(lng, 0, 'f', 8)
.arg(lat, 0, 'f', 8));
}
if (m_altitudeMode == 2)
{
addKmlTextElement(kmlGeometry, QLatin1String("altitudeMode"), QLatin1String("absolute"));
}
else if (m_altitudeMode == 1)
{
addKmlTextElement(kmlGeometry, QLatin1String("altitudeMode"), QLatin1String("relativeToGround"));
}
else
{
addKmlTextElement(kmlGeometry, QLatin1String("altitudeMode"), QLatin1String("clampToGround"));
}
addKmlTextElement(kmlGeometry, QLatin1String("extrude"), QLatin1String("1"));
// we try to load exif value if any otherwise, try the application db
/** we need to take the DateTimeOriginal
* if we refer to http://www.exif.org/Exif2-2.PDF
* (standard)DateTime: is The date and time of image creation. In this standard it is the date and time the file was changed
* DateTimeOriginal: The date and time when the original image data was generated.
* For a DSC the date and time the picture was taken are recorded.
* DateTimeDigitized: The date and time when the image was stored as digital data.
* So for:
* - a DSC: the right time is the DateTimeDigitized which is also DateTimeOriginal
* if the picture has been modified the (standard)DateTime should change.
* - a scanned picture, the right time is the DateTimeOriginal which should also be the DateTime
* the (standard)DateTime should be the same except if the picture is modified
* - a panorama created from several pictures, the right time is the DateTimeOriginal (average of DateTimeOriginal actually)
* The (standard)DateTime is the creation date of the panorama.
- * it's seems the time to take into acccount is the DateTimeOriginal.
+ * it's seems the time to take into account is the DateTimeOriginal.
* but the MetadataProcessor::getImageDateTime() return the (standard)DateTime first
- * MetadataProcessor seems to take Original dateTime first so it shoul be alright now.
+ * MetadataProcessor seems to take Original dateTime first so it should be alright now.
*/
QDateTime datetime;
m_meta.getImageDateTime();
if (datetime.isValid())
{
QDomElement kmlTimeStamp = addKmlElement(kmlPlacemark, QLatin1String("TimeStamp"));
addKmlTextElement(kmlTimeStamp, QLatin1String("when"), datetime.toString(QLatin1String("yyyy-MM-ddThh:mm:ssZ")));
}
else
{
QDomElement kmlTimeStamp = addKmlElement(kmlPlacemark, QLatin1String("TimeStamp"));
addKmlTextElement(kmlTimeStamp, QLatin1String("when"), (info.dateTime()).toString(QLatin1String("yyyy-MM-ddThh:mm:ssZ")));
}
QString my_description;
if (m_optimize_googlemap)
{
my_description = QLatin1String("<img src=\"") + m_UrlDestDir + m_imageDirBasename + QLatin1Char('/') + fullFileName + QLatin1String("\">");
}
else
{
my_description = QLatin1String("<img src=\"") + m_imageDirBasename + QLatin1Char('/') + fullFileName + QLatin1String("\">");
}
my_description += QLatin1String("<br/>") + info.comment() ;
addKmlTextElement(kmlPlacemark, QLatin1String("description"), my_description);
logInfo(i18n("Creation of placemark '%1'", fullFileName));
// Save icon
QString iconFileName = QLatin1String("thumb_") + baseFileName + QLatin1Char('.') + imageFormat.toLower();
QString destPath = m_imageDir.filePath(iconFileName);
if (!icon.save(destPath, imageFormat.toLatin1().constData(), 85))
{
logWarning(i18n("Could not save icon for image '%1' to '%2'",path,destPath));
}
else
{
logInfo(i18n("Creation of icon '%1'", iconFileName));
// style et icon
QDomElement kmlStyle = addKmlElement(kmlPlacemark, QLatin1String("Style"));
QDomElement kmlIconStyle = addKmlElement(kmlStyle, QLatin1String("IconStyle"));
QDomElement kmlIcon = addKmlElement(kmlIconStyle, QLatin1String("Icon"));
if (m_optimize_googlemap)
{
addKmlTextElement(kmlIcon, QLatin1String("href"), m_UrlDestDir + m_imageDirBasename + QLatin1Char('/') + iconFileName);
}
else
{
addKmlTextElement(kmlIcon, QLatin1String("href"), m_imageDirBasename + QLatin1Char('/') + iconFileName);
}
QDomElement kmlBallonStyle = addKmlElement(kmlStyle, QLatin1String("BalloonStyle"));
addKmlTextElement(kmlBallonStyle, QLatin1String("text"), QLatin1String("$[description]"));
}
}
}
void KmlExport::addTrack(QDomElement& kmlAlbum)
{
if (m_GPXFile.isEmpty())
{
logWarning(i18n("No GPX file chosen."));
return;
}
m_gpxParser.clear();
bool ret = m_gpxParser.loadGPXFile(QUrl::fromLocalFile(m_GPXFile));
if (!ret)
{
logError(i18n("Cannot parse %1 GPX file.",m_GPXFile));
return;
}
if (m_gpxParser.numPoints() <= 0)
{
logError(i18n("The %1 GPX file do not have a date-time track to use.",
m_GPXFile));
return;
}
// create a folder that will contain tracks and points
QDomElement kmlFolder = addKmlElement(kmlAlbum, QLatin1String("Folder"));
addKmlTextElement(kmlFolder, QLatin1String("name"), i18n("Tracks"));
if (!m_optimize_googlemap)
{
// style of points and track
QDomElement kmlTrackStyle = addKmlElement(kmlAlbum, QLatin1String("Style"));
kmlTrackStyle.setAttribute(QLatin1String("id"), QLatin1String("track"));
QDomElement kmlIconStyle = addKmlElement(kmlTrackStyle, QLatin1String("IconStyle"));
QDomElement kmlIcon = addKmlElement(kmlIconStyle, QLatin1String("Icon"));
//! FIXME is there a way to be sure of the location of the icon?
addKmlTextElement(kmlIcon, QLatin1String("href"), QLatin1String("http://maps.google.com/mapfiles/kml/pal4/icon60.png"));
m_gpxParser.CreateTrackPoints(kmlFolder, *m_kmlDocument, m_TimeZone - 12, m_GPXAltitudeMode);
}
// linetrack style
QDomElement kmlLineTrackStyle = addKmlElement(kmlAlbum, QLatin1String("Style"));
kmlLineTrackStyle.setAttribute(QLatin1String("id"), QLatin1String("linetrack"));
QDomElement kmlLineStyle = addKmlElement(kmlLineTrackStyle, QLatin1String("LineStyle"));
// the KML color is not #RRGGBB but AABBGGRR
QString KMLColorValue = QString::fromUtf8("%1%2%3%4")
.arg((int)m_GPXOpacity*256/100, 2, 16)
.arg((&m_GPXColor)->blue(), 2, 16)
.arg((&m_GPXColor)->green(), 2, 16)
.arg((&m_GPXColor)->red(), 2, 16);
addKmlTextElement(kmlLineStyle, QLatin1String("color"), KMLColorValue);
addKmlTextElement(kmlLineStyle, QLatin1String("width"), QString::fromUtf8("%1").arg(m_LineWidth));
m_gpxParser.CreateTrackLine(kmlAlbum, *m_kmlDocument, m_GPXAltitudeMode);
}
void KmlExport::generate()
{
getConfig();
m_logData.clear();
//! @todo perform a test here before continuing.
QDir().mkpath(m_tempDestDir.absolutePath());
QDir().mkpath(m_imageDir.absolutePath());
// create the document, and it's root
m_kmlDocument = new QDomDocument(QLatin1String(""));
QDomImplementation impl;
QDomProcessingInstruction instr = m_kmlDocument->createProcessingInstruction(QLatin1String("xml"), QLatin1String("version=\"1.0\" encoding=\"UTF-8\""));
m_kmlDocument->appendChild(instr);
QDomElement kmlRoot = m_kmlDocument->createElementNS(QLatin1String("http://www.opengis.net/kml/2.2"), QLatin1String("kml"));
m_kmlDocument->appendChild(kmlRoot);
QDomElement kmlAlbum = addKmlElement(kmlRoot, QLatin1String("Document"));
QDomElement kmlName = addKmlTextElement(kmlAlbum, QLatin1String("name"), m_KMLFileName);
QDomElement kmlDescription = addKmlHtmlElement(kmlAlbum, QLatin1String("description"),
QLatin1String("Created with Geolocation Editor from <a href=\"http://www.digikam.org/\">digiKam</a>"));
if (m_GPXtracks)
{
addTrack(kmlAlbum);
}
QList<QUrl> images = m_urls;
int pos = 1;
QList<QUrl>::ConstIterator imagesEnd (images.constEnd());
for (QList<QUrl>::ConstIterator selIt = images.constBegin();
selIt != imagesEnd; ++selIt, ++pos)
{
double alt, lat, lng;
QUrl url = *selIt;
DItemInfo info(m_iface->itemInfo(url));
bool hasGPSInfo = info.hasGeolocationInfo();
if (hasGPSInfo)
{
lat = info.latitude();
lng = info.longitude();
alt = info.altitude();
}
else if (m_meta.load(url.toLocalFile()))
{
hasGPSInfo = m_meta.getGPSInfo(alt, lat, lng);
}
if (hasGPSInfo)
{
// generation de l'image et de l'icone
generateImagesthumb(url, kmlAlbum);
}
else
{
logWarning(i18n("No position data for '%1'", info.name()));
}
emit(signalProgressChanged(pos));
QApplication::processEvents();
}
/** @todo change to kml or kmz if compressed */
QFile file(m_tempDestDir.filePath(m_KMLFileName + QLatin1String(".kml")));
if (!file.open(QIODevice::WriteOnly))
{
logError(i18n("Cannot open file for writing"));
delete m_kmlDocument;
m_kmlDocument = 0;
return;
}
QTextStream stream(&file); // we will serialize the data into the file
stream << m_kmlDocument->toString();
file.close();
delete m_kmlDocument;
m_kmlDocument = 0;
logInfo(i18n("Move %1 to final directory %2", m_tempDestDir.absolutePath(), m_baseDestDir));
if (!copyDir(m_tempDestDir.absolutePath(), m_baseDestDir))
{
logWarning(i18n("Cannot move data to destination directory"));
}
QDir(m_tempDestDir.absolutePath()).removeRecursively();
if (!m_logData.isEmpty())
{
DMessageBox::showInformationList(QMessageBox::Information,
qApp->activeWindow(),
qApp->applicationName(),
i18n("Report below have been generated while KML file processing:"),
m_logData);
}
}
bool KmlExport::copyDir(const QString& srcFilePath, const QString& dstFilePath)
{
if (QFileInfo(srcFilePath).isDir())
{
QDir srcDir(srcFilePath);
QDir dstDir(dstFilePath);
if (!QDir().mkpath(dstDir.absolutePath()))
return false;
QStringList files = srcDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
foreach(const QString& file, files)
{
const QString newSrcFilePath = srcDir.absolutePath() + QLatin1Char('/') + file;
const QString newDstFilePath = dstDir.absolutePath() + QLatin1Char('/') + file;
if (!copyDir(newSrcFilePath, newDstFilePath))
return false;
}
}
else
{
if (srcFilePath != dstFilePath && QFile::exists(srcFilePath) && QFile::exists(dstFilePath))
{
if (!QFile::remove(dstFilePath))
return false;
}
if (!QFile::copy(srcFilePath, dstFilePath))
return false;
}
return true;
}
void KmlExport::getConfig()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(QLatin1String("KMLExport Settings"));
m_localTarget = group.readEntry(QLatin1String("localTarget"), true);
m_optimize_googlemap = group.readEntry(QLatin1String("optimize_googlemap"), false);
m_iconSize = group.readEntry(QLatin1String("iconSize"), 33);
// googlemapSize = group.readNumEntry(QLatin1String("googlemapSize"));
m_size = group.readEntry(QLatin1String("size"), 320);
// UrlDestDir have to have the trailing
m_baseDestDir = group.readEntry(QLatin1String("baseDestDir"), QString::fromUtf8("/tmp/"));
m_UrlDestDir = group.readEntry(QLatin1String("UrlDestDir"), QString::fromUtf8("http://www.example.com/"));
m_KMLFileName = group.readEntry(QLatin1String("KMLFileName"), QString::fromUtf8("kmldocument"));
m_altitudeMode = group.readEntry(QLatin1String("Altitude Mode"), 0);
m_GPXtracks = group.readEntry(QLatin1String("UseGPXTracks"), false);
m_GPXFile = group.readEntry(QLatin1String("GPXFile"), QString());
m_TimeZone = group.readEntry(QLatin1String("Time Zone"), 12);
m_LineWidth = group.readEntry(QLatin1String("Line Width"), 4);
m_GPXColor = group.readEntry(QLatin1String("Track Color"), QColor("#17eeee"));
m_GPXOpacity = group.readEntry(QLatin1String("Track Opacity"), 64);
m_GPXAltitudeMode = group.readEntry(QLatin1String("GPX Altitude Mode"), 0);
m_tempDestDir = QDir(QDir::temp().filePath(QString::fromLatin1("digiKam-kmlexport-%1").arg(qApp->applicationPid())));
m_imageDirBasename = QLatin1String("images");
m_imageDir = QDir(m_tempDestDir.filePath(m_imageDirBasename));
m_googlemapSize = 32;
}
void KmlExport::logInfo(const QString& msg)
{
qCDebug(DIGIKAM_GENERAL_LOG) << msg;
}
void KmlExport::logError(const QString& msg)
{
qCDebug(DIGIKAM_GENERAL_LOG) << msg;
m_logData.append(msg);
}
void KmlExport::logWarning(const QString& msg)
{
qCDebug(DIGIKAM_GENERAL_LOG) << msg;
m_logData.append(msg);
}
QDomElement KmlExport::addKmlElement(QDomElement& target,
const QString& tag) const
{
QDomElement kmlElement = m_kmlDocument->createElement( tag );
target.appendChild( kmlElement );
return kmlElement;
}
QDomElement KmlExport::addKmlTextElement(QDomElement& target,
const QString& tag,
const QString& text) const
{
QDomElement kmlElement = m_kmlDocument->createElement( tag );
target.appendChild( kmlElement );
QDomText kmlTextElement = m_kmlDocument->createTextNode( text );
kmlElement.appendChild( kmlTextElement );
return kmlElement;
}
QDomElement KmlExport::addKmlHtmlElement(QDomElement& target,
const QString& tag,
const QString& text) const
{
QDomElement kmlElement = m_kmlDocument->createElement( tag );
target.appendChild( kmlElement );
QDomText kmlTextElement = m_kmlDocument->createCDATASection( text );
kmlElement.appendChild( kmlTextElement );
return kmlElement;
}
} // namespace Digikam
diff --git a/core/utilities/geolocation/editor/kmlexport/kmlexport.h b/core/utilities/geolocation/editor/kmlexport/kmlexport.h
index 4e49b4afab..339604cd27 100644
--- a/core/utilities/geolocation/editor/kmlexport/kmlexport.h
+++ b/core/utilities/geolocation/editor/kmlexport/kmlexport.h
@@ -1,189 +1,189 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-05-16
* Description : a tool to export GPS data to KML file.
*
* Copyright (C) 2006-2007 by Stephane Pontier <shadow dot walker at free dot fr>
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_KML_EXPORT_H
#define DIGIKAM_KML_EXPORT_H
// Qt includes
#include <QObject>
#include <QColor>
#include <QList>
#include <QUrl>
#include <QDir>
#include <QDomDocument>
#include <QPointer>
#include <QImage>
// Local includes
#include "kmlgpsdataparser.h"
#include "dmetadata.h"
#include "dinfointerface.h"
namespace Digikam
{
class KmlExport : public QObject
{
Q_OBJECT
public:
explicit KmlExport(DInfoInterface* const iface);
~KmlExport();
void setUrls(const QList<QUrl>& urls);
- /*! generate the kml element for pictures with tumbnails
+ /*! generate the kml element for pictures with thumbnails
* @param QUrl the URL of the picture
* @param kmlAlbum the album used
*/
void generateImagesthumb(const QUrl&, QDomElement& kmlAlbum);
/*! Produce a web-friendly file name
* otherwise, while google earth works fine, maps.google.com may not find pictures and thumbnail
* thank htmlexport
* @param the filename
* @return the webifyed filename
*/
QString webifyFileName(const QString& fileName) const;
/*! Generate a square thumbnail from @fullImage of @size x @size pixels
* @param fullImage the original image
* @param size the size of the thumbnail
* @return the thumbnail
*/
QImage generateSquareThumbnail(const QImage& fullImage, int size) const;
/*! Generate a square thumbnail from @fullImage of @size x @size pixels
* with a white border
* @param fullImage the original image
* @param size the size of the thumbnail
* @return the thumbnail
*/
QImage generateBorderedThumbnail(const QImage& fullImage, int size) const;
void addTrack(QDomElement& kmlAlbum);
void generate();
Q_SIGNALS:
void signalProgressChanged(const int currentProgress);
private:
void getConfig();
void logInfo(const QString& msg);
void logError(const QString& msg);
void logWarning(const QString& msg);
bool copyDir(const QString& srcFilePath, const QString& dstFilePath);
/*!
* \fn KmlExport::addKmlElement(QDomElement target, QString tag)
* Add a new element
* @param target the parent element to which add the element
* @param tag the new element name
* @return the New element
*/
QDomElement addKmlElement(QDomElement& target,
const QString& tag) const;
/*!
* \fn KmlExport::addKmlTextElement(QDomElement target, QString tag, QString text)
* Add a new element with a text
* @param target the parent element to which add the element
* @param tag the new element name
* @param text the text content of the new element
* @return the New element
*/
QDomElement addKmlTextElement(QDomElement& target,
const QString& tag,
const QString& text) const;
/*!
* \fn KmlExport::addKmlHtmlElement(QDomElement target, QString tag, QString text)
* Add a new element with html content (html entities are escaped and text is wrapped in a CDATA section)
* @param target the parent element to which add the element
* @param tag the new element name
* @param text the HTML content of the new element
* @return the New element
*/
QDomElement addKmlHtmlElement(QDomElement& target,
const QString& tag,
const QString& text) const;
private:
bool m_localTarget;
bool m_optimize_googlemap;
bool m_GPXtracks;
int m_iconSize;
int m_googlemapSize;
int m_size;
int m_altitudeMode;
int m_TimeZone;
int m_LineWidth;
int m_GPXOpacity;
int m_GPXAltitudeMode;
/** directory used in kmldocument structure */
QString m_imageDirBasename;
QString m_GPXFile;
QString m_UrlDestDir;
/**
* Temporary directory where everything will be created.
* m_imageDir is nested in m_tempDestDir.
*/
QDir m_tempDestDir;
QDir m_imageDir;
// Directory selected by user
QString m_baseDestDir;
QString m_imgdir;
QString m_KMLFileName;
QColor m_GPXColor;
QList<QUrl> m_urls;
DInfoInterface* m_iface;
DMetadata m_meta;
// the root document, used to create all QDomElements
QDomDocument* m_kmlDocument;
// the GPS parsed data
KMLGeoDataParser m_gpxParser;
// To store errors and warnings while processing.
QStringList m_logData;
};
} // namespace Digikam
#endif // DIGIKAM_KML_EXPORT_H
diff --git a/core/utilities/imageeditor/core/editorcore.cpp b/core/utilities/imageeditor/core/editorcore.cpp
index a00303aa59..9e1e25edf8 100644
--- a/core/utilities/imageeditor/core/editorcore.cpp
+++ b/core/utilities/imageeditor/core/editorcore.cpp
@@ -1,862 +1,862 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2003-01-15
* Description : DImg interface for image editor
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2004-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "editorcore.h"
#include "editorcore_p.h"
// C++ includes
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
// Qt includes
#include <QWidget>
#include <QImage>
#include <QPixmap>
#include <QBitmap>
#include <QColor>
#include <QFile>
#include <QVariant>
#include <QImageReader>
#include <QPainter>
namespace Digikam
{
EditorCore* EditorCore::m_defaultInstance = 0;
EditorCore* EditorCore::defaultInstance()
{
return m_defaultInstance;
}
void EditorCore::setDefaultInstance(EditorCore* const instance)
{
m_defaultInstance = instance;
}
EditorCore::EditorCore()
: QObject(),
d(new Private)
{
d->undoMan = new UndoManager(this);
d->thread = new SharedLoadSaveThread;
connect( d->thread, SIGNAL(signalImageLoaded(LoadingDescription,DImg)),
this, SLOT(slotImageLoaded(LoadingDescription,DImg)) );
connect( d->thread, SIGNAL(signalImageSaved(QString,bool)),
this, SLOT(slotImageSaved(QString,bool)) );
connect( d->thread, SIGNAL(signalLoadingProgress(LoadingDescription,float)),
this, SLOT(slotLoadingProgress(LoadingDescription,float)) );
connect( d->thread, SIGNAL(signalSavingProgress(QString,float)),
this, SLOT(slotSavingProgress(QString,float)) );
}
EditorCore::~EditorCore()
{
delete d->undoMan;
delete d->thread;
delete d;
if (m_defaultInstance == this)
{
m_defaultInstance = 0;
}
}
void EditorCore::setDisplayingWidget(QWidget* const widget)
{
d->displayingWidget = widget;
}
void EditorCore::load(const QString& filePath, IOFileSettings* const iofileSettings)
{
LoadingDescription description(filePath, LoadingDescription::ConvertForEditor);
if (DImg::fileFormat(filePath) == DImg::RAW)
{
description = LoadingDescription(filePath, iofileSettings->rawDecodingSettings,
LoadingDescription::RawDecodingGlobalSettings,
LoadingDescription::ConvertForEditor);
if (EditorToolIface::editorToolIface() && iofileSettings->useRAWImport)
{
d->nextRawDescription = description;
RawImport* const rawImport = new RawImport(QUrl::fromLocalFile(filePath), this);
EditorToolIface::editorToolIface()->loadTool(rawImport);
connect(rawImport, SIGNAL(okClicked()),
this, SLOT(slotLoadRawFromTool()));
connect(rawImport, SIGNAL(cancelClicked()),
this, SLOT(slotLoadRaw()));
d->thread->stopLoading();
return;
}
}
else
{
d->nextRawDescription = LoadingDescription();
}
d->load(description);
}
void EditorCore::slotLoadRawFromTool()
{
if (EditorToolIface::editorToolIface())
{
RawImport* const rawImport = dynamic_cast<RawImport*>(EditorToolIface::editorToolIface()->currentTool());
if (!rawImport)
return;
d->nextRawDescription.rawDecodingSettings = rawImport->rawDecodingSettings();
d->nextRawDescription.rawDecodingHint = LoadingDescription::RawDecodingCustomSettings;
if (rawImport->hasPostProcessedImage())
{
d->resetValues();
d->currentDescription = d->nextRawDescription;
d->nextRawDescription = LoadingDescription();
emit signalLoadingStarted(d->currentDescription.filePath);
slotImageLoaded(d->currentDescription, rawImport->postProcessedImage());
EditorToolIface::editorToolIface()->unLoadTool();
emit signalImageLoaded(d->currentDescription.filePath, true);
}
else
{
slotLoadRaw();
}
}
}
void EditorCore::slotLoadRaw()
{
//qCDebug(DIGIKAM_GENERAL_LOG) << d->nextRawDescription.rawDecodingSettings;
d->load(d->nextRawDescription);
d->nextRawDescription = LoadingDescription();
}
void EditorCore::applyTransform(const IccTransform& transform)
{
if (!d->valid)
{
return;
}
d->currentDescription.postProcessingParameters.colorManagement = LoadingDescription::ApplyTransform;
d->currentDescription.postProcessingParameters.setTransform(transform);
d->loadCurrent();
if (EditorToolIface::editorToolIface())
{
EditorToolIface::editorToolIface()->unLoadTool();
}
}
void EditorCore::restore()
{
LoadingDescription description = d->currentDescription;
d->resetValues();
d->load(description);
}
void EditorCore::resetImage()
{
if (EditorToolIface::editorToolIface())
{
EditorToolIface::editorToolIface()->unLoadTool();
}
d->resetValues();
d->image.reset();
}
void EditorCore::setICCSettings(const ICCSettingsContainer& cmSettings)
{
d->cmSettings = cmSettings;
}
ICCSettingsContainer EditorCore::getICCSettings() const
{
return d->cmSettings;
}
void EditorCore::setExposureSettings(ExposureSettingsContainer* const expoSettings)
{
d->expoSettings = expoSettings;
}
ExposureSettingsContainer* EditorCore::getExposureSettings() const
{
return d->expoSettings;
}
void EditorCore::slotImageLoaded(const LoadingDescription& loadingDescription, const DImg& img)
{
if (loadingDescription != d->currentDescription)
{
return;
}
// RAW tool active? Discard previous loaded image
if (!d->nextRawDescription.filePath.isNull())
{
return;
}
bool valRet = false;
d->image = img;
if (!d->image.isNull())
{
d->valid = true;
valRet = true;
d->resolvedInitialHistory = d->image.getOriginalImageHistory();
d->resolvedInitialHistory.clearReferredImages(); // default empty, real values set by higher level
// Raw files are already rotated properly by Raw engine. Only perform auto-rotation with non-RAW files.
// We don't have a feedback from Raw engine about auto-rotated RAW file during decoding.
// Setting rotatedOrFlipped to true will reset the exif flag on save (the data is then already rotated)
if (d->image.detectedFormat() == DImg::RAW)
{
d->rotatedOrFlipped = true;
}
else if (d->exifOrient)
{
// Do not rotate twice if already rotated, e.g. for full size preview.
QVariant attribute(d->image.attribute(QLatin1String("exifRotated")));
if (!attribute.isValid() || !attribute.toBool())
{
d->rotatedOrFlipped = d->image.rotateAndFlip(LoadSaveThread::exifOrientation(d->image, loadingDescription.filePath));
}
}
// set after rotation
d->origWidth = d->image.width();
d->origHeight = d->image.height();
d->width = d->origWidth;
d->height = d->origHeight;
d->image.setAttribute(QLatin1String("originalSize"), d->image.size());
}
else
{
valRet = false;
}
emit signalImageLoaded(d->currentDescription.filePath, valRet);
setModified();
}
void EditorCore::setSoftProofingEnabled(bool enabled)
{
d->doSoftProofing = enabled;
}
bool EditorCore::softProofingEnabled() const
{
return d->doSoftProofing;
}
void EditorCore::slotLoadingProgress(const LoadingDescription& loadingDescription, float progress)
{
if (loadingDescription == d->currentDescription)
{
emit signalLoadingProgress(loadingDescription.filePath, progress);
}
}
bool EditorCore::exifRotated() const
{
return d->rotatedOrFlipped;
}
void EditorCore::setExifOrient(bool exifOrient)
{
d->exifOrient = exifOrient;
}
void EditorCore::undo()
{
if (!d->undoMan->anyMoreUndo())
{
emit signalUndoStateChanged();
return;
}
d->undoMan->undo();
emit signalUndoStateChanged();
}
void EditorCore::redo()
{
if (!d->undoMan->anyMoreRedo())
{
emit signalUndoStateChanged();
return;
}
d->undoMan->redo();
emit signalUndoStateChanged();
}
void EditorCore::rollbackToOrigin()
{
d->undoMan->rollbackToOrigin();
emit signalUndoStateChanged();
}
void EditorCore::saveAs(const QString& filePath, IOFileSettings* const iofileSettings,
bool setExifOrientationTag, const QString& givenMimeType,
const QString& intendedFilePath)
{
d->saveAs(filePath, iofileSettings, setExifOrientationTag, givenMimeType,
VersionFileOperation(), intendedFilePath);
}
void EditorCore::saveAs(const QString& filePath, IOFileSettings* const iofileSettings,
bool setExifOrientationTag, const QString& givenMimeType,
const VersionFileOperation& op)
{
d->saveAs(filePath, iofileSettings, setExifOrientationTag, givenMimeType, op, op.saveFile.filePath());
}
void EditorCore::slotImageSaved(const QString& filePath, bool success)
{
if (d->filesToSave.isEmpty() || d->filesToSave[d->currentFileToSave].filePath != filePath)
{
return;
}
Private::FileToSave& savedFile = d->filesToSave[d->currentFileToSave];
if (success)
{
if (savedFile.historyStep == -1)
{
// Note: We operate on a temp file here, so we cannot
// add it as referred image yet. Done in addLastSavedToHistory
LoadingDescription description(filePath, LoadingDescription::ConvertForEditor);
d->currentDescription = description;
}
else
{
HistoryImageId id = savedFile.image.addAsReferredImage(filePath);
// for all images following in history, we need to insert the now saved file at the right place
for (int i = d->currentFileToSave + 1; i < d->filesToSave.size(); ++i)
{
d->filesToSave[i].image.insertAsReferredImage(savedFile.historyStep, id);
}
}
}
else
{
qCWarning(DIGIKAM_GENERAL_LOG) << "error saving image '" << QFile::encodeName(filePath).constData();
}
d->currentFileToSave++;
if (d->currentFileToSave == d->filesToSave.size())
{
d->filesToSave.clear();
emit signalImageSaved(filePath, success);
}
else
{
d->saveNext();
}
}
void EditorCore::slotSavingProgress(const QString& filePath, float progress)
{
if (!d->filesToSave.isEmpty() && d->filesToSave.at(d->currentFileToSave).filePath == filePath)
{
emit signalSavingProgress(filePath, progress);
}
}
void EditorCore::abortSaving()
{
// failure will be reported by a signal
if (!d->filesToSave.isEmpty())
{
d->thread->stopSaving(d->filesToSave.at(d->currentFileToSave).filePath);
d->filesToSave.clear();
}
}
QString EditorCore::ensureHasCurrentUuid() const
{
/*
* 1) An image is loaded. The DImgLoader adds the HistoryImageId of the loaded file as "Current" entry.
- * 2) The loaded image has no UUID (created by camera etc.). Highler level calls ensureHasCurrentUuid
+ * 2) The loaded image has no UUID (created by camera etc.). Higher level calls ensureHasCurrentUuid
* before any saving is started
* 3) We create a new UUID and add it to the image's history. When the new image is saved,
* it references the original by UUID. Because we, here, do not touch the original,
* it is out of scope to add the UUID to the original file's metadata.
* Higher level is responsible for this.
* 4) When the image is saved, DImg::updateMetadata will create a new UUID for the saved
* image, which is then of course written to the newly saved file.
*/
if (!d->image.getImageHistory().currentReferredImage().hasUuid())
{
// if there is no uuid in the image, we create one.
QString uuid = QString::fromUtf8(d->image.createImageUniqueId());
d->image.addCurrentUniqueImageId(uuid);
}
return d->image.getImageHistory().currentReferredImage().uuid();
}
void EditorCore::provideCurrentUuid(const QString& uuid)
{
// If the (original) image did not yet have a UUID, one is provided by higher level
// Higher level decides how this UUID is stored; we don't touch the original here.
if (!d->image.getImageHistory().currentReferredImage().hasUuid())
{
d->image.addCurrentUniqueImageId(uuid);
}
}
void EditorCore::setLastSaved(const QString& filePath)
{
if (getImageFilePath() == filePath)
{
// if the file was overwritten, a complete undo, to the state of original loading,
// does not return to a real image anymore - it's overwritten
d->undoMan->clearPreviousOriginData();
}
// We cannot do it in slotImageSaved because we may operate on a temporary filePath.
d->image.imageSavedAs(filePath);
}
void EditorCore::switchToLastSaved(const DImageHistory& resolvedCurrentHistory)
{
// Higher level wants to use the current DImg object to represent the file
// it has previously been saved to.
// setLastSaved shall have been called before.
d->image.switchOriginToLastSaved();
if (resolvedCurrentHistory.isNull())
{
d->resolvedInitialHistory = d->image.getOriginalImageHistory();
d->resolvedInitialHistory.clearReferredImages();
}
else
{
d->resolvedInitialHistory = resolvedCurrentHistory;
}
setUndoManagerOrigin();
}
void EditorCore::setHistoryIsBranch(bool isBranching)
{
// The first added step (on top of the initial history) will be marked as branch
d->image.setHistoryBranchAfter(d->resolvedInitialHistory, isBranching);
}
void EditorCore::setModified()
{
emit signalModified();
emit signalUndoStateChanged();
}
void EditorCore::readMetadataFromFile(const QString& file)
{
DMetadata meta(file);
// This can overwrite metadata changes introduced by tools.
// Currently, this is ProfileConversion and lensfun.
// ProfileConversion's changes is redone when saving by DImgLoader.
// Lensfun is not critical.
// For a clean solution, we'd need to record a sort of metadata changeset in UndoMetadataContainer.
d->image.setMetadata(meta.data());
// If we are editing, and someone else at the same time, there's nothing we can do.
if (!d->undoMan->hasChanges())
{
d->image.setImageHistory(DImageHistory::fromXml(meta.getImageHistory()));
}
}
void EditorCore::clearUndoManager()
{
d->undoMan->clear();
d->undoMan->setOrigin();
emit signalUndoStateChanged();
}
void EditorCore::setUndoManagerOrigin()
{
d->undoMan->setOrigin();
emit signalUndoStateChanged();
emit signalFileOriginChanged(getImageFilePath());
}
bool EditorCore::isValid() const
{
return d->valid;
}
int EditorCore::width() const
{
return d->width;
}
int EditorCore::height() const
{
return d->height;
}
int EditorCore::origWidth() const
{
return d->origWidth;
}
int EditorCore::origHeight() const
{
return d->origHeight;
}
int EditorCore::bytesDepth() const
{
return d->image.bytesDepth();
}
bool EditorCore::sixteenBit() const
{
return d->image.sixteenBit();
}
bool EditorCore::hasAlpha() const
{
return d->image.hasAlpha();
}
bool EditorCore::isReadOnly() const
{
if (d->image.isNull())
{
return true;
}
else
{
return d->image.isReadOnly();
}
}
void EditorCore::setSelectedArea(const QRect& rect)
{
d->selX = rect.x();
d->selY = rect.y();
d->selW = rect.width();
d->selH = rect.height();
}
QRect EditorCore::getSelectedArea() const
{
return (QRect(d->selX, d->selY, d->selW, d->selH));
}
void EditorCore::zoom(double val)
{
d->zoom = val;
d->width = (int)(d->origWidth * val);
d->height = (int)(d->origHeight * val);
}
void EditorCore::rotate90()
{
d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Rotate90));
}
void EditorCore::rotate180()
{
d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Rotate180));
}
void EditorCore::rotate270()
{
d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Rotate270));
}
void EditorCore::flipHoriz()
{
d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::FlipHorizontally));
}
void EditorCore::flipVert()
{
d->applyReversibleBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::FlipVertically));
}
void EditorCore::crop(const QRect& rect)
{
d->applyBuiltinFilter(DImgBuiltinFilter(DImgBuiltinFilter::Crop, rect), new UndoActionIrreversible(this, QLatin1String("Crop")));
}
void EditorCore::convertDepth(int depth)
{
d->applyBuiltinFilter(DImgBuiltinFilter(depth == 32 ? DImgBuiltinFilter::ConvertTo8Bit : DImgBuiltinFilter::ConvertTo16Bit),
new UndoActionIrreversible(this, QLatin1String("Convert Color Depth")));
}
DImg* EditorCore::getImg() const
{
if (!d->image.isNull())
{
return &d->image;
}
else
{
qCWarning(DIGIKAM_GENERAL_LOG) << "d->image is NULL";
return 0;
}
}
DImageHistory EditorCore::getImageHistory() const
{
return d->image.getImageHistory();
}
DImageHistory EditorCore::getInitialImageHistory() const
{
return d->image.getOriginalImageHistory();
}
DImageHistory EditorCore::getImageHistoryOfFullRedo() const
{
return d->undoMan->getImageHistoryOfFullRedo();
}
DImageHistory EditorCore::getResolvedInitialHistory() const
{
return d->resolvedInitialHistory;
}
void EditorCore::setResolvedInitialHistory(const DImageHistory& history)
{
d->resolvedInitialHistory = history;
}
void EditorCore::putImg(const QString& caller, const FilterAction& action, const DImg& img)
{
d->undoMan->addAction(new UndoActionIrreversible(this, caller));
d->putImageData(img.bits(), img.width(), img.height(), img.sixteenBit());
d->image.addFilterAction(action);
setModified();
}
void EditorCore::setUndoImg(const UndoMetadataContainer& c, const DImg& img)
{
// called from UndoManager
d->putImageData(img.bits(), img.width(), img.height(), img.sixteenBit());
c.toImage(d->image);
}
void EditorCore::imageUndoChanged(const UndoMetadataContainer& c)
{
// called from UndoManager
d->origWidth = d->image.width();
d->origHeight = d->image.height();
c.toImage(d->image);
}
void EditorCore::setFileOriginData(const QVariant& data)
{
d->image.setFileOriginData(data);
emit signalFileOriginChanged(getImageFilePath());
}
DImg EditorCore::getImgSelection() const
{
if (!d->selW || !d->selH)
{
return DImg();
}
if (!d->image.isNull())
{
DImg im = d->image.copy(d->selX, d->selY, d->selW, d->selH);
im.detach();
return im;
}
return DImg();
}
void EditorCore::putImgSelection(const QString& caller, const FilterAction& action, const DImg& img)
{
if (img.isNull() || d->image.isNull())
{
return;
}
d->undoMan->addAction(new UndoActionIrreversible(this, caller));
d->image.bitBltImage(img.bits(), 0, 0, d->selW, d->selH, d->selX, d->selY, d->selW, d->selH, d->image.bytesDepth());
d->image.addFilterAction(action);
setModified();
}
void EditorCore::putIccProfile(const IccProfile& profile)
{
if (d->image.isNull())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "d->image is NULL";
return;
}
//qCDebug(DIGIKAM_GENERAL_LOG) << "Embedding profile: " << profile;
d->image.setIccProfile(profile);
setModified();
}
QStringList EditorCore::getUndoHistory() const
{
return d->undoMan->getUndoHistory();
}
QStringList EditorCore::getRedoHistory() const
{
return d->undoMan->getRedoHistory();
}
int EditorCore::availableUndoSteps() const
{
return d->undoMan->availableUndoSteps();
}
int EditorCore::availableRedoSteps() const
{
return d->undoMan->availableRedoSteps();
}
IccProfile EditorCore::getEmbeddedICC() const
{
return d->image.getIccProfile();
}
MetaEngineData EditorCore::getMetadata() const
{
return d->image.getMetadata();
}
QString EditorCore::getImageFilePath() const
{
return d->image.originalFilePath();
}
QString EditorCore::getImageFileName() const
{
return getImageFilePath().section(QLatin1Char('/'), -1);
}
QString EditorCore::getImageFormat() const
{
if (d->image.isNull())
{
return QString();
}
QString mimeType = d->image.format();
// It is a bug in the loader if format attribute is not given
if (mimeType.isEmpty())
{
qCWarning(DIGIKAM_GENERAL_LOG) << "DImg object does not contain attribute \"format\"";
mimeType = QString::fromUtf8(QImageReader::imageFormat(getImageFilePath()));
}
return mimeType;
}
QPixmap EditorCore::convertToPixmap(DImg& img) const
{
QPixmap pix;
if (d->cmSettings.enableCM && (d->cmSettings.useManagedView || d->doSoftProofing))
{
// do not use d->monitorICCtrans here, because img may have a different embedded profile
IccManager manager(img);
IccTransform transform;
if (d->doSoftProofing)
{
transform = manager.displaySoftProofingTransform(IccProfile(d->cmSettings.defaultProofProfile));
}
else
{
transform = manager.displayTransform();
}
pix = img.convertToPixmap(transform);
}
else
{
pix = img.convertToPixmap();
}
// Show the Over/Under exposure pixels indicators
if (d->expoSettings->underExposureIndicator || d->expoSettings->overExposureIndicator)
{
QPainter painter(&pix);
QImage pureColorMask = img.pureColorMask(d->expoSettings);
QPixmap pixMask = QPixmap::fromImage(pureColorMask);
painter.drawPixmap(0, 0, pixMask, 0, 0, pixMask.width(), pixMask.height());
}
return pix;
}
UndoState EditorCore::undoState() const
{
UndoState state;
state.hasUndo = d->undoMan->anyMoreUndo();
state.hasRedo = d->undoMan->anyMoreRedo();
state.hasUndoableChanges = !d->undoMan->isAtOrigin();
// Includes the edit step performed by RAW import, which is not undoable
state.hasChanges = d->undoMan->hasChanges();
return state;
}
} // namespace Digikam
diff --git a/core/utilities/imageeditor/editor/editorstackview.cpp b/core/utilities/imageeditor/editor/editorstackview.cpp
index 24045ef7bd..a2d8580c0b 100644
--- a/core/utilities/imageeditor/editor/editorstackview.cpp
+++ b/core/utilities/imageeditor/editor/editorstackview.cpp
@@ -1,336 +1,336 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2008-08-20
* Description : A widget stack to embed editor view.
*
* Copyright (C) 2008-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "editorstackview.h"
// Local includes
#include "dzoombar.h"
#include "canvas.h"
#include "thumbnailsize.h"
#include "graphicsdimgview.h"
#include "previewlayout.h"
namespace Digikam
{
class Q_DECL_HIDDEN EditorStackView::Private
{
public:
explicit Private()
: toolView(0),
canvas(0)
{
}
QWidget* toolView;
Canvas* canvas;
};
EditorStackView::EditorStackView(QWidget* const parent)
: QStackedWidget(parent),
d(new Private)
{
}
EditorStackView::~EditorStackView()
{
delete d;
}
void EditorStackView::setCanvas(Canvas* const canvas)
{
if (d->canvas)
{
return;
}
d->canvas = canvas;
insertWidget(CanvasMode, d->canvas);
connect(d->canvas, SIGNAL(signalZoomChanged(double)),
this, SLOT(slotZoomChanged(double)));
connect(d->canvas, SIGNAL(signalToggleOffFitToWindow()),
this, SIGNAL(signalToggleOffFitToWindow()));
}
Canvas* EditorStackView::canvas() const
{
return d->canvas;
}
void EditorStackView::setToolView(QWidget* const view)
{
if (d->toolView)
{
removeWidget(d->toolView);
}
d->toolView = view;
if (d->toolView)
{
insertWidget(ToolViewMode, d->toolView);
}
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
connect(preview->layout(), SIGNAL(zoomFactorChanged(double)),
this, SLOT(slotZoomChanged(double)));
connect(preview->layout(), SIGNAL(fitToWindowToggled(bool)),
this, SLOT(slotToggleOffFitToWindow(bool)));
}
}
QWidget* EditorStackView::toolView() const
{
return d->toolView;
}
int EditorStackView::viewMode() const
{
return indexOf(currentWidget());
}
void EditorStackView::setViewMode(int mode)
{
if (mode != CanvasMode && mode != ToolViewMode)
{
return;
}
setCurrentIndex(mode);
}
void EditorStackView::increaseZoom()
{
if (viewMode() == CanvasMode)
{
d->canvas->layout()->increaseZoom();
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
preview->layout()->increaseZoom();
}
}
}
void EditorStackView::decreaseZoom()
{
if (viewMode() == CanvasMode)
{
d->canvas->layout()->decreaseZoom();
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
preview->layout()->decreaseZoom();
}
}
}
void EditorStackView::toggleFitToWindow()
{
// Fit to window action is common place to switch view in this mode.
- // User want to see the same behavors between canvas and tool preview.
+ // User want to see the same behaviors between canvas and tool preview.
// Both are toggle at the same time.
d->canvas->layout()->toggleFitToWindow();
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
preview->layout()->toggleFitToWindow();
}
}
void EditorStackView::fitToSelect()
{
if (viewMode() == CanvasMode)
{
d->canvas->fitToSelect();
}
}
void EditorStackView::zoomTo100Percent()
{
if (viewMode() == CanvasMode)
{
d->canvas->layout()->toggleFitToWindowOr100();
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
preview->layout()->toggleFitToWindowOr100();
}
}
}
void EditorStackView::setZoomFactor(double zoom)
{
if (viewMode() == CanvasMode)
{
d->canvas->layout()->setZoomFactor(zoom);
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
preview->layout()->setZoomFactor(zoom);
}
}
}
double EditorStackView::zoomMax() const
{
if (viewMode() == CanvasMode)
{
return d->canvas->layout()->maxZoomFactor();
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
return preview->layout()->maxZoomFactor();
}
else
{
return -1.0;
}
}
}
double EditorStackView::zoomMin() const
{
if (viewMode() == CanvasMode)
{
return d->canvas->layout()->minZoomFactor();
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
return preview->layout()->minZoomFactor();
}
else
{
return -1.0;
}
}
}
void EditorStackView::slotZoomSliderChanged(int size)
{
if (viewMode() == ToolViewMode && !isZoomablePreview())
{
return;
}
double z = DZoomBar::zoomFromSize(size, zoomMin(), zoomMax());
if (viewMode() == CanvasMode)
{
d->canvas->layout()->setZoomFactorSnapped(z);
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
return preview->layout()->setZoomFactorSnapped(z);
}
}
}
void EditorStackView::slotZoomChanged(double zoom)
{
bool max, min;
if (viewMode() == CanvasMode)
{
max = d->canvas->layout()->atMaxZoom();
min = d->canvas->layout()->atMinZoom();
emit signalZoomChanged(max, min, zoom);
}
else
{
GraphicsDImgView* const preview = previewWidget();
if (preview)
{
max = preview->layout()->atMaxZoom();
min = preview->layout()->atMinZoom();
emit signalZoomChanged(max, min, zoom);
}
}
}
void EditorStackView::slotToggleOffFitToWindow(bool b)
{
if (b)
{
emit signalToggleOffFitToWindow();
}
}
GraphicsDImgView* EditorStackView::previewWidget() const
{
GraphicsDImgView* const preview = dynamic_cast<GraphicsDImgView*>(d->toolView);
if (preview)
{
return preview;
}
return 0;
}
bool EditorStackView::isZoomablePreview() const
{
return previewWidget();
}
} // namespace Digikam
diff --git a/core/utilities/imageeditor/editor/editorwindow.cpp b/core/utilities/imageeditor/editor/editorwindow.cpp
index e868c04bba..dd1e49cfc5 100644
--- a/core/utilities/imageeditor/editor/editorwindow.cpp
+++ b/core/utilities/imageeditor/editor/editorwindow.cpp
@@ -1,3640 +1,3640 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-01-20
* Description : core image editor GUI implementation
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2009-2011 by Andi Clemens <andi dot clemens at gmail dot com>
* Copyright (C) 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "editorwindow.h"
#include "editorwindow_p.h"
// C++ includes
#include <cmath>
// Qt includes
#include <QApplication>
#include <QByteArray>
#include <QCursor>
#include <QDir>
#include <QEasingCurve>
#include <QEventLoop>
#include <QFile>
#include <QFileInfo>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QImageReader>
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QPointer>
#include <QProgressBar>
#include <QSplitter>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidgetAction>
#include <QButtonGroup>
#include <QLineEdit>
#include <QKeySequence>
#include <QPushButton>
#include <QAction>
#include <QMenuBar>
#include <QStatusBar>
#include <QMenu>
#include <QIcon>
// KDE includes
#include <klocalizedstring.h>
#include <kactioncategory.h>
#include <kactioncollection.h>
#include <kconfiggroup.h>
#include <kservicetype.h>
#include <kservicetypetrader.h>
#include <ktoolbarpopupaction.h>
#include <kwindowsystem.h>
#include <kxmlguifactory.h>
#ifdef HAVE_KIO
# include <kopenwithdialog.h>
#endif
// Local includes
#include "digikam_debug.h"
#include "digikam_globals.h"
#include "dmessagebox.h"
#include "applicationsettings.h"
#include "actioncategorizedview.h"
#include "canvas.h"
#include "categorizeditemmodel.h"
#include "colorcorrectiondlg.h"
#include "editorcore.h"
#include "dlogoaction.h"
#include "dmetadata.h"
#include "dzoombar.h"
#include "drawdecoderwidget.h"
#include "editorstackview.h"
#include "editortool.h"
#include "editortoolsettings.h"
#include "editortooliface.h"
#include "exposurecontainer.h"
#include "dfileoperations.h"
#include "filereadwritelock.h"
#include "filesaveoptionsbox.h"
#include "filesaveoptionsdlg.h"
#include "iccpostloadingmanager.h"
#include "iccsettings.h"
#include "iccsettingscontainer.h"
#include "icctransform.h"
#include "imagedialog.h"
#include "iofilesettings.h"
#include "metadatasettings.h"
#include "libsinfodlg.h"
#include "loadingcacheinterface.h"
#include "printhelper.h"
#include "jpegsettings.h"
#include "pngsettings.h"
#include "savingcontext.h"
#include "sidebar.h"
#include "slideshowsettings.h"
#include "softproofdialog.h"
#include "statusprogressbar.h"
#include "thememanager.h"
#include "thumbnailsize.h"
#include "thumbnailloadthread.h"
#include "versioningpromptusersavedlg.h"
#include "undostate.h"
#include "versionmanager.h"
#include "dexpanderbox.h"
#include "inserttexttool.h"
#include "bordertool.h"
#include "texturetool.h"
#include "colorfxtool.h"
#include "charcoaltool.h"
#include "embosstool.h"
#include "oilpainttool.h"
#include "blurfxtool.h"
#include "distortionfxtool.h"
#include "raindroptool.h"
#include "filmgraintool.h"
#include "invertfilter.h"
#include "imageiface.h"
#include "iccprofilescombobox.h"
#include "autocorrectiontool.h"
#include "bcgtool.h"
#include "bwsepiatool.h"
#include "hsltool.h"
#include "profileconversiontool.h"
#include "cbtool.h"
#include "whitebalancetool.h"
#include "channelmixertool.h"
#include "adjustcurvestool.h"
#include "adjustlevelstool.h"
#include "filmtool.h"
#include "restorationtool.h"
#include "blurtool.h"
#include "healingclonetool.h"
#include "sharpentool.h"
#include "noisereductiontool.h"
#include "localcontrasttool.h"
#include "redeyetool.h"
#include "antivignettingtool.h"
#include "lensdistortiontool.h"
#include "hotpixelstool.h"
#include "perspectivetool.h"
#include "freerotationtool.h"
#include "sheartool.h"
#include "resizetool.h"
#include "ratiocroptool.h"
#include "dfiledialog.h"
#ifdef HAVE_LIBLQR_1
# include "contentawareresizetool.h"
#endif
#ifdef HAVE_LENSFUN
# include "lensautofixtool.h"
#endif
namespace Digikam
{
EditorWindow::EditorWindow(const QString& name)
: DXmlGuiWindow(0),
d(new Private)
{
setConfigGroupName(QLatin1String("ImageViewer Settings"));
setObjectName(name);
setWindowFlags(Qt::Window);
setFullScreenOptions(FS_EDITOR);
m_nonDestructive = true;
m_contextMenu = 0;
m_servicesMenu = 0;
m_serviceAction = 0;
m_canvas = 0;
m_openVersionAction = 0;
m_saveAction = 0;
m_saveAsAction = 0;
m_saveCurrentVersionAction = 0;
m_saveNewVersionAction = 0;
m_saveNewVersionAsAction = 0;
m_saveNewVersionInFormatAction = 0;
m_resLabel = 0;
m_nameLabel = 0;
m_exportAction = 0;
m_revertAction = 0;
m_discardChangesAction = 0;
m_fileDeleteAction = 0;
m_forwardAction = 0;
m_backwardAction = 0;
m_firstAction = 0;
m_lastAction = 0;
m_applyToolAction = 0;
m_closeToolAction = 0;
m_undoAction = 0;
m_redoAction = 0;
m_showBarAction = 0;
m_splitter = 0;
m_vSplitter = 0;
m_stackView = 0;
m_setExifOrientationTag = true;
m_editingOriginalImage = true;
m_actionEnabledState = false;
m_cancelSlideShow = false;
// Settings containers instance.
d->exposureSettings = new ExposureSettingsContainer();
d->toolIface = new EditorToolIface(this);
m_IOFileSettings = new IOFileSettings();
//d->waitingLoop = new QEventLoop(this);
}
EditorWindow::~EditorWindow()
{
delete m_canvas;
delete m_IOFileSettings;
delete d->toolIface;
delete d->exposureSettings;
delete d;
}
EditorStackView* EditorWindow::editorStackView() const
{
return m_stackView;
}
ExposureSettingsContainer* EditorWindow::exposureSettings() const
{
return d->exposureSettings;
}
void EditorWindow::setupContextMenu()
{
m_contextMenu = new QMenu(this);
addAction2ContextMenu(QLatin1String("editorwindow_fullscreen"), true);
addAction2ContextMenu(QLatin1String("options_show_menubar"), true);
m_contextMenu->addSeparator();
// --------------------------------------------------------
addAction2ContextMenu(QLatin1String("editorwindow_backward"), true);
addAction2ContextMenu(QLatin1String("editorwindow_forward"), true);
m_contextMenu->addSeparator();
// --------------------------------------------------------
addAction2ContextMenu(QLatin1String("editorwindow_slideshow"), true);
addAction2ContextMenu(QLatin1String("editorwindow_transform_rotateleft"), true);
addAction2ContextMenu(QLatin1String("editorwindow_transform_rotateright"), true);
addAction2ContextMenu(QLatin1String("editorwindow_transform_crop"), true);
m_contextMenu->addSeparator();
// --------------------------------------------------------
addAction2ContextMenu(QLatin1String("editorwindow_delete"), true);
}
void EditorWindow::setupStandardConnections()
{
connect(m_stackView, SIGNAL(signalToggleOffFitToWindow()),
this, SLOT(slotToggleOffFitToWindow()));
// -- Canvas connections ------------------------------------------------
connect(m_canvas, SIGNAL(signalShowNextImage()),
this, SLOT(slotForward()));
connect(m_canvas, SIGNAL(signalShowPrevImage()),
this, SLOT(slotBackward()));
connect(m_canvas, SIGNAL(signalRightButtonClicked()),
this, SLOT(slotContextMenu()));
connect(m_stackView, SIGNAL(signalZoomChanged(bool,bool,double)),
this, SLOT(slotZoomChanged(bool,bool,double)));
connect(m_canvas, SIGNAL(signalChanged()),
this, SLOT(slotChanged()));
connect(m_canvas, SIGNAL(signalAddedDropedItems(QDropEvent*)),
this, SLOT(slotAddedDropedItems(QDropEvent*)));
connect(m_canvas->interface(), SIGNAL(signalUndoStateChanged()),
this, SLOT(slotUndoStateChanged()));
connect(m_canvas, SIGNAL(signalSelected(bool)),
this, SLOT(slotSelected(bool)));
connect(m_canvas, SIGNAL(signalPrepareToLoad()),
this, SLOT(slotPrepareToLoad()));
connect(m_canvas, SIGNAL(signalLoadingStarted(QString)),
this, SLOT(slotLoadingStarted(QString)));
connect(m_canvas, SIGNAL(signalLoadingFinished(QString,bool)),
this, SLOT(slotLoadingFinished(QString,bool)));
connect(m_canvas, SIGNAL(signalLoadingProgress(QString,float)),
this, SLOT(slotLoadingProgress(QString,float)));
connect(m_canvas, SIGNAL(signalSavingStarted(QString)),
this, SLOT(slotSavingStarted(QString)));
connect(m_canvas, SIGNAL(signalSavingFinished(QString,bool)),
this, SLOT(slotSavingFinished(QString,bool)));
connect(m_canvas, SIGNAL(signalSavingProgress(QString,float)),
this, SLOT(slotSavingProgress(QString,float)));
connect(m_canvas, SIGNAL(signalSelectionChanged(QRect)),
this, SLOT(slotSelectionChanged(QRect)));
connect(m_canvas, SIGNAL(signalSelectionSetText(QRect)),
this, SLOT(slotSelectionSetText(QRect)));
connect(m_canvas->interface(), SIGNAL(signalFileOriginChanged(QString)),
this, SLOT(slotFileOriginChanged(QString)));
// -- status bar connections --------------------------------------
connect(m_nameLabel, SIGNAL(signalCancelButtonPressed()),
this, SLOT(slotNameLabelCancelButtonPressed()));
connect(m_nameLabel, SIGNAL(signalCancelButtonPressed()),
d->toolIface, SLOT(slotToolAborted()));
// -- Icc settings connections --------------------------------------
connect(IccSettings::instance(), SIGNAL(settingsChanged()),
this, SLOT(slotColorManagementOptionsChanged()));
}
void EditorWindow::setupStandardActions()
{
// -- Standard 'File' menu actions ---------------------------------------------
KActionCollection* const ac = actionCollection();
m_backwardAction = buildStdAction(StdBackAction, this, SLOT(slotBackward()), this);
ac->addAction(QLatin1String("editorwindow_backward"), m_backwardAction);
ac->setDefaultShortcuts(m_backwardAction, QList<QKeySequence>() << Qt::Key_PageUp << Qt::Key_Backspace
<< Qt::Key_Up << Qt::Key_Left);
m_backwardAction->setEnabled(false);
m_forwardAction = buildStdAction(StdForwardAction, this, SLOT(slotForward()), this);
ac->addAction(QLatin1String("editorwindow_forward"), m_forwardAction);
ac->setDefaultShortcuts(m_forwardAction, QList<QKeySequence>() << Qt::Key_PageDown << Qt::Key_Space
<< Qt::Key_Down << Qt::Key_Right);
m_forwardAction->setEnabled(false);
m_firstAction = new QAction(QIcon::fromTheme(QLatin1String("go-first")), i18n("&First"), this);
connect(m_firstAction, SIGNAL(triggered()), this, SLOT(slotFirst()));
ac->addAction(QLatin1String("editorwindow_first"), m_firstAction);
ac->setDefaultShortcuts(m_firstAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_Home);
m_firstAction->setEnabled(false);
m_lastAction = new QAction(QIcon::fromTheme(QLatin1String("go-last")), i18n("&Last"), this);
connect(m_lastAction, SIGNAL(triggered()), this, SLOT(slotLast()));
ac->addAction(QLatin1String("editorwindow_last"), m_lastAction);
ac->setDefaultShortcuts(m_lastAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_End);
m_lastAction->setEnabled(false);
m_openVersionAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")),
i18nc("@action", "Open Original"), this);
connect(m_openVersionAction, SIGNAL(triggered()), this, SLOT(slotOpenOriginal()));
ac->addAction(QLatin1String("editorwindow_openversion"), m_openVersionAction);
ac->setDefaultShortcuts(m_openVersionAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_End);
m_saveAction = buildStdAction(StdSaveAction, this, SLOT(save()), this);
ac->addAction(QLatin1String("editorwindow_save"), m_saveAction);
m_saveAsAction = buildStdAction(StdSaveAsAction, this, SLOT(saveAs()), this);
ac->addAction(QLatin1String("editorwindow_saveas"), m_saveAsAction);
m_saveCurrentVersionAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")),
i18nc("@action Save changes to current version", "Save Changes"), this);
m_saveCurrentVersionAction->setToolTip(i18nc("@info:tooltip", "Save the modifications to the current version of the file"));
connect(m_saveCurrentVersionAction, SIGNAL(triggered()), this, SLOT(saveCurrentVersion()));
ac->addAction(QLatin1String("editorwindow_savecurrentversion"), m_saveCurrentVersionAction);
m_saveNewVersionAction = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("list-add")),
i18nc("@action Save changes to a newly created version", "Save As New Version"), this);
m_saveNewVersionAction->setToolTip(i18nc("@info:tooltip", "Save the current modifications to a new version of the file"));
connect(m_saveNewVersionAction, SIGNAL(triggered()), this, SLOT(saveNewVersion()));
ac->addAction(QLatin1String("editorwindow_savenewversion"), m_saveNewVersionAction);
QAction* const m_saveNewVersionAsAction = new QAction(QIcon::fromTheme(QLatin1String("document-save-as")),
i18nc("@action Save changes to a newly created version, specifying the filename and format",
"Save New Version As..."), this);
m_saveNewVersionAsAction->setToolTip(i18nc("@info:tooltip", "Save the current modifications to a new version of the file, "
"specifying the filename and format"));
connect(m_saveNewVersionAsAction, SIGNAL(triggered()), this, SLOT(saveNewVersionAs()));
m_saveNewVersionInFormatAction = new QMenu(i18nc("@action Save As New Version...Save in format...",
"Save in Format"), this);
m_saveNewVersionInFormatAction->setIcon(QIcon::fromTheme(QLatin1String("view-preview")));
d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "JPEG"), QLatin1String("JPG"));
d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "TIFF"), QLatin1String("TIFF"));
d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "PNG"), QLatin1String("PNG"));
d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "PGF"), QLatin1String("PGF"));
#ifdef HAVE_JASPER
d->plugNewVersionInFormatAction(this, m_saveNewVersionInFormatAction, i18nc("@action:inmenu", "JPEG 2000"), QLatin1String("JP2"));
#endif // HAVE_JASPER
m_saveNewVersionAction->menu()->addAction(m_saveNewVersionAsAction);
m_saveNewVersionAction->menu()->addAction(m_saveNewVersionInFormatAction->menuAction());
// This also triggers saveAs, but in the context of non-destructive we want a slightly different appearance
m_exportAction = new QAction(QIcon::fromTheme(QLatin1String("document-export")),
i18nc("@action", "Export"), this);
m_exportAction->setToolTip(i18nc("@info:tooltip", "Save the file in a folder outside your collection"));
connect(m_exportAction, SIGNAL(triggered()), this, SLOT(saveAs()));
ac->addAction(QLatin1String("editorwindow_export"), m_exportAction);
ac->setDefaultShortcut(m_exportAction, Qt::CTRL + Qt::SHIFT + Qt::Key_E); // NOTE: Gimp shortcut
m_revertAction = buildStdAction(StdRevertAction, this, SLOT(slotRevert()), this);
ac->addAction(QLatin1String("editorwindow_revert"), m_revertAction);
m_discardChangesAction = new QAction(QIcon::fromTheme(QLatin1String("task-reject")),
i18nc("@action", "Discard Changes"), this);
m_discardChangesAction->setToolTip(i18nc("@info:tooltip", "Discard all current changes to this file"));
connect(m_discardChangesAction, SIGNAL(triggered()), this, SLOT(slotDiscardChanges()));
ac->addAction(QLatin1String("editorwindow_discardchanges"), m_discardChangesAction);
m_openVersionAction->setEnabled(false);
m_saveAction->setEnabled(false);
m_saveAsAction->setEnabled(false);
m_saveCurrentVersionAction->setEnabled(false);
m_saveNewVersionAction->setEnabled(false);
m_revertAction->setEnabled(false);
m_discardChangesAction->setEnabled(false);
d->filePrintAction = new QAction(QIcon::fromTheme(QLatin1String("document-print-frame")), i18n("Print Image..."), this);
connect(d->filePrintAction, SIGNAL(triggered()), this, SLOT(slotFilePrint()));
ac->addAction(QLatin1String("editorwindow_print"), d->filePrintAction);
ac->setDefaultShortcut(d->filePrintAction, Qt::CTRL + Qt::Key_P);
d->filePrintAction->setEnabled(false);
d->openWithAction = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-filetype-association")), i18n("Open With Default Application"), this);
d->openWithAction->setWhatsThis(i18n("Open the item with default assigned application."));
connect(d->openWithAction, SIGNAL(triggered()), this, SLOT(slotFileWithDefaultApplication()));
ac->addAction(QLatin1String("open_with_default_application"), d->openWithAction);
ac->setDefaultShortcut(d->openWithAction, Qt::META + Qt::Key_F4);
d->openWithAction->setEnabled(false);
m_fileDeleteAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18nc("Non-pluralized", "Move to Trash"), this);
connect(m_fileDeleteAction, SIGNAL(triggered()), this, SLOT(slotDeleteCurrentItem()));
ac->addAction(QLatin1String("editorwindow_delete"), m_fileDeleteAction);
ac->setDefaultShortcut(m_fileDeleteAction, Qt::Key_Delete);
m_fileDeleteAction->setEnabled(false);
QAction* const closeAction = buildStdAction(StdCloseAction, this, SLOT(close()), this);
ac->addAction(QLatin1String("editorwindow_close"), closeAction);
// -- Standard 'Edit' menu actions ---------------------------------------------
d->copyAction = buildStdAction(StdCopyAction, m_canvas, SLOT(slotCopy()), this);
ac->addAction(QLatin1String("editorwindow_copy"), d->copyAction);
d->copyAction->setEnabled(false);
m_undoAction = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("edit-undo")), i18n("Undo"), this);
m_undoAction->setEnabled(false);
ac->addAction(QLatin1String("editorwindow_undo"), m_undoAction);
ac->setDefaultShortcuts(m_undoAction, QList<QKeySequence>() << Qt::CTRL + Qt::Key_Z);
connect(m_undoAction->menu(), SIGNAL(aboutToShow()),
this, SLOT(slotAboutToShowUndoMenu()));
// we are using a signal mapper to identify which of a bunch of actions was triggered
d->undoSignalMapper = new QSignalMapper(this);
// connect mapper to view
connect(d->undoSignalMapper, SIGNAL(mapped(int)),
m_canvas, SLOT(slotUndo(int)));
// connect simple undo action
connect(m_undoAction, SIGNAL(triggered()), d->undoSignalMapper, SLOT(map()));
d->undoSignalMapper->setMapping(m_undoAction, 1);
m_redoAction = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("edit-redo")), i18n("Redo"), this);
m_redoAction->setEnabled(false);
ac->addAction(QLatin1String("editorwindow_redo"), m_redoAction);
ac->setDefaultShortcuts(m_redoAction, QList<QKeySequence>() << Qt::CTRL + Qt::SHIFT + Qt::Key_Z);
connect(m_redoAction->menu(), SIGNAL(aboutToShow()),
this, SLOT(slotAboutToShowRedoMenu()));
d->redoSignalMapper = new QSignalMapper(this);
connect(d->redoSignalMapper, SIGNAL(mapped(int)),
m_canvas, SLOT(slotRedo(int)));
connect(m_redoAction, SIGNAL(triggered()), d->redoSignalMapper, SLOT(map()));
d->redoSignalMapper->setMapping(m_redoAction, 1);
d->selectAllAction = new QAction(i18nc("Create a selection containing the full image", "Select All"), this);
connect(d->selectAllAction, SIGNAL(triggered()), m_canvas, SLOT(slotSelectAll()));
ac->addAction(QLatin1String("editorwindow_selectAll"), d->selectAllAction);
ac->setDefaultShortcut(d->selectAllAction, Qt::CTRL + Qt::Key_A);
d->selectNoneAction = new QAction(i18n("Select None"), this);
connect(d->selectNoneAction, SIGNAL(triggered()), m_canvas, SLOT(slotSelectNone()));
ac->addAction(QLatin1String("editorwindow_selectNone"), d->selectNoneAction);
ac->setDefaultShortcut(d->selectNoneAction, Qt::CTRL + Qt::SHIFT + Qt::Key_A);
// -- Standard 'View' menu actions ---------------------------------------------
d->zoomPlusAction = buildStdAction(StdZoomInAction, this, SLOT(slotIncreaseZoom()), this);
QKeySequence keysPlus(d->zoomPlusAction->shortcut()[0], Qt::Key_Plus);
ac->addAction(QLatin1String("editorwindow_zoomplus"), d->zoomPlusAction);
ac->setDefaultShortcut(d->zoomPlusAction, keysPlus);
d->zoomMinusAction = buildStdAction(StdZoomOutAction, this, SLOT(slotDecreaseZoom()), this);
QKeySequence keysMinus(d->zoomMinusAction->shortcut()[0], Qt::Key_Minus);
ac->addAction(QLatin1String("editorwindow_zoomminus"), d->zoomMinusAction);
ac->setDefaultShortcut(d->zoomMinusAction, keysMinus);
d->zoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Zoom to 100%"), this);
connect(d->zoomTo100percents, SIGNAL(triggered()), this, SLOT(slotZoomTo100Percents()));
ac->addAction(QLatin1String("editorwindow_zoomto100percents"), d->zoomTo100percents);
ac->setDefaultShortcut(d->zoomTo100percents, Qt::CTRL + Qt::Key_Period);
d->zoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Fit to &Window"), this);
d->zoomFitToWindowAction->setCheckable(true);
connect(d->zoomFitToWindowAction, SIGNAL(triggered()), this, SLOT(slotToggleFitToWindow()));
ac->addAction(QLatin1String("editorwindow_zoomfit2window"), d->zoomFitToWindowAction);
ac->setDefaultShortcut(d->zoomFitToWindowAction, Qt::ALT + Qt::CTRL + Qt::Key_E);
d->zoomFitToSelectAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-select-fit")), i18n("Fit to &Selection"), this);
connect(d->zoomFitToSelectAction, SIGNAL(triggered()), this, SLOT(slotFitToSelect()));
ac->addAction(QLatin1String("editorwindow_zoomfit2select"), d->zoomFitToSelectAction);
ac->setDefaultShortcut(d->zoomFitToSelectAction, Qt::ALT + Qt::CTRL + Qt::Key_S); // NOTE: Photoshop 7 use ALT+CTRL+0
d->zoomFitToSelectAction->setEnabled(false);
d->zoomFitToSelectAction->setWhatsThis(i18n("This option can be used to zoom the image to the "
"current selection area."));
// -- Standard 'Decorate' menu actions ---------------------------------------------
d->insertTextAction = new QAction(QIcon::fromTheme(QLatin1String("insert-text")), i18n("Insert Text..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_decorate_inserttext"), d->insertTextAction );
actionCollection()->setDefaultShortcut(d->insertTextAction, Qt::SHIFT+Qt::CTRL+Qt::Key_T);
connect(d->insertTextAction, SIGNAL(triggered(bool)),
this, SLOT(slotInsertText()));
d->insertTextAction->setEnabled(false);
d->borderAction = new QAction(QIcon::fromTheme(QLatin1String("bordertool")), i18n("Add Border..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_decorate_border"), d->borderAction );
connect(d->borderAction, SIGNAL(triggered(bool)),
this, SLOT(slotBorder()));
d->borderAction->setEnabled(false);
d->textureAction = new QAction(QIcon::fromTheme(QLatin1String("texture")), i18n("Apply Texture..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_decorate_texture"), d->textureAction );
connect(d->textureAction, SIGNAL(triggered(bool)),
this, SLOT(slotTexture()));
d->textureAction->setEnabled(false);
// -- Standard 'Effects' menu actions ---------------------------------------------
d->colorEffectsAction = new QAction(QIcon::fromTheme(QLatin1String("colorfx")), i18n("Color Effects..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_colorfx"), d->colorEffectsAction);
connect(d->colorEffectsAction, SIGNAL(triggered(bool)),
this, SLOT(slotColorEffects()));
d->colorEffectsAction->setEnabled(false);
d->charcoalAction = new QAction(QIcon::fromTheme(QLatin1String("charcoaltool")), i18n("Charcoal Drawing..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_charcoal"), d->charcoalAction);
connect(d->charcoalAction, SIGNAL(triggered(bool)),
this, SLOT(slotCharcoal()));
d->charcoalAction->setEnabled(false);
d->embossAction = new QAction(QIcon::fromTheme(QLatin1String("embosstool")), i18n("Emboss..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_emboss"), d->embossAction);
connect(d->embossAction, SIGNAL(triggered(bool)),
this, SLOT(slotEmboss()));
d->embossAction->setEnabled(false);
d->oilpaintAction = new QAction(QIcon::fromTheme(QLatin1String("oilpaint")), i18n("Oil Paint..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_oilpaint"), d->oilpaintAction);
connect(d->oilpaintAction, SIGNAL(triggered(bool)),
this ,SLOT(slotOilPaint()));
d->oilpaintAction->setEnabled(false);
d->blurfxAction = new QAction(QIcon::fromTheme(QLatin1String("blurfx")), i18n("Blur Effects..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_blurfx"), d->blurfxAction);
connect(d->blurfxAction, SIGNAL(triggered(bool)),
this, SLOT(slotBlurFX()));
d->blurfxAction->setEnabled(false);
d->distortionfxAction = new QAction(QIcon::fromTheme(QLatin1String("draw-spiral")), i18n("Distortion Effects..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_distortionfx"), d->distortionfxAction );
connect(d->distortionfxAction, SIGNAL(triggered(bool)),
this, SLOT(slotDistortionFX()));
d->distortionfxAction->setEnabled(false);
d->raindropAction = new QAction(QIcon::fromTheme(QLatin1String("raindrop")), i18n("Raindrops..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_raindrop"), d->raindropAction);
connect(d->raindropAction, SIGNAL(triggered(bool)),
this, SLOT(slotRainDrop()));
d->raindropAction->setEnabled(false);
d->filmgrainAction = new QAction(QIcon::fromTheme(QLatin1String("filmgrain")), i18n("Add Film Grain..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_filter_filmgrain"), d->filmgrainAction);
connect(d->filmgrainAction, SIGNAL(triggered(bool)),
this, SLOT(slotFilmGrain()));
d->filmgrainAction->setEnabled(false);
// -- Standard 'Colors' menu actions ---------------------------------------------
d->BCGAction = new QAction(QIcon::fromTheme(QLatin1String("contrast")), i18n("Brightness/Contrast/Gamma..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_bcg"), d->BCGAction);
connect(d->BCGAction, SIGNAL(triggered(bool)),
this, SLOT(slotBCG()));
d->BCGAction->setEnabled(false);
// NOTE: Photoshop 7 use CTRL+U.
d->HSLAction = new QAction(QIcon::fromTheme(QLatin1String("adjusthsl")), i18n("Hue/Saturation/Lightness..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_hsl"), d->HSLAction);
actionCollection()->setDefaultShortcut(d->HSLAction, Qt::CTRL+Qt::Key_U);
connect(d->HSLAction, SIGNAL(triggered(bool)),
this, SLOT(slotHSL()));
d->HSLAction->setEnabled(false);
// NOTE: Photoshop 7 use CTRL+B.
d->CBAction = new QAction(QIcon::fromTheme(QLatin1String("adjustrgb")), i18n("Color Balance..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_rgb"), d->CBAction);
actionCollection()->setDefaultShortcut(d->CBAction, Qt::CTRL+Qt::Key_B);
connect(d->CBAction, SIGNAL(triggered(bool)),
this, SLOT(slotCB()));
d->CBAction->setEnabled(false);
// NOTE: Photoshop 7 use CTRL+SHIFT+B with
d->autoCorrectionAction = new QAction(QIcon::fromTheme(QLatin1String("autocorrection")), i18n("Auto-Correction..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_autocorrection"), d->autoCorrectionAction);
actionCollection()->setDefaultShortcut(d->autoCorrectionAction, Qt::CTRL+Qt::SHIFT+Qt::Key_B);
connect(d->autoCorrectionAction, SIGNAL(triggered(bool)),
this, SLOT(slotAutoCorrection()));
d->autoCorrectionAction->setEnabled(false);
// NOTE: Photoshop 7 use CTRL+I.
d->invertAction = new QAction(QIcon::fromTheme(QLatin1String("edit-select-invert")), i18n("Invert"), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_invert"), d->invertAction);
actionCollection()->setDefaultShortcut(d->invertAction, Qt::CTRL+Qt::Key_I);
connect(d->invertAction, SIGNAL(triggered(bool)),
this, SLOT(slotInvert()));
d->invertAction->setEnabled(false);
d->convertTo8Bits = new QAction(QIcon::fromTheme(QLatin1String("depth16to8")), i18n("8 bits"), this);
actionCollection()->addAction(QLatin1String("editorwindow_convertto8bits"), d->convertTo8Bits);
connect(d->convertTo8Bits, SIGNAL(triggered(bool)),
this, SLOT(slotConvertTo8Bits()));
d->convertTo8Bits->setEnabled(false);
d->convertTo16Bits = new QAction(QIcon::fromTheme(QLatin1String("depth8to16")), i18n("16 bits"), this);
actionCollection()->addAction(QLatin1String("editorwindow_convertto16bits"), d->convertTo16Bits);
connect(d->convertTo16Bits, SIGNAL(triggered(bool)),
this, SLOT(slotConvertTo16Bits()));
d->convertTo16Bits->setEnabled(false);
d->profileMenuAction = new IccProfilesMenuAction(QIcon::fromTheme(QLatin1String("preferences-desktop-display-color")), i18n("Color Spaces"), this);
actionCollection()->addAction(QLatin1String("editorwindow_colormanagement"), d->profileMenuAction->menuAction());
connect(d->profileMenuAction, SIGNAL(triggered(IccProfile)),
this, SLOT(slotConvertToColorSpace(IccProfile)));
d->profileMenuAction->setEnabled(false);
connect(IccSettings::instance(), SIGNAL(settingsChanged()),
this, SLOT(slotUpdateColorSpaceMenu()));
d->colorSpaceConverter = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-display-color")),
i18n("Color Space Converter..."), this);
connect(d->colorSpaceConverter, SIGNAL(triggered()),
this, SLOT(slotProfileConversionTool()));
d->colorSpaceConverter->setEnabled(false);
slotUpdateColorSpaceMenu();
d->BWAction = new QAction(QIcon::fromTheme(QLatin1String("bwtonal")), i18n("Black && White..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_blackwhite"), d->BWAction);
connect(d->BWAction, SIGNAL(triggered(bool)),
this, SLOT(slotBW()));
d->BWAction->setEnabled(false);
d->whitebalanceAction = new QAction(QIcon::fromTheme(QLatin1String("bordertool")), i18n("White Balance..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_whitebalance"), d->whitebalanceAction);
actionCollection()->setDefaultShortcut(d->whitebalanceAction, Qt::CTRL+Qt::SHIFT+Qt::Key_W);
connect(d->whitebalanceAction, SIGNAL(triggered(bool)),
this, SLOT(slotWhiteBalance()));
d->whitebalanceAction->setEnabled(false);
d->channelMixerAction = new QAction(QIcon::fromTheme(QLatin1String("channelmixer")), i18n("Channel Mixer..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_channelmixer"), d->channelMixerAction);
actionCollection()->setDefaultShortcut(d->channelMixerAction, Qt::CTRL+Qt::Key_H);
connect(d->channelMixerAction, SIGNAL(triggered(bool)),
this, SLOT(slotChannelMixer()));
d->channelMixerAction->setEnabled(false);
d->curvesAction = new QAction(QIcon::fromTheme(QLatin1String("adjustcurves")), i18n("Curves Adjust..."), this);
// NOTE: Photoshop 7 use CTRL+M (but it's used in KDE to toogle menu bar).
actionCollection()->addAction(QLatin1String("editorwindow_color_adjustcurves"), d->curvesAction);
actionCollection()->setDefaultShortcut(d->curvesAction, Qt::CTRL+Qt::SHIFT+Qt::Key_C);
connect(d->curvesAction, SIGNAL(triggered(bool)),
this, SLOT(slotCurvesAdjust()));
d->curvesAction->setEnabled(false);
d->levelsAction = new QAction(QIcon::fromTheme(QLatin1String("adjustlevels")), i18n("Levels Adjust..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_adjustlevels"), d->levelsAction);
actionCollection()->setDefaultShortcut(d->levelsAction, Qt::CTRL+Qt::Key_L);
connect(d->levelsAction, SIGNAL(triggered(bool)),
this, SLOT(slotLevelsAdjust()));
d->levelsAction->setEnabled(false);
d->filmAction = new QAction(QIcon::fromTheme(QLatin1String("colorneg")), i18n("Color Negative..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_color_film"), d->filmAction);
actionCollection()->setDefaultShortcut(d->filmAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I);
connect(d->filmAction, SIGNAL(triggered(bool)),
this, SLOT(slotFilm()));
d->filmAction->setEnabled(false);
// -- Standard 'Enhance' menu actions ---------------------------------------------
d->restorationAction = new QAction(QIcon::fromTheme(QLatin1String("restoration")), i18n("Restoration..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_restoration"), d->restorationAction);
connect(d->restorationAction, SIGNAL(triggered(bool)),
this, SLOT(slotRestoration()));
d->restorationAction->setEnabled(false);
d->sharpenAction = new QAction(QIcon::fromTheme(QLatin1String("sharpenimage")), i18n("Sharpen..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_sharpen"), d->sharpenAction);
connect(d->sharpenAction, SIGNAL(triggered(bool)),
this, SLOT(slotSharpen()));
d->sharpenAction->setEnabled(false);
d->blurAction = new QAction(QIcon::fromTheme(QLatin1String("blurimage")), i18n("Blur..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_blur"), d->blurAction);
connect(d->blurAction, SIGNAL(triggered(bool)),
this, SLOT(slotBlur()));
d->blurAction->setEnabled(false);
/*
d->healCloneAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clone")), i18n("Healing Clone..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_healingclone"), d->healCloneAction);
d->healCloneAction->setWhatsThis( i18n( "This filter can be used to clone a part in a photo to erase unwanted region.") );
connect(d->healCloneAction, SIGNAL(triggered(bool)),
this, SLOT(slotHealingClone()));
d->healCloneAction->setEnabled(false);
*/
d->noiseReductionAction = new QAction(QIcon::fromTheme(QLatin1String("noisereduction")), i18n("Noise Reduction..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_noisereduction"), d->noiseReductionAction);
connect(d->noiseReductionAction, SIGNAL(triggered(bool)),
this, SLOT(slotNoiseReduction()));
d->noiseReductionAction->setEnabled(false);
d->localContrastAction = new QAction(QIcon::fromTheme(QLatin1String("contrast")), i18n("Local Contrast..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_localcontrast"), d->localContrastAction);
connect(d->localContrastAction, SIGNAL(triggered(bool)),
this, SLOT(slotLocalContrast()));
d->localContrastAction->setEnabled(false);
d->redeyeAction = new QAction(QIcon::fromTheme(QLatin1String("redeyes")), i18n("Red Eye..."), this);
d->redeyeAction->setWhatsThis(i18n("This filter can be used to correct red eyes in a photo. "
"Select a region including the eyes to use this option."));
actionCollection()->addAction(QLatin1String("editorwindow_enhance_redeye"), d->redeyeAction);
connect(d->redeyeAction, SIGNAL(triggered(bool)),
this, SLOT(slotRedEye()));
d->redeyeAction->setEnabled(false);
d->antivignettingAction = new QAction(QIcon::fromTheme(QLatin1String("antivignetting")), i18n("Vignetting Correction..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_antivignetting"), d->antivignettingAction);
connect(d->antivignettingAction, SIGNAL(triggered(bool)),
this, SLOT(slotAntiVignetting()));
d->antivignettingAction->setEnabled(false);
d->lensdistortionAction = new QAction(QIcon::fromTheme(QLatin1String("lensdistortion")), i18n("Distortion..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_lensdistortion"), d->lensdistortionAction);
connect(d->lensdistortionAction, SIGNAL(triggered(bool)),
this, SLOT(slotLensDistortion()));
d->lensdistortionAction->setEnabled(false);
d->hotpixelsAction = new QAction(QIcon::fromTheme(QLatin1String("hotpixels")), i18n("Hot Pixels..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_hotpixels"), d->hotpixelsAction);
connect(d->hotpixelsAction, SIGNAL(triggered(bool)),
this, SLOT(slotHotPixels()));
d->hotpixelsAction->setEnabled(false);
#ifdef HAVE_LENSFUN
d->lensAutoFixAction = new QAction(QIcon::fromTheme(QLatin1String("lensautofix")), i18n("Auto-Correction..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_enhance_lensautofix"), d->lensAutoFixAction );
connect(d->lensAutoFixAction, SIGNAL(triggered(bool)),
this, SLOT(slotLensAutoFix()));
d->lensAutoFixAction->setEnabled(false);
#endif // HAVE_LENSFUN
HotPixelsTool::registerFilter();
// -- Standard 'Tools' menu actions ---------------------------------------------
createMetadataEditAction();
createGeolocationEditAction();
createTimeAdjustAction();
createHtmlGalleryAction();
createPanoramaAction();
createExpoBlendingAction();
createCalendarAction();
createVideoSlideshowAction();
createSendByMailAction();
createPrintCreatorAction();
createMediaServerAction();
createExportActions();
createImportActions();
m_metadataEditAction->setEnabled(false);
m_timeAdjustAction->setEnabled(false);
m_expoBlendingAction->setEnabled(false);
m_calendarAction->setEnabled(false);
m_sendByMailAction->setEnabled(false);
m_printCreatorAction->setEnabled(false);
m_mediaServerAction->setEnabled(false);
#ifdef HAVE_MARBLE
m_geolocationEditAction->setEnabled(false);
#endif
#ifdef HAVE_HTMLGALLERY
m_htmlGalleryAction->setEnabled(false);
#endif
#ifdef HAVE_PANORAMA
m_panoramaAction->setEnabled(false);
#endif
#ifdef HAVE_MEDIAPLAYER
m_videoslideshowAction->setEnabled(false);
#endif
foreach (QAction* const ac, exportActions())
ac->setEnabled(false);
// --------------------------------------------------------
createFullScreenAction(QLatin1String("editorwindow_fullscreen"));
createSidebarActions();
d->slideShowAction = new QAction(QIcon::fromTheme(QLatin1String("view-presentation")), i18n("Slideshow"), this);
connect(d->slideShowAction, SIGNAL(triggered()), this, SLOT(slotToggleSlideShow()));
ac->addAction(QLatin1String("editorwindow_slideshow"), d->slideShowAction);
ac->setDefaultShortcut(d->slideShowAction, Qt::Key_F9);
createPresentationAction();
d->viewUnderExpoAction = new QAction(QIcon::fromTheme(QLatin1String("underexposure")), i18n("Under-Exposure Indicator"), this);
d->viewUnderExpoAction->setCheckable(true);
d->viewUnderExpoAction->setWhatsThis(i18n("Set this option to display black "
"overlaid on the image. This will help you to avoid "
"under-exposing the image."));
connect(d->viewUnderExpoAction, SIGNAL(triggered(bool)), this, SLOT(slotSetUnderExposureIndicator(bool)));
ac->addAction(QLatin1String("editorwindow_underexposure"), d->viewUnderExpoAction);
ac->setDefaultShortcut(d->viewUnderExpoAction, Qt::Key_F10);
d->viewOverExpoAction = new QAction(QIcon::fromTheme(QLatin1String("overexposure")), i18n("Over-Exposure Indicator"), this);
d->viewOverExpoAction->setCheckable(true);
d->viewOverExpoAction->setWhatsThis(i18n("Set this option to display white "
"overlaid on the image. This will help you to avoid "
"over-exposing the image."));
connect(d->viewOverExpoAction, SIGNAL(triggered(bool)), this, SLOT(slotSetOverExposureIndicator(bool)));
ac->addAction(QLatin1String("editorwindow_overexposure"), d->viewOverExpoAction);
ac->setDefaultShortcut(d->viewOverExpoAction, Qt::Key_F11);
d->viewCMViewAction = new QAction(QIcon::fromTheme(QLatin1String("video-display")), i18n("Color-Managed View"), this);
d->viewCMViewAction->setCheckable(true);
connect(d->viewCMViewAction, SIGNAL(triggered()), this, SLOT(slotToggleColorManagedView()));
ac->addAction(QLatin1String("editorwindow_cmview"), d->viewCMViewAction);
ac->setDefaultShortcut(d->viewCMViewAction, Qt::Key_F12);
d->softProofOptionsAction = new QAction(QIcon::fromTheme(QLatin1String("printer")), i18n("Soft Proofing Options..."), this);
connect(d->softProofOptionsAction, SIGNAL(triggered()), this, SLOT(slotSoftProofingOptions()));
ac->addAction(QLatin1String("editorwindow_softproofoptions"), d->softProofOptionsAction);
d->viewSoftProofAction = new QAction(QIcon::fromTheme(QLatin1String("document-print-preview")), i18n("Soft Proofing View"), this);
d->viewSoftProofAction->setCheckable(true);
connect(d->viewSoftProofAction, SIGNAL(triggered()), this, SLOT(slotUpdateSoftProofingState()));
ac->addAction(QLatin1String("editorwindow_softproofview"), d->viewSoftProofAction);
// -- Standard 'Transform' menu actions ---------------------------------------------
d->cropAction = new QAction(QIcon::fromTheme(QLatin1String("transform-crop-and-resize")), i18nc("@action", "Crop to Selection"), this);
connect(d->cropAction, SIGNAL(triggered()), m_canvas, SLOT(slotCrop()));
d->cropAction->setEnabled(false);
d->cropAction->setWhatsThis(i18n("This option can be used to crop the image. "
"Select a region of the image to enable this action."));
ac->addAction(QLatin1String("editorwindow_transform_crop"), d->cropAction);
ac->setDefaultShortcut(d->cropAction, Qt::CTRL + Qt::Key_X);
d->autoCropAction = new QAction(QIcon::fromTheme(QLatin1String("transform-crop")), i18nc("@action", "Auto-Crop"), this);
d->autoCropAction->setWhatsThis(i18n("This option can be used to crop automatically the image."));
connect(d->autoCropAction, SIGNAL(triggered()), m_canvas, SLOT(slotAutoCrop()));
d->autoCropAction->setEnabled(false);
ac->addAction(QLatin1String("editorwindow_transform_autocrop"), d->autoCropAction);
ac->setDefaultShortcut(d->autoCropAction, Qt::SHIFT + Qt::CTRL + Qt::Key_X);
d->perspectiveAction = new QAction(QIcon::fromTheme(QLatin1String("perspective")), i18n("Perspective Adjustment..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_perspective"), d->perspectiveAction);
connect(d->perspectiveAction, SIGNAL(triggered(bool)),
this, SLOT(slotPerspective()));
d->perspectiveAction->setEnabled(false);
d->sheartoolAction = new QAction(QIcon::fromTheme(QLatin1String("transform-shear-left")), i18n("Shear..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_sheartool"), d->sheartoolAction);
connect(d->sheartoolAction, SIGNAL(triggered(bool)),
this, SLOT(slotShearTool()));
d->sheartoolAction->setEnabled(false);
d->resizeAction = new QAction(QIcon::fromTheme(QLatin1String("transform-scale")), i18n("&Resize..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_resize"), d->resizeAction);
connect(d->resizeAction, SIGNAL(triggered()),
this, SLOT(slotResize()));
d->resizeAction->setEnabled(false);
d->aspectRatioCropAction = new QAction(QIcon::fromTheme(QLatin1String("transform-crop")), i18n("Aspect Ratio Crop..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_ratiocrop"), d->aspectRatioCropAction);
connect(d->aspectRatioCropAction, SIGNAL(triggered(bool)),
this, SLOT(slotRatioCrop()));
d->aspectRatioCropAction->setEnabled(false);
#ifdef HAVE_LIBLQR_1
d->contentAwareResizingAction = new QAction(QIcon::fromTheme(QLatin1String("transform-scale")), i18n("Liquid Rescale..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_contentawareresizing"), d->contentAwareResizingAction);
connect(d->contentAwareResizingAction, SIGNAL(triggered(bool)),
this, SLOT(slotContentAwareResizing()));
d->contentAwareResizingAction->setEnabled(false);
#endif /* HAVE_LIBLQR_1 */
//-----------------------------------------------------------------------------------
d->freerotationAction = new QAction(QIcon::fromTheme(QLatin1String("transform-rotate")), i18n("Free Rotation..."), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_freerotation"), d->freerotationAction );
connect(d->freerotationAction, SIGNAL(triggered(bool)),
this, SLOT(slotFreeRotation()));
d->freerotationAction->setEnabled(false);
QAction* const point1Action = new QAction(i18n("Set Point 1"), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_freerotation_point1"), point1Action);
actionCollection()->setDefaultShortcut(point1Action, Qt::CTRL + Qt::SHIFT + Qt::Key_1);
connect(point1Action, SIGNAL(triggered(bool)),
this, SIGNAL(signalPoint1Action()));
QAction* const point2Action = new QAction(i18n("Set Point 2"), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_freerotation_point2"), point2Action);
actionCollection()->setDefaultShortcut(point2Action, Qt::CTRL + Qt::SHIFT + Qt::Key_2);
connect(point2Action, SIGNAL(triggered(bool)),
this, SIGNAL(signalPoint2Action()));
QAction* const autoAdjustAction = new QAction(i18n("Auto Adjust"), this);
actionCollection()->addAction(QLatin1String("editorwindow_transform_freerotation_autoadjust"), autoAdjustAction);
actionCollection()->setDefaultShortcut(autoAdjustAction, Qt::CTRL + Qt::SHIFT + Qt::Key_R);
connect(autoAdjustAction, SIGNAL(triggered(bool)),
this, SIGNAL(signalAutoAdjustAction()));
// -- Standard 'Flip' menu actions ---------------------------------------------
d->flipHorizAction = new QAction(QIcon::fromTheme(QLatin1String("object-flip-horizontal")), i18n("Flip Horizontally"), this);
connect(d->flipHorizAction, SIGNAL(triggered()), m_canvas, SLOT(slotFlipHoriz()));
connect(d->flipHorizAction, SIGNAL(triggered()), this, SLOT(slotFlipHIntoQue()));
ac->addAction(QLatin1String("editorwindow_transform_fliphoriz"), d->flipHorizAction);
ac->setDefaultShortcut(d->flipHorizAction, Qt::CTRL + Qt::Key_Asterisk);
d->flipHorizAction->setEnabled(false);
d->flipVertAction = new QAction(QIcon::fromTheme(QLatin1String("object-flip-vertical")), i18n("Flip Vertically"), this);
connect(d->flipVertAction, SIGNAL(triggered()), m_canvas, SLOT(slotFlipVert()));
connect(d->flipVertAction, SIGNAL(triggered()), this, SLOT(slotFlipVIntoQue()));
ac->addAction(QLatin1String("editorwindow_transform_flipvert"), d->flipVertAction);
ac->setDefaultShortcut(d->flipVertAction, Qt::CTRL + Qt::Key_Slash);
d->flipVertAction->setEnabled(false);
// -- Standard 'Rotate' menu actions ----------------------------------------
d->rotateLeftAction = new QAction(QIcon::fromTheme(QLatin1String("object-rotate-left")), i18n("Rotate Left"), this);
connect(d->rotateLeftAction, SIGNAL(triggered()), m_canvas, SLOT(slotRotate270()));
connect(d->rotateLeftAction, SIGNAL(triggered()), this, SLOT(slotRotateLeftIntoQue()));
ac->addAction(QLatin1String("editorwindow_transform_rotateleft"), d->rotateLeftAction);
ac->setDefaultShortcut(d->rotateLeftAction, Qt::SHIFT + Qt::CTRL + Qt::Key_Left);
d->rotateLeftAction->setEnabled(false);
d->rotateRightAction = new QAction(QIcon::fromTheme(QLatin1String("object-rotate-right")), i18n("Rotate Right"), this);
connect(d->rotateRightAction, SIGNAL(triggered()), m_canvas, SLOT(slotRotate90()));
connect(d->rotateRightAction, SIGNAL(triggered()), this, SLOT(slotRotateRightIntoQue()));
ac->addAction(QLatin1String("editorwindow_transform_rotateright"), d->rotateRightAction);
ac->setDefaultShortcut(d->rotateRightAction, Qt::SHIFT + Qt::CTRL + Qt::Key_Right);
d->rotateRightAction->setEnabled(false);
m_showBarAction = thumbBar()->getToggleAction(this);
ac->addAction(QLatin1String("editorwindow_showthumbs"), m_showBarAction);
ac->setDefaultShortcut(m_showBarAction, Qt::CTRL + Qt::Key_T);
// Provides a menu entry that allows showing/hiding the toolbar(s)
setStandardToolBarMenuEnabled(true);
// Provides a menu entry that allows showing/hiding the statusbar
createStandardStatusBarAction();
// Standard 'Configure' menu actions
createSettingsActions();
// ---------------------------------------------------------------------------------
ThemeManager::instance()->registerThemeActions(this);
connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()),
this, SLOT(slotThemeChanged()));
// -- Keyboard-only actions --------------------------------------------------------
QAction* const altBackwardAction = new QAction(i18n("Previous Image"), this);
ac->addAction(QLatin1String("editorwindow_backward_shift_space"), altBackwardAction);
ac->setDefaultShortcut(altBackwardAction, Qt::SHIFT + Qt::Key_Space);
connect(altBackwardAction, SIGNAL(triggered()), this, SLOT(slotBackward()));
// -- Tool control actions ---------------------------------------------------------
m_applyToolAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")), i18n("OK"), this);
ac->addAction(QLatin1String("editorwindow_applytool"), m_applyToolAction);
ac->setDefaultShortcut(m_applyToolAction, Qt::Key_Return);
connect(m_applyToolAction, SIGNAL(triggered()), this, SLOT(slotApplyTool()));
m_closeToolAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-cancel")), i18n("Cancel"), this);
ac->addAction(QLatin1String("editorwindow_closetool"), m_closeToolAction);
ac->setDefaultShortcut(m_closeToolAction, Qt::Key_Escape);
connect(m_closeToolAction, SIGNAL(triggered()), this, SLOT(slotCloseTool()));
toggleNonDestructiveActions();
toggleToolActions();
}
void EditorWindow::setupStatusBar()
{
m_nameLabel = new StatusProgressBar(statusBar());
m_nameLabel->setAlignment(Qt::AlignCenter);
statusBar()->addWidget(m_nameLabel, 100);
d->infoLabel = new DAdjustableLabel(statusBar());
d->infoLabel->setAdjustedText(i18n("No selection"));
d->infoLabel->setAlignment(Qt::AlignCenter);
statusBar()->addWidget(d->infoLabel, 100);
d->infoLabel->setToolTip(i18n("Information about current image selection"));
m_resLabel = new DAdjustableLabel(statusBar());
m_resLabel->setAlignment(Qt::AlignCenter);
statusBar()->addWidget(m_resLabel, 100);
m_resLabel->setToolTip(i18n("Information about image size"));
d->zoomBar = new DZoomBar(statusBar());
d->zoomBar->setZoomToFitAction(d->zoomFitToWindowAction);
d->zoomBar->setZoomTo100Action(d->zoomTo100percents);
d->zoomBar->setZoomPlusAction(d->zoomPlusAction);
d->zoomBar->setZoomMinusAction(d->zoomMinusAction);
d->zoomBar->setBarMode(DZoomBar::PreviewZoomCtrl);
statusBar()->addPermanentWidget(d->zoomBar);
connect(d->zoomBar, SIGNAL(signalZoomSliderChanged(int)),
m_stackView, SLOT(slotZoomSliderChanged(int)));
connect(d->zoomBar, SIGNAL(signalZoomValueEdited(double)),
m_stackView, SLOT(setZoomFactor(double)));
d->previewToolBar = new PreviewToolBar(statusBar());
d->previewToolBar->registerMenuActionGroup(this);
d->previewToolBar->setEnabled(false);
statusBar()->addPermanentWidget(d->previewToolBar);
connect(d->previewToolBar, SIGNAL(signalPreviewModeChanged(int)),
this, SIGNAL(signalPreviewModeChanged(int)));
QWidget* const buttonsBox = new QWidget(statusBar());
QHBoxLayout* const hlay = new QHBoxLayout(buttonsBox);
QButtonGroup* const buttonsGrp = new QButtonGroup(buttonsBox);
buttonsGrp->setExclusive(false);
d->underExposureIndicator = new QToolButton(buttonsBox);
d->underExposureIndicator->setDefaultAction(d->viewUnderExpoAction);
d->underExposureIndicator->setFocusPolicy(Qt::NoFocus);
d->overExposureIndicator = new QToolButton(buttonsBox);
d->overExposureIndicator->setDefaultAction(d->viewOverExpoAction);
d->overExposureIndicator->setFocusPolicy(Qt::NoFocus);
d->cmViewIndicator = new QToolButton(buttonsBox);
d->cmViewIndicator->setDefaultAction(d->viewCMViewAction);
d->cmViewIndicator->setFocusPolicy(Qt::NoFocus);
buttonsGrp->addButton(d->underExposureIndicator);
buttonsGrp->addButton(d->overExposureIndicator);
buttonsGrp->addButton(d->cmViewIndicator);
hlay->setSpacing(0);
hlay->setContentsMargins(QMargins());
hlay->addWidget(d->underExposureIndicator);
hlay->addWidget(d->overExposureIndicator);
hlay->addWidget(d->cmViewIndicator);
statusBar()->addPermanentWidget(buttonsBox);
}
void EditorWindow::printImage(const QUrl&)
{
DImg* const image = m_canvas->interface()->getImg();
if (!image || image->isNull())
{
return;
}
PrintHelper printHelp(this);
printHelp.print(*image);
}
void EditorWindow::slotAboutToShowUndoMenu()
{
m_undoAction->menu()->clear();
QStringList titles = m_canvas->interface()->getUndoHistory();
for (int i = 0; i < titles.size(); ++i)
{
QAction* const action = m_undoAction->menu()->addAction(titles.at(i), d->undoSignalMapper, SLOT(map()));
d->undoSignalMapper->setMapping(action, i + 1);
}
}
void EditorWindow::slotAboutToShowRedoMenu()
{
m_redoAction->menu()->clear();
QStringList titles = m_canvas->interface()->getRedoHistory();
for (int i = 0; i < titles.size(); ++i)
{
QAction* const action = m_redoAction->menu()->addAction(titles.at(i), d->redoSignalMapper, SLOT(map()));
d->redoSignalMapper->setMapping(action, i + 1);
}
}
void EditorWindow::slotIncreaseZoom()
{
m_stackView->increaseZoom();
}
void EditorWindow::slotDecreaseZoom()
{
m_stackView->decreaseZoom();
}
void EditorWindow::slotToggleFitToWindow()
{
d->zoomPlusAction->setEnabled(true);
d->zoomBar->setEnabled(true);
d->zoomMinusAction->setEnabled(true);
m_stackView->toggleFitToWindow();
}
void EditorWindow::slotFitToSelect()
{
d->zoomPlusAction->setEnabled(true);
d->zoomBar->setEnabled(true);
d->zoomMinusAction->setEnabled(true);
m_stackView->fitToSelect();
}
void EditorWindow::slotZoomTo100Percents()
{
d->zoomPlusAction->setEnabled(true);
d->zoomBar->setEnabled(true);
d->zoomMinusAction->setEnabled(true);
m_stackView->zoomTo100Percent();
}
void EditorWindow::slotZoomChanged(bool isMax, bool isMin, double zoom)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "EditorWindow::slotZoomChanged";
d->zoomPlusAction->setEnabled(!isMax);
d->zoomMinusAction->setEnabled(!isMin);
double zmin = m_stackView->zoomMin();
double zmax = m_stackView->zoomMax();
d->zoomBar->setZoom(zoom, zmin, zmax);
}
void EditorWindow::slotToggleOffFitToWindow()
{
d->zoomFitToWindowAction->blockSignals(true);
d->zoomFitToWindowAction->setChecked(false);
d->zoomFitToWindowAction->blockSignals(false);
}
void EditorWindow::readStandardSettings()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(configGroupName());
// Restore Canvas layout
if (group.hasKey(d->configVerticalSplitterSizesEntry) && m_vSplitter)
{
QByteArray state;
state = group.readEntry(d->configVerticalSplitterStateEntry, state);
m_vSplitter->restoreState(QByteArray::fromBase64(state));
}
// Restore full screen Mode
readFullScreenSettings(group);
// Restore Auto zoom action
bool autoZoom = group.readEntry(d->configAutoZoomEntry, true);
if (autoZoom)
{
d->zoomFitToWindowAction->trigger();
}
slotSetUnderExposureIndicator(group.readEntry(d->configUnderExposureIndicatorEntry, false));
slotSetOverExposureIndicator(group.readEntry(d->configOverExposureIndicatorEntry, false));
d->previewToolBar->readSettings(group);
}
void EditorWindow::applyStandardSettings()
{
applyColorManagementSettings();
d->toolIface->updateICCSettings();
applyIOSettings();
// -- GUI Settings -------------------------------------------------------
KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName());
d->legacyUpdateSplitterState(group);
m_splitter->restoreState(group);
readFullScreenSettings(group);
slotThemeChanged();
// -- Exposure Indicators Settings ---------------------------------------
d->exposureSettings->underExposureColor = group.readEntry(d->configUnderExposureColorEntry, QColor(Qt::white));
d->exposureSettings->underExposurePercent = group.readEntry(d->configUnderExposurePercentsEntry, 1.0);
d->exposureSettings->overExposureColor = group.readEntry(d->configOverExposureColorEntry, QColor(Qt::black));
d->exposureSettings->overExposurePercent = group.readEntry(d->configOverExposurePercentsEntry, 1.0);
d->exposureSettings->exposureIndicatorMode = group.readEntry(d->configExpoIndicatorModeEntry, true);
d->toolIface->updateExposureSettings();
// -- Metadata Settings --------------------------------------------------
MetadataSettingsContainer writeSettings = MetadataSettings::instance()->settings();
m_setExifOrientationTag = writeSettings.exifSetOrientation;
m_canvas->setExifOrient(writeSettings.exifRotate);
}
void EditorWindow::applyIOSettings()
{
// -- JPEG, PNG, TIFF JPEG2000 files format settings --------------------------------------
KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName());
m_IOFileSettings->JPEGCompression = JPEGSettings::convertCompressionForLibJpeg(group.readEntry(d->configJpegCompressionEntry, 75));
m_IOFileSettings->JPEGSubSampling = group.readEntry(d->configJpegSubSamplingEntry, 1); // Medium subsampling
m_IOFileSettings->PNGCompression = PNGSettings::convertCompressionForLibPng(group.readEntry(d->configPngCompressionEntry, 1));
// TIFF compression setting.
m_IOFileSettings->TIFFCompression = group.readEntry(d->configTiffCompressionEntry, false);
// JPEG2000 quality slider settings : 1 - 100
m_IOFileSettings->JPEG2000Compression = group.readEntry(d->configJpeg2000CompressionEntry, 100);
// JPEG2000 LossLess setting.
m_IOFileSettings->JPEG2000LossLess = group.readEntry(d->configJpeg2000LossLessEntry, true);
// PGF quality slider settings : 1 - 9
m_IOFileSettings->PGFCompression = group.readEntry(d->configPgfCompressionEntry, 3);
// PGF LossLess setting.
m_IOFileSettings->PGFLossLess = group.readEntry(d->configPgfLossLessEntry, true);
// -- RAW images decoding settings ------------------------------------------------------
m_IOFileSettings->useRAWImport = group.readEntry(d->configUseRawImportToolEntry, false);
DRawDecoderWidget::readSettings(m_IOFileSettings->rawDecodingSettings.rawPrm, group);
// Raw Color Management settings:
// If digiKam Color Management is enabled, no need to correct color of decoded RAW image,
// else, sRGB color workspace will be used.
ICCSettingsContainer settings = IccSettings::instance()->settings();
if (settings.enableCM)
{
if (settings.defaultUncalibratedBehavior & ICCSettingsContainer::AutomaticColors)
{
m_IOFileSettings->rawDecodingSettings.rawPrm.outputColorSpace = DRawDecoderSettings::CUSTOMOUTPUTCS;
m_IOFileSettings->rawDecodingSettings.rawPrm.outputProfile = settings.workspaceProfile;
}
else
{
m_IOFileSettings->rawDecodingSettings.rawPrm.outputColorSpace = DRawDecoderSettings::RAWCOLOR;
}
}
else
{
m_IOFileSettings->rawDecodingSettings.rawPrm.outputColorSpace = DRawDecoderSettings::SRGB;
}
}
void EditorWindow::applyColorManagementSettings()
{
ICCSettingsContainer settings = IccSettings::instance()->settings();
d->toolIface->updateICCSettings();
m_canvas->setICCSettings(settings);
d->viewCMViewAction->blockSignals(true);
d->viewCMViewAction->setEnabled(settings.enableCM);
d->viewCMViewAction->setChecked(settings.useManagedView);
setColorManagedViewIndicatorToolTip(settings.enableCM, settings.useManagedView);
d->viewCMViewAction->blockSignals(false);
d->viewSoftProofAction->setEnabled(settings.enableCM && !settings.defaultProofProfile.isEmpty());
d->softProofOptionsAction->setEnabled(settings.enableCM);
}
void EditorWindow::saveStandardSettings()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(configGroupName());
group.writeEntry(d->configAutoZoomEntry, d->zoomFitToWindowAction->isChecked());
m_splitter->saveState(group);
if (m_vSplitter)
{
group.writeEntry(d->configVerticalSplitterStateEntry, m_vSplitter->saveState().toBase64());
}
group.writeEntry("Show Thumbbar", thumbBar()->shouldBeVisible());
group.writeEntry(d->configUnderExposureIndicatorEntry, d->exposureSettings->underExposureIndicator);
group.writeEntry(d->configOverExposureIndicatorEntry, d->exposureSettings->overExposureIndicator);
d->previewToolBar->writeSettings(group);
config->sync();
}
-/** Method used by Editor Tools. Only tools based on imageregionwidget support zoomming.
+/** Method used by Editor Tools. Only tools based on imageregionwidget support zooming.
TODO: Fix this behavior when editor tool preview widgets will be factored.
*/
void EditorWindow::toggleZoomActions(bool val)
{
d->zoomMinusAction->setEnabled(val);
d->zoomPlusAction->setEnabled(val);
d->zoomTo100percents->setEnabled(val);
d->zoomFitToWindowAction->setEnabled(val);
d->zoomBar->setEnabled(val);
}
void EditorWindow::readSettings()
{
readStandardSettings();
}
void EditorWindow::saveSettings()
{
saveStandardSettings();
}
void EditorWindow::toggleActions(bool val)
{
toggleStandardActions(val);
}
void EditorWindow::toggleStandardActions(bool val)
{
d->zoomFitToSelectAction->setEnabled(val);
toggleZoomActions(val);
m_actionEnabledState = val;
m_forwardAction->setEnabled(val);
m_backwardAction->setEnabled(val);
m_firstAction->setEnabled(val);
m_lastAction->setEnabled(val);
d->rotateLeftAction->setEnabled(val);
d->rotateRightAction->setEnabled(val);
d->flipHorizAction->setEnabled(val);
d->flipVertAction->setEnabled(val);
m_fileDeleteAction->setEnabled(val);
m_saveAsAction->setEnabled(val);
d->openWithAction->setEnabled(val);
d->filePrintAction->setEnabled(val);
m_metadataEditAction->setEnabled(val);
m_timeAdjustAction->setEnabled(val);
m_exportAction->setEnabled(val);
d->selectAllAction->setEnabled(val);
d->selectNoneAction->setEnabled(val);
d->slideShowAction->setEnabled(val);
m_presentationAction->setEnabled(val);
m_calendarAction->setEnabled(val);
m_expoBlendingAction->setEnabled(val);
m_sendByMailAction->setEnabled(val);
m_printCreatorAction->setEnabled(val);
m_mediaServerAction->setEnabled(val);
#ifdef HAVE_MARBLE
m_geolocationEditAction->setEnabled(val);
#endif
#ifdef HAVE_HTMLGALLERY
m_htmlGalleryAction->setEnabled(val);
#endif
#ifdef HAVE_PANORAMA
m_panoramaAction->setEnabled(val);
#endif
#ifdef HAVE_MEDIAPLAYER
m_videoslideshowAction->setEnabled(val);
#endif
foreach (QAction* const ac, exportActions())
ac->setEnabled(val);
// these actions are special: They are turned off if val is false,
// but if val is true, they may be turned on or off.
if (val)
{
// Update actions by retrieving current values
slotUndoStateChanged();
}
else
{
m_openVersionAction->setEnabled(false);
m_revertAction->setEnabled(false);
m_saveAction->setEnabled(false);
m_saveCurrentVersionAction->setEnabled(false);
m_saveNewVersionAction->setEnabled(false);
m_discardChangesAction->setEnabled(false);
m_undoAction->setEnabled(false);
m_redoAction->setEnabled(false);
}
// Tools actions
d->insertTextAction->setEnabled(val);
d->borderAction->setEnabled(val);
d->textureAction->setEnabled(val);
d->charcoalAction->setEnabled(val);
d->colorEffectsAction->setEnabled(val);
d->embossAction->setEnabled(val);
d->oilpaintAction->setEnabled(val);
d->blurfxAction->setEnabled(val);
d->distortionfxAction->setEnabled(val);
d->raindropAction->setEnabled(val);
d->filmgrainAction->setEnabled(val);
d->convertTo8Bits->setEnabled(val);
d->convertTo16Bits->setEnabled(val);
d->invertAction->setEnabled(val);
d->BCGAction->setEnabled(val);
d->CBAction->setEnabled(val);
d->autoCorrectionAction->setEnabled(val);
d->BWAction->setEnabled(val);
d->HSLAction->setEnabled(val);
d->profileMenuAction->setEnabled(val);
d->colorSpaceConverter->setEnabled(val && IccSettings::instance()->isEnabled());
d->whitebalanceAction->setEnabled(val);
d->channelMixerAction->setEnabled(val);
d->curvesAction->setEnabled(val);
d->levelsAction->setEnabled(val);
d->filmAction->setEnabled(val);
d->restorationAction->setEnabled(val);
d->blurAction->setEnabled(val);
//d->healCloneAction->setEnabled(val);
d->sharpenAction->setEnabled(val);
d->noiseReductionAction->setEnabled(val);
d->localContrastAction->setEnabled(val);
d->redeyeAction->setEnabled(val);
d->lensdistortionAction->setEnabled(val);
d->antivignettingAction->setEnabled(val);
d->hotpixelsAction->setEnabled(val);
d->resizeAction->setEnabled(val);
d->autoCropAction->setEnabled(val);
d->perspectiveAction->setEnabled(val);
d->freerotationAction->setEnabled(val);
d->sheartoolAction->setEnabled(val);
d->aspectRatioCropAction->setEnabled(val);
#ifdef HAVE_LENSFUN
d->lensAutoFixAction->setEnabled(val);
#endif
#ifdef HAVE_LIBLQR_1
d->contentAwareResizingAction->setEnabled(val);
#endif
}
void EditorWindow::toggleNonDestructiveActions()
{
m_saveAction->setVisible(!m_nonDestructive);
m_saveAsAction->setVisible(!m_nonDestructive);
m_revertAction->setVisible(!m_nonDestructive);
m_openVersionAction->setVisible(m_nonDestructive);
m_saveCurrentVersionAction->setVisible(m_nonDestructive);
m_saveNewVersionAction->setVisible(m_nonDestructive);
m_exportAction->setVisible(m_nonDestructive);
m_discardChangesAction->setVisible(m_nonDestructive);
}
void EditorWindow::toggleToolActions(EditorTool* tool)
{
if (tool)
{
m_applyToolAction->setText(tool->toolSettings()->button(EditorToolSettings::Ok)->text());
m_applyToolAction->setIcon(tool->toolSettings()->button(EditorToolSettings::Ok)->icon());
m_applyToolAction->setToolTip(tool->toolSettings()->button(EditorToolSettings::Ok)->toolTip());
m_closeToolAction->setText(tool->toolSettings()->button(EditorToolSettings::Cancel)->text());
m_closeToolAction->setIcon(tool->toolSettings()->button(EditorToolSettings::Cancel)->icon());
m_closeToolAction->setToolTip(tool->toolSettings()->button(EditorToolSettings::Cancel)->toolTip());
}
m_applyToolAction->setVisible(tool);
m_closeToolAction->setVisible(tool);
}
void EditorWindow::slotLoadingProgress(const QString&, float progress)
{
m_nameLabel->setProgressValue((int)(progress * 100.0));
}
void EditorWindow::slotSavingProgress(const QString&, float progress)
{
m_nameLabel->setProgressValue((int)(progress * 100.0));
if (m_savingProgressDialog)
{
m_savingProgressDialog->setValue((int)(progress * 100.0));
}
}
void EditorWindow::execSavingProgressDialog()
{
if (m_savingProgressDialog)
{
return;
}
m_savingProgressDialog = new QProgressDialog(this);
m_savingProgressDialog->setWindowTitle(i18n("Saving image..."));
m_savingProgressDialog->setLabelText(i18n("Please wait for the image to be saved..."));
m_savingProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
m_savingProgressDialog->setAutoClose(true);
m_savingProgressDialog->setMinimumDuration(1000);
m_savingProgressDialog->setMaximum(100);
// we must enter a fully modal dialog, no QEventLoop is sufficient for KWin to accept longer waiting times
m_savingProgressDialog->setModal(true);
m_savingProgressDialog->exec();
}
bool EditorWindow::promptForOverWrite()
{
QUrl destination = saveDestinationUrl();
if (destination.isLocalFile())
{
QFileInfo fi(m_canvas->currentImageFilePath());
QString warnMsg(i18n("About to overwrite file \"%1\"\nAre you sure?", QDir::toNativeSeparators(fi.fileName())));
return (DMessageBox::showContinueCancel(QMessageBox::Warning,
this,
i18n("Warning"),
warnMsg,
QLatin1String("editorWindowSaveOverwrite"))
== QMessageBox::Yes);
}
else
{
- // in this case wil will handles the overwrite request
+ // in this case it will handle the overwrite request
return true;
}
}
void EditorWindow::slotUndoStateChanged()
{
UndoState state = m_canvas->interface()->undoState();
// RAW conversion qualifies as a "non-undoable" action
// You can save as new version, but cannot undo or revert
m_undoAction->setEnabled(state.hasUndo);
m_redoAction->setEnabled(state.hasRedo);
m_revertAction->setEnabled(state.hasUndoableChanges);
m_saveAction->setEnabled(state.hasChanges);
m_saveCurrentVersionAction->setEnabled(state.hasChanges);
m_saveNewVersionAction->setEnabled(state.hasChanges);
m_discardChangesAction->setEnabled(state.hasUndoableChanges);
m_openVersionAction->setEnabled(hasOriginalToRestore());
}
bool EditorWindow::hasOriginalToRestore()
{
return m_canvas->interface()->getResolvedInitialHistory().hasOriginalReferredImage();
}
DImageHistory EditorWindow::resolvedImageHistory(const DImageHistory& history)
{
// simple, database-less version
DImageHistory r = history;
QList<DImageHistory::Entry>::iterator it;
for (it = r.entries().begin(); it != r.entries().end(); ++it)
{
QList<HistoryImageId>::iterator hit;
for (hit = it->referredImages.begin(); hit != it->referredImages.end();)
{
QFileInfo info(hit->m_filePath + QLatin1Char('/') + hit->m_fileName);
if (!info.exists())
{
hit = it->referredImages.erase(hit);
}
else
{
++hit;
}
}
}
return r;
}
bool EditorWindow::promptUserSave(const QUrl& url, SaveAskMode mode, bool allowCancel)
{
if (d->currentWindowModalDialog)
{
d->currentWindowModalDialog->reject();
}
if (m_canvas->interface()->undoState().hasUndoableChanges)
{
// if window is minimized, show it
if (isMinimized())
{
KWindowSystem::unminimizeWindow(winId());
}
bool shallSave = true;
bool shallDiscard = false;
bool newVersion = false;
if (mode == AskIfNeeded)
{
if (m_nonDestructive)
{
if (versionManager()->settings().editorClosingMode == VersionManagerSettings::AutoSave)
{
shallSave = true;
}
else
{
QPointer<VersioningPromptUserSaveDialog> dialog = new VersioningPromptUserSaveDialog(this);
dialog->exec();
if (!dialog)
{
return false;
}
shallSave = dialog->shallSave() || dialog->newVersion();
shallDiscard = dialog->shallDiscard();
newVersion = dialog->newVersion();
}
}
else
{
QString boxMessage;
boxMessage = i18nc("@info",
"<qt>The image <b>%1</b> has been modified.<br/>"
"Do you want to save it?</qt>", url.fileName());
int result;
if (allowCancel)
{
result = QMessageBox::warning(this, qApp->applicationName(), boxMessage,
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
}
else
{
result = QMessageBox::warning(this, qApp->applicationName(), boxMessage,
QMessageBox::Save | QMessageBox::Discard);
}
shallSave = (result == QMessageBox::Save);
shallDiscard = (result == QMessageBox::Discard);
}
}
if (shallSave)
{
bool saving = false;
switch (mode)
{
case AskIfNeeded:
if (m_nonDestructive)
{
if (newVersion)
{
saving = saveNewVersion();
}
else
{
// will know on its own if new version is required
saving = saveCurrentVersion();
}
}
else
{
if (m_canvas->isReadOnly())
{
saving = saveAs();
}
else if (promptForOverWrite())
{
saving = save();
}
}
break;
case OverwriteWithoutAsking:
if (m_nonDestructive)
{
if (newVersion)
{
saving = saveNewVersion();
}
else
{
// will know on its own if new version is required
saving = saveCurrentVersion();
}
}
else
{
if (m_canvas->isReadOnly())
{
saving = saveAs();
}
else
{
saving = save();
}
}
break;
case AlwaysSaveAs:
if (m_nonDestructive)
{
saving = saveNewVersion();
}
else
{
saving = saveAs();
}
break;
}
// save and saveAs return false if they were canceled and did not enter saving at all
// In this case, do not call enterWaitingLoop because quitWaitingloop will not be called.
if (saving)
{
// Waiting for asynchronous image file saving operation running in separate thread.
m_savingContext.synchronizingState = SavingContext::SynchronousSaving;
enterWaitingLoop();
m_savingContext.synchronizingState = SavingContext::NormalSaving;
return m_savingContext.synchronousSavingResult;
}
else
{
return false;
}
}
else if (shallDiscard)
{
// Discard
m_saveAction->setEnabled(false);
return true;
}
else
{
return false;
}
}
return true;
}
bool EditorWindow::promptUserDelete(const QUrl& url)
{
if (d->currentWindowModalDialog)
{
d->currentWindowModalDialog->reject();
}
if (m_canvas->interface()->undoState().hasUndoableChanges)
{
// if window is minimized, show it
if (isMinimized())
{
KWindowSystem::unminimizeWindow(winId());
}
QString boxMessage = i18nc("@info",
"The image <b>%1</b> has been modified.<br/>"
"All changes will be lost.", url.fileName());
int result = DMessageBox::showContinueCancel(QMessageBox::Warning,
this,
QString(),
boxMessage);
if (result == QMessageBox::Cancel)
{
return false;
}
}
return true;
}
bool EditorWindow::waitForSavingToComplete()
{
// avoid reentrancy - return false means we have reentered the loop already.
if (m_savingContext.synchronizingState == SavingContext::SynchronousSaving)
{
return false;
}
if (m_savingContext.savingState != SavingContext::SavingStateNone)
{
// Waiting for asynchronous image file saving operation running in separate thread.
m_savingContext.synchronizingState = SavingContext::SynchronousSaving;
enterWaitingLoop();
m_savingContext.synchronizingState = SavingContext::NormalSaving;
}
return true;
}
void EditorWindow::enterWaitingLoop()
{
//d->waitingLoop->exec(QEventLoop::ExcludeUserInputEvents);
execSavingProgressDialog();
}
void EditorWindow::quitWaitingLoop()
{
//d->waitingLoop->quit();
if (m_savingProgressDialog)
{
m_savingProgressDialog->close();
}
}
void EditorWindow::slotSelected(bool val)
{
// Update menu actions.
d->cropAction->setEnabled(val);
d->zoomFitToSelectAction->setEnabled(val);
d->copyAction->setEnabled(val);
QRect sel = m_canvas->getSelectedArea();
// Update histogram into sidebar.
emit signalSelectionChanged(sel);
// Update status bar
if (val)
{
slotSelectionSetText(sel);
}
else
{
setToolInfoMessage(i18n("No selection"));
}
}
void EditorWindow::slotPrepareToLoad()
{
// Disable actions as appropriate during loading
emit signalNoCurrentItem();
unsetCursor();
m_animLogo->stop();
toggleActions(false);
slotUpdateItemInfo();
}
void EditorWindow::slotLoadingStarted(const QString& /*filename*/)
{
setCursor(Qt::WaitCursor);
toggleActions(false);
m_animLogo->start();
m_nameLabel->setProgressBarMode(StatusProgressBar::ProgressBarMode, i18n("Loading:"));
}
void EditorWindow::slotLoadingFinished(const QString& filename, bool success)
{
m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode);
// Enable actions as appropriate after loading
// No need to re-enable image properties sidebar here, it's will be done
// automatically by a signal from canvas
toggleActions(success);
slotUpdateItemInfo();
unsetCursor();
m_animLogo->stop();
if (success)
{
colorManage();
// Set a history which contains all available files as referredImages
DImageHistory resolved = resolvedImageHistory(m_canvas->interface()->getInitialImageHistory());
m_canvas->interface()->setResolvedInitialHistory(resolved);
}
else
{
DNotificationPopup::message(DNotificationPopup::Boxed,
i18n("Cannot load \"%1\"", filename),
m_canvas, m_canvas->mapToGlobal(QPoint(30, 30)));
}
}
void EditorWindow::resetOrigin()
{
// With versioning, "only" resetting undo history does not work anymore
// as we calculate undo state based on the initial history stored in the DImg
resetOriginSwitchFile();
}
void EditorWindow::resetOriginSwitchFile()
{
DImageHistory resolved = resolvedImageHistory(m_canvas->interface()->getImageHistory());
m_canvas->interface()->switchToLastSaved(resolved);
}
void EditorWindow::colorManage()
{
if (!IccSettings::instance()->isEnabled())
{
return;
}
DImg image = m_canvas->currentImage();
if (image.isNull())
{
return;
}
if (!IccManager::needsPostLoadingManagement(image))
{
return;
}
IccPostLoadingManager manager(image, m_canvas->currentImageFilePath());
if (!manager.hasValidWorkspace())
{
QString message = i18n("Cannot open the specified working space profile (\"%1\"). "
"No color transformation will be applied. "
"Please check the color management "
"configuration in digiKam's setup.", IccSettings::instance()->settings().workspaceProfile);
QMessageBox::information(this, qApp->applicationName(), message);
}
// Show dialog and get transform from user choice
IccTransform trans = manager.postLoadingManage(this);
// apply transform in thread.
// Do _not_ test for willHaveEffect() here - there are more side effects when calling this method
m_canvas->applyTransform(trans);
slotUpdateItemInfo();
}
void EditorWindow::slotNameLabelCancelButtonPressed()
{
// If we saving an image...
if (m_savingContext.savingState != SavingContext::SavingStateNone)
{
m_savingContext.abortingSaving = true;
m_canvas->abortSaving();
}
// If we preparing SlideShow...
m_cancelSlideShow = true;
}
void EditorWindow::slotFileOriginChanged(const QString&)
{
// implemented in subclass
}
bool EditorWindow::saveOrSaveAs()
{
if (m_canvas->isReadOnly())
{
return saveAs();
}
else if (promptForOverWrite())
{
return save();
}
return false;
}
void EditorWindow::slotSavingStarted(const QString& /*filename*/)
{
setCursor(Qt::WaitCursor);
m_animLogo->start();
// Disable actions as appropriate during saving
emit signalNoCurrentItem();
toggleActions(false);
m_nameLabel->setProgressBarMode(StatusProgressBar::CancelProgressBarMode, i18n("Saving:"));
}
void EditorWindow::slotSavingFinished(const QString& filename, bool success)
{
Q_UNUSED(filename);
qCDebug(DIGIKAM_GENERAL_LOG) << filename << success << (m_savingContext.savingState != SavingContext::SavingStateNone);
// only handle this if we really wanted to save a file...
if (m_savingContext.savingState != SavingContext::SavingStateNone)
{
m_savingContext.executedOperation = m_savingContext.savingState;
m_savingContext.savingState = SavingContext::SavingStateNone;
if (!success)
{
if (!m_savingContext.abortingSaving)
{
QMessageBox::critical(this, qApp->applicationName(),
i18n("Failed to save file\n\"%1\"\nto\n\"%2\".",
m_savingContext.destinationURL.fileName(),
m_savingContext.destinationURL.toLocalFile()));
}
finishSaving(false);
return;
}
moveFile();
}
else
{
qCWarning(DIGIKAM_GENERAL_LOG) << "Why was slotSavingFinished called if we did not want to save a file?";
}
}
void EditorWindow::movingSaveFileFinished(bool successful)
{
if (!successful)
{
finishSaving(false);
return;
}
// now that we know the real destination file name, pass it to be recorded in image history
m_canvas->interface()->setLastSaved(m_savingContext.destinationURL.toLocalFile());
// remove image from cache since it has changed
LoadingCacheInterface::fileChanged(m_savingContext.destinationURL.toLocalFile());
ThumbnailLoadThread::deleteThumbnail(m_savingContext.destinationURL.toLocalFile());
// restore state of disabled actions. saveIsComplete can start any other task
// (loading!) which might itself in turn change states
finishSaving(true);
switch (m_savingContext.executedOperation)
{
case SavingContext::SavingStateNone:
break;
case SavingContext::SavingStateSave:
saveIsComplete();
break;
case SavingContext::SavingStateSaveAs:
saveAsIsComplete();
break;
case SavingContext::SavingStateVersion:
saveVersionIsComplete();
break;
}
// Take all actions necessary to update information and re-enable sidebar
slotChanged();
}
void EditorWindow::finishSaving(bool success)
{
m_savingContext.synchronousSavingResult = success;
delete m_savingContext.saveTempFile;
m_savingContext.saveTempFile = 0;
// Exit of internal Qt event loop to unlock promptUserSave() method.
if (m_savingContext.synchronizingState == SavingContext::SynchronousSaving)
{
quitWaitingLoop();
}
// Enable actions as appropriate after saving
toggleActions(true);
unsetCursor();
m_animLogo->stop();
m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode);
/*if (m_savingProgressDialog)
{
m_savingProgressDialog->close();
}*/
// On error, continue using current image
if (!success)
{
/* Why this?
* m_canvas->switchToLastSaved(m_savingContext.srcURL.toLocalFile());*/
}
}
void EditorWindow::setupTempSaveFile(const QUrl& url)
{
// if the destination url is on local file system, try to set the temp file
// location to the destination folder, otherwise use a local default
QString tempDir = url.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).toLocalFile();
if (!url.isLocalFile() || tempDir.isEmpty())
{
tempDir = QDir::tempPath();
}
QFileInfo fi(url.toLocalFile());
QString suffix = fi.suffix();
// use magic file extension which tells the digikamalbums ioslave to ignore the file
m_savingContext.saveTempFile = new SafeTemporaryFile(tempDir + QLatin1String("/EditorWindow-XXXXXX.digikamtempfile.") + suffix);
m_savingContext.saveTempFile->setAutoRemove(false);
if (!m_savingContext.saveTempFile->open())
{
QMessageBox::critical(this, qApp->applicationName(),
i18n("Could not open a temporary file in the folder \"%1\": %2 (%3)",
QDir::toNativeSeparators(tempDir), m_savingContext.saveTempFile->errorString(),
m_savingContext.saveTempFile->error()));
return;
}
m_savingContext.saveTempFileName = m_savingContext.saveTempFile->fileName();
delete m_savingContext.saveTempFile;
m_savingContext.saveTempFile = 0;
}
void EditorWindow::startingSave(const QUrl& url)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "startSaving url = " << url;
// avoid any reentrancy. Should be impossible anyway since actions will be disabled.
if (m_savingContext.savingState != SavingContext::SavingStateNone)
{
return;
}
m_savingContext = SavingContext();
if (!checkPermissions(url))
{
return;
}
setupTempSaveFile(url);
m_savingContext.srcURL = url;
m_savingContext.destinationURL = m_savingContext.srcURL;
m_savingContext.destinationExisted = true;
m_savingContext.originalFormat = m_canvas->currentImageFileFormat();
m_savingContext.format = m_savingContext.originalFormat;
m_savingContext.abortingSaving = false;
m_savingContext.savingState = SavingContext::SavingStateSave;
m_savingContext.executedOperation = SavingContext::SavingStateNone;
m_canvas->interface()->saveAs(m_savingContext.saveTempFileName, m_IOFileSettings,
m_setExifOrientationTag && m_canvas->exifRotated(), m_savingContext.format,
m_savingContext.destinationURL.toLocalFile());
}
bool EditorWindow::showFileSaveDialog(const QUrl& initialUrl, QUrl& newURL)
{
QString all;
QStringList list = supportedImageMimeTypes(QIODevice::WriteOnly, all);
DFileDialog* const imageFileSaveDialog = new DFileDialog(this);
imageFileSaveDialog->setWindowTitle(i18n("New Image File Name"));
imageFileSaveDialog->setDirectoryUrl(initialUrl.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash));
imageFileSaveDialog->setAcceptMode(QFileDialog::AcceptSave);
imageFileSaveDialog->setFileMode(QFileDialog::AnyFile);
imageFileSaveDialog->setNameFilters(list);
// restore old settings for the dialog
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(configGroupName());
const QString optionLastExtension = QLatin1String("LastSavedImageExtension");
QString ext = group.readEntry(optionLastExtension, "png");
foreach(const QString& s, list)
{
if (s.contains(QString::fromLatin1("*.%1").arg(ext)))
{
imageFileSaveDialog->selectNameFilter(s);
break;
}
}
// adjust extension of proposed filename
QString fileName = initialUrl.fileName();
if (!fileName.isNull())
{
int lastDot = fileName.lastIndexOf(QLatin1Char('.'));
QString completeBaseName = (lastDot == -1) ? fileName : fileName.left(lastDot);
fileName = completeBaseName + QLatin1Char('.') + ext;
}
if (!fileName.isNull())
{
imageFileSaveDialog->selectFile(fileName);
}
// Start dialog and check if canceled.
int result;
if (d->currentWindowModalDialog)
{
// go application-modal - we will create utter confusion if descending into more than one window-modal dialog
imageFileSaveDialog->setModal(true);
result = imageFileSaveDialog->exec();
}
else
{
imageFileSaveDialog->setWindowModality(Qt::WindowModal);
d->currentWindowModalDialog = imageFileSaveDialog;
result = imageFileSaveDialog->exec();
d->currentWindowModalDialog = 0;
}
if (result != QDialog::Accepted || !imageFileSaveDialog)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "File Save Dialog rejected";
return false;
}
QList<QUrl> urls = imageFileSaveDialog->selectedUrls();
if (urls.isEmpty())
{
qCDebug(DIGIKAM_GENERAL_LOG) << "no target url";
return false;
}
newURL = urls.first();
newURL.setPath(QDir::cleanPath(newURL.path()));
QFileInfo fi(newURL.fileName());
if (fi.suffix().isEmpty())
{
ext = imageFileSaveDialog->selectedNameFilter().section(QLatin1String("*."), 1, 1);
ext = ext.left(ext.length() - 1);
if (ext.isEmpty())
{
ext = QLatin1String("jpg");
}
newURL.setPath(newURL.path() + QLatin1Char('.') + ext);
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Writing file to " << newURL;
//-- Show Settings Dialog ----------------------------------------------
const QString configShowImageSettingsDialog = QLatin1String("ShowImageSettingsDialog");
bool showDialog = group.readEntry(configShowImageSettingsDialog, true);
FileSaveOptionsBox* const options = new FileSaveOptionsBox();
if (showDialog && options->discoverFormat(newURL.fileName(), DImg::NONE) != DImg::NONE)
{
FileSaveOptionsDlg* const fileSaveOptionsDialog = new FileSaveOptionsDlg(this, options);
options->setImageFileFormat(newURL.fileName());
if (d->currentWindowModalDialog)
{
// go application-modal - we will create utter confusion if descending into more than one window-modal dialog
fileSaveOptionsDialog->setModal(true);
result = fileSaveOptionsDialog->exec();
}
else
{
fileSaveOptionsDialog->setWindowModality(Qt::WindowModal);
d->currentWindowModalDialog = fileSaveOptionsDialog;
result = fileSaveOptionsDialog->exec();
d->currentWindowModalDialog = 0;
}
if (result != QDialog::Accepted || !fileSaveOptionsDialog)
{
return false;
}
}
// write settings to config
options->applySettings();
// read settings from config to local container
applyIOSettings();
// select the format to save the image with
m_savingContext.format = selectValidSavingFormat(newURL);
if (m_savingContext.format.isNull())
{
QMessageBox::critical(this, qApp->applicationName(),
i18n("Unable to determine the format to save the target image with."));
return false;
}
if (!newURL.isValid())
{
QMessageBox::critical(this, qApp->applicationName(),
i18n("Cannot Save: Found file path <b>%1</b> is invalid.", newURL.toDisplayString()));
qCWarning(DIGIKAM_GENERAL_LOG) << "target URL is not valid !";
return false;
}
group.writeEntry(optionLastExtension, m_savingContext.format);
config->sync();
return true;
}
QString EditorWindow::selectValidSavingFormat(const QUrl& targetUrl)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Trying to find a saving format from targetUrl = " << targetUrl;
// build a list of valid types
QString all;
supportedImageMimeTypes(QIODevice::WriteOnly, all);
qCDebug(DIGIKAM_GENERAL_LOG) << "Qt Offered types: " << all;
QStringList validTypes = all.split(QLatin1String("*."), QString::SkipEmptyParts);
validTypes.replaceInStrings(QLatin1String(" "), QString());
qCDebug(DIGIKAM_GENERAL_LOG) << "Writable formats: " << validTypes;
// determine the format to use the format provided in the filename
QString suffix;
if (targetUrl.isLocalFile())
{
// for local files QFileInfo can be used
QFileInfo fi(targetUrl.toLocalFile());
suffix = fi.suffix();
qCDebug(DIGIKAM_GENERAL_LOG) << "Possible format from local file: " << suffix;
}
else
{
// for remote files string manipulation is needed unfortunately
QString fileName = targetUrl.fileName();
const int periodLocation = fileName.lastIndexOf(QLatin1Char('.'));
if (periodLocation >= 0)
{
suffix = fileName.right(fileName.size() - periodLocation - 1);
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Possible format from remote file: " << suffix;
}
if (!suffix.isEmpty() && validTypes.contains(suffix, Qt::CaseInsensitive))
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Using format from target url " << suffix;
return suffix;
}
// another way to determine the format is to use the original file
{
QString originalFormat = QString::fromUtf8(QImageReader::imageFormat(m_savingContext.srcURL.toLocalFile()));
if (validTypes.contains(originalFormat, Qt::CaseInsensitive))
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Using format from original file: " << originalFormat;
return originalFormat;
}
}
qCDebug(DIGIKAM_GENERAL_LOG) << "No suitable format found";
return QString();
}
bool EditorWindow::startingSaveAs(const QUrl& url)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "startSavingAs called";
if (m_savingContext.savingState != SavingContext::SavingStateNone)
{
return false;
}
m_savingContext = SavingContext();
m_savingContext.srcURL = url;
QUrl suggested = m_savingContext.srcURL;
// Run dialog -------------------------------------------------------------------
QUrl newURL;
if (!showFileSaveDialog(suggested, newURL))
{
return false;
}
// if new and original URL are equal use save() ------------------------------
QUrl currURL(m_savingContext.srcURL);
currURL.setPath(QDir::cleanPath(currURL.path()));
newURL.setPath(QDir::cleanPath(newURL.path()));
if (currURL.matches(newURL, QUrl::None))
{
save();
return false;
}
// Check for overwrite ----------------------------------------------------------
QFileInfo fi(newURL.toLocalFile());
m_savingContext.destinationExisted = fi.exists();
if (m_savingContext.destinationExisted)
{
if (!checkOverwrite(newURL))
{
return false;
}
// There will be two message boxes if the file is not writable.
// This may be controversial, and it may be changed, but it was a deliberate decision.
if (!checkPermissions(newURL))
{
return false;
}
}
// Now do the actual saving -----------------------------------------------------
setupTempSaveFile(newURL);
m_savingContext.destinationURL = newURL;
m_savingContext.originalFormat = m_canvas->currentImageFileFormat();
m_savingContext.savingState = SavingContext::SavingStateSaveAs;
m_savingContext.executedOperation = SavingContext::SavingStateNone;
m_savingContext.abortingSaving = false;
// in any case, destructive (Save as) or non (Export), mark as New Version
m_canvas->interface()->setHistoryIsBranch(true);
m_canvas->interface()->saveAs(m_savingContext.saveTempFileName, m_IOFileSettings,
m_setExifOrientationTag && m_canvas->exifRotated(),
m_savingContext.format.toLower(),
m_savingContext.destinationURL.toLocalFile());
return true;
}
bool EditorWindow::startingSaveCurrentVersion(const QUrl& url)
{
return startingSaveVersion(url, false, false, QString());
}
bool EditorWindow::startingSaveNewVersion(const QUrl& url)
{
return startingSaveVersion(url, true, false, QString());
}
bool EditorWindow::startingSaveNewVersionAs(const QUrl& url)
{
return startingSaveVersion(url, true, true, QString());
}
bool EditorWindow::startingSaveNewVersionInFormat(const QUrl& url, const QString& format)
{
return startingSaveVersion(url, true, false, format);
}
VersionFileOperation EditorWindow::saveVersionFileOperation(const QUrl& url, bool fork)
{
DImageHistory resolvedHistory = m_canvas->interface()->getResolvedInitialHistory();
DImageHistory history = m_canvas->interface()->getImageHistory();
VersionFileInfo currentName(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(),
url.fileName(), m_canvas->currentImageFileFormat());
return versionManager()->operation(fork ? VersionManager::NewVersionName : VersionManager::CurrentVersionName,
currentName, resolvedHistory, history);
}
VersionFileOperation EditorWindow::saveAsVersionFileOperation(const QUrl& url, const QUrl& saveUrl, const QString& format)
{
DImageHistory resolvedHistory = m_canvas->interface()->getResolvedInitialHistory();
DImageHistory history = m_canvas->interface()->getImageHistory();
VersionFileInfo currentName(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(),
url.fileName(), m_canvas->currentImageFileFormat());
VersionFileInfo saveLocation(saveUrl.adjusted(QUrl::RemoveFilename).toLocalFile(),
saveUrl.fileName(), format);
return versionManager()->operationNewVersionAs(currentName, saveLocation, resolvedHistory, history);
}
VersionFileOperation EditorWindow::saveInFormatVersionFileOperation(const QUrl& url, const QString& format)
{
DImageHistory resolvedHistory = m_canvas->interface()->getResolvedInitialHistory();
DImageHistory history = m_canvas->interface()->getImageHistory();
VersionFileInfo currentName(url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile(),
url.fileName(), m_canvas->currentImageFileFormat());
return versionManager()->operationNewVersionInFormat(currentName, format, resolvedHistory, history);
}
bool EditorWindow::startingSaveVersion(const QUrl& url, bool fork, bool saveAs, const QString& format)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Saving image" << url << "non-destructive, new version:"
<< fork << ", saveAs:" << saveAs << "format:" << format;
if (m_savingContext.savingState != SavingContext::SavingStateNone)
{
return false;
}
m_savingContext = SavingContext();
m_savingContext.versionFileOperation = saveVersionFileOperation(url, fork);
m_canvas->interface()->setHistoryIsBranch(fork);
if (saveAs)
{
QUrl suggested = m_savingContext.versionFileOperation.saveFile.fileUrl();
QUrl selectedUrl;
if (!showFileSaveDialog(suggested, selectedUrl))
{
return false;
}
m_savingContext.versionFileOperation = saveAsVersionFileOperation(url, selectedUrl, m_savingContext.format);
}
else if (!format.isNull())
{
m_savingContext.versionFileOperation = saveInFormatVersionFileOperation(url, format);
}
const QUrl newURL = m_savingContext.versionFileOperation.saveFile.fileUrl();
qCDebug(DIGIKAM_GENERAL_LOG) << "Writing file to " << newURL;
if (!newURL.isValid())
{
QMessageBox::critical(this, qApp->applicationName(),
i18nc("@info",
"Cannot save file <b>%1</b> to "
"the suggested version file name <b>%2</b>",
url.fileName(),
newURL.fileName()));
qCWarning(DIGIKAM_GENERAL_LOG) << "target URL is not valid !";
return false;
}
QFileInfo fi(newURL.toLocalFile());
m_savingContext.destinationExisted = fi.exists();
// Check for overwrite (saveAs only) --------------------------------------------
if (m_savingContext.destinationExisted)
{
// So, should we refuse to overwrite the original?
- // It's a frontal crash againt non-destructive principles.
+ // It's a frontal crash against non-destructive principles.
// It is tempting to refuse, yet I think the user has to decide in the end
/*QUrl currURL(m_savingContext.srcURL);
currURL.cleanPath();
newURL.cleanPath();
if (currURL.equals(newURL))
{
...
return false;
}*/
// check for overwrite, unless the operation explicitly tells us to overwrite
if (!(m_savingContext.versionFileOperation.tasks & VersionFileOperation::Replace) &&
!checkOverwrite(newURL))
{
return false;
}
// There will be two message boxes if the file is not writable.
// This may be controversial, and it may be changed, but it was a deliberate decision.
if (!checkPermissions(newURL))
{
return false;
}
}
setupTempSaveFile(newURL);
m_savingContext.srcURL = url;
m_savingContext.destinationURL = newURL;
m_savingContext.originalFormat = m_canvas->currentImageFileFormat();
m_savingContext.format = m_savingContext.versionFileOperation.saveFile.format;
m_savingContext.abortingSaving = false;
m_savingContext.savingState = SavingContext::SavingStateVersion;
m_savingContext.executedOperation = SavingContext::SavingStateNone;
m_canvas->interface()->saveAs(m_savingContext.saveTempFileName, m_IOFileSettings,
m_setExifOrientationTag && m_canvas->exifRotated(),
m_savingContext.format.toLower(),
m_savingContext.versionFileOperation);
return true;
}
bool EditorWindow::checkPermissions(const QUrl& url)
{
//TODO: Check that the permissions can actually be changed
// if write permissions are not available.
QFileInfo fi(url.toLocalFile());
if (fi.exists() && !fi.isWritable())
{
int result = QMessageBox::warning(this, i18n("Overwrite File?"),
i18n("You do not have write permissions "
"for the file named \"%1\". "
"Are you sure you want "
"to overwrite it?",
url.fileName()),
QMessageBox::Save | QMessageBox::Cancel);
if (result != QMessageBox::Save)
{
return false;
}
}
return true;
}
bool EditorWindow::checkOverwrite(const QUrl& url)
{
int result = QMessageBox::warning(this, i18n("Overwrite File?"),
i18n("A file named \"%1\" already "
"exists. Are you sure you want "
"to overwrite it?",
url.fileName()),
QMessageBox::Save | QMessageBox::Cancel);
return (result == QMessageBox::Save);
}
bool EditorWindow::moveLocalFile(const QString& org, const QString& dst)
{
QString sidecarOrg = DMetadata::sidecarFilePathForFile(org);
QString source = m_savingContext.srcURL.toLocalFile();
if (QFileInfo(sidecarOrg).exists())
{
QString sidecarDst = DMetadata::sidecarFilePathForFile(dst);
if (!DFileOperations::localFileRename(source, sidecarOrg, sidecarDst))
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to move sidecar file";
}
}
if (!DFileOperations::localFileRename(source, org, dst))
{
QMessageBox::critical(this, i18n("Error Saving File"),
i18n("Failed to overwrite original file"));
return false;
}
return true;
}
void EditorWindow::moveFile()
{
// Move local file.
if (m_savingContext.executedOperation == SavingContext::SavingStateVersion)
{
// check if we need to move the current file to an intermediate name
if (m_savingContext.versionFileOperation.tasks & VersionFileOperation::MoveToIntermediate)
{
//qCDebug(DIGIKAM_GENERAL_LOG) << "MoveToIntermediate: Moving " << m_savingContext.srcURL.toLocalFile() << "to"
// << m_savingContext.versionFileOperation.intermediateForLoadedFile.filePath()
moveLocalFile(m_savingContext.srcURL.toLocalFile(),
m_savingContext.versionFileOperation.intermediateForLoadedFile.filePath());
LoadingCacheInterface::fileChanged(m_savingContext.destinationURL.toLocalFile());
ThumbnailLoadThread::deleteThumbnail(m_savingContext.destinationURL.toLocalFile());
}
}
bool moveSuccessful = moveLocalFile(m_savingContext.saveTempFileName,
m_savingContext.destinationURL.toLocalFile());
if (m_savingContext.executedOperation == SavingContext::SavingStateVersion)
{
if (moveSuccessful &&
m_savingContext.versionFileOperation.tasks & VersionFileOperation::SaveAndDelete)
{
QFile file(m_savingContext.versionFileOperation.loadedFile.filePath());
file.remove();
}
}
movingSaveFileFinished(moveSuccessful);
}
void EditorWindow::slotDiscardChanges()
{
m_canvas->interface()->rollbackToOrigin();
}
void EditorWindow::slotOpenOriginal()
{
// no-op in this base class
}
void EditorWindow::slotColorManagementOptionsChanged()
{
applyColorManagementSettings();
applyIOSettings();
}
void EditorWindow::slotToggleColorManagedView()
{
if (!IccSettings::instance()->isEnabled())
{
return;
}
bool cmv = !IccSettings::instance()->settings().useManagedView;
IccSettings::instance()->setUseManagedView(cmv);
}
void EditorWindow::setColorManagedViewIndicatorToolTip(bool available, bool cmv)
{
QString tooltip;
if (available)
{
if (cmv)
{
tooltip = i18n("Color-Managed View is enabled.");
}
else
{
tooltip = i18n("Color-Managed View is disabled.");
}
}
else
{
tooltip = i18n("Color Management is not configured, so the Color-Managed View is not available.");
}
d->cmViewIndicator->setToolTip(tooltip);
}
void EditorWindow::slotSoftProofingOptions()
{
// Adjusts global settings
QPointer<SoftProofDialog> dlg = new SoftProofDialog(this);
dlg->exec();
d->viewSoftProofAction->setChecked(dlg->shallEnableSoftProofView());
slotUpdateSoftProofingState();
delete dlg;
}
void EditorWindow::slotUpdateSoftProofingState()
{
bool on = d->viewSoftProofAction->isChecked();
m_canvas->setSoftProofingEnabled(on);
d->toolIface->updateICCSettings();
}
void EditorWindow::slotSetUnderExposureIndicator(bool on)
{
d->exposureSettings->underExposureIndicator = on;
d->toolIface->updateExposureSettings();
d->viewUnderExpoAction->setChecked(on);
setUnderExposureToolTip(on);
}
void EditorWindow::setUnderExposureToolTip(bool on)
{
d->underExposureIndicator->setToolTip(
on ? i18n("Under-Exposure indicator is enabled")
: i18n("Under-Exposure indicator is disabled"));
}
void EditorWindow::slotSetOverExposureIndicator(bool on)
{
d->exposureSettings->overExposureIndicator = on;
d->toolIface->updateExposureSettings();
d->viewOverExpoAction->setChecked(on);
setOverExposureToolTip(on);
}
void EditorWindow::setOverExposureToolTip(bool on)
{
d->overExposureIndicator->setToolTip(
on ? i18n("Over-Exposure indicator is enabled")
: i18n("Over-Exposure indicator is disabled"));
}
void EditorWindow::slotToggleSlideShow()
{
SlideShowSettings settings;
settings.readFromConfig();
slideShow(settings);
}
void EditorWindow::slotSelectionChanged(const QRect& sel)
{
slotSelectionSetText(sel);
emit signalSelectionChanged(sel);
}
void EditorWindow::slotSelectionSetText(const QRect& sel)
{
setToolInfoMessage(QString::fromLatin1("(%1, %2) (%3 x %4)").arg(sel.x()).arg(sel.y()).arg(sel.width()).arg(sel.height()));
}
void EditorWindow::slotComponentsInfo()
{
LibsInfoDlg* const dlg = new LibsInfoDlg(this);
dlg->show();
}
void EditorWindow::setToolStartProgress(const QString& toolName)
{
m_animLogo->start();
m_nameLabel->setProgressValue(0);
m_nameLabel->setProgressBarMode(StatusProgressBar::CancelProgressBarMode, QString::fromUtf8("%1:").arg(toolName));
}
void EditorWindow::setToolProgress(int progress)
{
m_nameLabel->setProgressValue(progress);
}
void EditorWindow::setToolStopProgress()
{
m_animLogo->stop();
m_nameLabel->setProgressValue(0);
m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode);
slotUpdateItemInfo();
}
void EditorWindow::slotCloseTool()
{
if (d->toolIface)
{
d->toolIface->slotCloseTool();
}
}
void EditorWindow::slotApplyTool()
{
if (d->toolIface)
{
d->toolIface->slotApplyTool();
}
}
void EditorWindow::setPreviewModeMask(int mask)
{
d->previewToolBar->setPreviewModeMask(mask);
}
PreviewToolBar::PreviewMode EditorWindow::previewMode() const
{
return d->previewToolBar->previewMode();
}
void EditorWindow::setToolInfoMessage(const QString& txt)
{
d->infoLabel->setAdjustedText(txt);
}
VersionManager* EditorWindow::versionManager() const
{
return &d->defaultVersionManager;
}
void EditorWindow::setupSelectToolsAction()
{
// Create action model
ActionItemModel* const actionModel = new ActionItemModel(this);
actionModel->setMode(ActionItemModel::ToplevelMenuCategory | ActionItemModel::SortCategoriesByInsertionOrder);
// Builtin actions
QString transformCategory = i18nc("@title Image Transform", "Transform");
actionModel->addAction(d->rotateLeftAction, transformCategory);
actionModel->addAction(d->rotateRightAction, transformCategory);
actionModel->addAction(d->flipHorizAction, transformCategory);
actionModel->addAction(d->flipVertAction, transformCategory);
actionModel->addAction(d->cropAction, transformCategory);
actionModel->addAction(d->autoCropAction, transformCategory);
actionModel->addAction(d->aspectRatioCropAction, transformCategory);
actionModel->addAction(d->resizeAction, transformCategory);
actionModel->addAction(d->sheartoolAction, transformCategory);
actionModel->addAction(d->freerotationAction, transformCategory);
actionModel->addAction(d->perspectiveAction, transformCategory);
#ifdef HAVE_LIBLQR_1
actionModel->addAction(d->contentAwareResizingAction, transformCategory);
#endif
QString decorateCategory = i18nc("@title Image Decorate", "Decorate");
actionModel->addAction(d->textureAction, decorateCategory);
actionModel->addAction(d->borderAction, decorateCategory);
actionModel->addAction(d->insertTextAction, decorateCategory);
QString effectsCategory = i18nc("@title Image Effect", "Effects");
actionModel->addAction(d->filmgrainAction, effectsCategory);
actionModel->addAction(d->raindropAction, effectsCategory);
actionModel->addAction(d->distortionfxAction, effectsCategory);
actionModel->addAction(d->blurfxAction, effectsCategory);
actionModel->addAction(d->oilpaintAction, effectsCategory);
actionModel->addAction(d->embossAction, effectsCategory);
actionModel->addAction(d->charcoalAction, effectsCategory);
actionModel->addAction(d->colorEffectsAction, effectsCategory);
QString colorsCategory = i18nc("@title Image Colors", "Colors");
actionModel->addAction(d->convertTo8Bits, colorsCategory);
actionModel->addAction(d->convertTo16Bits, colorsCategory);
actionModel->addAction(d->invertAction, colorsCategory);
actionModel->addAction(d->BCGAction, colorsCategory);
actionModel->addAction(d->CBAction, colorsCategory);
actionModel->addAction(d->autoCorrectionAction, colorsCategory);
actionModel->addAction(d->BWAction, colorsCategory);
actionModel->addAction(d->HSLAction, colorsCategory);
actionModel->addAction(d->whitebalanceAction, colorsCategory);
actionModel->addAction(d->channelMixerAction, colorsCategory);
actionModel->addAction(d->curvesAction, colorsCategory);
actionModel->addAction(d->levelsAction, colorsCategory);
actionModel->addAction(d->filmAction, colorsCategory);
actionModel->addAction(d->colorSpaceConverter, colorsCategory);
QString enhanceCategory = i18nc("@title Image Enhance", "Enhance");
actionModel->addAction(d->restorationAction, enhanceCategory);
actionModel->addAction(d->blurAction, enhanceCategory);
//actionModel->addAction(d->healCloneAction, enhanceCategory);
actionModel->addAction(d->sharpenAction, enhanceCategory);
actionModel->addAction(d->noiseReductionAction, enhanceCategory);
actionModel->addAction(d->localContrastAction, enhanceCategory);
actionModel->addAction(d->redeyeAction, enhanceCategory);
actionModel->addAction(d->lensdistortionAction, enhanceCategory);
actionModel->addAction(d->antivignettingAction, enhanceCategory);
actionModel->addAction(d->hotpixelsAction, enhanceCategory);
#ifdef HAVE_LENSFUN
actionModel->addAction(d->lensAutoFixAction, enhanceCategory);
#endif
QString postCategory = i18nc("@title Post Processing Tools", "Post-Processing");
actionModel->addAction(m_calendarAction, postCategory);
actionModel->addAction(m_metadataEditAction, postCategory);
actionModel->addAction(m_timeAdjustAction, postCategory);
actionModel->addAction(m_presentationAction, postCategory);
actionModel->addAction(m_expoBlendingAction, postCategory);
actionModel->addAction(m_sendByMailAction, postCategory);
actionModel->addAction(m_printCreatorAction, postCategory);
actionModel->addAction(m_mediaServerAction, postCategory);
#ifdef HAVE_HTMLGALLERY
actionModel->addAction(m_htmlGalleryAction, postCategory);
#endif
#ifdef HAVE_PANORAMA
actionModel->addAction(m_panoramaAction, postCategory);
#endif
#ifdef HAVE_MEDIAPLAYER
actionModel->addAction(m_videoslideshowAction, postCategory);
#endif
#ifdef HAVE_MARBLE
actionModel->addAction(m_geolocationEditAction, postCategory);
#endif
QString exportCategory = i18nc("@title Export Tools", "Export");
foreach(QAction* const ac, exportActions())
{
actionModel->addAction(ac, exportCategory);
}
QString importCategory = i18nc("@title Import Tools", "Import");
foreach(QAction* const ac, importActions())
{
actionModel->addAction(ac, importCategory);
}
// setup categorized view
DCategorizedSortFilterProxyModel* const filterModel = actionModel->createFilterModel();
ActionCategorizedView* const selectToolsActionView = new ActionCategorizedView;
selectToolsActionView->setupIconMode();
selectToolsActionView->setModel(filterModel);
selectToolsActionView->adjustGridSize();
connect(selectToolsActionView, SIGNAL(clicked(QModelIndex)),
actionModel, SLOT(trigger(QModelIndex)));
EditorToolIface::editorToolIface()->setToolsIconView(selectToolsActionView);
}
void EditorWindow::slotThemeChanged()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(configGroupName());
if (!group.readEntry(d->configUseThemeBackgroundColorEntry, true))
{
m_bgColor = group.readEntry(d->configBackgroundColorEntry, QColor(Qt::black));
}
else
{
m_bgColor = palette().color(QPalette::Base);
}
m_canvas->setBackgroundBrush(QBrush(m_bgColor));
d->toolIface->themeChanged();
}
void EditorWindow::addAction2ContextMenu(const QString& actionName, bool addDisabled)
{
if (!m_contextMenu)
{
return;
}
QAction* const action = actionCollection()->action(actionName);
if (action && (action->isEnabled() || addDisabled))
{
m_contextMenu->addAction(action);
}
}
void EditorWindow::showSideBars(bool visible)
{
if (visible)
{
rightSideBar()->restore(QList<QWidget*>() << thumbBar(), d->fullscreenSizeBackup);
}
else
{
// See bug #166472, a simple backup()/restore() will hide non-sidebar splitter child widgets
// in horizontal mode thumbbar wont be member of the splitter, it is just ignored then
rightSideBar()->backup(QList<QWidget*>() << thumbBar(), &d->fullscreenSizeBackup);
}
}
void EditorWindow::slotToggleRightSideBar()
{
rightSideBar()->isExpanded() ? rightSideBar()->shrink()
: rightSideBar()->expand();
}
void EditorWindow::slotPreviousRightSideBarTab()
{
rightSideBar()->activePreviousTab();
}
void EditorWindow::slotNextRightSideBarTab()
{
rightSideBar()->activeNextTab();
}
void EditorWindow::showThumbBar(bool visible)
{
visible ? thumbBar()->restoreVisibility()
: thumbBar()->hide();
}
bool EditorWindow::thumbbarVisibility() const
{
return thumbBar()->isVisible();
}
void EditorWindow::customizedFullScreenMode(bool set)
{
set ? m_canvas->setBackgroundBrush(QBrush(Qt::black))
: m_canvas->setBackgroundBrush(QBrush(m_bgColor));
showStatusBarAction()->setEnabled(!set);
toolBarMenuAction()->setEnabled(!set);
showMenuBarAction()->setEnabled(!set);
m_showBarAction->setEnabled(!set);
}
void EditorWindow::addServicesMenuForUrl(const QUrl& url)
{
KService::List offers = DFileOperations::servicesForOpenWith(QList<QUrl>() << url);
qCDebug(DIGIKAM_GENERAL_LOG) << offers.count() << " services found to open " << url;
if (m_servicesMenu)
{
delete m_servicesMenu;
m_servicesMenu = 0;
}
if (m_serviceAction)
{
delete m_serviceAction;
m_serviceAction = 0;
}
if (!offers.isEmpty())
{
m_servicesMenu = new QMenu(this);
QAction* const serviceAction = m_servicesMenu->menuAction();
serviceAction->setText(i18n("Open With"));
foreach(const KService::Ptr& service, offers)
{
QString name = service->name().replace(QLatin1Char('&'), QLatin1String("&&"));
QAction* const action = m_servicesMenu->addAction(name);
action->setIcon(QIcon::fromTheme(service->icon()));
action->setData(service->name());
d->servicesMap[name] = service;
}
#ifdef HAVE_KIO
m_servicesMenu->addSeparator();
m_servicesMenu->addAction(i18n("Other..."));
m_contextMenu->addAction(serviceAction);
connect(m_servicesMenu, SIGNAL(triggered(QAction*)),
this, SLOT(slotOpenWith(QAction*)));
}
else
{
m_serviceAction = new QAction(i18n("Open With..."), this);
m_contextMenu->addAction(m_serviceAction);
connect(m_servicesMenu, SIGNAL(triggered()),
this, SLOT(slotOpenWith()));
#endif // HAVE_KIO
}
}
void EditorWindow::openWith(const QUrl& url, QAction* action)
{
KService::Ptr service;
QString name = action ? action->data().toString() : QString();
#ifdef HAVE_KIO
if (name.isEmpty())
{
QPointer<KOpenWithDialog> dlg = new KOpenWithDialog(QList<QUrl>() << url);
if (dlg->exec() != KOpenWithDialog::Accepted)
{
delete dlg;
return;
}
service = dlg->service();
if (!service)
{
// User entered a custom command
if (!dlg->text().isEmpty())
{
DFileOperations::runFiles(dlg->text(), QList<QUrl>() << url);
}
delete dlg;
return;
}
delete dlg;
}
else
#endif // HAVE_KIO
{
service = d->servicesMap[name];
}
DFileOperations::runFiles(service.data(), QList<QUrl>() << url);
}
void EditorWindow::loadTool(EditorTool* const tool)
{
EditorToolIface::editorToolIface()->loadTool(tool);
connect(tool, SIGNAL(okClicked()),
this, SLOT(slotToolDone()));
connect(tool, SIGNAL(cancelClicked()),
this, SLOT(slotToolDone()));
}
void EditorWindow::slotToolDone()
{
EditorToolIface::editorToolIface()->unLoadTool();
}
void EditorWindow::slotInsertText()
{
loadTool(new InsertTextTool(this));
}
void EditorWindow::slotBorder()
{
loadTool(new BorderTool(this));
}
void EditorWindow::slotTexture()
{
loadTool(new TextureTool(this));
}
void EditorWindow::slotColorEffects()
{
loadTool(new ColorFxTool(this));
}
void EditorWindow::slotCharcoal()
{
loadTool(new CharcoalTool(this));
}
void EditorWindow::slotEmboss()
{
loadTool(new EmbossTool(this));
}
void EditorWindow::slotOilPaint()
{
loadTool(new OilPaintTool(this));
}
void EditorWindow::slotBlurFX()
{
loadTool(new BlurFXTool(this));
}
void EditorWindow::slotDistortionFX()
{
loadTool(new DistortionFXTool(this));
}
void EditorWindow::slotRainDrop()
{
loadTool(new RainDropTool(this));
}
void EditorWindow::slotFilmGrain()
{
loadTool(new FilmGrainTool(this));
}
void EditorWindow::slotInvert()
{
qApp->setOverrideCursor(Qt::WaitCursor);
ImageIface iface;
InvertFilter invert(iface.original(), 0L);
invert.startFilterDirectly();
iface.setOriginal(i18n("Invert"), invert.filterAction(), invert.getTargetImage());
qApp->restoreOverrideCursor();
}
void EditorWindow::slotConvertTo8Bits()
{
ImageIface iface;
if (!iface.originalSixteenBit())
{
QMessageBox::critical(qApp->activeWindow(),
qApp->applicationName(),
i18n("This image is already using a depth of 8 bits / color / pixel."));
return;
}
else
{
if (DMessageBox::showContinueCancel(QMessageBox::Warning,
qApp->activeWindow(),
qApp->applicationName(),
i18n("Performing this operation will reduce image color quality. "
"Do you want to continue?"),
QLatin1String("ToolColor16To8Bits"))
== QMessageBox::Cancel)
{
return;
}
}
qApp->setOverrideCursor(Qt::WaitCursor);
iface.convertOriginalColorDepth(32);
qApp->restoreOverrideCursor();
}
void EditorWindow::slotConvertTo16Bits()
{
ImageIface iface;
if (iface.originalSixteenBit())
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("This image is already using a depth of 16 bits / color / pixel."));
return;
}
qApp->setOverrideCursor(Qt::WaitCursor);
iface.convertOriginalColorDepth(64);
qApp->restoreOverrideCursor();
}
void EditorWindow::slotConvertToColorSpace(const IccProfile& profile)
{
ImageIface iface;
if (iface.originalIccProfile().isNull())
{
QMessageBox::critical(qApp->activeWindow(), qApp->applicationName(),
i18n("This image is not color managed."));
return;
}
qApp->setOverrideCursor(Qt::WaitCursor);
ProfileConversionTool::fastConversion(profile);
qApp->restoreOverrideCursor();
}
void EditorWindow::slotUpdateColorSpaceMenu()
{
d->profileMenuAction->clear();
if (!IccSettings::instance()->isEnabled())
{
QAction* const action = new QAction(i18n("Color Management is disabled..."), this);
d->profileMenuAction->addAction(action);
connect(action, SIGNAL(triggered()),
this, SLOT(slotSetupICC()));
}
else
{
ICCSettingsContainer settings = IccSettings::instance()->settings();
QList<IccProfile> standardProfiles, favoriteProfiles;
QSet<QString> standardProfilePaths, favoriteProfilePaths;
standardProfiles << IccProfile::sRGB()
<< IccProfile::adobeRGB()
<< IccProfile::wideGamutRGB()
<< IccProfile::proPhotoRGB();
foreach(IccProfile profile, standardProfiles) // krazy:exclude=foreach
{
d->profileMenuAction->addProfile(profile, profile.description());
standardProfilePaths << profile.filePath();
}
d->profileMenuAction->addSeparator();
favoriteProfilePaths = QSet<QString>::fromList(ProfileConversionTool::favoriteProfiles());
favoriteProfilePaths -= standardProfilePaths;
foreach(const QString& path, favoriteProfilePaths)
{
favoriteProfiles << IccProfile(path);
}
d->profileMenuAction->addProfiles(favoriteProfiles);
}
d->profileMenuAction->addSeparator();
d->profileMenuAction->addAction(d->colorSpaceConverter);
d->colorSpaceConverter->setEnabled(m_actionEnabledState && IccSettings::instance()->isEnabled());
}
void EditorWindow::slotProfileConversionTool()
{
ProfileConversionTool* const tool = new ProfileConversionTool(this);
connect(tool, SIGNAL(okClicked()),
this, SLOT(slotUpdateColorSpaceMenu()));
loadTool(tool);
}
void EditorWindow::slotBW()
{
loadTool(new BWSepiaTool(this));
}
void EditorWindow::slotHSL()
{
loadTool(new HSLTool(this));
}
void EditorWindow::slotWhiteBalance()
{
loadTool(new WhiteBalanceTool(this));
}
void EditorWindow::slotChannelMixer()
{
loadTool(new ChannelMixerTool(this));
}
void EditorWindow::slotCurvesAdjust()
{
loadTool(new AdjustCurvesTool(this));
}
void EditorWindow::slotLevelsAdjust()
{
loadTool(new AdjustLevelsTool(this));
}
void EditorWindow::slotFilm()
{
loadTool(new FilmTool(this));
}
void EditorWindow::slotBCG()
{
loadTool(new BCGTool(this));
}
void EditorWindow::slotCB()
{
loadTool(new CBTool(this));
}
void EditorWindow::slotAutoCorrection()
{
loadTool(new AutoCorrectionTool(this));
}
void EditorWindow::slotHotPixels()
{
loadTool(new HotPixelsTool(this));
}
void EditorWindow::slotLensDistortion()
{
loadTool(new LensDistortionTool(this));
}
void EditorWindow::slotRestoration()
{
loadTool(new RestorationTool(this));
}
void EditorWindow::slotBlur()
{
loadTool(new BlurTool(this));
}
void EditorWindow::slotHealingClone()
{
loadTool(new HealingCloneTool(this));
}
void EditorWindow::slotSharpen()
{
loadTool(new SharpenTool(this));
}
void EditorWindow::slotNoiseReduction()
{
loadTool(new NoiseReductionTool(this));
}
void EditorWindow::slotLocalContrast()
{
loadTool(new LocalContrastTool(this));
}
void EditorWindow::slotRedEye()
{
loadTool(new RedEyeTool(this));
}
void EditorWindow::slotLensAutoFix()
{
#ifdef HAVE_LENSFUN
loadTool(new LensAutoFixTool(this));
#endif
}
void EditorWindow::slotAntiVignetting()
{
loadTool(new AntiVignettingTool(this));
}
void EditorWindow::slotPerspective()
{
loadTool(new PerspectiveTool(this));
}
void EditorWindow::slotShearTool()
{
loadTool(new ShearTool(this));
}
void EditorWindow::slotResize()
{
loadTool(new ResizeTool(this));
}
void EditorWindow::slotRatioCrop()
{
loadTool(new RatioCropTool(this));
}
void EditorWindow::slotContentAwareResizing()
{
#ifdef HAVE_LIBLQR_1
loadTool(new ContentAwareResizeTool(this));
#endif
}
void EditorWindow::slotFreeRotation()
{
FreeRotationTool* const tool = new FreeRotationTool(this);
connect(this, SIGNAL(signalPoint1Action()),
tool, SLOT(slotAutoAdjustP1Clicked()));
connect(this, SIGNAL(signalPoint2Action()),
tool, SLOT(slotAutoAdjustP2Clicked()));
connect(this, SIGNAL(signalAutoAdjustAction()),
tool, SLOT(slotAutoAdjustClicked()));
loadTool(tool);
}
void EditorWindow::slotRotateLeftIntoQue()
{
m_transformQue.append(TransformType::RotateLeft);
}
void EditorWindow::slotRotateRightIntoQue()
{
m_transformQue.append(TransformType::RotateRight);
}
void EditorWindow::slotFlipHIntoQue()
{
m_transformQue.append(TransformType::FlipHorizontal);
}
void EditorWindow::slotFlipVIntoQue()
{
m_transformQue.append(TransformType::FlipVertical);
}
} // namespace Digikam
diff --git a/core/utilities/imageeditor/tools/decorate/inserttextwidget.cpp b/core/utilities/imageeditor/tools/decorate/inserttextwidget.cpp
index 9718fd9f14..6ed3d532d9 100644
--- a/core/utilities/imageeditor/tools/decorate/inserttextwidget.cpp
+++ b/core/utilities/imageeditor/tools/decorate/inserttextwidget.cpp
@@ -1,750 +1,750 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2005-02-14
* Description : a widget to insert a text over an image.
*
* Copyright (C) 2005-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "inserttextwidget.h"
// Qt includes
#include <QFont>
#include <QFontMetrics>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QPixmap>
#include <QResizeEvent>
// Local includes
#include "imageiface.h"
namespace Digikam
{
class Q_DECL_HIDDEN InsertTextWidget::Private
{
public:
explicit Private()
: currentMoving(false),
textBorder(false),
textTransparent(false),
alignMode(0),
textOpacity(0),
h(0),
textRotation(0),
transparency(0),
w(0),
xpos(0),
ypos(0),
pixmap(0),
iface(0)
{
}
bool currentMoving;
bool textBorder;
bool textTransparent;
int alignMode;
int textOpacity;
int h;
int textRotation;
int transparency;
int w;
int xpos;
int ypos;
QColor backgroundColor; // For text
QColor bgColor; // For Pixmap
QColor textColor;
QFont textFont;
QPixmap* pixmap;
QRect positionHint;
QRect rect;
QRect textRect;
QString textString;
ImageIface* iface;
};
InsertTextWidget::InsertTextWidget(int w, int h, QWidget* const parent)
: QWidget(parent),
d(new Private)
{
d->currentMoving = false;
d->bgColor = palette().color(QPalette::Background);
d->backgroundColor = QColor(0xCC, 0xCC, 0xCC);
d->transparency = 210;
d->iface = new ImageIface(QSize(w, h));
d->w = d->iface->previewSize().width();
d->h = d->iface->previewSize().height();
d->pixmap = new QPixmap(w, h);
d->pixmap->fill(d->bgColor);
setMinimumSize(w, h);
setMouseTracking(true);
setAttribute(Qt::WA_DeleteOnClose);
d->rect = QRect(width()/2-d->w/2, height()/2-d->h/2, d->w, d->h);
d->textRect = QRect();
}
InsertTextWidget::~InsertTextWidget()
{
delete d->iface;
delete d->pixmap;
delete d;
}
ImageIface* InsertTextWidget::imageIface() const
{
return d->iface;
}
void InsertTextWidget::resetEdit()
{
// signal this needs to be filled by makePixmap
d->textRect = QRect();
makePixmap();
repaint();
}
void InsertTextWidget::setText(const QString& text, const QFont& font, const QColor& color, int opacity,
int alignMode, bool border, bool transparent, int rotation)
{
d->textString = text;
d->textColor = color;
d->textOpacity = opacity;
d->textBorder = border;
d->textTransparent = transparent;
d->textRotation = rotation;
switch (alignMode)
{
case ALIGN_LEFT:
d->alignMode = Qt::AlignLeft;
break;
case ALIGN_RIGHT:
d->alignMode = Qt::AlignRight;
break;
case ALIGN_CENTER:
d->alignMode = Qt::AlignHCenter;
break;
case ALIGN_BLOCK:
d->alignMode = Qt::AlignJustify;
break;
}
// Center text if top left corner text area is not visible.
/*
if ( d->textFont.pointSize() != font.pointSize() &&
!rect().contains( d->textRect.x(), d->textRect.y() ) )
{
d->textFont = font;
resetEdit();
return;
}
*/
d->textFont = font;
makePixmap();
repaint();
}
void InsertTextWidget::setBackgroundColor(const QColor& bg)
{
d->bgColor = bg;
makePixmap();
repaint();
}
void InsertTextWidget::setPositionHint(const QRect& hint)
{
// interpreted by composeImage
d->positionHint = hint;
if (d->textRect.isValid())
{
// invalidate current position so that hint is certainly interpreted
d->textRect = QRect();
makePixmap();
repaint();
}
}
QRect InsertTextWidget::getPositionHint() const
{
QRect hint;
if (d->textRect.isValid())
{
// We normalize on the size of the image, but we store as int. Precision loss is no problem.
hint.setX( (int) ((float)(d->textRect.x() - d->rect.x()) / (float)d->rect.width() * 10000.0) );
hint.setY( (int) ((float)(d->textRect.y() - d->rect.y()) / (float)d->rect.height() * 10000.0) );
hint.setWidth( (int) ((float)d->textRect.width() / (float)d->rect.width() * 10000.0) );
hint.setHeight( (int) ((float)d->textRect.height() / (float)d->rect.height() * 10000.0) );
}
return hint;
}
DImg InsertTextWidget::makeInsertText()
{
int orgW = d->iface->originalSize().width();
int orgH = d->iface->originalSize().height();
float ratioW = (float)orgW/(float)d->w;
float ratioH = (float)orgH/(float)d->h;
int x, y;
if (d->textRect.isValid())
{
// convert from widget to image coordinates, then to original size
x = qRound((d->textRect.x() - d->rect.x()) * ratioW);
y = qRound((d->textRect.y() - d->rect.y()) * ratioH);
}
else
{
x = -1;
y = -1;
}
// Get original image
DImg image = d->iface->original()->copy();
int borderWidth = qMax(1, qRound(ratioW));
// compose and draw result on image
composeImage(&image, 0, x, y,
d->textFont, d->textFont.pointSizeF(),
d->textRotation, d->textColor, d->textOpacity,
d->alignMode, d->textString, d->textTransparent, d->backgroundColor,
d->textBorder ? BORDER_NORMAL : BORDER_NONE, borderWidth, borderWidth);
return image;
}
void InsertTextWidget::makePixmap()
{
int orgW = d->iface->originalSize().width();
int orgH = d->iface->originalSize().height();
float ratioW = (float)d->w / (float)orgW;
float ratioH = (float)d->h / (float)orgH;
int x, y;
if (d->textRect.isValid())
{
// convert from widget to image coordinates
x = d->textRect.x() - d->rect.x();
y = d->textRect.y() - d->rect.y();
}
else
{
x = -1;
y = -1;
}
// get preview image data
DImg image = d->iface->preview();
image.setIccProfile( d->iface->original()->getIccProfile() );
// paint pixmap for drawing this widget
// First, fill with background color
d->pixmap->fill(d->bgColor);
QPainter p(d->pixmap);
// Convert image to pixmap and draw it
QPixmap imagePixmap = d->iface->convertToPixmap(image);
p.drawPixmap(d->rect.x(), d->rect.y(),
imagePixmap, 0, 0, imagePixmap.width(), imagePixmap.height());
// prepare painter for use by compose image
p.setClipRect(d->rect);
p.translate(d->rect.x(), d->rect.y());
int borderWidth = qMax(1, qRound(ratioW));
// compose image and draw result directly on pixmap, with correct offset
QRect textRect = composeImage(&image, &p, x, y,
d->textFont, d->textFont.pointSizeF(),
d->textRotation, d->textColor, d->textOpacity,
d->alignMode, d->textString, d->textTransparent, d->backgroundColor,
d->textBorder ? BORDER_NORMAL : BORDER_SUPPORT, borderWidth, borderWidth,
(ratioW > ratioH) ? ratioW : ratioH);
p.end();
// store new text rectangle
// convert from image to widget coordinates
d->textRect.setX(textRect.x() + d->rect.x());
d->textRect.setY(textRect.y() + d->rect.y());
d->textRect.setSize(textRect.size());
}
/**
Take data from image, draw text at x|y with specified parameters.
If destPainter is null, draw to image,
if destPainter is not null, draw directly using the painter.
Returns modified area of image.
*/
QRect InsertTextWidget::composeImage(DImg* const image, QPainter* const destPainter,
int x, int y,
QFont font, float pointSize, int textRotation, QColor textColor,
int textOpacity, int alignMode, const QString& textString,
bool transparentBackground, QColor backgroundColor,
BorderMode borderMode, int borderWidth, int spacing, float fontScale)
{
/*
The problem we have to solve is that we have no pixel access to font rendering,
we have to let Qt do the drawing. On the other hand we need to support 16 bit, which
cannot be done with QPixmap.
The current solution cuts out the text area, lets Qt do its drawing, converts back and blits to original.
*/
int maxWidth, maxHeight;
if (x == -1 && y == -1)
{
maxWidth = image->width();
maxHeight = image->height();
}
else
{
maxWidth = image->width() - x;
maxHeight = image->height() - y;
}
fontScale = qMax(0.01f, fontScale);
// find out size of the area that we are drawing to
font.setPointSizeF(pointSize);
QFontMetrics fontMt(font);
QRect fontRect = fontMt.boundingRect(0, 0,
qRound(maxWidth / fontScale),
qRound(maxHeight / fontScale),
alignMode, textString);
fontRect.setWidth(qRound(fontRect.width() * fontScale));
fontRect.setHeight(qRound(fontRect.height() * fontScale));
if (!fontRect.isValid())
{
return QRect();
}
int fontWidth, fontHeight;
switch (textRotation)
{
case ROTATION_NONE:
case ROTATION_180:
default:
fontWidth = fontRect.width();
fontHeight = fontRect.height();
break;
case ROTATION_90:
case ROTATION_270:
fontWidth = fontRect.height();
fontHeight = fontRect.width();
break;
}
// x, y == -1 means that we have to find a good initial position for the text here
if (x == -1 && y == -1)
{
int boxWidth = fontWidth + 2 * borderWidth + 2 * spacing;
int boxHeight = fontHeight + 2 * borderWidth + 2 * spacing;
// was a valid position hint stored from last use?
if (d->positionHint.isValid())
{
// We assume that people tend to orient text along the edges,
// so we do some guessing so that positions such as "in the lower right corner"
// will be remembered across different image sizes.
// get relative positions
float fromTop = (float)d->positionHint.top() / 10000.0;
float fromBottom = 1.0 - (float)d->positionHint.bottom() / 10000.0;
float fromLeft = (float)d->positionHint.left() / 10000.0;
float fromRight = 1.0 - (float)d->positionHint.right() / 10000.0;
// calculate horizontal position
if (fromLeft < fromRight)
{
x = qRound(fromLeft * maxWidth);
// we are placing from the smaller distance,
// so if now the larger distance is actually too small,
// fall back to standard placement, nothing to lose.
if (x + boxWidth > maxWidth)
{
x = qMax( (maxWidth - boxWidth) / 2, 0);
}
}
else
{
x = maxWidth - qRound(fromRight * maxWidth) - boxWidth;
if ( x < 0 )
{
x = qMax( (maxWidth - boxWidth) / 2, 0);
}
}
// calculate vertical position
if (fromTop < fromBottom)
{
y = qRound(fromTop * maxHeight);
if (y + boxHeight > maxHeight)
{
y = qMax( (maxHeight - boxHeight) / 2, 0);
}
}
else
{
y = maxHeight - qRound(fromBottom * maxHeight) - boxHeight;
if ( y < 0 )
{
y = qMax( (maxHeight - boxHeight) / 2, 0);
}
}
if (! QRect(x, y, boxWidth, boxHeight).
intersects(QRect(0, 0, maxWidth, maxHeight)) )
{
// emergency fallback - nothing is visible
x = qMax( (maxWidth - boxWidth) / 2, 0);
y = qMax( (maxHeight - boxHeight) / 2, 0);
}
// invalidate position hint, use only once
d->positionHint = QRect();
}
else
{
// use standard position
x = qMax( (maxWidth - boxWidth) / 2, 0);
y = qMax( (maxHeight - boxHeight) / 2, 0);
}
}
// create a rectangle relative to image
QRect drawRect( x, y, fontWidth + 2 * borderWidth + 2 * spacing, fontHeight + 2 * borderWidth + 2 * spacing);
// create a rectangle relative to textArea, excluding the border
QRect textAreaBackgroundRect( borderWidth, borderWidth, fontWidth + 2 * spacing, fontHeight + 2 * spacing);
// create a rectangle relative to textArea, excluding the border and spacing
QRect textAreaTextRect( borderWidth + spacing, borderWidth + spacing, fontWidth, fontHeight );
// create a rectangle relative to textArea, including the border,
// for drawing the rectangle, taking into account that the width of the QPen goes in and out in equal parts
QRect textAreaDrawRect( borderWidth / 2, borderWidth / 2, fontWidth + borderWidth + 2 * spacing,
fontHeight + borderWidth + 2 * spacing );
// cut out the text area
DImg textArea = image->copy(drawRect);
if (textArea.isNull())
{
return QRect();
}
// compose semi-transparent background over textArea
DColorComposer* composer = DColorComposer::getComposer(DColorComposer::PorterDuffNone);
if (transparentBackground)
{
DImg transparentLayer(textAreaBackgroundRect.width(), textAreaBackgroundRect.height(), textArea.sixteenBit(), true);
DColor transparent(backgroundColor);
transparent.setAlpha(d->transparency);
if (image->sixteenBit())
{
transparent.convertToSixteenBit();
}
transparentLayer.fill(transparent);
textArea.bitBlendImage(composer, &transparentLayer, 0, 0, transparentLayer.width(), transparentLayer.height(),
textAreaBackgroundRect.x(), textAreaBackgroundRect.y());
}
DImg textNotDrawn;
if (textArea.sixteenBit())
{
textNotDrawn = textArea.copy();
textNotDrawn.convertToEightBit();
}
else
{
textNotDrawn = textArea;
}
// We have no direct pixel access to font rendering, so now we need to use Qt/X11 for the drawing
// convert text area to pixmap
QPixmap pixmap;
if (destPainter)
{
// We working on tool preview, deal with CM as well
pixmap = d->iface->convertToPixmap(textNotDrawn);
}
else
{
- // We working on target image. Do no apply double CM adjustement here.
+ // We working on target image. Do no apply double CM adjustment here.
pixmap = textNotDrawn.convertToPixmap();
}
int fontScaleWidth = qRound(fontWidth / fontScale);
int fontScaleHeight = qRound(fontHeight / fontScale);
QPixmap textPixmap(fontScaleWidth, fontScaleHeight);
textPixmap.fill(Qt::transparent);
QPainter tp(&textPixmap);
tp.setOpacity((qreal)textOpacity / 100.0);
tp.setPen(QPen(textColor, 1));
tp.setFont(font);
switch (textRotation)
{
case ROTATION_NONE:
tp.drawText(0, 0, fontScaleWidth, fontScaleHeight,
alignMode, textString);
break;
case ROTATION_90:
tp.translate(fontScaleWidth, 0);
tp.rotate(90.0);
tp.drawText(0, 0, fontScaleHeight, fontScaleWidth,
alignMode, textString);
break;
case ROTATION_180:
tp.translate(fontScaleWidth, fontScaleHeight);
tp.rotate(180.0);
tp.drawText(0, 0, fontScaleWidth, fontScaleHeight,
alignMode, textString);
break;
case ROTATION_270:
tp.translate(0, fontScaleHeight);
tp.rotate(270.0);
tp.drawText(0, 0, fontScaleHeight, fontScaleWidth,
alignMode, textString);
break;
}
tp.end();
// paint on pixmap
QPainter p(&pixmap);
p.drawPixmap(textAreaTextRect, textPixmap.scaled(fontWidth,
fontHeight,
Qt::IgnoreAspectRatio,
Qt::SmoothTransformation));
// Drawing rectangle around text.
if (borderMode == BORDER_NORMAL) // Decorative border using text color.
{
p.setPen( QPen(textColor, borderWidth, Qt::SolidLine,
Qt::SquareCap, Qt::RoundJoin) ) ;
p.drawRect(textAreaDrawRect);
}
else if (borderMode == BORDER_SUPPORT) // Make simple dot line border to help user.
{
p.setPen(QPen(Qt::white, 1, Qt::SolidLine));
p.drawRect(textAreaDrawRect);
p.setPen(QPen(Qt::red, 1, Qt::DotLine));
p.drawRect(textAreaDrawRect);
}
p.end();
if (!destPainter)
{
// convert to QImage, then to DImg
QImage pixmapImage = pixmap.toImage();
DImg textDrawn(pixmapImage.width(), pixmapImage.height(), false, true, pixmapImage.bits());
// This does not work: during the conversion, colors are altered significantly (diffs of 1 to 10 in each component),
// so we cannot find out which pixels have actually been touched.
/*
// Compare the result of drawing with the previous version.
// Set all unchanged pixels to transparent
DColor color, ncolor;
uchar *ptr, *nptr;
ptr = textDrawn.bits();
nptr = textNotDrawn.bits();
int bytesDepth = textDrawn.bytesDepth();
int numPixels = textDrawn.width() * textDrawn.height();
for (int i = 0; i < numPixels; ++i, ptr+= bytesDepth, nptr += bytesDepth)
{
color.setColor(ptr, false);
ncolor.setColor(nptr, false);
if ( color.red() == ncolor.red() &&
color.green() == ncolor.green() &&
color.blue() == ncolor.blue())
{
color.setAlpha(0);
color.setPixel(ptr);
}
}
// convert to 16 bit if needed
*/
textDrawn.convertToDepthOfImage(&textArea);
// now compose to original: only pixels affected by drawing text and border are changed, not whole area
textArea.bitBlendImage(composer, &textDrawn, 0, 0, textDrawn.width(), textDrawn.height(), 0, 0);
// copy result to original image
image->bitBltImage(&textArea, drawRect.x(), drawRect.y());
}
else
{
destPainter->drawPixmap(drawRect.x(), drawRect.y(), pixmap, 0, 0, pixmap.width(), pixmap.height());
}
delete composer;
return drawRect;
}
void InsertTextWidget::paintEvent(QPaintEvent*)
{
QPainter p(this);
p.drawPixmap(0, 0, *d->pixmap);
p.end();
}
void InsertTextWidget::resizeEvent(QResizeEvent* e)
{
blockSignals(true);
delete d->pixmap;
int w = e->size().width();
int h = e->size().height();
int textX = d->textRect.x() - d->rect.x();
int textY = d->textRect.y() - d->rect.y();
int old_w = d->w;
int old_h = d->h;
d->iface->setPreviewSize(QSize(w, h));
d->w = d->iface->previewSize().width();
d->h = d->iface->previewSize().height();
d->pixmap = new QPixmap(w, h);
d->rect = QRect(w/2-d->w/2, h/2-d->h/2, d->w, d->h);
if (d->textRect.isValid())
{
int textWidth = d->textRect.width();
int textHeight = d->textRect.height();
textX = qRound(textX * (float)d->w / (float)old_w);
textY = qRound(textY * (float)d->h / (float)old_h);
textWidth = qRound(textWidth * (float)d->w / (float)old_w);
textHeight = qRound(textHeight * (float)d->h / (float)old_h);
d->textRect.setX(textX + d->rect.x());
d->textRect.setY(textY + d->rect.y());
d->textRect.setWidth(textWidth);
d->textRect.setHeight(textHeight);
makePixmap();
}
blockSignals(false);
}
void InsertTextWidget::mousePressEvent(QMouseEvent* e)
{
if ( e->button() == Qt::LeftButton &&
d->textRect.contains( e->x(), e->y() ) )
{
d->xpos = e->x();
d->ypos = e->y();
setCursor ( Qt::SizeAllCursor );
d->currentMoving = true;
}
}
void InsertTextWidget::mouseReleaseEvent(QMouseEvent*)
{
setCursor ( Qt::ArrowCursor );
d->currentMoving = false;
}
void InsertTextWidget::mouseMoveEvent(QMouseEvent* e)
{
if ( rect().contains( e->x(), e->y() ) )
{
if ( e->buttons() == Qt::LeftButton && d->currentMoving )
{
uint newxpos = e->x();
uint newypos = e->y();
d->textRect.translate(newxpos - d->xpos, newypos - d->ypos);
if (d->textRect.x() - d->rect.x() < 0)
{
d->textRect.setX(d->rect.x());
}
if (d->textRect.y() - d->rect.y() < 0)
{
d->textRect.setY(d->rect.y());
}
makePixmap();
repaint();
d->xpos = newxpos;
d->ypos = newypos;
setCursor( Qt::PointingHandCursor );
}
else if ( d->textRect.contains( e->x(), e->y() ) )
{
setCursor ( Qt::SizeAllCursor );
}
else
{
setCursor ( Qt::ArrowCursor );
}
}
}
} // namespace Digikam
diff --git a/core/utilities/imageeditor/widgets/imagebrushguidewidget.h b/core/utilities/imageeditor/widgets/imagebrushguidewidget.h
index 93cedf7499..82d8dfa079 100644
--- a/core/utilities/imageeditor/widgets/imagebrushguidewidget.h
+++ b/core/utilities/imageeditor/widgets/imagebrushguidewidget.h
@@ -1,77 +1,77 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-06-15
* Description : a brush for use with tool to replace part of the image using another
*
* Copyright (C) 2004-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2017 by Shaza Ismail Kaoud <shaza dot ismail dot k at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_IMAGE_BRUSH_GUIDE_WIDGET_H
#define DIGIKAM_IMAGE_BRUSH_GUIDE_WIDGET_H
// Local includes
#include "imageguidewidget.h"
namespace Digikam
{
class ImageBrushGuideWidget : public ImageGuideWidget
{
Q_OBJECT
public:
/**
* Using the parent's constructor
* Should be changed to get rid of the inheritance
*/
using ImageGuideWidget::ImageGuideWidget;
public Q_SLOTS:
/**
* @brief slotSrcSet toggles the fixing of the brush source center
*/
void slotSetSourcePoint();
Q_SIGNALS:
/**
* @brief signalClone emitted when the src is set and the user initiated a brush click
- * and keeps emmitting with motion
+ * and keeps emitting with motion
*/
void signalClone(const QPoint& currentSrc, const QPoint& currentDst);
protected:
void mouseReleaseEvent(QMouseEvent*);
void mousePressEvent(QMouseEvent*);
void mouseMoveEvent(QMouseEvent*);
private:
bool srcSet = true;
QPoint src;
QPoint dst;
};
} // namespace Digikam
#endif // DIGIKAM_IMAGE_BRUSH_GUIDE_WIDGET_H
diff --git a/core/utilities/import/backend/cameracontroller.cpp b/core/utilities/import/backend/cameracontroller.cpp
index 8675c338dd..9a3c9fdb6c 100644
--- a/core/utilities/import/backend/cameracontroller.cpp
+++ b/core/utilities/import/backend/cameracontroller.cpp
@@ -1,1256 +1,1256 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-09-17
* Description : digital camera controller
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2012 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2015 by Mohamed_Anwer <m_dot_anwer at gmx dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "cameracontroller.h"
// Qt includes
#include <QMutex>
#include <QWaitCondition>
#include <QVariant>
#include <QImage>
#include <QFile>
#include <QRegExp>
#include <QFileInfo>
#include <QUrl>
#include <QDir>
#include <QMessageBox>
#include <QProcess>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "digikam_debug.h"
#include "digikam_config.h"
#include "digikam_globals.h"
#include "template.h"
#include "templatemanager.h"
#include "gpcamera.h"
#include "umscamera.h"
#include "jpegutils.h"
#include "dfileoperations.h"
namespace Digikam
{
class Q_DECL_HIDDEN CameraCommand
{
public:
enum Action
{
cam_none = 0,
cam_connect,
cam_cancel,
cam_cameraInformation,
cam_listfolders,
cam_listfiles,
cam_download,
cam_upload,
cam_delete,
cam_lock,
cam_thumbsinfo,
cam_metadata,
cam_open,
cam_freeSpace,
cam_preview,
cam_capture
};
Action action;
QMap<QString, QVariant> map;
};
class Q_DECL_HIDDEN CameraController::Private
{
public:
explicit Private()
: close(false),
canceled(false),
running(false),
conflictRule(SetupCamera::DIFFNAME),
parent(0),
timer(0),
camera(0)
{
}
bool close;
bool canceled;
bool running;
SetupCamera::ConflictRule conflictRule;
QStringList folderList;
QWidget* parent;
QTimer* timer;
DKCamera* camera;
QMutex mutex;
QWaitCondition condVar;
QList<CameraCommand*> cmdThumbs;
QList<CameraCommand*> commands;
};
CameraController::CameraController(QWidget* const parent,
const QString& title, const QString& model,
const QString& port, const QString& path)
: QThread(parent),
d(new Private)
{
d->parent = parent;
// URL parsing (c) Stephan Kulow
if (path.startsWith(QLatin1String("camera:/")))
{
QUrl url(path);
qCDebug(DIGIKAM_IMPORTUI_LOG) << "path " << path << " " << url << " " << url.host();
QString xport = url.host();
if (xport.startsWith(QLatin1String("usb:")))
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "xport " << xport;
QRegExp x = QRegExp(QLatin1String("(usb:[0-9,]*)"));
if (x.indexIn(xport) != -1)
{
QString usbport = x.cap(1);
qCDebug(DIGIKAM_IMPORTUI_LOG) << "USB " << xport << " " << usbport;
//if ((xport == usbport) || ((count == 1) && (xport == "usb:")))
//{
// model = xmodel;
d->camera = new GPCamera(title, url.userName(), QLatin1String("usb:"), QLatin1String("/"));
//}
}
}
}
if (!d->camera)
{
if (model.toLower() == QLatin1String("directory browse"))
{
d->camera = new UMSCamera(title, model, port, path);
}
else
{
d->camera = new GPCamera(title, model, port, path);
}
}
connect(d->camera, SIGNAL(signalFolderList(QStringList)),
this, SIGNAL(signalFolderList(QStringList)));
// setup inter-thread signals
qRegisterMetaType<CamItemInfo>("CamItemInfo");
qRegisterMetaType<CamItemInfoList>("CamItemInfoList");
connect(this, SIGNAL(signalInternalCheckRename(QString,QString,QString,QString,QString)),
this, SLOT(slotCheckRename(QString,QString,QString,QString,QString)),
Qt::BlockingQueuedConnection);
connect(this, SIGNAL(signalInternalDownloadFailed(QString,QString)),
this, SLOT(slotDownloadFailed(QString,QString)),
Qt::BlockingQueuedConnection);
connect(this, SIGNAL(signalInternalUploadFailed(QString,QString,QString)),
this, SLOT(slotUploadFailed(QString,QString,QString)),
Qt::BlockingQueuedConnection);
connect(this, SIGNAL(signalInternalDeleteFailed(QString,QString)),
this, SLOT(slotDeleteFailed(QString,QString)),
Qt::BlockingQueuedConnection);
connect(this, SIGNAL(signalInternalLockFailed(QString,QString)),
this, SLOT(slotLockFailed(QString,QString)),
Qt::BlockingQueuedConnection);
d->running = true;
}
CameraController::~CameraController()
{
// clear commands, stop camera
slotCancel();
// stop thread
{
QMutexLocker lock(&d->mutex);
d->running = false;
d->condVar.wakeAll();
}
wait();
delete d->camera;
delete d;
}
bool CameraController::cameraThumbnailSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->thumbnailSupport();
}
bool CameraController::cameraDeleteSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->deleteSupport();
}
bool CameraController::cameraUploadSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->uploadSupport();
}
bool CameraController::cameraMkDirSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->mkDirSupport();
}
bool CameraController::cameraDelDirSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->delDirSupport();
}
bool CameraController::cameraCaptureImageSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->captureImageSupport();
}
bool CameraController::cameraCaptureImagePreviewSupport() const
{
if (!d->camera)
{
return false;
}
return d->camera->captureImageSupport() && d->camera->captureImagePreviewSupport();
}
QString CameraController::cameraPath() const
{
if (!d->camera)
{
return QString();
}
return d->camera->path();
}
QString CameraController::cameraTitle() const
{
if (!d->camera)
{
return QString();
}
return d->camera->title();
}
DKCamera::CameraDriverType CameraController::cameraDriverType() const
{
if (!d->camera)
{
return DKCamera::UMSDriver;
}
return d->camera->cameraDriverType();
}
QByteArray CameraController::cameraMD5ID() const
{
if (!d->camera)
{
return QByteArray();
}
return d->camera->cameraMD5ID();
}
QIcon CameraController::mimeTypeThumbnail(const QString& itemName) const
{
if (!d->camera)
{
return QPixmap();
}
QFileInfo fi(itemName);
QString mime = d->camera->mimeType(fi.suffix().toLower());
if (mime.startsWith(QLatin1String("image/x-raw")))
{
return QIcon::fromTheme(QLatin1String("image-x-adobe-dng"));
}
else if (mime.startsWith(QLatin1String("image/")))
{
return QIcon::fromTheme(QLatin1String("view-preview"));
}
else if (mime.startsWith(QLatin1String("video/")))
{
return QIcon::fromTheme(QLatin1String("video-x-generic"));
}
else if (mime.startsWith(QLatin1String("audio/")))
{
return QIcon::fromTheme(QLatin1String("audio-x-generic"));
}
return QIcon::fromTheme(QLatin1String("unknown"));
}
void CameraController::slotCancel()
{
d->canceled = true;
d->camera->cancel();
QMutexLocker lock(&d->mutex);
d->cmdThumbs.clear();
d->commands.clear();
}
void CameraController::run()
{
while (d->running)
{
CameraCommand* command = 0;
{
QMutexLocker lock(&d->mutex);
if (!d->commands.isEmpty())
{
command = d->commands.takeFirst();
emit signalBusy(true);
}
else if (!d->cmdThumbs.isEmpty())
{
command = d->cmdThumbs.takeLast();
emit signalBusy(false);
}
else
{
emit signalBusy(false);
d->condVar.wait(&d->mutex);
continue;
}
}
if (command)
{
executeCommand(command);
delete command;
}
}
emit signalBusy(false);
}
void CameraController::executeCommand(CameraCommand* const cmd)
{
if (!cmd)
{
return;
}
switch (cmd->action)
{
case (CameraCommand::cam_connect):
{
sendLogMsg(i18n("Connecting to camera..."));
bool result = d->camera->doConnect();
emit signalConnected(result);
if (result)
{
d->camera->printSupportedFeatures();
sendLogMsg(i18n("Connection established."));
}
else
{
sendLogMsg(i18n("Connection failed."));
}
break;
}
case (CameraCommand::cam_cameraInformation):
{
QString summary, manual, about;
d->camera->cameraSummary(summary);
d->camera->cameraManual(manual);
d->camera->cameraAbout(about);
emit signalCameraInformation(summary, manual, about);
break;
}
case (CameraCommand::cam_freeSpace):
{
unsigned long kBSize = 0;
unsigned long kBAvail = 0;
if (!d->camera->getFreeSpace(kBSize, kBAvail))
{
sendLogMsg(i18n("Failed to get free space from camera"), DHistoryView::ErrorEntry);
}
emit signalFreeSpace(kBSize, kBAvail);
break;
}
case (CameraCommand::cam_preview):
{
QImage preview;
if (!d->camera->getPreview(preview))
{
sendLogMsg(i18n("Failed to get preview from camera"), DHistoryView::ErrorEntry);
}
emit signalPreview(preview);
break;
}
case (CameraCommand::cam_capture):
{
CamItemInfo itemInfo;
if (!d->camera->capture(itemInfo))
{
sendLogMsg(i18n("Failed to process capture from camera"), DHistoryView::ErrorEntry);
}
emit signalUploaded(itemInfo);
break;
}
case (CameraCommand::cam_listfolders):
{
QString folder = cmd->map[QLatin1String("folder")].toString();
if (!d->camera->getFolders(folder))
{
sendLogMsg(xi18n("Failed to list folder <filename>%1</filename>", folder), DHistoryView::ErrorEntry);
}
break;
}
case (CameraCommand::cam_listfiles):
{
QString folder = cmd->map[QLatin1String("folder")].toString();
bool useMetadata = cmd->map[QLatin1String("useMetadata")].toBool();
CamItemInfoList itemsList;
if (!d->camera->getItemsInfoList(folder, useMetadata, itemsList))
{
sendLogMsg(xi18n("Failed to list files in <filename>%1</filename>", folder), DHistoryView::ErrorEntry);
}
// TODO would it be okay to pass this to the ImportImageModel and let it filter it for us?
for (CamItemInfoList::iterator it = itemsList.begin() ; it != itemsList.end() ; )
{
CamItemInfo &info = (*it);
if (info.mime.isEmpty())
{
it = itemsList.erase(it);
continue;
}
++it;
}
emit signalFileList(itemsList);
break;
}
case (CameraCommand::cam_thumbsinfo):
{
QList<QVariant> list = cmd->map[QLatin1String("list")].toList();
int thumbSize = cmd->map[QLatin1String("thumbSize")].toInt();
for (QList<QVariant>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it)
{
if (d->canceled)
{
break;
}
QString folder = (*it).toStringList().at(0);
QString file = (*it).toStringList().at(1);
CamItemInfo info;
info.folder = folder;
info.name = file;
QImage thumbnail;
if (d->camera->getThumbnail(folder, file, thumbnail))
{
thumbnail = thumbnail.scaled(thumbSize, thumbSize, Qt::KeepAspectRatio);
emit signalThumbInfo(folder, file, info, thumbnail);
}
else
{
sendLogMsg(xi18n("Failed to get thumbnail for <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
emit signalThumbInfoFailed(folder, file, info);
}
}
break;
}
case (CameraCommand::cam_metadata):
{
QString folder = cmd->map[QLatin1String("folder")].toString();
QString file = cmd->map[QLatin1String("file")].toString();
DMetadata meta;
if (!d->camera->getMetadata(folder, file, meta))
sendLogMsg(xi18n("Failed to get Metadata for <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
emit signalMetadata(folder, file, meta);
break;
}
case (CameraCommand::cam_download):
{
QString folder = cmd->map[QLatin1String("folder")].toString();
QString file = cmd->map[QLatin1String("file")].toString();
QString mime = cmd->map[QLatin1String("mime")].toString();
QString dest = cmd->map[QLatin1String("dest")].toString();
bool documentName = cmd->map[QLatin1String("documentName")].toBool();
bool fixDateTime = cmd->map[QLatin1String("fixDateTime")].toBool();
QDateTime newDateTime = cmd->map[QLatin1String("newDateTime")].toDateTime();
QString templateTitle = cmd->map[QLatin1String("template")].toString();
bool convertJpeg = cmd->map[QLatin1String("convertJpeg")].toBool();
QString losslessFormat = cmd->map[QLatin1String("losslessFormat")].toString();
bool backupRaw = cmd->map[QLatin1String("backupRaw")].toBool();
bool convertDng = cmd->map[QLatin1String("convertDng")].toBool();
bool compressDng = cmd->map[QLatin1String("compressDng")].toBool();
int previewMode = cmd->map[QLatin1String("previewMode")].toInt();
QString script = cmd->map[QLatin1String("script")].toString();
int pickLabel = cmd->map[QLatin1String("pickLabel")].toInt();
int colorLabel = cmd->map[QLatin1String("colorLabel")].toInt();
int rating = cmd->map[QLatin1String("rating")].toInt();
// download to a temp file
emit signalDownloaded(folder, file, CamItemInfo::DownloadStarted);
QString tempFile = QLatin1String("/Camera-tmp%1-") +
QString::number(QCoreApplication::applicationPid()) +
QLatin1String(".digikamtempfile.");
QUrl tempURL = QUrl::fromLocalFile(dest).adjusted(QUrl::RemoveFilename |
QUrl::StripTrailingSlash);
QString temp = tempURL.toLocalFile() + tempFile.arg(1) + file;
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Downloading: " << file << " using " << temp;
bool result = d->camera->downloadItem(folder, file, temp);
if (!result)
{
QFile::remove(temp);
sendLogMsg(xi18n("Failed to download <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
emit signalDownloaded(folder, file, CamItemInfo::DownloadFailed);
break;
}
else if (mime == QLatin1String("image/jpeg"))
{
// Possible modification operations. Only apply it to JPEG for the moment.
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Set metadata from: " << file << " using " << temp;
DMetadata metadata(temp);
bool applyChanges = false;
if (documentName)
{
metadata.setExifTagString("Exif.Image.DocumentName", file);
applyChanges = true;
}
if (fixDateTime)
{
metadata.setImageDateTime(newDateTime, true);
applyChanges = true;
}
// TODO: Set image tags using DMetadata.
if (colorLabel > NoColorLabel)
{
metadata.setImageColorLabel(colorLabel);
applyChanges = true;
}
if (pickLabel > NoPickLabel)
{
metadata.setImagePickLabel(pickLabel);
applyChanges = true;
}
if (rating > RatingMin)
{
metadata.setImageRating(rating);
applyChanges = true;
}
if (!templateTitle.isNull() && !templateTitle.isEmpty())
{
TemplateManager* const tm = TemplateManager::defaultManager();
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Metadata template title : " << templateTitle;
if (tm && templateTitle == Template::removeTemplateTitle())
{
metadata.removeMetadataTemplate();
applyChanges = true;
}
else if (tm)
{
metadata.removeMetadataTemplate();
metadata.setMetadataTemplate(tm->findByTitle(templateTitle));
applyChanges = true;
}
}
if (applyChanges)
{
metadata.applyChanges();
}
// Convert JPEG file to lossless format if wanted,
// and move converted image to destination.
if (convertJpeg)
{
QString temp2 = tempURL.toLocalFile() + tempFile.arg(2) + file;
// When converting a file, we need to set the new format extension..
// The new extension is already set in importui.cpp.
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert to LossLess: " << file;
if (!JPEGUtils::jpegConvert(temp, temp2, file, losslessFormat))
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert failed to JPEG!";
// convert failed. delete the temp file
QFile::remove(temp);
QFile::remove(temp2);
sendLogMsg(xi18n("Failed to convert file <filename>%1</filename> to JPEG", file), DHistoryView::ErrorEntry, folder, file);
}
else
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Done, removing the temp file: " << temp;
// Else remove only the first temp file.
QFile::remove(temp);
temp = temp2;
}
}
}
else if (convertDng && mime == QLatin1String("image/x-raw"))
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert to DNG: " << file;
if (QFileInfo(file).suffix().toUpper() != QLatin1String("DNG"))
{
QString temp2 = tempURL.toLocalFile() + tempFile.arg(2) + file;
DNGWriter dngWriter;
dngWriter.setInputFile(temp);
dngWriter.setOutputFile(temp2);
dngWriter.setBackupOriginalRawFile(backupRaw);
dngWriter.setCompressLossLess(compressDng);
dngWriter.setPreviewMode(previewMode);
if (dngWriter.convert() != DNGWriter::PROCESSCOMPLETE)
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert failed to DNG!";
// convert failed. delete the temp file
QFile::remove(temp);
QFile::remove(temp2);
sendLogMsg(xi18n("Failed to convert file <filename>%1</filename> to DNG", file), DHistoryView::ErrorEntry, folder, file);
}
else
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Done, removing the temp file: " << temp;
// Else remove only the first temp file.
QFile::remove(temp);
temp = temp2;
}
}
else
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Convert skipped to DNG";
sendLogMsg(xi18n("Skipped to convert file <filename>%1</filename> to DNG", file), DHistoryView::WarningEntry, folder, file);
}
}
// Now we need to move from temp file to destination file.
// This possibly involves UI operation, do it from main thread
emit signalInternalCheckRename(folder, file, dest, temp, script);
break;
}
case (CameraCommand::cam_upload):
{
QString folder = cmd->map[QLatin1String("destFolder")].toString();
// We will using the same source file name to create the dest file
// name in camera.
QString file = cmd->map[QLatin1String("destFile")].toString();
// The source file path to download in camera.
QString src = cmd->map[QLatin1String("srcFilePath")].toString();
CamItemInfo itemsInfo;
bool result = d->camera->uploadItem(folder, file, src, itemsInfo);
if (result)
{
emit signalUploaded(itemsInfo);
}
else
{
emit signalInternalUploadFailed(folder, file, src);
}
break;
}
case (CameraCommand::cam_delete):
{
QString folder = cmd->map[QLatin1String("folder")].toString();
QString file = cmd->map[QLatin1String("file")].toString();
bool result = d->camera->deleteItem(folder, file);
if (result)
{
emit signalDeleted(folder, file, true);
}
else
{
emit signalInternalDeleteFailed(folder, file);
}
break;
}
case (CameraCommand::cam_lock):
{
QString folder = cmd->map[QLatin1String("folder")].toString();
QString file = cmd->map[QLatin1String("file")].toString();
bool lock = cmd->map[QLatin1String("lock")].toBool();
bool result = d->camera->setLockItem(folder, file, lock);
if (result)
{
emit signalLocked(folder, file, true);
}
else
{
emit signalInternalLockFailed(folder, file);
}
break;
}
default:
{
qCWarning(DIGIKAM_IMPORTUI_LOG) << " unknown action specified";
break;
}
}
}
void CameraController::sendLogMsg(const QString& msg, DHistoryView::EntryType type,
const QString& folder, const QString& file)
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Log (" << file << " " << folder << ": " << msg;
if (!d->canceled)
{
emit signalLogMsg(msg, type, folder, file);
}
}
void CameraController::slotCheckRename(const QString& folder, const QString& file,
const QString& destination, const QString& temp,
const QString& script)
{
// this is the direct continuation of executeCommand, case CameraCommand::cam_download
QString dest = destination;
QFileInfo info(dest);
if (info.exists() && d->conflictRule == SetupCamera::SKIPFILE)
{
QFile::remove(temp);
sendLogMsg(xi18n("Skipped file <filename>%1</filename>", file), DHistoryView::WarningEntry, folder, file);
emit signalSkipped(folder, file);
return;
}
else if (d->conflictRule != SetupCamera::OVERWRITE)
{
bool newurl = false;
dest = DFileOperations::getUniqueFileUrl(QUrl::fromLocalFile(dest), &newurl).toLocalFile();
info = QFileInfo(dest);
if (newurl)
{
sendLogMsg(xi18n("Rename file to <filename>%1</filename>", info.fileName()), DHistoryView::WarningEntry, folder, file);
}
}
// move the file to the destination file
if (DMetadata::hasSidecar(temp))
{
QString sctemp = DMetadata::sidecarPath(temp);
QString scdest = DMetadata::sidecarPath(dest);
qCDebug(DIGIKAM_IMPORTUI_LOG) << "File" << temp << " has a sidecar, renaming it to " << scdest;
// remove scdest file if it exist
if (sctemp != scdest && QFile::exists(sctemp) && QFile::exists(scdest))
{
QFile::remove(scdest);
}
if (!QFile::rename(sctemp, scdest))
{
sendLogMsg(xi18n("Failed to save sidecar file for <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
}
}
// remove dest file if it exist
if (temp != dest && QFile::exists(temp) && QFile::exists(dest))
{
QFile::remove(dest);
}
if (!QFile::rename(temp, dest))
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Renaming " << temp << " to " << dest << " failed";
// rename failed. delete the temp file
QFile::remove(temp);
emit signalDownloaded(folder, file, CamItemInfo::DownloadFailed);
sendLogMsg(xi18n("Failed to download <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
}
else
{
- qCDebug(DIGIKAM_IMPORTUI_LOG) << "Rename done, emiting downloaded signals:" << file << " info.filename: " << info.fileName();
+ qCDebug(DIGIKAM_IMPORTUI_LOG) << "Rename done, emitting downloaded signals:" << file << " info.filename: " << info.fileName();
// TODO why two signals??
emit signalDownloaded(folder, file, CamItemInfo::DownloadedYes);
emit signalDownloadComplete(folder, file, info.path(), info.fileName());
// Run script
if (!script.isEmpty())
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Got a script, processing: " << script;
QString s = script;
if (s.indexOf(QLatin1Char('%')) > -1)
{
// %filename must be replaced before %file
s.replace(QLatin1String("%orgfilename"), file, Qt::CaseSensitive);
s.replace(QLatin1String("%filename"), info.fileName(), Qt::CaseSensitive);
s.replace(QLatin1String("%orgpath"), folder, Qt::CaseSensitive);
s.replace(QLatin1String("%path"), info.path(), Qt::CaseSensitive);
s.replace(QLatin1String("%file"), dest, Qt::CaseSensitive);
}
else
{
s.append(QLatin1String(" \"") + dest + QLatin1String("\""));
}
QProcess process;
process.setProcessChannelMode(QProcess::SeparateChannels);
process.setProcessEnvironment(adjustedEnvironmentForAppImage());
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Running: " << s;
process.start(s);
if (!process.waitForFinished(60000))
{
sendLogMsg(xi18n("Timeout from script for <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
process.kill();
}
if (process.exitCode() != 0)
{
sendLogMsg(xi18n("Failed to run script for <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
}
qCDebug(DIGIKAM_IMPORTUI_LOG) << "stdout" << process.readAllStandardOutput();
qCDebug(DIGIKAM_IMPORTUI_LOG) << "stderr" << process.readAllStandardError();
}
}
}
void CameraController::slotDownloadFailed(const QString& folder, const QString& file)
{
sendLogMsg(xi18n("Failed to download <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
if (!d->canceled)
{
if (queueIsEmpty())
{
QMessageBox::critical(d->parent, qApp->applicationName(), i18n("Failed to download file <b>%1</b>.", file));
}
else
{
const QString msg = i18n("Failed to download file <b>%1</b>. Do you want to continue?", file);
int result = QMessageBox::warning(d->parent, qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
slotCancel();
}
}
}
}
void CameraController::slotUploadFailed(const QString& folder, const QString& file, const QString& src)
{
Q_UNUSED(folder);
Q_UNUSED(src);
sendLogMsg(xi18n("Failed to upload <filename>%1</filename>", file), DHistoryView::ErrorEntry);
if (!d->canceled)
{
if (queueIsEmpty())
{
QMessageBox::critical(d->parent, qApp->applicationName(), i18n("Failed to upload file <b>%1</b>.", file));
}
else
{
const QString msg = i18n("Failed to upload file <b>%1</b>. Do you want to continue?", file);
int result = QMessageBox::warning(d->parent, qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
slotCancel();
}
}
}
}
void CameraController::slotDeleteFailed(const QString& folder, const QString& file)
{
emit signalDeleted(folder, file, false);
sendLogMsg(xi18n("Failed to delete <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
if (!d->canceled)
{
if (queueIsEmpty())
{
QMessageBox::critical(d->parent, qApp->applicationName(), i18n("Failed to delete file <b>%1</b>.", file));
}
else
{
const QString msg = i18n("Failed to delete file <b>%1</b>. Do you want to continue?", file);
int result = QMessageBox::warning(d->parent, qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
slotCancel();
}
}
}
}
void CameraController::slotLockFailed(const QString& folder, const QString& file)
{
emit signalLocked(folder, file, false);
sendLogMsg(xi18n("Failed to lock <filename>%1</filename>", file), DHistoryView::ErrorEntry, folder, file);
if (!d->canceled)
{
if (queueIsEmpty())
{
QMessageBox::critical(d->parent, qApp->applicationName(), i18n("Failed to toggle lock file <b>%1</b>.", file));
}
else
{
const QString msg = i18n("Failed to toggle lock file <b>%1</b>. Do you want to continue?", file);
int result = QMessageBox::warning(d->parent, qApp->applicationName(), msg, QMessageBox::Yes | QMessageBox::Cancel);
if (result != QMessageBox::Yes)
{
slotCancel();
}
}
}
}
void CameraController::addCommand(CameraCommand* const cmd)
{
QMutexLocker lock(&d->mutex);
if (cmd->action == CameraCommand::cam_thumbsinfo)
{
d->cmdThumbs << cmd;
}
else
{
d->commands << cmd;
}
d->condVar.wakeAll();
}
bool CameraController::queueIsEmpty() const
{
QMutexLocker lock(&d->mutex);
return (d->commands.isEmpty() && d->cmdThumbs.isEmpty());
}
void CameraController::slotConnect()
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_connect;
addCommand(cmd);
}
void CameraController::listRootFolder(bool useMetadata)
{
listFolders(d->camera->path());
listFiles(d->camera->path(), useMetadata);
}
void CameraController::listFolders(const QString& folder)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_listfolders;
cmd->map.insert(QLatin1String("folder"), QVariant(folder));
addCommand(cmd);
}
void CameraController::listFiles(const QString& folder, bool useMetadata)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_listfiles;
cmd->map.insert(QLatin1String("folder"), QVariant(folder));
cmd->map.insert(QLatin1String("useMetadata"), QVariant(useMetadata));
addCommand(cmd);
}
void CameraController::getThumbsInfo(const CamItemInfoList& list, int thumbSize)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_thumbsinfo;
QList<QVariant> itemsList;
foreach(CamItemInfo info, list)
{
itemsList.append(QStringList() << info.folder << info.name);
}
cmd->map.insert(QLatin1String("list"), QVariant(itemsList));
cmd->map.insert(QLatin1String("thumbSize"), QVariant(thumbSize));
addCommand(cmd);
}
void CameraController::getMetadata(const QString& folder, const QString& file)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_metadata;
cmd->map.insert(QLatin1String("folder"), QVariant(folder));
cmd->map.insert(QLatin1String("file"), QVariant(file));
addCommand(cmd);
}
void CameraController::getCameraInformation()
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_cameraInformation;
addCommand(cmd);
}
void CameraController::getFreeSpace()
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_freeSpace;
addCommand(cmd);
}
void CameraController::getPreview()
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_preview;
addCommand(cmd);
}
void CameraController::capture()
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_capture;
addCommand(cmd);
}
void CameraController::upload(const QFileInfo& srcFileInfo, const QString& destFile, const QString& destFolder)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_upload;
cmd->map.insert(QLatin1String("srcFilePath"), QVariant(srcFileInfo.filePath()));
cmd->map.insert(QLatin1String("destFile"), QVariant(destFile));
cmd->map.insert(QLatin1String("destFolder"), QVariant(destFolder));
addCommand(cmd);
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Uploading '" << srcFileInfo.filePath() << "' into camera : '" << destFolder
<< "' (" << destFile << ")";
}
void CameraController::downloadPrep(const SetupCamera::ConflictRule& rule)
{
d->conflictRule = rule;
}
void CameraController::download(const DownloadSettingsList& list)
{
foreach(const DownloadSettings& downloadSettings, list)
{
download(downloadSettings);
}
}
void CameraController::download(const DownloadSettings& downloadSettings)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_download;
cmd->map.insert(QLatin1String("folder"), QVariant(downloadSettings.folder));
cmd->map.insert(QLatin1String("file"), QVariant(downloadSettings.file));
cmd->map.insert(QLatin1String("mime"), QVariant(downloadSettings.mime));
cmd->map.insert(QLatin1String("dest"), QVariant(downloadSettings.dest));
cmd->map.insert(QLatin1String("documentName"), QVariant(downloadSettings.documentName));
cmd->map.insert(QLatin1String("fixDateTime"), QVariant(downloadSettings.fixDateTime));
cmd->map.insert(QLatin1String("newDateTime"), QVariant(downloadSettings.newDateTime));
cmd->map.insert(QLatin1String("template"), QVariant(downloadSettings.templateTitle));
cmd->map.insert(QLatin1String("convertJpeg"), QVariant(downloadSettings.convertJpeg));
cmd->map.insert(QLatin1String("losslessFormat"), QVariant(downloadSettings.losslessFormat));
cmd->map.insert(QLatin1String("backupRaw"), QVariant(downloadSettings.backupRaw));
cmd->map.insert(QLatin1String("convertDng"), QVariant(downloadSettings.convertDng));
cmd->map.insert(QLatin1String("compressDng"), QVariant(downloadSettings.compressDng));
cmd->map.insert(QLatin1String("previewMode"), QVariant(downloadSettings.previewMode));
cmd->map.insert(QLatin1String("script"), QVariant(downloadSettings.script));
cmd->map.insert(QLatin1String("pickLabel"), QVariant(downloadSettings.pickLabel));
cmd->map.insert(QLatin1String("colorLabel"), QVariant(downloadSettings.colorLabel));
cmd->map.insert(QLatin1String("rating"), QVariant(downloadSettings.rating));
//cmd->map.insert(QLatin1String("tagIds"), QVariant(downloadSettings.tagIds));
addCommand(cmd);
}
void CameraController::deleteFile(const QString& folder, const QString& file)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_delete;
cmd->map.insert(QLatin1String("folder"), QVariant(folder));
cmd->map.insert(QLatin1String("file"), QVariant(file));
addCommand(cmd);
}
void CameraController::lockFile(const QString& folder, const QString& file, bool locked)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_lock;
cmd->map.insert(QLatin1String("folder"), QVariant(folder));
cmd->map.insert(QLatin1String("file"), QVariant(file));
cmd->map.insert(QLatin1String("lock"), QVariant(locked));
addCommand(cmd);
}
void CameraController::openFile(const QString& folder, const QString& file)
{
d->canceled = false;
CameraCommand* const cmd = new CameraCommand;
cmd->action = CameraCommand::cam_open;
cmd->map.insert(QLatin1String("folder"), QVariant(folder));
cmd->map.insert(QLatin1String("file"), QVariant(file));
cmd->map.insert(QLatin1String("dest"), QVariant(QDir::tempPath() + QLatin1Char('/') + file));
addCommand(cmd);
}
} // namespace Digikam
diff --git a/core/utilities/import/dialogs/cameramessagebox.h b/core/utilities/import/dialogs/cameramessagebox.h
index d5b1b5d1e3..b10e0263b1 100644
--- a/core/utilities/import/dialogs/cameramessagebox.h
+++ b/core/utilities/import/dialogs/cameramessagebox.h
@@ -1,119 +1,119 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-01-04
* Description : a message box to manage camera items
*
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_CAMERA_MESSAGE_BOX_H
#define DIGIKAM_CAMERA_MESSAGE_BOX_H
// Qt includes
#include <QWidget>
#include <QTreeWidget>
// Local includes
#include "camerathumbsctrl.h"
#include "digikam_export.h"
class QDialog;
class QDialogButtonBox;
namespace Digikam
{
class DIGIKAM_EXPORT CameraItem : public QTreeWidgetItem
{
public:
CameraItem(QTreeWidget* const parent, const CamItemInfo& info);
virtual ~CameraItem();
bool hasValidThumbnail() const;
CamItemInfo info() const;
void setThumb(const QPixmap& pix, bool hasThumb = true);
private:
class Private;
Private* const d;
};
// -----------------------------------------------------------
class DIGIKAM_EXPORT CameraItemList : public QTreeWidget
{
Q_OBJECT
public:
explicit CameraItemList(QWidget* const parent = 0);
virtual ~CameraItemList();
void setThumbCtrl(CameraThumbsCtrl* const ctrl);
void setItems(const CamItemInfoList& items);
private :
void drawRow(QPainter* p, const QStyleOptionViewItem& opt, const QModelIndex& index) const;
private Q_SLOTS:
void slotThumbnailLoaded(const CamItemInfo&);
private:
class Private;
Private* const d;
};
// -----------------------------------------------------------
class DIGIKAM_EXPORT CameraMessageBox
{
public:
/** Show List of camera items into an informative message box.
*/
static void informationList(CameraThumbsCtrl* const ctrl,
QWidget* const parent,
const QString& caption,
const QString& text,
const CamItemInfoList& items,
const QString& dontShowAgainName = QString());
- /** Show List of camera items to processs into a message box and wait user feedback.
+ /** Show List of camera items to process into a message box and wait user feedback.
* Return QMessageBox::Yes or QMessageBox::Cancel
*/
static int warningContinueCancelList(CameraThumbsCtrl* const ctrl,
QWidget* const parent,
const QString& caption,
const QString& text,
const CamItemInfoList& items,
const QString& dontAskAgainName = QString());
};
} // namespace Digikam
#endif // DIGIKAM_CAMERA_MESSAGE_BOX_H
diff --git a/core/utilities/import/main/importui.cpp b/core/utilities/import/main/importui.cpp
index 941a5987b7..6b3dfab2b4 100644
--- a/core/utilities/import/main/importui.cpp
+++ b/core/utilities/import/main/importui.cpp
@@ -1,2700 +1,2700 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2004-09-16
* Description : Import tool interface
*
* Copyright (C) 2004-2005 by Renchi Raju <renchi dot raju at gmail dot com>
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
* Copyright (C) 2006-2011 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
* Copyright (C) 2012 by Andi Clemens <andi dot clemens at gmail dot com>
* Copyright (C) 2012 by Islam Wazery <wazery at ubuntu dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "importui.h"
#include "importui_p.h"
// Qt includes
#include <QCheckBox>
#include <QCloseEvent>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QFrame>
#include <QGridLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QLabel>
#include <QLineEdit>
#include <QPixmap>
#include <QPointer>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QSignalMapper>
#include <QSplitter>
#include <QTimer>
#include <QToolButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QKeySequence>
#include <QInputDialog>
#include <QMenuBar>
#include <QMenu>
#include <QIcon>
#include <QMessageBox>
#include <QStatusBar>
#include <QApplication>
// KDE includes
#include <klocalizedstring.h>
#include <kactioncollection.h>
// Local includes
#include "drawdecoder.h"
#include "dlayoutbox.h"
#include "dexpanderbox.h"
#include "dfileselector.h"
#include "digikam_debug.h"
#include "digikam_globals.h"
#include "cameramessagebox.h"
#include "advancedrenamemanager.h"
#include "album.h"
#include "albummanager.h"
#include "applicationsettings.h"
#include "albumselectdialog.h"
#include "cameracontroller.h"
#include "camerafolderdialog.h"
#include "camerainfodialog.h"
#include "cameralist.h"
#include "cameranamehelper.h"
#include "cameratype.h"
#include "capturedlg.h"
#include "collectionlocation.h"
#include "collectionmanager.h"
#include "collectionscanner.h"
#include "componentsinfo.h"
#include "dlogoaction.h"
#include "coredbdownloadhistory.h"
#include "dzoombar.h"
#include "fileactionmngr.h"
#include "freespacewidget.h"
#include "iccsettings.h"
#include "imagepropertiessidebarcamgui.h"
#include "importsettings.h"
#include "importview.h"
#include "imagedialog.h"
#include "dnotificationwrapper.h"
#include "newitemsfinder.h"
#include "parsesettings.h"
#include "renamecustomizer.h"
#include "scancontroller.h"
#include "setup.h"
#include "sidebar.h"
#include "statusprogressbar.h"
#include "thememanager.h"
#include "thumbnailsize.h"
#include "importthumbnailmodel.h"
#include "imagepropertiestab.h"
namespace Digikam
{
ImportUI* ImportUI::m_instance = 0;
ImportUI::ImportUI(const QString& cameraTitle, const QString& model,
const QString& port, const QString& path, int startIndex)
: DXmlGuiWindow(0),
d(new Private)
{
setConfigGroupName(QLatin1String("Camera Settings"));
setXMLFile(QLatin1String("importui5.rc"));
setFullScreenOptions(FS_IMPORTUI);
setWindowFlags(Qt::Window);
m_instance = this;
// --------------------------------------------------------
QString title = CameraNameHelper::cameraName(cameraTitle);
d->cameraTitle = (title.isEmpty()) ? cameraTitle : title;
setCaption(d->cameraTitle);
setupCameraController(model, port, path);
setupUserArea();
setInitialSorting();
setupActions();
setupStatusBar();
setupAccelerators();
// -- Make signals/slots connections ---------------------------------
setupConnections();
sidebarTabTitleStyleChanged();
slotColorManagementOptionsChanged();
// -- Read settings --------------------------------------------------
readSettings();
setAutoSaveSettings(configGroupName(), true);
// -------------------------------------------------------------------
//d->historyUpdater = new CameraHistoryUpdater(this);
//connect (d->historyUpdater, SIGNAL(signalHistoryMap(CHUpdateItemMap)),
//this, SLOT(slotRefreshIconView(CHUpdateItemMap)));
//connect(d->historyUpdater, SIGNAL(signalBusy(bool)),
// this, SLOT(slotBusy(bool)));
// --------------------------------------------------------
d->progressTimer = new QTimer(this);
connect(d->progressTimer, SIGNAL(timeout()),
this, SLOT(slotProgressTimerDone()));
// --------------------------------------------------------
d->renameCustomizer->setStartIndex(startIndex);
d->view->setFocus();
// -- Init icon view zoom factor --------------------------
slotThumbSizeChanged(ImportSettings::instance()->getDefaultIconSize());
slotZoomSliderChanged(ImportSettings::instance()->getDefaultIconSize());
// try to connect in the end, this allows us not to block the UI to show up..
QTimer::singleShot(0, d->controller, SLOT(slotConnect()));
}
ImportUI::~ImportUI()
{
saveSettings();
m_instance = 0;
disconnect(d->view, 0, this, 0);
delete d->view;
delete d->rightSideBar;
delete d->controller;
delete d;
}
ImportUI* ImportUI::instance()
{
return m_instance;
}
void ImportUI::setupUserArea()
{
DHBox* const widget = new DHBox(this);
d->splitter = new SidebarSplitter(widget);
DVBox* const vbox = new DVBox(d->splitter);
d->view = new ImportView(this, vbox);
d->view->importFilterModel()->setCameraThumbsController(d->camThumbsCtrl);
d->view->importFilterModel()->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural());
d->historyView = new DHistoryView(vbox);
d->rightSideBar = new ImagePropertiesSideBarCamGui(widget, d->splitter, Qt::RightEdge, true);
d->rightSideBar->setObjectName(QLatin1String("CameraGui Sidebar Right"));
d->splitter->setFrameStyle(QFrame::NoFrame);
d->splitter->setFrameShadow(QFrame::Plain);
d->splitter->setFrameShape(QFrame::NoFrame);
d->splitter->setOpaqueResize(false);
d->splitter->setStretchFactor(0, 10); // set iconview default size to max.
vbox->setStretchFactor(d->view, 10);
vbox->setStretchFactor(d->historyView, 2);
vbox->setContentsMargins(QMargins());
vbox->setSpacing(0);
d->errorWidget = new DNotificationWidget(vbox);
d->errorWidget->setMessageType(DNotificationWidget::Error);
d->errorWidget->setCloseButtonVisible(false);
d->errorWidget->hide();
// -------------------------------------------------------------------------
d->advBox = new DExpanderBox(d->rightSideBar);
d->advBox->setObjectName(QLatin1String("Camera Settings Expander"));
d->renameCustomizer = new RenameCustomizer(d->advBox, d->cameraTitle);
d->renameCustomizer->setWhatsThis(i18n("Set how digiKam will rename files as they are downloaded."));
d->advBox->addItem(d->renameCustomizer, QIcon::fromTheme(QLatin1String("insert-image")), i18n("File Renaming Options"),
QLatin1String("RenameCustomizer"), true);
// -- Albums Auto-creation options -----------------------------------------
d->albumCustomizer = new AlbumCustomizer(d->advBox);
d->advBox->addItem(d->albumCustomizer, QIcon::fromTheme(QLatin1String("folder-new")), i18n("Auto-creation of Albums"),
QLatin1String("AlbumBox"), false);
// -- On the Fly options ---------------------------------------------------
d->advancedSettings = new AdvancedSettings(d->advBox);
d->advBox->addItem(d->advancedSettings, QIcon::fromTheme(QLatin1String("system-run")), i18n("On the Fly Operations (JPEG only)"),
QLatin1String("OnFlyBox"), true);
// -- DNG convert options --------------------------------------------------
d->dngConvertSettings = new DNGConvertSettings(d->advBox);
d->advBox->addItem(d->dngConvertSettings, QIcon::fromTheme(QLatin1String("image-x-adobe-dng")), i18n("DNG Convert Options"),
QLatin1String("DNGSettings"), false);
// -- Scripting options ----------------------------------------------------
d->scriptingSettings = new ScriptingSettings(d->advBox);
d->advBox->addItem(d->scriptingSettings, QIcon::fromTheme(QLatin1String("utilities-terminal")), i18n("Scripting"),
QLatin1String("ScriptingBox"), false);
d->advBox->addStretch();
d->rightSideBar->appendTab(d->advBox, QIcon::fromTheme(QLatin1String("configure")), i18n("Settings"));
d->rightSideBar->loadState();
// -------------------------------------------------------------------------
setCentralWidget(widget);
}
void ImportUI::setupActions()
{
d->cameraActions = new QActionGroup(this);
KActionCollection* const ac = actionCollection();
// -- File menu ----------------------------------------------------
d->cameraCancelAction = new QAction(QIcon::fromTheme(QLatin1String("process-stop")), i18nc("@action Cancel process", "Cancel"), this);
connect(d->cameraCancelAction, SIGNAL(triggered()), this, SLOT(slotCancelButton()));
ac->addAction(QLatin1String("importui_cancelprocess"), d->cameraCancelAction);
d->cameraCancelAction->setEnabled(false);
// -----------------------------------------------------------------
d->cameraInfoAction = new QAction(QIcon::fromTheme(QLatin1String("camera-photo")), i18nc("@action Information about camera", "Information"), this);
connect(d->cameraInfoAction, SIGNAL(triggered()), this, SLOT(slotInformation()));
ac->addAction(QLatin1String("importui_info"), d->cameraInfoAction);
d->cameraActions->addAction(d->cameraInfoAction);
// -----------------------------------------------------------------
d->cameraCaptureAction = new QAction(QIcon::fromTheme(QLatin1String("webcamreceive")), i18nc("@action Capture photo from camera", "Capture"), this);
connect(d->cameraCaptureAction, SIGNAL(triggered()), this, SLOT(slotCapture()));
ac->addAction(QLatin1String("importui_capture"), d->cameraCaptureAction);
d->cameraActions->addAction(d->cameraCaptureAction);
// -----------------------------------------------------------------
QAction* const closeAction = buildStdAction(StdCloseAction, this, SLOT(close()), this);
ac->addAction(QLatin1String("importui_close"), closeAction);
// -- Edit menu ----------------------------------------------------
d->selectAllAction = new QAction(i18nc("@action:inmenu", "Select All"), this);
connect(d->selectAllAction, SIGNAL(triggered()), d->view, SLOT(slotSelectAll()));
ac->addAction(QLatin1String("importui_selectall"), d->selectAllAction);
ac->setDefaultShortcut(d->selectAllAction, Qt::CTRL + Qt::Key_A);
d->cameraActions->addAction(d->selectAllAction);
// -----------------------------------------------------------------
d->selectNoneAction = new QAction(i18nc("@action:inmenu", "Select None"), this);
connect(d->selectNoneAction, SIGNAL(triggered()), d->view, SLOT(slotSelectNone()));
ac->addAction(QLatin1String("importui_selectnone"), d->selectNoneAction);
ac->setDefaultShortcut(d->selectNoneAction, Qt::CTRL + Qt::SHIFT + Qt::Key_A);
d->cameraActions->addAction(d->selectNoneAction);
// -----------------------------------------------------------------
d->selectInvertAction = new QAction(i18nc("@action:inmenu", "Invert Selection"), this);
connect(d->selectInvertAction, SIGNAL(triggered()), d->view, SLOT(slotSelectInvert()));
ac->addAction(QLatin1String("importui_selectinvert"), d->selectInvertAction);
ac->setDefaultShortcut(d->selectInvertAction, Qt::CTRL + Qt::Key_Asterisk);
d->cameraActions->addAction(d->selectInvertAction);
// -----------------------------------------------------------
d->selectNewItemsAction = new QAction(QIcon::fromTheme(QLatin1String("folder-favorites")), i18nc("@action:inmenu", "Select New Items"), this);
connect(d->selectNewItemsAction, SIGNAL(triggered()), this, SLOT(slotSelectNew()));
ac->addAction(QLatin1String("importui_selectnewitems"), d->selectNewItemsAction);
d->cameraActions->addAction(d->selectNewItemsAction);
// -----------------------------------------------------------
d->selectLockedItemsAction = new QAction(QIcon::fromTheme(QLatin1String("object-locked")), i18nc("@action:inmenu", "Select Locked Items"), this);
connect(d->selectLockedItemsAction, SIGNAL(triggered()), this, SLOT(slotSelectLocked()));
ac->addAction(QLatin1String("importui_selectlockeditems"), d->selectLockedItemsAction);
ac->setDefaultShortcut(d->selectLockedItemsAction, Qt::CTRL + Qt::Key_L);
d->cameraActions->addAction(d->selectLockedItemsAction);
// --- Download actions ----------------------------------------------------
d->downloadAction = new QMenu(i18nc("@title:menu", "Download"), this);
d->downloadAction->setIcon(QIcon::fromTheme(QLatin1String("document-save")));
ac->addAction(QLatin1String("importui_imagedownload"), d->downloadAction->menuAction());
d->cameraActions->addAction(d->downloadAction->menuAction());
d->downloadNewAction = new QAction(QIcon::fromTheme(QLatin1String("folder-favorites")), i18nc("@action", "Download New"), this);
connect(d->downloadNewAction, SIGNAL(triggered()), this, SLOT(slotDownloadNew()));
ac->addAction(QLatin1String("importui_imagedownloadnew"), d->downloadNewAction);
ac->setDefaultShortcut(d->downloadNewAction, Qt::CTRL + Qt::Key_N);
d->downloadAction->addAction(d->downloadNewAction);
d->cameraActions->addAction(d->downloadNewAction);
d->downloadSelectedAction = new QAction(QIcon::fromTheme(QLatin1String("document-save")), i18nc("@action", "Download Selected"), this);
connect(d->downloadSelectedAction, SIGNAL(triggered()), this, SLOT(slotDownloadSelected()));
ac->addAction(QLatin1String("importui_imagedownloadselected"), d->downloadSelectedAction);
d->downloadSelectedAction->setEnabled(false);
d->downloadAction->addAction(d->downloadSelectedAction);
d->cameraActions->addAction(d->downloadSelectedAction);
d->downloadAllAction = new QAction(QIcon::fromTheme(QLatin1String("document-save")), i18nc("@action", "Download All"), this);
connect(d->downloadAllAction, SIGNAL(triggered()), this, SLOT(slotDownloadAll()));
ac->addAction(QLatin1String("importui_imagedownloadall"), d->downloadAllAction);
d->downloadAction->addAction(d->downloadAllAction);
d->cameraActions->addAction(d->downloadAllAction);
// -------------------------------------------------------------------------
d->downloadDelNewAction = new QAction(i18nc("@action", "Download && Delete New"), this);
connect(d->downloadDelNewAction, SIGNAL(triggered()), this, SLOT(slotDownloadAndDeleteNew()));
ac->addAction(QLatin1String("importui_imagedownloaddeletenew"), d->downloadDelNewAction);
ac->setDefaultShortcut(d->downloadDelNewAction, Qt::CTRL + Qt::SHIFT + Qt::Key_N);
d->cameraActions->addAction(d->downloadDelNewAction);
// -----------------------------------------------------------------
d->downloadDelSelectedAction = new QAction(i18nc("@action", "Download && Delete Selected"), this);
connect(d->downloadDelSelectedAction, SIGNAL(triggered()), this, SLOT(slotDownloadAndDeleteSelected()));
ac->addAction(QLatin1String("importui_imagedownloaddeleteselected"), d->downloadDelSelectedAction);
d->downloadDelSelectedAction->setEnabled(false);
d->cameraActions->addAction(d->downloadDelSelectedAction);
// -------------------------------------------------------------------------
d->downloadDelAllAction = new QAction(i18nc("@action", "Download && Delete All"), this);
connect(d->downloadDelAllAction, SIGNAL(triggered()), this, SLOT(slotDownloadAndDeleteAll()));
ac->addAction(QLatin1String("importui_imagedownloaddeleteall"), d->downloadDelAllAction);
d->cameraActions->addAction(d->downloadDelAllAction);
// -------------------------------------------------------------------------
d->uploadAction = new QAction(QIcon::fromTheme(QLatin1String("media-flash-sd-mmc")), i18nc("@action", "Upload..."), this);
connect(d->uploadAction, SIGNAL(triggered()), this, SLOT(slotUpload()));
ac->addAction(QLatin1String("importui_imageupload"), d->uploadAction);
ac->setDefaultShortcut(d->uploadAction, Qt::CTRL + Qt::Key_U);
d->cameraActions->addAction(d->uploadAction);
// -------------------------------------------------------------------------
d->lockAction = new QAction(QIcon::fromTheme(QLatin1String("object-locked")), i18nc("@action", "Toggle Lock"), this);
connect(d->lockAction, SIGNAL(triggered()), this, SLOT(slotToggleLock()));
ac->addAction(QLatin1String("importui_imagelock"), d->lockAction);
ac->setDefaultShortcut(d->lockAction, Qt::CTRL + Qt::Key_G);
d->cameraActions->addAction(d->lockAction);
// -------------------------------------------------------------------------
d->markAsDownloadedAction = new QAction(QIcon::fromTheme(QLatin1String("dialog-ok-apply")), i18nc("@action", "Mark as downloaded"), this);
connect(d->markAsDownloadedAction, SIGNAL(triggered()), this, SLOT(slotMarkAsDownloaded()));
ac->addAction(QLatin1String("importui_imagemarkasdownloaded"), d->markAsDownloadedAction);
d->cameraActions->addAction(d->markAsDownloadedAction);
// --- Delete actions ------------------------------------------------------
d->deleteAction = new QMenu(i18nc("@title:menu", "Delete"), this);
d->deleteAction->setIcon(QIcon::fromTheme(QLatin1String("user-trash")));
ac->addAction(QLatin1String("importui_delete"), d->deleteAction->menuAction());
d->cameraActions->addAction(d->deleteAction->menuAction());
d->deleteSelectedAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18nc("@action", "Delete Selected"), this);
connect(d->deleteSelectedAction, SIGNAL(triggered()), this, SLOT(slotDeleteSelected()));
ac->addAction(QLatin1String("importui_imagedeleteselected"), d->deleteSelectedAction);
ac->setDefaultShortcut(d->deleteSelectedAction, Qt::Key_Delete);
d->deleteSelectedAction->setEnabled(false);
d->deleteAction->addAction(d->deleteSelectedAction);
d->cameraActions->addAction(d->deleteSelectedAction);
d->deleteAllAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18nc("@action", "Delete All"), this);
connect(d->deleteAllAction, SIGNAL(triggered()), this, SLOT(slotDeleteAll()));
ac->addAction(QLatin1String("importui_imagedeleteall"), d->deleteAllAction);
d->deleteAction->addAction(d->deleteAllAction);
d->cameraActions->addAction(d->deleteAllAction);
d->deleteNewAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18nc("@action", "Delete New"), this);
connect(d->deleteNewAction, SIGNAL(triggered()), this, SLOT(slotDeleteNew()));
ac->addAction(QLatin1String("importui_imagedeletenew"), d->deleteNewAction);
d->deleteAction->addAction(d->deleteNewAction);
d->cameraActions->addAction(d->deleteNewAction);
// --- Icon view, items preview, and map actions ------------------------------------------------------
d->imageViewSelectionAction = new KSelectAction(QIcon::fromTheme(QLatin1String("view-preview")), i18nc("@title:group", "Views"), this);
ac->addAction(QLatin1String("importui_view_selection"), d->imageViewSelectionAction);
d->iconViewAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-icons")),
i18nc("@action Go to thumbnails (icon) view", "Thumbnails"), this);
d->iconViewAction->setCheckable(true);
ac->addAction(QLatin1String("importui_icon_view"), d->iconViewAction);
connect(d->iconViewAction, SIGNAL(triggered()), d->view, SLOT(slotIconView()));
d->imageViewSelectionAction->addAction(d->iconViewAction);
d->camItemPreviewAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")),
i18nc("@action View the selected image", "Preview Item"), this);
d->camItemPreviewAction->setCheckable(true);
ac->addAction(QLatin1String("importui_item_view"), d->camItemPreviewAction);
ac->setDefaultShortcut(d->camItemPreviewAction, Qt::Key_F3);
connect(d->camItemPreviewAction, SIGNAL(triggered()), d->view, SLOT(slotImagePreview()));
d->imageViewSelectionAction->addAction(d->camItemPreviewAction);
#ifdef HAVE_MARBLE
d->mapViewAction = new QAction(QIcon::fromTheme(QLatin1String("globe")),
i18nc("@action Switch to map view", "Map"), this);
d->mapViewAction->setCheckable(true);
ac->addAction(QLatin1String("importui_map_view"), d->mapViewAction);
connect(d->mapViewAction, SIGNAL(triggered()), d->view, SLOT(slotMapWidgetView()));
d->imageViewSelectionAction->addAction(d->mapViewAction);
#endif // HAVE_MARBLE
/// @todo Add table view stuff here
// -- Item Sorting ------------------------------------------------------------
d->itemSortAction = new KSelectAction(i18nc("@title:menu", "&Sort Items"), this);
d->itemSortAction->setWhatsThis(i18nc("@info:whatsthis", "The value by which the items are sorted in the thumbnail view"));
QSignalMapper* const imageSortMapper = new QSignalMapper(this);
connect(imageSortMapper, SIGNAL(mapped(int)), d->view, SLOT(slotSortImagesBy(int)));
ac->addAction(QLatin1String("item_sort"), d->itemSortAction);
// map to CamItemSortSettings enum
QAction* const sortByNameAction = d->itemSortAction->addAction(i18nc("item:inmenu Sort by", "By Name"));
QAction* const sortByPathAction = d->itemSortAction->addAction(i18nc("item:inmenu Sort by", "By Path"));
QAction* const sortByDateAction = d->itemSortAction->addAction(i18nc("item:inmenu Sort by", "By Date")); //TODO: Implement sort by creation date.
QAction* const sortByFileSizeAction = d->itemSortAction->addAction(i18nc("item:inmenu Sort by", "By Size"));
QAction* const sortByRatingAction = d->itemSortAction->addAction(i18nc("item:inmenu Sort by", "By Rating"));
QAction* const sortByDownloadAction = d->itemSortAction->addAction(i18nc("item:inmenu Sort by", "By Download State"));
connect(sortByNameAction, SIGNAL(triggered()), imageSortMapper, SLOT(map()));
connect(sortByPathAction, SIGNAL(triggered()), imageSortMapper, SLOT(map()));
connect(sortByDateAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); //TODO: Implement sort by creation date.
connect(sortByFileSizeAction, SIGNAL(triggered()), imageSortMapper, SLOT(map()));
connect(sortByRatingAction, SIGNAL(triggered()), imageSortMapper, SLOT(map()));
connect(sortByDownloadAction, SIGNAL(triggered()), imageSortMapper, SLOT(map()));
imageSortMapper->setMapping(sortByNameAction, (int)CamItemSortSettings::SortByFileName);
imageSortMapper->setMapping(sortByPathAction, (int)CamItemSortSettings::SortByFilePath);
imageSortMapper->setMapping(sortByDateAction, (int)CamItemSortSettings::SortByCreationDate); //TODO: Implement sort by creation date.
imageSortMapper->setMapping(sortByFileSizeAction, (int)CamItemSortSettings::SortByFileSize);
imageSortMapper->setMapping(sortByRatingAction, (int)CamItemSortSettings::SortByRating);
imageSortMapper->setMapping(sortByDownloadAction, (int)CamItemSortSettings::SortByDownloadState);
d->itemSortAction->setCurrentItem(ImportSettings::instance()->getImageSortBy());
// -- Item Sort Order ------------------------------------------------------------
d->itemSortOrderAction = new KSelectAction(i18nc("@title:inmenu", "Item Sorting &Order"), this);
d->itemSortOrderAction->setWhatsThis(i18nc("@info:whatsthis", "Defines whether items are sorted in ascending or descending manner."));
QSignalMapper* const imageSortOrderMapper = new QSignalMapper(this);
connect(imageSortOrderMapper, SIGNAL(mapped(int)), d->view, SLOT(slotSortImagesOrder(int)));
ac->addAction(QLatin1String("item_sort_order"), d->itemSortOrderAction);
QAction* const sortAscendingAction = d->itemSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-ascending")), i18nc("@item:inmenu Sorting Order", "Ascending"));
QAction* const sortDescendingAction = d->itemSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-descending")), i18nc("@item:inmenu Sorting Order", "Descending"));
connect(sortAscendingAction, SIGNAL(triggered()), imageSortOrderMapper, SLOT(map()));
connect(sortDescendingAction, SIGNAL(triggered()), imageSortOrderMapper, SLOT(map()));
imageSortOrderMapper->setMapping(sortAscendingAction, (int)CamItemSortSettings::AscendingOrder);
imageSortOrderMapper->setMapping(sortDescendingAction, (int)CamItemSortSettings::DescendingOrder);
d->itemSortOrderAction->setCurrentItem(ImportSettings::instance()->getImageSortOrder());
// -- Item Grouping ------------------------------------------------------------
d->itemsGroupAction = new KSelectAction(i18nc("@title:menu", "&Group Items"), this);
d->itemsGroupAction->setWhatsThis(i18nc("@info:whatsthis", "The categories in which the items in the thumbnail view are displayed"));
QSignalMapper* const itemSeparationMapper = new QSignalMapper(this);
connect(itemSeparationMapper, SIGNAL(mapped(int)), d->view, SLOT(slotSeparateImages(int)));
ac->addAction(QLatin1String("item_group"), d->itemsGroupAction);
// map to CamItemSortSettings enum
QAction* const noCategoriesAction = d->itemsGroupAction->addAction(i18nc("@item:inmenu Group Items", "Flat List"));
QAction* const groupByFolderAction = d->itemsGroupAction->addAction(i18nc("@item:inmenu Group Items", "By Folder"));
QAction* const groupByFormatAction = d->itemsGroupAction->addAction(i18nc("@item:inmenu Group Items", "By Format"));
QAction* const groupByDateAction = d->itemsGroupAction->addAction(i18nc("@item:inmenu Group Items", "By Date"));
connect(noCategoriesAction, SIGNAL(triggered()), itemSeparationMapper, SLOT(map()));
connect(groupByFolderAction, SIGNAL(triggered()), itemSeparationMapper, SLOT(map()));
connect(groupByFormatAction, SIGNAL(triggered()), itemSeparationMapper, SLOT(map()));
connect(groupByDateAction, SIGNAL(triggered()), itemSeparationMapper, SLOT(map()));
itemSeparationMapper->setMapping(noCategoriesAction, (int)CamItemSortSettings::NoCategories);
itemSeparationMapper->setMapping(groupByFolderAction, (int)CamItemSortSettings::CategoryByFolder);
itemSeparationMapper->setMapping(groupByFormatAction, (int)CamItemSortSettings::CategoryByFormat);
itemSeparationMapper->setMapping(groupByDateAction, (int)CamItemSortSettings::CategoryByDate);
d->itemsGroupAction->setCurrentItem(ImportSettings::instance()->getImageSeparationMode());
// -- Standard 'View' menu actions ---------------------------------------------
d->increaseThumbsAction = buildStdAction(StdZoomInAction, d->view, SLOT(slotZoomIn()), this);
d->increaseThumbsAction->setEnabled(false);
QKeySequence keysPlus(d->increaseThumbsAction->shortcut()[0], Qt::Key_Plus);
ac->addAction(QLatin1String("importui_zoomplus"), d->increaseThumbsAction);
ac->setDefaultShortcut(d->increaseThumbsAction, keysPlus);
d->decreaseThumbsAction = buildStdAction(StdZoomOutAction, d->view, SLOT(slotZoomOut()), this);
d->decreaseThumbsAction->setEnabled(false);
QKeySequence keysMinus(d->decreaseThumbsAction->shortcut()[0], Qt::Key_Minus);
ac->addAction(QLatin1String("importui_zoomminus"), d->decreaseThumbsAction);
ac->setDefaultShortcut(d->decreaseThumbsAction, keysMinus);
d->zoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18nc("@action:inmenu", "Fit to &Window"), this);
connect(d->zoomFitToWindowAction, SIGNAL(triggered()), d->view, SLOT(slotFitToWindow()));
ac->addAction(QLatin1String("import_zoomfit2window"), d->zoomFitToWindowAction);
ac->setDefaultShortcut(d->zoomFitToWindowAction, Qt::ALT + Qt::CTRL + Qt::Key_E);
d->zoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18nc("@action:inmenu", "Zoom to 100%"), this);
connect(d->zoomTo100percents, SIGNAL(triggered()), d->view, SLOT(slotZoomTo100Percents()));
ac->addAction(QLatin1String("import_zoomto100percents"), d->zoomTo100percents);
ac->setDefaultShortcut(d->zoomTo100percents, Qt::CTRL + Qt::Key_Period);
// ------------------------------------------------------------------------------------------------
d->viewCMViewAction = new QAction(QIcon::fromTheme(QLatin1String("video-display")), i18n("Color-Managed View"), this);
d->viewCMViewAction->setCheckable(true);
connect(d->viewCMViewAction, SIGNAL(triggered()), this, SLOT(slotToggleColorManagedView()));
ac->addAction(QLatin1String("color_managed_view"), d->viewCMViewAction);
ac->setDefaultShortcut(d->viewCMViewAction, Qt::Key_F12);
// ------------------------------------------------------------------------------------------------
createFullScreenAction(QLatin1String("importui_fullscreen"));
createSidebarActions();
d->showLogAction = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18nc("@option:check", "Show History"), this);
d->showLogAction->setCheckable(true);
connect(d->showLogAction, SIGNAL(triggered()), this, SLOT(slotShowLog()));
ac->addAction(QLatin1String("importui_showlog"), d->showLogAction);
ac->setDefaultShortcut(d->showLogAction, Qt::CTRL + Qt::Key_H);
d->showBarAction = new QAction(QIcon::fromTheme(QLatin1String("view-choose")), i18nc("@option:check", "Show Thumbbar"), this);
d->showBarAction->setCheckable(true);
connect(d->showBarAction, SIGNAL(triggered()), this, SLOT(slotToggleShowBar()));
ac->addAction(QLatin1String("showthumbs"), d->showBarAction);
ac->setDefaultShortcut(d->showBarAction, Qt::CTRL+Qt::Key_T);
d->showBarAction->setEnabled(false);
// ---------------------------------------------------------------------------------
ThemeManager::instance()->registerThemeActions(this);
// Standard 'Help' menu actions
createHelpActions();
// Provides a menu entry that allows showing/hiding the toolbar(s)
setStandardToolBarMenuEnabled(true);
// Provides a menu entry that allows showing/hiding the statusbar
createStandardStatusBarAction();
// Standard 'Configure' menu actions
createSettingsActions();
// -- Keyboard-only actions added to <MainWindow> ----------------------------------
QAction* const altBackwardAction = new QAction(i18nc("@action", "Previous Image"), this);
ac->addAction(QLatin1String("importui_backward_shift_space"), altBackwardAction);
ac->setDefaultShortcut(altBackwardAction, Qt::SHIFT + Qt::Key_Space);
connect(altBackwardAction, SIGNAL(triggered()), d->view, SLOT(slotPrevItem()));
// ---------------------------------------------------------------------------------
d->connectAction = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18nc("@action Connection failed, try again?", "Retry"), this);
connect(d->connectAction, SIGNAL(triggered()), d->controller, SLOT(slotConnect()));
createGUI(xmlFile());
cleanupActions();
showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080
}
void ImportUI::updateActions()
{
CamItemInfoList list = d->view->selectedCamItemInfos();
bool hasSelection = list.count() > 0;
d->downloadDelSelectedAction->setEnabled(hasSelection && d->controller->cameraDeleteSupport());
d->deleteSelectedAction->setEnabled(hasSelection && d->controller->cameraDeleteSupport());
d->camItemPreviewAction->setEnabled(hasSelection && cameraUseUMSDriver());
d->downloadSelectedAction->setEnabled(hasSelection);
d->lockAction->setEnabled(hasSelection);
if (hasSelection)
{
// only enable "Mark as downloaded" if at least one
// selected image has not been downloaded
bool haveNotDownloadedItem = false;
foreach (const CamItemInfo& info, list)
{
haveNotDownloadedItem = !(info.downloaded == CamItemInfo::DownloadedYes);
if (haveNotDownloadedItem)
{
break;
}
}
d->markAsDownloadedAction->setEnabled(haveNotDownloadedItem);
}
else
{
d->markAsDownloadedAction->setEnabled(false);
}
}
void ImportUI::setupConnections()
{
//TODO: Needs testing.
connect(d->advancedSettings, SIGNAL(signalDownloadNameChanged()),
this, SLOT(slotUpdateDownloadName()));
connect(d->dngConvertSettings, SIGNAL(signalDownloadNameChanged()),
this, SLOT(slotUpdateDownloadName()));
connect(d->historyView, SIGNAL(signalEntryClicked(QVariant)),
this, SLOT(slotHistoryEntryClicked(QVariant)));
connect(IccSettings::instance(), SIGNAL(settingsChanged()),
this, SLOT(slotColorManagementOptionsChanged()));
// -------------------------------------------------------------------------
connect(d->view, SIGNAL(signalImageSelected(CamItemInfoList,CamItemInfoList)),
this, SLOT(slotImageSelected(CamItemInfoList,CamItemInfoList)));
connect(d->view, SIGNAL(signalSwitchedToPreview()),
this, SLOT(slotSwitchedToPreview()));
connect(d->view, SIGNAL(signalSwitchedToIconView()),
this, SLOT(slotSwitchedToIconView()));
connect(d->view, SIGNAL(signalSwitchedToMapView()),
this, SLOT(slotSwitchedToMapView()));
connect(d->view, SIGNAL(signalNewSelection(bool)),
this, SLOT(slotNewSelection(bool)));
// -------------------------------------------------------------------------
connect(d->view, SIGNAL(signalThumbSizeChanged(int)),
this, SLOT(slotThumbSizeChanged(int)));
connect(d->view, SIGNAL(signalZoomChanged(double)),
this, SLOT(slotZoomChanged(double)));
connect(d->zoomBar, SIGNAL(signalZoomSliderChanged(int)),
this, SLOT(slotZoomSliderChanged(int)));
connect(d->zoomBar, SIGNAL(signalZoomValueEdited(double)),
d->view, SLOT(setZoomFactor(double)));
connect(this, SIGNAL(signalWindowHasMoved()),
d->zoomBar, SLOT(slotUpdateTrackerPos()));
// -------------------------------------------------------------------------
connect(CollectionManager::instance(), SIGNAL(locationStatusChanged(CollectionLocation,int)),
this, SLOT(slotCollectionLocationStatusChanged(CollectionLocation,int)));
connect(ApplicationSettings::instance(), SIGNAL(setupChanged()),
this, SLOT(slotSetupChanged()));
connect(d->renameCustomizer, SIGNAL(signalChanged()),
this, SLOT(slotUpdateDownloadName()));
}
void ImportUI::setupStatusBar()
{
d->statusProgressBar = new StatusProgressBar(statusBar());
d->statusProgressBar->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
d->statusProgressBar->setNotificationTitle(d->cameraTitle, QIcon::fromTheme(QLatin1String("camera-photo")));
statusBar()->addWidget(d->statusProgressBar, 100);
//------------------------------------------------------------------------------
d->cameraFreeSpace = new FreeSpaceWidget(statusBar(), 100);
if (cameraUseGPhotoDriver())
{
d->cameraFreeSpace->setMode(FreeSpaceWidget::GPhotoCamera);
connect(d->controller, SIGNAL(signalFreeSpace(ulong,ulong)),
this, SLOT(slotCameraFreeSpaceInfo(ulong,ulong)));
}
else
{
d->cameraFreeSpace->setMode(FreeSpaceWidget::UMSCamera);
d->cameraFreeSpace->setPath(d->controller->cameraPath());
}
statusBar()->addWidget(d->cameraFreeSpace, 1);
//------------------------------------------------------------------------------
d->albumLibraryFreeSpace = new FreeSpaceWidget(statusBar(), 100);
d->albumLibraryFreeSpace->setMode(FreeSpaceWidget::AlbumLibrary);
statusBar()->addWidget(d->albumLibraryFreeSpace, 1);
refreshCollectionFreeSpace();
//------------------------------------------------------------------------------
- //TODO: Replace it with FilterStatusBar after advanced filtring is implemented.
+ //TODO: Replace it with FilterStatusBar after advanced filtering is implemented.
d->filterComboBox = new FilterComboBox(statusBar());
setFilter(d->filterComboBox->currentFilter());
statusBar()->addWidget(d->filterComboBox, 1);
connect(d->filterComboBox, SIGNAL(filterChanged(Filter*)), this, SLOT(setFilter(Filter*)));
//------------------------------------------------------------------------------
d->zoomBar = new DZoomBar(statusBar());
d->zoomBar->setZoomToFitAction(d->zoomFitToWindowAction);
d->zoomBar->setZoomTo100Action(d->zoomTo100percents);
d->zoomBar->setZoomPlusAction(d->increaseThumbsAction);
d->zoomBar->setZoomMinusAction(d->decreaseThumbsAction);
d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl);
statusBar()->addPermanentWidget(d->zoomBar, 1);
}
void ImportUI::setupCameraController(const QString& model, const QString& port, const QString& path)
{
d->controller = new CameraController(this, d->cameraTitle, model, port, path);
connect(d->controller, SIGNAL(signalConnected(bool)),
this, SLOT(slotConnected(bool)));
connect(d->controller, SIGNAL(signalLogMsg(QString,DHistoryView::EntryType,QString,QString)),
this, SLOT(slotLogMsg(QString,DHistoryView::EntryType,QString,QString)));
connect(d->controller, SIGNAL(signalCameraInformation(QString,QString,QString)),
this, SLOT(slotCameraInformation(QString,QString,QString)));
connect(d->controller, SIGNAL(signalBusy(bool)),
this, SLOT(slotBusy(bool)));
connect(d->controller, SIGNAL(signalFolderList(QStringList)),
this, SLOT(slotFolderList(QStringList)));
connect(d->controller, SIGNAL(signalDownloaded(QString,QString,int)),
this, SLOT(slotDownloaded(QString,QString,int)));
connect(d->controller, SIGNAL(signalDownloadComplete(QString,QString,QString,QString)),
this, SLOT(slotDownloadComplete(QString,QString,QString,QString)));
connect(d->controller, SIGNAL(signalSkipped(QString,QString)),
this, SLOT(slotSkipped(QString,QString)));
connect(d->controller, SIGNAL(signalDeleted(QString,QString,bool)),
this, SLOT(slotDeleted(QString,QString,bool)));
connect(d->controller, SIGNAL(signalLocked(QString,QString,bool)),
this, SLOT(slotLocked(QString,QString,bool)));
connect(d->controller, SIGNAL(signalMetadata(QString,QString,DMetadata)),
this, SLOT(slotMetadata(QString,QString,DMetadata)));
connect(d->controller, SIGNAL(signalUploaded(CamItemInfo)),
this, SLOT(slotUploaded(CamItemInfo)));
d->controller->start();
// Setup Thumbnails controller -------------------------------------------------------
d->camThumbsCtrl = new CameraThumbsCtrl(d->controller, this);
}
CameraThumbsCtrl* ImportUI::getCameraThumbsCtrl() const
{
return d->camThumbsCtrl;
}
void ImportUI::setupAccelerators()
{
KActionCollection* const ac = actionCollection();
QAction* const escapeAction = new QAction(i18nc("@action", "Exit Preview Mode"), this);
ac->addAction(QLatin1String("exit_preview_mode"), escapeAction);
ac->setDefaultShortcut(escapeAction, Qt::Key_Escape);
connect(escapeAction, SIGNAL(triggered()), this, SIGNAL(signalEscapePressed()));
QAction* const nextImageAction = new QAction(i18nc("@action","Next Image"), this);
nextImageAction->setIcon(QIcon::fromTheme(QLatin1String("go-next")));
ac->addAction(QLatin1String("next_image"), nextImageAction);
ac->setDefaultShortcut(nextImageAction, Qt::Key_Space);
connect(nextImageAction, SIGNAL(triggered()), d->view, SLOT(slotNextItem()));
QAction* const previousImageAction = new QAction(i18nc("@action", "Previous Image"), this);
previousImageAction->setIcon(QIcon::fromTheme(QLatin1String("go-previous")));
ac->addAction(QLatin1String("previous_image"), previousImageAction);
ac->setDefaultShortcuts(previousImageAction, QList<QKeySequence>() << Qt::Key_Backspace << Qt::SHIFT+Qt::Key_Space);
connect(previousImageAction, SIGNAL(triggered()), d->view, SLOT(slotPrevItem()));
QAction* const firstImageAction = new QAction(i18nc("@action Go to first image", "First Image"), this);
ac->addAction(QLatin1String("first_image"), firstImageAction);
ac->setDefaultShortcut(firstImageAction, Qt::Key_Home);
connect(firstImageAction, SIGNAL(triggered()), d->view, SLOT(slotFirstItem()));
QAction* const lastImageAction = new QAction(i18nc("@action Go to last image", "Last Image"), this);
ac->addAction(QLatin1String("last_image"), lastImageAction);
ac->setDefaultShortcut(lastImageAction, Qt::Key_End);
connect(lastImageAction, SIGNAL(triggered()), d->view, SLOT(slotLastItem()));
}
void ImportUI::readSettings()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
readFullScreenSettings(group);
d->showBarAction->setChecked(ImportSettings::instance()->getShowThumbbar());
d->showLogAction->setChecked(group.readEntry(QLatin1String("ShowLog"), false));
d->albumCustomizer->readSettings(group);
d->advancedSettings->readSettings(group);
d->dngConvertSettings->readSettings(group);
d->scriptingSettings->readSettings(group);
d->advBox->readSettings(group);
d->splitter->restoreState(group);
slotShowLog();
}
void ImportUI::saveSettings()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
ImportSettings::instance()->setShowThumbbar(d->showBarAction->isChecked());
ImportSettings::instance()->saveSettings();
group.writeEntry(QLatin1String("ShowLog"), d->showLogAction->isChecked());
d->albumCustomizer->saveSettings(group);
d->advancedSettings->saveSettings(group);
d->dngConvertSettings->saveSettings(group);
d->scriptingSettings->saveSettings(group);
d->advBox->writeSettings(group);
d->rightSideBar->saveState();
d->splitter->saveState(group);
d->filterComboBox->saveSettings();
config->sync();
}
bool ImportUI::isBusy() const
{
return d->busy;
}
bool ImportUI::isClosed() const
{
return d->closed;
}
QString ImportUI::cameraTitle() const
{
return d->cameraTitle;
}
DownloadSettings ImportUI::downloadSettings() const
{
DownloadSettings settings = d->advancedSettings->settings();
d->dngConvertSettings->settings(&settings);
d->scriptingSettings->settings(&settings);
return settings;
}
void ImportUI::setInitialSorting()
{
d->view->slotSeparateImages(ImportSettings::instance()->getImageSeparationMode());
d->view->slotSortImagesBy(ImportSettings::instance()->getImageSortBy());
d->view->slotSortImagesOrder(ImportSettings::instance()->getImageSortOrder());
}
void ImportUI::slotCancelButton()
{
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18nc("@info:status", "Canceling current operation, please wait..."));
d->controller->slotCancel();
//d->historyUpdater->slotCancel();
d->currentlyDeleting.clear();
refreshFreeSpace();
}
void ImportUI::refreshFreeSpace()
{
if (cameraUseGPhotoDriver())
{
d->controller->getFreeSpace();
}
else
{
d->cameraFreeSpace->refresh();
}
}
void ImportUI::closeEvent(QCloseEvent* e)
{
if (dialogClosed())
{
DXmlGuiWindow::closeEvent(e);
}
else
{
e->ignore();
}
}
void ImportUI::moveEvent(QMoveEvent* e)
{
Q_UNUSED(e)
emit signalWindowHasMoved();
}
void ImportUI::slotClose()
{
/* FIXME
if (dialogClosed())
reject();
*/
}
bool ImportUI::dialogClosed()
{
if (d->closed)
{
return true;
}
if (isBusy())
{
if (QMessageBox::question(this, qApp->applicationName(),
i18nc("@info", "Do you want to close the dialog "
"and cancel the current operation?"),
QMessageBox::Yes | QMessageBox::No
) == QMessageBox::No)
{
return false;
}
}
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18nc("@info:status", "Disconnecting from camera, please wait..."));
if (isBusy())
{
d->controller->slotCancel();
// will be read in slotBusy later and finishDialog
// will be called only when everything is finished
d->closed = true;
}
else
{
d->closed = true;
finishDialog();
}
return true;
}
void ImportUI::finishDialog()
{
// Look if an item have been downloaded to computer during camera GUI session.
// If yes, update the starting number value used to rename camera items from camera list.
if (d->view->downloadedCamItemInfos() > 0)
{
CameraList* const clist = CameraList::defaultList();
if (clist)
{
clist->changeCameraStartIndex(d->cameraTitle, d->renameCustomizer->startIndex());
}
}
if (!d->foldersToScan.isEmpty())
{
// TODO is this note valid anymore with new progress handling?
// When a directory is created, a watch is put on it to spot new files
// but it can occur that the file is copied there before the watch is
// completely setup. That is why as an extra safeguard run CollectionScanner
// over the folders we used. Bug: 119201
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18nc("@info:status", "Scanning for new files, please wait..."));
NewItemsFinder* const tool = new NewItemsFinder(NewItemsFinder::ScheduleCollectionScan, d->foldersToScan.toList());
tool->start();
d->foldersToScan.clear();
}
deleteLater();
if (!d->lastDestURL.isEmpty())
{
emit signalLastDestination(d->lastDestURL);
}
saveSettings();
}
void ImportUI::slotBusy(bool val)
{
if (!val) // Camera is available for actions.
{
if (!d->busy)
{
return;
}
d->busy = false;
d->cameraCancelAction->setEnabled(false);
d->cameraActions->setEnabled(true);
d->advBox->setEnabled(true);
d->view->setEnabled(true);
// selection-dependent update of lockAction, markAsDownloadedAction,
// downloadSelectedAction, downloadDelSelectedAction, deleteSelectedAction
updateActions();
m_animLogo->stop();
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode, i18nc("@info:status", "Ready"));
// like WDestructiveClose, but after camera controller operation has safely finished
if (d->closed)
{
finishDialog();
}
}
else // Camera is busy.
{
if (d->busy)
{
return;
}
if (!m_animLogo->running())
{
m_animLogo->start();
}
d->busy = true;
d->cameraActions->setEnabled(false);
}
}
void ImportUI::slotZoomSliderChanged(int size)
{
d->view->setThumbSize(size);
}
void ImportUI::slotZoomChanged(double zoom)
{
double zmin = d->view->zoomMin();
double zmax = d->view->zoomMax();
d->zoomBar->setZoom(zoom, zmin, zmax);
if (!fullScreenIsActive())
{
d->zoomBar->triggerZoomTrackerToolTip();
}
}
void ImportUI::slotThumbSizeChanged(int size)
{
d->zoomBar->setThumbsSize(size);
if (!fullScreenIsActive())
{
d->zoomBar->triggerZoomTrackerToolTip();
}
}
void ImportUI::slotConnected(bool val)
{
if (!val)
{
d->errorWidget->setText(i18nc("@info", "Failed to connect to the camera. "
"Please make sure it is connected "
"properly and turned on."));
d->errorWidget->actions().clear();
d->errorWidget->addAction(d->connectAction);
d->errorWidget->addAction(d->showPreferencesAction);
d->errorWidget->animatedShow();
}
else
{
// disable unsupported actions
d->uploadAction->setEnabled(d->controller->cameraUploadSupport());
d->cameraCaptureAction->setEnabled(d->controller->cameraCaptureImageSupport());
d->errorWidget->hide();
refreshFreeSpace();
// FIXME ugly c&p from slotFolderList
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
bool useMetadata = group.readEntry(d->configUseFileMetadata, false);
d->controller->listRootFolder(useMetadata);
}
}
void ImportUI::slotFolderList(const QStringList& folderList)
{
if (d->closed)
{
return;
}
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressTotalSteps(0);
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
bool useMetadata = group.readEntry(d->configUseFileMetadata, false);
// when getting a list of subfolders, request their contents and also their subfolders
for (QStringList::const_iterator it = folderList.constBegin() ;
it != folderList.constEnd() ; ++it)
{
d->controller->listFiles(*it, useMetadata);
d->controller->listFolders(*it);
}
}
void ImportUI::setFilter(Filter *filter)
{
d->view->importFilterModel()->setFilter(filter);
}
void ImportUI::slotCapture()
{
if (d->busy)
{
return;
}
CaptureDlg* const captureDlg = new CaptureDlg(this, d->controller, d->cameraTitle);
captureDlg->show();
}
void ImportUI::slotInformation()
{
if (d->busy)
{
return;
}
d->controller->getCameraInformation();
}
void ImportUI::slotCameraInformation(const QString& summary, const QString& manual, const QString& about)
{
CameraInfoDialog* const infoDlg = new CameraInfoDialog(this, summary, manual, about);
infoDlg->show();
}
void ImportUI::slotUpload()
{
if (d->busy)
{
return;
}
QList<QUrl> urls = ImageDialog::getImageURLs(this,
QUrl::fromLocalFile(CollectionManager::instance()->oneAlbumRootPath()),
i18nc("@title:window", "Select Image to Upload"));
if (!urls.isEmpty())
{
slotUploadItems(urls);
}
}
void ImportUI::slotUploadItems(const QList<QUrl>& urls)
{
if (d->busy)
{
return;
}
if (urls.isEmpty())
{
return;
}
if (d->cameraFreeSpace->isValid())
{
// Check if space require to upload new items in camera is enough.
quint64 totalKbSize = 0;
for (QList<QUrl>::const_iterator it = urls.constBegin() ; it != urls.constEnd() ; ++it)
{
QFileInfo fi((*it).toLocalFile());
totalKbSize += fi.size() / 1024;
}
if (totalKbSize >= d->cameraFreeSpace->kBAvail())
{
QMessageBox::critical(this, qApp->applicationName(),
i18nc("@info", "There is not enough free space on the Camera Medium "
"to upload pictures.\n\n"
"Space require: %1\n"
"Available free space: %2",
ImagePropertiesTab::humanReadableBytesCount(totalKbSize * 1024),
ImagePropertiesTab::humanReadableBytesCount(d->cameraFreeSpace->kBAvail() * 1024)));
return;
}
}
QMap<QString, int> map = countItemsByFolders();
QPointer<CameraFolderDialog> dlg = new CameraFolderDialog(this, map, d->controller->cameraTitle(),
d->controller->cameraPath());
if (dlg->exec() != QDialog::Accepted)
{
delete dlg;
return;
}
// since we access members here, check if the pointer is still valid
if (!dlg)
{
return;
}
QString cameraFolder = dlg->selectedFolderPath();
for (QList<QUrl>::const_iterator it = urls.constBegin(); it != urls.constEnd(); ++it)
{
QFileInfo fi((*it).toLocalFile());
if (!fi.exists())
{
continue;
}
if (fi.isDir())
{
continue;
}
QString ext = QLatin1Char('.') + fi.completeSuffix();
QString name = fi.fileName();
name.truncate(fi.fileName().length() - ext.length());
bool ok;
CamItemInfo uploadInfo;
uploadInfo.folder = cameraFolder;
uploadInfo.name = name + ext;
while (d->view->hasImage(uploadInfo))
{
QString msg(i18nc("@info", "<qt>Camera Folder <resource>%1</resource> already contains the item <resource>%2</resource>.<br/>"
"Please enter a new filename (without extension):</qt>",
QDir::toNativeSeparators(cameraFolder), fi.fileName()));
uploadInfo.name = QInputDialog::getText(this,
i18nc("@title:window", "File already exists"),
msg,
QLineEdit::Normal,
name,
&ok) + ext;
if (!ok)
{
return;
}
}
d->controller->upload(fi, uploadInfo.name, cameraFolder);
}
delete dlg;
}
void ImportUI::slotUploaded(const CamItemInfo& /*itemInfo*/)
{
if (d->closed)
{
return;
}
refreshFreeSpace();
}
void ImportUI::slotDownloadNew()
{
slotSelectNew();
QTimer::singleShot(0, this, SLOT(slotDownloadSelected()));
}
void ImportUI::slotDownloadAndDeleteNew()
{
slotSelectNew();
QTimer::singleShot(0, this, SLOT(slotDownloadAndDeleteSelected()));
}
void ImportUI::slotDownloadSelected()
{
slotDownload(true, false);
}
void ImportUI::slotDownloadAndDeleteSelected()
{
slotDownload(true, true);
}
void ImportUI::slotDownloadAll()
{
slotDownload(false, false);
}
void ImportUI::slotDownloadAndDeleteAll()
{
slotDownload(false, true);
}
void ImportUI::slotDownload(bool onlySelected, bool deleteAfter, Album* album)
{
if (d->albumCustomizer->folderDateFormat() == AlbumCustomizer::CustomDateFormat &&
!d->albumCustomizer->customDateFormatIsValid())
{
QMessageBox::information(this, qApp->applicationName(),
i18nc("@info", "Your custom target album date format is not valid. Please check your settings..."));
return;
}
// See bug #143934: force to select all items to prevent problem
// when !renameCustomizer->useDefault() ==> iconItem->getDownloadName()
// can return an empty string in this case because it depends on selection.
if (!onlySelected)
{
d->view->slotSelectAll();
}
// Update the download names.
slotNewSelection(d->view->selectedUrls().count() > 0);
// -- Get the destination album from digiKam library ---------------
PAlbum* pAlbum = 0;
if (!album)
{
AlbumManager* const man = AlbumManager::instance();
// Check if default target album option is enabled.
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
bool useDefaultTarget = group.readEntry(d->configUseDefaultTargetAlbum, false);
if (useDefaultTarget)
{
PAlbum* const pa = man->findPAlbum(group.readEntry(d->configDefaultTargetAlbumId, 0));
if (pa)
{
CollectionLocation cl = CollectionManager::instance()->locationForAlbumRootId(pa->albumRootId());
if (!cl.isAvailable() || cl.isNull())
{
QMessageBox::information(this,qApp->applicationName(),
i18nc("@info", "Collection which host your default target album set to process "
"download from camera device is not available. Please select another one from "
"camera configuration dialog."));
return;
}
}
else
{
QMessageBox::information(this, qApp->applicationName(),
i18nc("@info", "Your default target album set to process download "
"from camera device is not available. Please select another one from "
"camera configuration dialog."));
return;
}
pAlbum = pa;
}
else
{
AlbumList list = man->currentAlbums();
int albumId = 0;
if (!list.isEmpty())
{
albumId = group.readEntry(d->configLastTargetAlbum, list.first()->globalID());
}
album = man->findAlbum(albumId);
if (album && album->type() != Album::PHYSICAL)
{
album = 0;
}
QString header(i18nc("@info", "<p>Please select the destination album from the digiKam library to "
"import the camera pictures into.</p>"));
album = AlbumSelectDialog::selectAlbum(this, dynamic_cast<PAlbum*>(album), header);
if (!album)
{
return;
}
pAlbum = dynamic_cast<PAlbum*>(album);
group.writeEntry(d->configLastTargetAlbum, album->globalID());
}
}
else
{
pAlbum = dynamic_cast<PAlbum*>(album);
}
if (!pAlbum)
{
qCDebug(DIGIKAM_IMPORTUI_LOG) << "Destination Album is null";
return;
}
// -- Check disk space ------------------------
// See bug #139519: Always check free space available before to
// download items selection from camera.
if (!checkDiskSpace(pAlbum))
{
return;
}
// -- Prepare and download camera items ------------------------
// Since we show camera items in reverse order, downloading need to be done also in reverse order.
downloadCameraItems(pAlbum, onlySelected, deleteAfter);
}
void ImportUI::slotDownloaded(const QString& folder, const QString& file, int status)
{
// Is auto-rotate option checked?
bool autoRotate = downloadSettings().autoRotate;
bool previewItems = ImportSettings::instance()->getPreviewItemsWhileDownload();
CamItemInfo& info = d->view->camItemInfoRef(folder, file);
if (!info.isNull())
{
setDownloaded(info, status);
if (status == CamItemInfo::DownloadStarted && previewItems)
{
emit signalPreviewRequested(info, true);
}
if (d->rightSideBar->url() == info.url())
{
updateRightSideBar(info);
}
if (info.downloaded == CamItemInfo::DownloadedYes)
{
int curr = d->statusProgressBar->progressValue();
d->statusProgressBar->setProgressValue(curr + 1);
d->renameCustomizer->setStartIndex(d->renameCustomizer->startIndex() + 1);
CoreDbDownloadHistory::setDownloaded(QString::fromUtf8(d->controller->cameraMD5ID()),
info.name,
info.size,
info.ctime);
}
}
// Download all items is complete ?
if (d->statusProgressBar->progressValue() == d->statusProgressBar->progressTotalSteps())
{
if (d->deleteAfter)
{
// No need passive pop-up here, because we will ask to confirm items deletion with dialog.
deleteItems(true, true);
}
else
{
// Pop-up a notification to inform user when all is done, and inform if auto-rotation will take place.
if (autoRotate)
{
DNotificationWrapper(QLatin1String("cameradownloaded"),
i18nc("@info Popup notification",
"Images download finished, you can now detach your camera while the images will be auto-rotated"),
this, windowTitle());
}
else
{
DNotificationWrapper(QLatin1String("cameradownloaded"),
i18nc("@info Popup notification",
"Images download finished"),
this, windowTitle());
}
}
}
}
void ImportUI::slotDownloadComplete(const QString&, const QString&,
const QString& destFolder, const QString&)
{
ScanController::instance()->scheduleCollectionScanRelaxed(destFolder);
autoRotateItems();
}
void ImportUI::slotSkipped(const QString& folder, const QString& file)
{
CamItemInfo info = d->view->camItemInfo(folder, file);
if (!info.isNull())
{
setDownloaded(info, CamItemInfo::DownloadedNo);
}
int curr = d->statusProgressBar->progressValue();
d->statusProgressBar->setProgressValue(curr + 1);
}
void ImportUI::slotMarkAsDownloaded()
{
CamItemInfoList list = d->view->selectedCamItemInfos();
foreach(const CamItemInfo& info, list)
{
setDownloaded(d->view->camItemInfoRef(info.folder, info.name), CamItemInfo::DownloadedYes);
CoreDbDownloadHistory::setDownloaded(QString::fromUtf8(d->controller->cameraMD5ID()),
info.name,
info.size,
info.ctime);
}
}
void ImportUI::slotToggleLock()
{
CamItemInfoList list = d->view->selectedCamItemInfos();
int count = list.count();
if (count > 0)
{
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressTotalSteps(count);
d->statusProgressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode);
}
foreach(const CamItemInfo& info, list)
{
QString folder = info.folder;
QString file = info.name;
int writePerm = info.writePermissions;
bool lock = true;
// If item is currently locked, unlock it.
if (writePerm == 0)
{
lock = false;
}
d->controller->lockFile(folder, file, lock);
}
}
void ImportUI::slotLocked(const QString& folder, const QString& file, bool status)
{
if (status)
{
CamItemInfo& info = d->view->camItemInfoRef(folder, file);
if (!info.isNull())
{
toggleLock(info);
if (d->rightSideBar->url() == info.url())
{
updateRightSideBar(info);
}
}
}
int curr = d->statusProgressBar->progressValue();
d->statusProgressBar->setProgressValue(curr + 1);
}
void ImportUI::slotUpdateDownloadName()
{
QList<QUrl> selected = d->view->selectedUrls();
bool hasNoSelection = selected.count() == 0;
CamItemInfoList list = d->view->allItems();
DownloadSettings settings = downloadSettings();
QString newName;
foreach(const CamItemInfo& info, list)
{
CamItemInfo& refInfo = d->view->camItemInfoRef(info.folder, info.name);
// qCDebug(DIGIKAM_IMPORTUI_LOG) << "slotDownloadNameChanged, old: " << refInfo.downloadName;
newName = info.name;
if (hasNoSelection || selected.contains(info.url()))
{
if (d->renameCustomizer->useDefault())
{
newName = d->renameCustomizer->newName(info.name);
}
else if (d->renameCustomizer->isEnabled())
{
newName = d->renameCustomizer->newName(info.url().toLocalFile());
}
else if (!refInfo.downloadName.isEmpty())
{
newName = refInfo.downloadName;
}
// Renaming files for the converting jpg to a lossless format
// is from cameracontroller.cpp moved here.
if (settings.convertJpeg && info.mime == QLatin1String("image/jpeg"))
{
QFileInfo fi(newName);
QString ext = fi.suffix();
if (!ext.isEmpty())
{
if (ext[0].isUpper() && ext[ext.length()-1].isUpper())
{
ext = settings.losslessFormat.toUpper();
}
else if (ext[0].isUpper())
{
ext = settings.losslessFormat.toLower();
ext[0] = ext[0].toUpper();
}
else
{
ext = settings.losslessFormat.toLower();
}
newName = fi.completeBaseName() + QLatin1Char('.') + ext;
}
else
{
newName = newName + QLatin1Char('.') + settings.losslessFormat.toLower();
}
}
else if (settings.convertDng && info.mime == QLatin1String("image/x-raw"))
{
QFileInfo fi(newName);
QString ext = fi.suffix();
if (!ext.isEmpty())
{
if (ext[0].isUpper() && (ext[ext.length()-1].isUpper() || ext[ext.length()-1].isDigit()))
{
ext = QLatin1String("DNG");
}
else if (ext[0].isUpper())
{
ext = QLatin1String("Dng");
}
else
{
ext = QLatin1String("dng");
}
newName = fi.completeBaseName() + QLatin1Char('.') + ext;
}
else
{
newName = newName + QLatin1Char('.') + QLatin1String("dng");
}
}
}
refInfo.downloadName = newName;
// qCDebug(DIGIKAM_IMPORTUI_LOG) << "slotDownloadNameChanged, new: " << refInfo.downloadName;
}
d->view->updateIconView();
}
//FIXME: the new pictures are marked by CameraHistoryUpdater which is not working yet.
void ImportUI::slotSelectNew()
{
CamItemInfoList infos = d->view->allItems();
CamItemInfoList toBeSelected;
foreach (const CamItemInfo& info, infos)
{
if (info.downloaded == CamItemInfo::DownloadedNo)
{
toBeSelected << info;
}
}
d->view->setSelectedCamItemInfos(toBeSelected);
}
void ImportUI::slotSelectLocked()
{
CamItemInfoList allItems = d->view->allItems();
CamItemInfoList toBeSelected;
foreach (const CamItemInfo& info, allItems)
{
if (info.writePermissions == 0)
{
toBeSelected << info;
}
}
d->view->setSelectedCamItemInfos(toBeSelected);
}
void ImportUI::toggleLock(CamItemInfo& info)
{
if (!info.isNull())
{
if (info.writePermissions == 0)
{
info.writePermissions = 1;
}
else
{
info.writePermissions = 0;
}
}
}
// TODO is this really necessary? why not just use the folders from listfolders call?
QMap<QString, int> ImportUI::countItemsByFolders() const
{
QString path;
QMap<QString, int> map;
QMap<QString, int>::iterator it;
CamItemInfoList infos = d->view->allItems();
foreach (const CamItemInfo& info, infos)
{
path = info.folder;
if (!path.isEmpty() && path.endsWith(QLatin1Char('/')))
{
path.truncate(path.length() - 1);
}
it = map.find(path);
if (it == map.end())
{
map.insert(path, 1);
}
else
{
it.value() ++;
}
}
return map;
}
void ImportUI::setDownloaded(CamItemInfo& itemInfo, int status)
{
itemInfo.downloaded = status;
d->progressValue = 0;
if (itemInfo.downloaded == CamItemInfo::DownloadStarted)
{
d->progressTimer->start(500);
}
else
{
d->progressTimer->stop();
}
}
void ImportUI::slotProgressTimerDone()
{
d->progressTimer->start(300);
}
void ImportUI::itemsSelectionSizeInfo(unsigned long& fSizeKB, unsigned long& dSizeKB)
{
qint64 fSize = 0; // Files size
qint64 dSize = 0; // Estimated space requires to download and process files.
QList<QUrl> selected = d->view->selectedUrls();
CamItemInfoList list = d->view->allItems();
DownloadSettings settings = downloadSettings();
foreach (const CamItemInfo& info, list)
{
if (selected.contains(info.url()))
{
qint64 size = info.size;
if (size < 0) // -1 if size is not provided by camera
{
continue;
}
fSize += size;
if (info.mime == QLatin1String("image/jpeg"))
{
if (settings.convertJpeg)
{
// Estimated size is around 5 x original size when JPEG=>PNG.
dSize += size * 5;
}
else if (settings.autoRotate)
{
// We need a double size to perform rotation.
dSize += size * 2;
}
else
{
// Real file size is added.
dSize += size;
}
}
else if (settings.convertDng && info.mime == QLatin1String("image/x-raw"))
{
// Estimated size is around 2 x original size when RAW=>DNG.
dSize += size * 2;
}
else
{
dSize += size;
}
}
}
fSizeKB = fSize / 1024;
dSizeKB = dSize / 1024;
}
void ImportUI::checkItem4Deletion(const CamItemInfo& info, QStringList& folders, QStringList& files,
CamItemInfoList& deleteList, CamItemInfoList& lockedList)
{
if (info.writePermissions != 0) // Item not locked ?
{
QString folder = info.folder;
QString file = info.name;
folders.append(folder);
files.append(file);
deleteList.append(info);
}
else
{
lockedList.append(info);
}
}
void ImportUI::deleteItems(bool onlySelected, bool onlyDownloaded)
{
QStringList folders;
QStringList files;
CamItemInfoList deleteList;
CamItemInfoList lockedList;
CamItemInfoList list = onlySelected ? d->view->selectedCamItemInfos() : d->view->allItems();
foreach (const CamItemInfo& info, list)
{
if (onlyDownloaded)
{
if (info.downloaded == CamItemInfo::DownloadedYes)
{
checkItem4Deletion(info, folders, files, deleteList, lockedList);
}
}
else
{
checkItem4Deletion(info, folders, files, deleteList, lockedList);
}
}
// If we want to delete some locked files, just give a feedback to user.
if (!lockedList.isEmpty())
{
QString infoMsg(i18nc("@info", "The items listed below are locked by camera (read-only). "
"These items will not be deleted. If you really want to delete these items, "
"please unlock them and try again."));
CameraMessageBox::informationList(d->camThumbsCtrl, this, i18n("Information"), infoMsg, lockedList);
}
if (folders.isEmpty())
{
return;
}
QString warnMsg(i18ncp("@info", "About to delete this image. "
"<b>Deleted file is unrecoverable.</b> "
"Are you sure?",
"About to delete these %1 images. "
"<b>Deleted files are unrecoverable.</b> "
"Are you sure?",
deleteList.count()));
if (CameraMessageBox::warningContinueCancelList(d->camThumbsCtrl,
this,
i18n("Warning"),
warnMsg,
deleteList,
QLatin1String("DontAskAgainToDeleteItemsFromCamera"))
== QMessageBox::Yes)
{
QStringList::const_iterator itFolder = folders.constBegin();
QStringList::const_iterator itFile = files.constBegin();
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressTotalSteps(deleteList.count());
d->statusProgressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode);
// enable cancel action.
d->cameraCancelAction->setEnabled(true);
for ( ; itFolder != folders.constEnd() ; ++itFolder, ++itFile)
{
d->controller->deleteFile(*itFolder, *itFile);
// the currentlyDeleting list is used to prevent loading items which
// will immanently be deleted into the sidebar and wasting time
d->currentlyDeleting.append(*itFolder + *itFile);
}
}
}
bool ImportUI::checkDiskSpace(PAlbum *pAlbum)
{
if (!pAlbum)
{
return false;
}
unsigned long fSize = 0;
unsigned long dSize = 0;
itemsSelectionSizeInfo(fSize, dSize);
QString albumRootPath = pAlbum->albumRootPath();
unsigned long kBAvail = d->albumLibraryFreeSpace->kBAvail(albumRootPath);
if (dSize >= kBAvail)
{
int result = QMessageBox::warning(this, i18nc("@title:window", "Insufficient Disk Space"),
i18nc("@info", "There is not enough free space on the disk of the album you selected "
"to download and process the selected pictures from the camera.\n\n"
"Estimated space required: %1\n"
"Available free space: %2\n\n"
"Try Anyway?",
ImagePropertiesTab::humanReadableBytesCount(dSize * 1024),
ImagePropertiesTab::humanReadableBytesCount(kBAvail * 1024)),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No)
{
return false;
}
}
return true;
}
bool ImportUI::downloadCameraItems(PAlbum* pAlbum, bool onlySelected, bool deleteAfter)
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
KConfigGroup group = config->group(d->configGroupName);
SetupCamera::ConflictRule rule = (SetupCamera::ConflictRule)group.readEntry(d->configFileSaveConflictRule,
(int)SetupCamera::DIFFNAME);
d->controller->downloadPrep(rule);
QString downloadName;
DownloadSettingsList allItems;
DownloadSettings settings = downloadSettings();
QUrl url = pAlbum->fileUrl();
int downloadedItems = 0;
// -- Download camera items -------------------------------
QList<QUrl> selected = d->view->selectedUrls();
CamItemInfoList list = d->view->allItems();
QSet<QString> usedDownloadPaths;
foreach (const CamItemInfo& info, list)
{
if (onlySelected && !(selected.contains(info.url())))
{
continue;
}
settings.folder = info.folder;
settings.file = info.name;
settings.mime = info.mime;
settings.pickLabel = info.pickLabel;
settings.colorLabel = info.colorLabel;
settings.rating = info.rating;
// downloadName should already be set by now
downloadName = info.downloadName;
QUrl downloadUrl(url);
if (!createSubAlbums(downloadUrl, info))
{
return false;
}
d->foldersToScan << downloadUrl.toLocalFile();
if (downloadName.isEmpty())
{
downloadUrl = downloadUrl.adjusted(QUrl::StripTrailingSlash);
downloadUrl.setPath(downloadUrl.path() + QLatin1Char('/') + settings.file);
}
else
{
// when using custom renaming (e.g. by date, see bug 179902)
// make sure that we create unique names
downloadUrl = downloadUrl.adjusted(QUrl::StripTrailingSlash);
downloadUrl.setPath(downloadUrl.path() + QLatin1Char('/') + downloadName);
QString suggestedPath = downloadUrl.toLocalFile();
if (usedDownloadPaths.contains(suggestedPath))
{
QFileInfo fi(downloadName);
QString suffix = QLatin1Char('.') + fi.suffix();
QString pathWithoutSuffix(suggestedPath);
pathWithoutSuffix.chop(suffix.length());
QString currentVariant;
int counter = 1;
do
{
currentVariant = pathWithoutSuffix + QLatin1Char('-') + QString::number(counter++) + suffix;
}
while (usedDownloadPaths.contains(currentVariant));
usedDownloadPaths << currentVariant;
downloadUrl = QUrl::fromLocalFile(currentVariant);
}
else
{
usedDownloadPaths << suggestedPath;
}
}
settings.dest = downloadUrl.toLocalFile();
allItems.append(settings);
if (settings.autoRotate && settings.mime == QLatin1String("image/jpeg"))
{
d->autoRotateItemsList << downloadUrl.toLocalFile();
qCDebug(DIGIKAM_IMPORTUI_LOG) << "autorotating for " << downloadUrl;
}
++downloadedItems;
}
if (downloadedItems <= 0)
{
return false;
}
d->lastDestURL = url;
d->statusProgressBar->setNotify(true);
d->statusProgressBar->setProgressValue(0);
d->statusProgressBar->setProgressTotalSteps(downloadedItems);
d->statusProgressBar->setProgressBarMode(StatusProgressBar::ProgressBarMode);
// enable cancel action.
d->cameraCancelAction->setEnabled(true);
// disable settings tab here instead of slotBusy:
// Only needs to be disabled while downloading
d->advBox->setEnabled(false);
d->view->setEnabled(false);
d->deleteAfter = deleteAfter;
d->controller->download(allItems);
return true;
}
bool ImportUI::createSubAlbums(QUrl& downloadUrl, const CamItemInfo& info)
{
bool success = true;
if (d->albumCustomizer->autoAlbumDateEnabled())
{
success &= createDateBasedSubAlbum(downloadUrl, info);
}
if (d->albumCustomizer->autoAlbumExtEnabled())
{
success &= createExtBasedSubAlbum(downloadUrl, info);
}
return success;
}
bool ImportUI::createSubAlbum(QUrl& downloadUrl, const QString& subalbum, const QDate& date)
{
QString errMsg;
if (!createAutoAlbum(downloadUrl, subalbum, date, errMsg))
{
QMessageBox::critical(this, qApp->applicationName(), errMsg);
return false;
}
downloadUrl = downloadUrl.adjusted(QUrl::StripTrailingSlash);
downloadUrl.setPath(downloadUrl.path() + QLatin1Char('/') + subalbum);
return true;
}
bool ImportUI::createDateBasedSubAlbum(QUrl& downloadUrl, const CamItemInfo& info)
{
QString dirName;
QDateTime dateTime = info.ctime;
switch (d->albumCustomizer->folderDateFormat())
{
case AlbumCustomizer::TextDateFormat:
dirName = dateTime.date().toString(Qt::TextDate);
break;
case AlbumCustomizer::LocalDateFormat:
dirName = dateTime.date().toString(Qt::LocalDate);
break;
case AlbumCustomizer::IsoDateFormat:
dirName = dateTime.date().toString(Qt::ISODate);
break;
default: // Custom
dirName = dateTime.date().toString(d->albumCustomizer->customDateFormat());
break;
}
return createSubAlbum(downloadUrl, dirName, dateTime.date());
}
bool ImportUI::createExtBasedSubAlbum(QUrl& downloadUrl, const CamItemInfo& info)
{
// We use the target file name to compute sub-albums name to take a care about
// conversion on the fly option.
QFileInfo fi(info.downloadName.isEmpty()
? info.name
: info.downloadName);
QString subAlbum = fi.suffix().toUpper();
if (fi.suffix().toUpper() == QLatin1String("JPEG") ||
fi.suffix().toUpper() == QLatin1String("JPE"))
{
subAlbum = QLatin1String("JPG");
}
if (fi.suffix().toUpper() == QLatin1String("TIFF"))
{
subAlbum = QLatin1String("TIF");
}
if (fi.suffix().toUpper() == QLatin1String("MPEG") ||
fi.suffix().toUpper() == QLatin1String("MPE") ||
fi.suffix().toUpper() == QLatin1String("MPO"))
{
subAlbum = QLatin1String("MPG");
}
return createSubAlbum(downloadUrl, subAlbum, info.ctime.date());
}
void ImportUI::slotDeleteNew()
{
slotSelectNew();
QTimer::singleShot(0, this, SLOT(slotDeleteSelected()));
}
void ImportUI::slotDeleteSelected()
{
deleteItems(true, false);
}
void ImportUI::slotDeleteAll()
{
deleteItems(false, false);
}
void ImportUI::slotDeleted(const QString& folder, const QString& file, bool status)
{
if (status)
{
// do this after removeItem.
d->currentlyDeleting.removeAll(folder + file);
}
int curr = d->statusProgressBar->progressValue();
d->statusProgressBar->setProgressTotalSteps(curr + 1);
refreshFreeSpace();
}
void ImportUI::slotMetadata(const QString& folder, const QString& file, const DMetadata& meta)
{
CamItemInfo info = d->view->camItemInfo(folder, file);
if (!info.isNull())
{
d->rightSideBar->itemChanged(info, meta);
}
}
void ImportUI::slotNewSelection(bool hasSelection)
{
updateActions();
QList<ParseSettings> renameFiles;
CamItemInfoList list = hasSelection ? d->view->selectedCamItemInfos() : d->view->allItems();
foreach(const CamItemInfo& info, list)
{
ParseSettings parseSettings;
parseSettings.fileUrl = info.url();
parseSettings.creationTime = info.ctime;
renameFiles.append(parseSettings);
}
d->renameCustomizer->renameManager()->reset();
d->renameCustomizer->renameManager()->addFiles(renameFiles);
d->renameCustomizer->renameManager()->parseFiles();
slotUpdateDownloadName();
unsigned long fSize = 0;
unsigned long dSize = 0;
itemsSelectionSizeInfo(fSize, dSize);
d->albumLibraryFreeSpace->setEstimatedDSizeKb(dSize);
}
void ImportUI::slotImageSelected(const CamItemInfoList& selection, const CamItemInfoList& listAll)
{
if (d->cameraCancelAction->isEnabled())
{
return;
}
int num_images = listAll.count();
switch (selection.count())
{
case 0:
{
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18ncp("@info:status",
"No item selected (%1 item)",
"No item selected (%1 items)",
num_images));
d->rightSideBar->slotNoCurrentItem();
break;
}
case 1:
{
// if selected item is in the list of item which will be deleted, set no current item
if (!d->currentlyDeleting.contains(selection.first().folder + selection.first().name))
{
updateRightSideBar(selection.first());
int index = listAll.indexOf(selection.first()) + 1;
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18nc("@info:status Filename of first selected item of number of items",
"<b>%1</b> (%2 of %3)",
selection.first().url().fileName(), index, num_images));
}
else
{
d->rightSideBar->slotNoCurrentItem();
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18ncp("@info:status",
"No item selected (%1 item)",
"No item selected (%1 items)",
num_images));
}
break;
}
default:
{
d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode,
i18ncp("@info:status", "%2/%1 item selected",
"%2/%1 items selected",
num_images, selection.count()));
break;
}
}
slotNewSelection(d->view->selectedCamItemInfos().count() > 0);
}
void ImportUI::updateRightSideBar(const CamItemInfo& info)
{
d->rightSideBar->itemChanged(info, DMetadata());
if (!d->busy)
{
d->controller->getMetadata(info.folder, info.name);
}
}
QString ImportUI::identifyCategoryforMime(const QString& mime)
{
return mime.split(QLatin1Char('/')).at(0);
}
void ImportUI::autoRotateItems()
{
if (d->statusProgressBar->progressValue() != d->statusProgressBar->progressTotalSteps())
{
return;
}
if (d->autoRotateItemsList.isEmpty())
{
return;
}
ImageInfoList list;
CollectionScanner scanner;
foreach (const QString& downloadPath, d->autoRotateItemsList)
{
qlonglong id = scanner.scanFile(downloadPath,
CollectionScanner::ModifiedScan);
list << ImageInfo(id);
}
FileActionMngr::instance()->transform(list, MetaEngineRotation::NoTransformation);
d->autoRotateItemsList.clear();
}
bool ImportUI::createAutoAlbum(const QUrl& parentURL, const QString& sub,
const QDate& date, QString& errMsg) const
{
QUrl url(parentURL);
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + QLatin1Char('/') + sub);
// first stat to see if the album exists
QFileInfo info(url.toLocalFile());
if (info.exists())
{
// now check if its really a directory
if (info.isDir())
{
return true;
}
else
{
errMsg = i18nc("@info", "A file with the same name (<b>%1</b>) already exists in folder <resource>%2</resource>.",
sub, QDir::toNativeSeparators(parentURL.toLocalFile()));
return false;
}
}
// looks like the directory does not exist, try to create it.
// First we make sure that the parent exists.
PAlbum* parent = AlbumManager::instance()->findPAlbum(parentURL);
if (!parent)
{
errMsg = i18nc("@info", "Failed to find Album for path <b>%1</b>.", QDir::toNativeSeparators(parentURL.toLocalFile()));
return false;
}
// Create the album, with any parent albums required for the structure
QUrl albumUrl(parentURL);
foreach (const QString& folder, sub.split(QLatin1Char('/'), QString::SkipEmptyParts))
{
albumUrl = albumUrl.adjusted(QUrl::StripTrailingSlash);
albumUrl.setPath(albumUrl.path() + QLatin1Char('/') + folder);
PAlbum* album = AlbumManager::instance()->findPAlbum(albumUrl);
if (!album)
{
album = AlbumManager::instance()->createPAlbum(parent, folder, QString(), date, QString(), errMsg);
if (!album)
{
return false;
}
}
parent = album;
}
return true;
}
void ImportUI::slotSetup()
{
Setup::execDialog(this);
}
void ImportUI::slotCameraFreeSpaceInfo(unsigned long kBSize, unsigned long kBAvail)
{
d->cameraFreeSpace->addInformation(kBSize, kBSize - kBAvail, kBAvail, QString());
}
bool ImportUI::cameraDeleteSupport() const
{
return d->controller->cameraDeleteSupport();
}
bool ImportUI::cameraUploadSupport() const
{
return d->controller->cameraUploadSupport();
}
bool ImportUI::cameraMkDirSupport() const
{
return d->controller->cameraMkDirSupport();
}
bool ImportUI::cameraDelDirSupport() const
{
return d->controller->cameraDelDirSupport();
}
bool ImportUI::cameraUseUMSDriver() const
{
return d->controller->cameraDriverType() == DKCamera::UMSDriver;
}
bool ImportUI::cameraUseGPhotoDriver() const
{
return d->controller->cameraDriverType() == DKCamera::GPhotoDriver;
}
void ImportUI::enableZoomPlusAction(bool val)
{
d->increaseThumbsAction->setEnabled(val);
}
void ImportUI::enableZoomMinusAction(bool val)
{
d->decreaseThumbsAction->setEnabled(val);
}
void ImportUI::slotComponentsInfo()
{
showDigikamComponentsInfo();
}
void ImportUI::slotDBStat()
{
showDigikamDatabaseStat();
}
void ImportUI::refreshCollectionFreeSpace()
{
d->albumLibraryFreeSpace->setPaths(CollectionManager::instance()->allAvailableAlbumRootPaths());
}
void ImportUI::slotCollectionLocationStatusChanged(const CollectionLocation&, int)
{
refreshCollectionFreeSpace();
}
void ImportUI::slotToggleShowBar()
{
showThumbBar(d->showBarAction->isChecked());
}
void ImportUI::slotLogMsg(const QString& msg, DHistoryView::EntryType type,
const QString& folder, const QString& file)
{
d->statusProgressBar->setProgressText(msg);
QStringList meta;
meta << folder << file;
d->historyView->addEntry(msg, type, QVariant(meta));
}
void ImportUI::slotShowLog()
{
d->showLogAction->isChecked() ? d->historyView->show() : d->historyView->hide();
}
void ImportUI::slotHistoryEntryClicked(const QVariant& metadata)
{
QStringList meta = metadata.toStringList();
QString folder = meta.at(0);
QString file = meta.at(1);
d->view->scrollTo(folder, file);
}
void ImportUI::showSideBars(bool visible)
{
visible ? d->rightSideBar->restore()
: d->rightSideBar->backup();
}
void ImportUI::slotToggleRightSideBar()
{
d->rightSideBar->isExpanded() ? d->rightSideBar->shrink()
: d->rightSideBar->expand();
}
void ImportUI::slotPreviousRightSideBarTab()
{
d->rightSideBar->activePreviousTab();
}
void ImportUI::slotNextRightSideBarTab()
{
d->rightSideBar->activeNextTab();
}
void ImportUI::showThumbBar(bool visible)
{
d->view->toggleShowBar(visible);
}
bool ImportUI::thumbbarVisibility() const
{
return d->showBarAction->isChecked();
}
void ImportUI::slotSwitchedToPreview()
{
d->zoomBar->setBarMode(DZoomBar::PreviewZoomCtrl);
d->imageViewSelectionAction->setCurrentAction(d->camItemPreviewAction);
toogleShowBar();
}
void ImportUI::slotSwitchedToIconView()
{
d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl);
d->imageViewSelectionAction->setCurrentAction(d->iconViewAction);
toogleShowBar();
}
void ImportUI::slotSwitchedToMapView()
{
d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl);
#ifdef HAVE_MARBLE
d->imageViewSelectionAction->setCurrentAction(d->mapViewAction);
#endif
toogleShowBar();
}
void ImportUI::customizedFullScreenMode(bool set)
{
toolBarMenuAction()->setEnabled(!set);
showMenuBarAction()->setEnabled(!set);
showStatusBarAction()->setEnabled(!set);
set ? d->showBarAction->setEnabled(false)
: toogleShowBar();
d->view->toggleFullScreen(set);
}
void ImportUI::toogleShowBar()
{
switch (d->view->viewMode())
{
case ImportStackedView::PreviewImageMode:
case ImportStackedView::MediaPlayerMode:
d->showBarAction->setEnabled(true);
break;
default:
d->showBarAction->setEnabled(false);
break;
}
}
void ImportUI::slotSetupChanged()
{
d->view->importFilterModel()->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural());
// Load full-screen options
KConfigGroup group = KSharedConfig::openConfig()->group(ApplicationSettings::instance()->generalConfigGroupName());
readFullScreenSettings(group);
d->view->applySettings();
sidebarTabTitleStyleChanged();
}
void ImportUI::sidebarTabTitleStyleChanged()
{
d->rightSideBar->setStyle(ApplicationSettings::instance()->getSidebarTitleStyle());
d->rightSideBar->applySettings();
}
void ImportUI::slotToggleColorManagedView()
{
if (!IccSettings::instance()->isEnabled())
{
return;
}
bool cmv = !IccSettings::instance()->settings().useManagedPreviews;
IccSettings::instance()->setUseManagedPreviews(cmv);
d->camThumbsCtrl->clearCache();
}
void ImportUI::slotColorManagementOptionsChanged()
{
ICCSettingsContainer settings = IccSettings::instance()->settings();
d->viewCMViewAction->blockSignals(true);
d->viewCMViewAction->setEnabled(settings.enableCM);
d->viewCMViewAction->setChecked(settings.useManagedPreviews);
d->viewCMViewAction->blockSignals(false);
}
} // namespace Digikam
diff --git a/core/utilities/import/views/importiconview.cpp b/core/utilities/import/views/importiconview.cpp
index 4f5bbe0c41..4d96b72c9f 100644
--- a/core/utilities/import/views/importiconview.cpp
+++ b/core/utilities/import/views/importiconview.cpp
@@ -1,484 +1,484 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-22-07
* Description : Icon view for import tool items
*
* Copyright (C) 2012 by Islam Wazery <wazery at ubuntu dot com>
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "importiconview.h"
#include "importiconview_p.h"
// Qt includes
#include <QPointer>
#include <QAction>
#include <QMenu>
// Local includes
#include "importcategorizedview.h"
#include "importoverlays.h"
#include "importsettings.h"
#include "camitemsortsettings.h"
#include "fileactionmngr.h"
#include "importdelegate.h"
#include "advancedrenamedialog.h"
#include "advancedrenameprocessdialog.h"
#include "imageviewutilities.h"
#include "importcontextmenu.h"
#include "importdragdrop.h"
namespace Digikam
{
ImportIconView::ImportIconView(QWidget* const parent)
: ImportCategorizedView(parent),
d(new Private(this))
{
ImportThumbnailModel* const model = new ImportThumbnailModel(this);
ImportFilterModel* const filterModel = new ImportFilterModel(this);
filterModel->setSourceImportModel(model);
filterModel->sort(0); // an initial sorting is necessary
setModels(model, filterModel);
d->normalDelegate = new ImportNormalDelegate(this);
setItemDelegate(d->normalDelegate);
setSpacing(10);
ImportSettings* const settings = ImportSettings::instance();
setThumbnailSize(ThumbnailSize(settings->getDefaultIconSize()));
importImageModel()->setDragDropHandler(new ImportDragDropHandler(importImageModel()));
setDragEnabled(true);
setAcceptDrops(true);
setDropIndicatorShown(false);
setToolTipEnabled(settings->showToolTipsIsValid());
// selection overlay
addSelectionOverlay(d->normalDelegate);
//TODO: addSelectionOverlay(d->faceDelegate);
// rotation overlays
d->rotateLeftOverlay = ImportRotateOverlay::left(this);
d->rotateRightOverlay = ImportRotateOverlay::right(this);
addOverlay(new ImportDownloadOverlay(this));
addOverlay(new ImportLockOverlay(this));
addOverlay(new ImportCoordinatesOverlay(this));
d->updateOverlays();
// rating overlay
ImportRatingOverlay* const ratingOverlay = new ImportRatingOverlay(this);
addOverlay(ratingOverlay);
//TODO: GroupIndicatorOverlay* groupOverlay = new GroupIndicatorOverlay(this);
//TODO: addOverlay(groupOverlay);
connect(ratingOverlay, SIGNAL(ratingEdited(QList<QModelIndex>,int)),
this, SLOT(assignRating(QList<QModelIndex>,int)));
//TODO: connect(groupOverlay, SIGNAL(toggleGroupOpen(QModelIndex)),
//this, SLOT(groupIndicatorClicked(QModelIndex)));
//TODO: connect(groupOverlay, SIGNAL(showButtonContextMenu(QModelIndex,QContextMenuEvent*)),
//this, SLOT(showGroupContextMenu(QModelIndex,QContextMenuEvent*)));
//TODO: connect(importImageModel()->dragDropHandler(), SIGNAL(assignTags(QList<CamItemInfo>,QList<int>)),
//FileActionMngr::instance(), SLOT(assignTags(QList<CamItemInfo>,QList<int>)));
//TODO: connect(importImageModel()->dragDropHandler(), SIGNAL(addToGroup(CamItemInfo,QList<CamItemInfo>)),
//FileActionMngr::instance(), SLOT(addToGroup(CamItemInfo,QList<CamItemInfo>)));
connect(settings, SIGNAL(setupChanged()),
this, SLOT(slotSetupChanged()));
slotSetupChanged();
}
ImportIconView::~ImportIconView()
{
delete d;
}
ImageViewUtilities* ImportIconView::utilities() const
{
return d->utilities;
}
void ImportIconView::setThumbnailSize(const ThumbnailSize& size)
{
ImportCategorizedView::setThumbnailSize(size);
}
int ImportIconView::fitToWidthIcons()
{
return delegate()->calculatethumbSizeToFit(viewport()->size().width());
}
CamItemInfo ImportIconView::camItemInfo(const QString& folder, const QString& file)
{
QUrl url = QUrl::fromLocalFile(folder);
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + QLatin1Char('/') + file);
QModelIndex indexForCamItemInfo = importFilterModel()->indexForPath(url.toLocalFile());
if (indexForCamItemInfo.isValid())
{
return importFilterModel()->camItemInfo(indexForCamItemInfo);
}
return CamItemInfo();
}
CamItemInfo& ImportIconView::camItemInfoRef(const QString& folder, const QString& file)
{
QUrl url = QUrl::fromLocalFile(folder);
url = url.adjusted(QUrl::StripTrailingSlash);
url.setPath(url.path() + QLatin1Char('/') + file);
QModelIndex indexForCamItemInfo = importFilterModel()->indexForPath(url.toLocalFile());
QModelIndex mappedIndex = importFilterModel()->mapToSource(indexForCamItemInfo);
return importImageModel()->camItemInfoRef(mappedIndex);
}
void ImportIconView::slotSetupChanged()
{
setToolTipEnabled(ImportSettings::instance()->showToolTipsIsValid());
setFont(ImportSettings::instance()->getIconViewFont());
d->updateOverlays();
ImportCategorizedView::slotSetupChanged();
}
void ImportIconView::rename()
{
QList<QUrl> urls = selectedUrls();
NewNamesList newNamesList;
QPointer<AdvancedRenameDialog> dlg = new AdvancedRenameDialog(this);
dlg->slotAddImages(urls);
if (dlg->exec() == QDialog::Accepted)
{
newNamesList = dlg->newNames();
}
delete dlg;
if (!newNamesList.isEmpty())
{
QPointer<AdvancedRenameProcessDialog> dlg = new AdvancedRenameProcessDialog(newNamesList);
dlg->exec();
delete dlg;
}
}
void ImportIconView::deleteSelected(bool /*permanently*/)
{
CamItemInfoList camItemInfoList = selectedCamItemInfos();
//FIXME: This way of deletion may not working with camera items.
/*
if (d->utilities->deleteImages(camItemInfoList, permanently))
{
awayFromSelection();
}
*/
}
void ImportIconView::deleteSelectedDirectly(bool /*permanently*/)
{
CamItemInfoList camItemInfoList = selectedCamItemInfos();
//FIXME: This way of deletion may not working with camera items.
//d->utilities->deleteImagesDirectly(camItemInfoList, permanently);
awayFromSelection();
}
void ImportIconView::createGroupFromSelection()
{
- //TODO: Impelemnt grouping in import tool.
+ //TODO: Implement grouping in import tool.
/*
QList<CamItemInfo> selectedInfos = selectedCamItemInfosCurrentFirst();
CamItemInfo groupLeader = selectedInfos.takeFirst();
FileActionMngr::instance()->addToGroup(groupLeader, selectedInfos);
*/
}
void ImportIconView::createGroupByTimeFromSelection()
{
- //TODO: Impelemnt grouping in import tool.
+ //TODO: Implement grouping in import tool.
/*
QList<CamItemInfo> selectedInfos = selectedCamItemInfosCurrentFirst();
while (selectedInfos.size() > 0)
{
QList<CamItemInfo> group;
CamItemInfo groupLeader = selectedInfos.takeFirst();
QDateTime dateTime = groupLeader.dateTime();
while (selectedInfos.size() > 0 && abs(dateTime.secsTo(selectedInfos.first().dateTime())) < 2)
{
group.push_back(selectedInfos.takeFirst());
}
FileActionMngr::instance()->addToGroup(groupLeader, group);
}
*/
}
void ImportIconView::ungroupSelected()
{
- //TODO: Impelemnt grouping in import tool.
+ //TODO: Implement grouping in import tool.
//FileActionMngr::instance()->ungroup(selectedCamItemInfos());
}
void ImportIconView::removeSelectedFromGroup()
{
- //TODO: Impelemnt grouping in import tool.
+ //TODO: Implement grouping in import tool.
//FileActionMngr::instance()->removeFromGroup(selectedCamItemInfos());
}
void ImportIconView::slotRotateLeft(const QList<QModelIndex>& /*indexes*/)
{
/*
QList<ImageInfo> imageInfos;
foreach(const QModelIndex& index, indexes)
{
ImageInfo imageInfo(importFilterModel()->camItemInfo(index).url());
imageInfos << imageInfo;
}
FileActionMngr::instance()->transform(imageInfos, MetaEngineRotation::Rotate270);
*/
}
void ImportIconView::slotRotateRight(const QList<QModelIndex>& /*indexes*/)
{
/*
QList<ImageInfo> imageInfos;
foreach(const QModelIndex& index, indexes)
{
ImageInfo imageInfo(importFilterModel()->camItemInfo(index).url());
imageInfos << imageInfo;
}
FileActionMngr::instance()->transform(imageInfos, MetaEngineRotation::Rotate90);
*/
}
void ImportIconView::activated(const CamItemInfo& info, Qt::KeyboardModifiers)
{
if (info.isNull())
{
return;
}
if (ImportSettings::instance()->getItemLeftClickAction() == ImportSettings::ShowPreview)
{
emit previewRequested(info, false);
}
else
{
//TODO: openFile(info);
}
}
void ImportIconView::showContextMenuOnInfo(QContextMenuEvent* event, const CamItemInfo& /*info*/)
{
QList<CamItemInfo> selectedInfos = selectedCamItemInfosCurrentFirst();
QList<qlonglong> selectedItemIDs;
foreach(const CamItemInfo& info, selectedInfos)
{
selectedItemIDs << info.id;
}
// --------------------------------------------------------
QMenu popmenu(this);
ImportContextMenuHelper cmhelper(&popmenu);
cmhelper.addAction(QLatin1String("importui_fullscreen"));
cmhelper.addAction(QLatin1String("options_show_menubar"));
cmhelper.addAction(QLatin1String("import_zoomfit2window"));
cmhelper.addSeparator();
// --------------------------------------------------------
cmhelper.addAction(QLatin1String("importui_imagedownload"));
cmhelper.addAction(QLatin1String("importui_imagemarkasdownloaded"));
cmhelper.addAction(QLatin1String("importui_imagelock"));
cmhelper.addAction(QLatin1String("importui_delete"));
cmhelper.addSeparator();
cmhelper.addAction(QLatin1String("importui_item_view"));
cmhelper.addServicesMenu(selectedUrls());
//TODO: cmhelper.addRotateMenu(selectedItemIDs);
cmhelper.addSeparator();
// --------------------------------------------------------
cmhelper.addAction(QLatin1String("importui_selectall"));
cmhelper.addAction(QLatin1String("importui_selectnone"));
cmhelper.addAction(QLatin1String("importui_selectinvert"));
cmhelper.addSeparator();
// --------------------------------------------------------
//cmhelper.addAssignTagsMenu(selectedItemIDs);
//cmhelper.addRemoveTagsMenu(selectedItemIDs);
//cmhelper.addSeparator();
// --------------------------------------------------------
cmhelper.addLabelsAction();
//if (!d->faceMode)
//{
// cmhelper.addGroupMenu(selectedItemIDs);
//}
// special action handling --------------------------------
//connect(&cmhelper, SIGNAL(signalAssignTag(int)),
// this, SLOT(assignTagToSelected(int)));
//TODO: Implement tag view for import tool.
//connect(&cmhelper, SIGNAL(signalPopupTagsView()),
// this, SIGNAL(signalPopupTagsView()));
//connect(&cmhelper, SIGNAL(signalRemoveTag(int)),
// this, SLOT(removeTagFromSelected(int)));
//connect(&cmhelper, SIGNAL(signalGotoTag(int)),
//this, SIGNAL(gotoTagAndImageRequested(int)));
connect(&cmhelper, SIGNAL(signalAssignPickLabel(int)),
this, SLOT(assignPickLabelToSelected(int)));
connect(&cmhelper, SIGNAL(signalAssignColorLabel(int)),
this, SLOT(assignColorLabelToSelected(int)));
connect(&cmhelper, SIGNAL(signalAssignRating(int)),
this, SLOT(assignRatingToSelected(int)));
//connect(&cmhelper, SIGNAL(signalAddToExistingQueue(int)),
//this, SLOT(insertSelectedToExistingQueue(int)));
//FIXME: connect(&cmhelper, SIGNAL(signalCreateGroup()),
//this, SLOT(createGroupFromSelection()));
//connect(&cmhelper, SIGNAL(signalUngroup()),
//this, SLOT(ungroupSelected()));
//connect(&cmhelper, SIGNAL(signalRemoveFromGroup()),
//this, SLOT(removeSelectedFromGroup()));
// --------------------------------------------------------
cmhelper.exec(event->globalPos());
}
void ImportIconView::showContextMenu(QContextMenuEvent* event)
{
QMenu popmenu(this);
ImportContextMenuHelper cmhelper(&popmenu);
cmhelper.addAction(QLatin1String("importui_fullscreen"));
cmhelper.addAction(QLatin1String("options_show_menubar"));
cmhelper.addSeparator();
cmhelper.addAction(QLatin1String("importui_close"));
// --------------------------------------------------------
cmhelper.exec(event->globalPos());
}
void ImportIconView::assignTagToSelected(int tagID)
{
CamItemInfoList infos = selectedCamItemInfos();
foreach(const CamItemInfo& info, infos)
{
importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).tagIds.append(tagID);
}
}
void ImportIconView::removeTagFromSelected(int tagID)
{
CamItemInfoList infos = selectedCamItemInfos();
foreach(const CamItemInfo& info, infos)
{
importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).tagIds.removeAll(tagID);
}
}
void ImportIconView::assignPickLabel(const QModelIndex& index, int pickId)
{
importImageModel()->camItemInfoRef(index).pickLabel = pickId;
}
void ImportIconView::assignPickLabelToSelected(int pickId)
{
CamItemInfoList infos = selectedCamItemInfos();
foreach(const CamItemInfo& info, infos)
{
importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).pickLabel = pickId;
}
}
void ImportIconView::assignColorLabel(const QModelIndex& index, int colorId)
{
importImageModel()->camItemInfoRef(index).colorLabel = colorId;
}
void ImportIconView::assignColorLabelToSelected(int colorId)
{
CamItemInfoList infos = selectedCamItemInfos();
foreach(const CamItemInfo& info, infos)
{
importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).colorLabel = colorId;
}
}
void ImportIconView::assignRating(const QList<QModelIndex>& indexes, int rating)
{
foreach(const QModelIndex& index, indexes)
{
if (index.isValid())
{
importImageModel()->camItemInfoRef(index).rating = rating;
}
}
}
void ImportIconView::assignRatingToSelected(int rating)
{
CamItemInfoList infos = selectedCamItemInfos();
foreach(const CamItemInfo& info, infos)
{
importImageModel()->camItemInfoRef(importImageModel()->indexForCamItemInfo(info)).rating = rating;
}
}
} // namespace Digikam
diff --git a/core/utilities/import/views/importstackedview.h b/core/utilities/import/views/importstackedview.h
index 00da7afcef..364d17fcec 100644
--- a/core/utilities/import/views/importstackedview.h
+++ b/core/utilities/import/views/importstackedview.h
@@ -1,158 +1,158 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-05-07
* Description : QStackedWidget to handle different types of views
* (icon view, items preview, media view)
*
* Copyright (C) 2012 by Islam Wazery <wazery at ubuntu dot com>
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_IMPORT_STACKED_VIEW_H
#define DIGIKAM_IMPORT_STACKED_VIEW_H
-// Qt inclueds
+// Qt includes
#include <QStackedWidget>
// Local includes
#include "digikam_config.h"
#include "importthumbnailbar.h"
#include "importpreviewview.h"
#include "thumbbardock.h"
#include "camiteminfo.h"
#include "importiconview.h"
#include "digikam_export.h"
#ifdef HAVE_MEDIAPLAYER
#include "mediaplayerview.h"
#endif //HAVE_MEDIAPLAYER
#ifdef HAVE_MARBLE
#include "mapwidgetview.h"
#endif // HAVE_MARBLE
namespace Digikam
{
class DIGIKAM_EXPORT ImportStackedView : public QStackedWidget
{
Q_OBJECT
public:
enum StackedViewMode
{
PreviewCameraMode = 0, // previewing the set of items on the camera
PreviewImageMode,
#ifdef HAVE_MARBLE
MapWidgetMode,
MediaPlayerMode
#else
MediaPlayerMode,
MapWidgetMode
#endif // HAVE_MARBLE
};
public:
explicit ImportStackedView(QWidget*const parent = 0);
~ImportStackedView();
void setDockArea(QMainWindow*);
ThumbBarDock* thumbBarDock() const;
ImportThumbnailBar* thumbBar() const;
ImportIconView* importIconView() const;
ImportPreviewView* importPreviewView() const;
#ifdef HAVE_MARBLE
MapWidgetView* mapWidgetView() const;
#endif // HAVE_MARBLE
#ifdef HAVE_MEDIAPLAYER
MediaPlayerView* mediaPlayerView() const;
#endif //HAVE_MEDIAPLAYER
bool isInSingleFileMode() const;
bool isInMultipleFileMode() const;
//FIXME: bool isInAbstractMode() const;
void setPreviewItem(const CamItemInfo& info = CamItemInfo(),
const CamItemInfo& previous = CamItemInfo(),
const CamItemInfo& next = CamItemInfo());
StackedViewMode viewMode() const;
void setViewMode(const StackedViewMode mode);
void previewLoaded();
void increaseZoom();
void decreaseZoom();
void fitToWindow();
void toggleFitToWindowOr100();
void zoomTo100Percents();
void setZoomFactor(double z);
void setZoomFactorSnapped(double z);
bool maxZoom() const;
bool minZoom() const;
double zoomFactor() const;
double zoomMin() const;
double zoomMax() const;
Q_SIGNALS:
void signalNextItem();
void signalPrevItem();
void signalViewModeChanged();
void signalEscapePreview();
void signalZoomFactorChanged(double);
//FIXME: void signalGotoAlbumAndItem(const CamItemInfo&);
//FIXME: void signalGotoDateAndItem(const CamItemInfo&);
//FIXME: void signalGotoTagAndItem(int);
public Q_SLOTS:
void slotEscapePreview();
private Q_SLOTS:
void slotPreviewLoaded(bool);
void slotZoomFactorChanged(double);
void slotThumbBarSelectionChanged();
void slotIconViewSelectionChanged();
private:
void readSettings();
void syncSelection(ImportCategorizedView* const from, ImportCategorizedView* const to);
/// Used to return the category for a specified camera item.
QString identifyCategoryforMime(const QString& mime) const;
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_IMPORT_STACKED_VIEW_H
diff --git a/core/utilities/maintenance/databasetask.cpp b/core/utilities/maintenance/databasetask.cpp
index 19d41f6cc1..007ce66549 100644
--- a/core/utilities/maintenance/databasetask.cpp
+++ b/core/utilities/maintenance/databasetask.cpp
@@ -1,546 +1,546 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2017-01-29
* Description : Thread actions task for database cleanup.
*
* Copyright (C) 2017-2018 by Mario Frank <mario dot frank at uni minus potsdam dot de>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "databasetask.h"
// Qt includes
#include <QUrlQuery>
// Local includes
#include "digikam_debug.h"
#include "imageinfo.h"
#include "thumbsdb.h"
#include "thumbsdbaccess.h"
#include "coredb.h"
#include "coredbaccess.h"
#include "recognitiondatabase.h"
#include "facetagseditor.h"
#include "maintenancedata.h"
#include "similaritydb.h"
#include "similaritydbaccess.h"
namespace Digikam
{
class Q_DECL_HIDDEN DatabaseTask::Private
{
public:
explicit Private()
: scanThumbsDb(false),
scanRecognitionDb(false),
scanSimilarityDb(false),
mode(Mode::Unknown),
data(0)
{
}
QString objectIdentification;
bool scanThumbsDb;
bool scanRecognitionDb;
bool scanSimilarityDb;
Mode mode;
MaintenanceData* data;
};
// -------------------------------------------------------
DatabaseTask::DatabaseTask()
: ActionJob(),
d(new Private)
{
}
DatabaseTask::~DatabaseTask()
{
cancel();
delete d;
}
void DatabaseTask::computeDatabaseJunk(bool thumbsDb, bool facesDb, bool similarityDb)
{
d->scanThumbsDb = thumbsDb;
d->scanRecognitionDb = facesDb;
d->scanSimilarityDb = similarityDb;
}
void DatabaseTask::setMode(Mode mode)
{
d->mode = mode;
}
void DatabaseTask::setMaintenanceData(MaintenanceData* const data)
{
d->data = data;
}
void DatabaseTask::run()
{
if (m_cancel)
{
return;
}
emit signalStarted();
QThread::sleep(1);
if (d->mode == Mode::ShrinkDatabases)
{
qCDebug(DIGIKAM_GENERAL_LOG) << "Shrinking databases";
if (CoreDbAccess().db()->integrityCheck())
{
CoreDbAccess().db()->vacuum();
if (!CoreDbAccess().db()->integrityCheck())
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for core DB failed after vacuum. Something went wrong.";
// Signal that the database was vacuumed but failed the integrity check afterwards.
emit signalFinished(true,false);
}
else
{
qCDebug(DIGIKAM_DATABASE_LOG) << "Finished vacuuming of core DB. Integrity check after vacuuming was positive.";
emit signalFinished(true,true);
}
}
else
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for core DB failed. Will not vacuum.";
// Signal that the integrity check failed and thus the vacuum was skipped
emit signalFinished(false,false);
}
QThread::sleep(1);
if (m_cancel)
{
return;
}
if (ThumbsDbAccess::isInitialized())
{
if (ThumbsDbAccess().db()->integrityCheck())
{
ThumbsDbAccess().db()->vacuum();
if (!ThumbsDbAccess().db()->integrityCheck())
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for thumbnails DB failed after vacuum. Something went wrong.";
// Signal that the database was vacuumed but failed the integrity check afterwards.
emit signalFinished(true,false);
}
else
{
qCDebug(DIGIKAM_DATABASE_LOG) << "Finished vacuuming of thumbnails DB. Integrity check after vacuuming was positive.";
emit signalFinished(true,true);
}
}
else
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for thumbnails DB failed. Will not vacuum.";
// Signal that the integrity check failed and thus the vacuum was skipped
emit signalFinished(false,false);
}
}
else
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Thumbnails DB is not initialised. Will not vacuum.";
emit signalFinished(false,false);
}
QThread::sleep(1);
if (m_cancel)
{
return;
}
if (RecognitionDatabase().integrityCheck())
{
RecognitionDatabase().vacuum();
if (!RecognitionDatabase().integrityCheck())
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for recognition DB failed after vacuum. Something went wrong.";
// Signal that the database was vacuumed but failed the integrity check afterwards.
emit signalFinished(true,false);
}
else
{
qCDebug(DIGIKAM_DATABASE_LOG) << "Finished vacuuming of recognition DB. Integrity check after vacuuming was positive.";
emit signalFinished(true,true);
}
}
else
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for recognition DB failed. Will not vacuum.";
// Signal that the integrity check failed and thus the vacuum was skipped
emit signalFinished(false,false);
}
QThread::sleep(1);
if (m_cancel)
{
return;
}
if (SimilarityDbAccess::isInitialized())
{
if (SimilarityDbAccess().db()->integrityCheck())
{
SimilarityDbAccess().db()->vacuum();
if (!SimilarityDbAccess().db()->integrityCheck())
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for similarity DB failed after vacuum. Something went wrong.";
// Signal that the database was vacuumed but failed the integrity check afterwards.
emit signalFinished(true,false);
}
else
{
qCDebug(DIGIKAM_DATABASE_LOG) << "Finished vacuuming of similarity DB. Integrity check after vacuuming was positive.";
emit signalFinished(true,true);
}
}
else
{
qCWarning(DIGIKAM_DATABASE_LOG) << "Integrity check for recognition DB failed. Will not vacuum.";
// Signal that the integrity check failed and thus the vacuum was skipped
emit signalFinished(false,false);
}
}
QThread::sleep(1);
}
else if (d->mode == Mode::ComputeDatabaseJunk)
{
QList<qlonglong> staleImageIds;
QList<int> staleThumbIds;
QList<Identity> staleIdentities;
QList<qlonglong> staleSimilarityImageIds;
int additionalItemsToProcess = 0;
QList<qlonglong> coredbItems = CoreDbAccess().db()->getAllItems();
// Get the count of image entries in DB to delete.
staleImageIds = CoreDbAccess().db()->getImageIds(DatabaseItem::Status::Obsolete);
// get the count of items to process for thumbnails cleanup it enabled.
if (d->scanThumbsDb && ThumbsDbAccess::isInitialized())
{
additionalItemsToProcess += coredbItems.size();
}
// get the count of items to process for identities cleanup it enabled.
if (d->scanRecognitionDb)
{
additionalItemsToProcess += RecognitionDatabase().allIdentities().size();
}
if (d->scanSimilarityDb)
{
additionalItemsToProcess += coredbItems.size();
}
if (additionalItemsToProcess > 0)
{
emit signalAddItemsToProcess(additionalItemsToProcess);
}
emit signalFinished();
// Get the stale thumbnail paths.
if (d->scanThumbsDb && ThumbsDbAccess::isInitialized())
{
// Thumbnails should be deleted, if the following conditions hold:
// 1) The file path to which the thumb is assigned does not lead to an item
// 2) The unique hash and file size are not used in core db for an item.
// 3) The custom identifier does not exist in core db for an item.
// OR
// The thumbnail is stale, i.e. no thumbs db table references it.
QSet<int> thumbIds = ThumbsDbAccess().db()->findAll().toSet();
FaceTagsEditor editor;
foreach(const qlonglong& item, coredbItems)
{
if (m_cancel)
{
return;
}
ImageInfo info(item);
if (!info.isNull())
{
QString hash = CoreDbAccess().db()->getImagesFields(item,DatabaseFields::ImagesField::UniqueHash).first().toString();
qlonglong fileSize = info.fileSize();
// Remove the id that is found by the file path. Finding the id -1 does no harm
bool removed = thumbIds.remove(ThumbsDbAccess().db()->findByFilePath(info.filePath()).id);
if (!removed)
{
// Remove the id that is found by the hash and file size. Finding the id -1 does no harm
thumbIds.remove(ThumbsDbAccess().db()->findByHash(hash,fileSize).id);
}
// Add the custom identifier.
// get all faces for the image and generate the custom identifiers
QUrl url;
url.setScheme(QLatin1String("detail"));
url.setPath(info.filePath());
QList<FaceTagsIface> faces = editor.databaseFaces(item);
foreach(const FaceTagsIface& face, faces)
{
QRect rect = face.region().toRect();
QString r = QString::fromLatin1("%1,%2-%3x%4").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height());
QUrlQuery q(url);
// Remove the previous query if existent.
q.removeQueryItem(QLatin1String("rect"));
q.addQueryItem(QLatin1String("rect"), r);
url.setQuery(q);
//qCDebug(DIGIKAM_GENERAL_LOG) << "URL: " << url.toString();
- // Remove the id that is found by the custom identifyer. Finding the id -1 does no harm
+ // Remove the id that is found by the custom identifier. Finding the id -1 does no harm
thumbIds.remove(ThumbsDbAccess().db()->findByCustomIdentifier(url.toString()).id);
}
}
// Signal that this item was processed.
emit signalFinished();
}
// The remaining thumbnail ids should be used to remove them since they are stale.
staleThumbIds = thumbIds.toList();
// Signal that the database was processed.
emit signalFinished();
}
if (m_cancel)
{
return;
}
// Get the stale face identities.
if (d->scanRecognitionDb)
{
QList<TagProperty> properties = CoreDbAccess().db()->getTagProperties(TagPropertyName::faceEngineUuid());
QSet<QString> uuidSet;
foreach(const TagProperty& prop, properties)
{
uuidSet << prop.value;
}
QList<Identity> identities = RecognitionDatabase().allIdentities();
- // Get all identitites to remove. Don't remove now in order to make sure no side effects occur.
+ // Get all identities to remove. Don't remove now in order to make sure no side effects occur.
foreach(const Identity& identity, identities)
{
QString value = identity.attribute(QLatin1String("uuid"));
if (!value.isEmpty() && !uuidSet.contains(value))
{
staleIdentities << identity;
}
// Signal that this identity was processed.
emit signalFinished();
}
// Signal that the database was processed.
emit signalFinished();
}
if (m_cancel)
{
return;
}
if (d->scanSimilarityDb)
{
// Get all registered image ids from the similarity db.
QSet<qlonglong> similarityDbItems = SimilarityDbAccess().db()->registeredImageIds();
// Remove all image ids that are existent in the core db
foreach(const qlonglong& imageId, coredbItems)
{
similarityDbItems.remove(imageId);
// Signal that this image id was processed.
signalFinished();
}
// The remaining image ids should be removed from the similarity db.
staleSimilarityImageIds = similarityDbItems.toList();
// Signal that the database was processed.
signalFinished();
}
emit signalData(staleImageIds,staleThumbIds,staleIdentities, staleSimilarityImageIds);
}
else if (d->mode == Mode::CleanCoreDb)
{
// While we have data (using this as check for non-null)
while (d->data)
{
if (m_cancel)
{
return;
}
qlonglong imageId = d->data->getImageId();
if (imageId == -1)
{
break;
}
CoreDbAccess().db()->deleteItem(imageId);
emit signalFinished();
}
}
else if (d->mode == Mode::CleanThumbsDb)
{
BdEngineBackend::QueryState lastQueryState = BdEngineBackend::QueryState(BdEngineBackend::ConnectionError);
// Connect to the database
lastQueryState = ThumbsDbAccess().backend()->beginTransaction();
if (BdEngineBackend::NoErrors == lastQueryState)
{
// Start removing.
// While we have data (using this as check for non-null)
while (d->data)
{
if (m_cancel)
{
return;
}
int thumbId = d->data->getThumbnailId();
if (thumbId == -1)
{
break;
}
lastQueryState = ThumbsDbAccess().db()->remove(thumbId);
emit signalFinished();
}
// Check for errors.
if (BdEngineBackend::NoErrors == lastQueryState)
{
- // Commit the removel if everything was fine.
+ // Commit the removal if everything was fine.
lastQueryState = ThumbsDbAccess().backend()->commitTransaction();
if (BdEngineBackend::NoErrors != lastQueryState)
{
qCWarning(DIGIKAM_THUMBSDB_LOG) << "Could not commit the removal of "
<< d->objectIdentification
<< " due to error ";
}
}
else
{
qCWarning(DIGIKAM_THUMBSDB_LOG) << "Could not start the removal of "
<< d->objectIdentification
<< " due to error ";
}
}
else
{
qCWarning(DIGIKAM_THUMBSDB_LOG) << "Could not begin the transaction for the removal of "
<< d->objectIdentification
<< " due to error ";
}
}
else if (d->mode == Mode::CleanRecognitionDb)
{
// While we have data (using this as check for non-null)
while (d->data)
{
if (m_cancel)
{
return;
}
Identity identity = d->data->getIdentity();
if (identity.isNull())
{
break;
}
RecognitionDatabase().deleteIdentity(identity);
emit signalFinished();
}
}
else if (d->mode == Mode::CleanSimilarityDb)
{
// While we have data (using this as check for non-null)
while (d->data)
{
if (m_cancel)
{
return;
}
qlonglong imageId = d->data->getSimilarityImageId();
if (imageId == -1)
{
break;
}
SimilarityDbAccess().db()->removeImageFingerprint(imageId, FuzzyAlgorithm::Haar);
SimilarityDbAccess().db()->removeImageFingerprint(imageId, FuzzyAlgorithm::TfIdf);
emit signalFinished();
}
}
emit signalDone();
}
} // namespace Digikam
diff --git a/core/utilities/maintenance/imagequalitytask.cpp b/core/utilities/maintenance/imagequalitytask.cpp
index 76964501bc..1bd58ef2f7 100644
--- a/core/utilities/maintenance/imagequalitytask.cpp
+++ b/core/utilities/maintenance/imagequalitytask.cpp
@@ -1,138 +1,138 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2013-08-19
* Description : Thread actions task for image quality sorter.
*
* Copyright (C) 2013-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "imagequalitytask.h"
// Local includes
#include "digikam_debug.h"
#include "dimg.h"
#include "previewloadthread.h"
#include "imagequalitysettings.h"
#include "imgqsort.h"
#include "imageinfo.h"
#include "maintenancedata.h"
namespace Digikam
{
class Q_DECL_HIDDEN ImageQualityTask::Private
{
public:
explicit Private()
: imgqsort(0),
data(0)
{
}
ImageQualitySettings quality;
ImgQSort* imgqsort;
MaintenanceData* data;
};
// -------------------------------------------------------
ImageQualityTask::ImageQualityTask()
: ActionJob(),
d(new Private)
{
}
ImageQualityTask::~ImageQualityTask()
{
slotCancel();
cancel();
delete d;
}
void ImageQualityTask::setQuality(const ImageQualitySettings& quality)
{
d->quality = quality;
}
void ImageQualityTask::setMaintenanceData(MaintenanceData* const data)
{
d->data = data;
}
void ImageQualityTask::slotCancel()
{
if (d->imgqsort)
{
d->imgqsort->cancelAnalyse();
}
}
void ImageQualityTask::run()
{
// While we have data (using this as check for non-null)
while (d->data)
{
if (m_cancel)
{
return;
}
QString path = d->data->getImagePath();
if (path.isEmpty())
{
break;
}
// Get item preview to perform quality analysis. No need to load whole image, this will be slower.
// TODO : check if 1024 pixels size is enough to get suitable Quality results.
DImg dimg = PreviewLoadThread::loadFastSynchronously(path, 1024);
if (!dimg.isNull() && !m_cancel)
{
// TODO : run here Quality analysis backend and store Pick Label result to DB.
// Backend Input : d->quality as Quality analysis settings,
// dimg as reduced size image data to parse,
// path as file path to patch DB properties.
// Result : Backend must scan Quality of image depending of settings and compute a Quality estimation accordingly.
- // Finaly, using file path, DB Pick Label properties must be assigned through ImageInfo interface.
+ // Finally, using file path, DB Pick Label properties must be assigned through ImageInfo interface.
// Warning : All code here will run in a separated thread and must be re-entrant/thread-safe. Only pure computation
// must be processed. GUI calls are prohibited. ImageInfo and DImg can be used safety in thread.
PickLabel pick;
d->imgqsort = new ImgQSort(dimg, d->quality, &pick);
d->imgqsort->startAnalyse();
ImageInfo info = ImageInfo::fromLocalFile(path);
info.setPickLabel(pick);
delete d->imgqsort; //delete image data after setting label
d->imgqsort = 0;
}
// Dispatch progress to Progress Manager
QImage qimg = dimg.smoothScale(22, 22, Qt::KeepAspectRatio).copyQImage();
emit signalFinished(qimg);
}
emit signalDone();
}
} // namespace Digikam
diff --git a/core/utilities/maintenance/metadatasynchronizer.h b/core/utilities/maintenance/metadatasynchronizer.h
index ade313d32d..37bed86d52 100644
--- a/core/utilities/maintenance/metadatasynchronizer.h
+++ b/core/utilities/maintenance/metadatasynchronizer.h
@@ -1,94 +1,94 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2007-22-01
* Description : batch sync pictures metadata with database
*
* Copyright (C) 2007-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_METADATA_SYNCHRONIZER_H
#define DIGIKAM_METADATA_SYNCHRONIZER_H
// Qt includes
#include <QObject>
// Local includes
#include "album.h"
#include "imageinfo.h"
#include "maintenancetool.h"
namespace Digikam
{
class Album;
class MetadataSynchronizer : public MaintenanceTool
{
Q_OBJECT
public:
enum SyncDirection
{
WriteFromDatabaseToFile = 0,
ReadFromFileToDatabase
};
public:
- /** Constructor which sync all pictures metadata from an Albums list. If list is empty, whole Albums collection is procressed.
+ /** Constructor which sync all pictures metadata from an Albums list. If list is empty, whole Albums collection is processed.
*/
explicit MetadataSynchronizer(const AlbumList& list=AlbumList(), SyncDirection direction = WriteFromDatabaseToFile, ProgressItem* const parent = 0);
/** Constructor which sync all pictures metadata from an Images list
*/
explicit MetadataSynchronizer(const ImageInfoList& list, SyncDirection = WriteFromDatabaseToFile, ProgressItem* const parent = 0);
void setTagsOnly(bool value);
~MetadataSynchronizer();
void setUseMultiCoreCPU(bool b);
private Q_SLOTS:
void slotStart();
void slotParseAlbums();
void slotAlbumParsed(const ImageInfoList&);
void slotAdvance();
void slotOneAlbumIsComplete();
void slotCancel();
private:
void init(SyncDirection direction);
void parseList();
void parsePicture();
void processOneAlbum();
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_METADATA_SYNCHRONIZER_H
diff --git a/core/utilities/maintenance/newitemsfinder.h b/core/utilities/maintenance/newitemsfinder.h
index a1460de679..a60026d63d 100644
--- a/core/utilities/maintenance/newitemsfinder.h
+++ b/core/utilities/maintenance/newitemsfinder.h
@@ -1,77 +1,77 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2012-01-20
* Description : new items finder.
*
* Copyright (C) 2012-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_NEW_ITEMS_FINDER_H
#define DIGIKAM_NEW_ITEMS_FINDER_H
// Qt includes
#include <QObject>
#include <QString>
// Local includes
#include "maintenancetool.h"
#include "digikam_export.h"
namespace Digikam
{
class DIGIKAM_EXPORT NewItemsFinder : public MaintenanceTool
{
Q_OBJECT
public:
enum FinderMode
{
- CompleteCollectionScan, /** Scan whole collection imediatly. */
+ CompleteCollectionScan, /** Scan whole collection immediately. */
ScanDeferredFiles, /** Defer whole collection scan. */
- ScheduleCollectionScan /** Scan imediatly folders list passed in contructor. */
+ ScheduleCollectionScan /** Scan immediately folders list passed in constructor. */
};
public:
explicit NewItemsFinder(const FinderMode mode = CompleteCollectionScan,
const QStringList& foldersToScan = QStringList(),
ProgressItem* const parent = 0);
~NewItemsFinder();
private Q_SLOTS:
void slotStart();
void slotScanStarted(const QString&);
void slotPartialScanDone(const QString&);
void slotTotalFilesToScan(int);
void slotFilesScanned(int);
void slotCancel();
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_NEW_ITEMS_FINDER_H
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptDataBuffer.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptDataBuffer.cpp
index f5ab03ced3..d3cc53ce80 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptDataBuffer.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptDataBuffer.cpp
@@ -1,256 +1,256 @@
/*****************************************************************
|
| Neptune - Data Buffer
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptDataBuffer.h"
#include "NptUtils.h"
#include "NptResults.h"
/*----------------------------------------------------------------------
| NPT_DataBuffer::NPT_DataBuffer
+---------------------------------------------------------------------*/
NPT_DataBuffer::NPT_DataBuffer() :
m_BufferIsLocal(true),
m_Buffer(NULL),
m_BufferSize(0),
m_DataSize(0)
{
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::NPT_DataBuffer
+---------------------------------------------------------------------*/
NPT_DataBuffer::NPT_DataBuffer(NPT_Size bufferSize) :
m_BufferIsLocal(true),
m_Buffer(bufferSize?new NPT_Byte[bufferSize]:NULL),
m_BufferSize(bufferSize),
m_DataSize(0)
{
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::NPT_DataBuffer
+---------------------------------------------------------------------*/
NPT_DataBuffer::NPT_DataBuffer(const void* data, NPT_Size data_size, bool copy) :
m_BufferIsLocal(copy),
m_Buffer(copy?(data_size?new NPT_Byte[data_size]:NULL):reinterpret_cast<NPT_Byte*>(const_cast<void*>(data))),
m_BufferSize(data_size),
m_DataSize(data_size)
{
if (copy && data_size) NPT_CopyMemory(m_Buffer, data, data_size);
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::NPT_DataBuffer
+---------------------------------------------------------------------*/
NPT_DataBuffer::NPT_DataBuffer(const NPT_DataBuffer& other) :
m_BufferIsLocal(true),
m_Buffer(NULL),
m_BufferSize(other.m_DataSize),
m_DataSize(other.m_DataSize)
{
if (m_BufferSize) {
m_Buffer = new NPT_Byte[m_BufferSize];
NPT_CopyMemory(m_Buffer, other.m_Buffer, m_BufferSize);
}
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::~NPT_DataBuffer
+---------------------------------------------------------------------*/
NPT_DataBuffer::~NPT_DataBuffer()
{
Clear();
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::Clear
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::Clear()
{
if (m_BufferIsLocal) {
delete[] m_Buffer;
}
m_Buffer = NULL;
m_DataSize = 0;
m_BufferSize = 0;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::operator=
+---------------------------------------------------------------------*/
NPT_DataBuffer&
NPT_DataBuffer::operator=(const NPT_DataBuffer& copy)
{
// do nothing if we're assigning to ourselves
if (this != &copy) {
Clear();
m_BufferIsLocal = true;
m_BufferSize = copy.m_BufferSize;
m_DataSize = copy.m_DataSize;
if (m_BufferSize) {
m_Buffer = new NPT_Byte[m_BufferSize];
NPT_CopyMemory(m_Buffer, copy.m_Buffer, m_BufferSize);
}
}
return *this;
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::operator==
+---------------------------------------------------------------------*/
bool
NPT_DataBuffer::operator==(const NPT_DataBuffer& other) const
{
// check that the sizes match
if (m_DataSize != other.m_DataSize) return false;
return NPT_MemoryEqual(m_Buffer,
other.m_Buffer,
m_DataSize);
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::SetBuffer
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::SetBuffer(NPT_Byte* buffer, NPT_Size buffer_size)
{
Clear();
// we're now using an external buffer
m_BufferIsLocal = false;
m_Buffer = buffer;
m_BufferSize = buffer_size;
m_DataSize = 0;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::SetBufferSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::SetBufferSize(NPT_Size buffer_size)
{
if (m_BufferIsLocal) {
return ReallocateBuffer(buffer_size);
} else {
return NPT_ERROR_NOT_SUPPORTED; // you cannot change the
// buffer management mode
}
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::Reserve
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::Reserve(NPT_Size size)
{
if (size <= m_BufferSize) return NPT_SUCCESS;
- // try doubling the buffer to accomodate for the new size
+ // try doubling the buffer to accommodate for the new size
NPT_Size new_size = m_BufferSize*2;
if (new_size < size) new_size = size;
return SetBufferSize(new_size);
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::SetDataSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::SetDataSize(NPT_Size size)
{
if (size > m_BufferSize) {
// the buffer is too small, we need to reallocate it
if (m_BufferIsLocal) {
NPT_CHECK(ReallocateBuffer(size));
} else {
// we cannot reallocate an external buffer
return NPT_ERROR_NOT_SUPPORTED;
}
}
m_DataSize = size;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::SetData
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::SetData(const NPT_Byte* data, NPT_Size size)
{
if (size > m_BufferSize) {
if (m_BufferIsLocal) {
NPT_CHECK(ReallocateBuffer(size));
} else {
return NPT_ERROR_INVALID_STATE;
}
}
if (data) NPT_CopyMemory(m_Buffer, data, size);
m_DataSize = size;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_DataBuffer::ReallocateBuffer
+---------------------------------------------------------------------*/
NPT_Result
NPT_DataBuffer::ReallocateBuffer(NPT_Size size)
{
// check that the existing data fits
if (m_DataSize > size) return NPT_ERROR_INVALID_PARAMETERS;
// allocate a new buffer
NPT_Byte* newBuffer = new NPT_Byte[size];
// copy the contents of the previous buffer, if any
if (m_Buffer && m_DataSize) {
NPT_CopyMemory(newBuffer, m_Buffer, m_DataSize);
}
// destroy the previous buffer
delete[] m_Buffer;
// use the new buffer
m_Buffer = newBuffer;
m_BufferSize = size;
return NPT_SUCCESS;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptFile.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptFile.cpp
index 2c2af402e3..579d1273fd 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptFile.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptFile.cpp
@@ -1,406 +1,406 @@
/*****************************************************************
|
| Neptune - Files
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptFile.h"
#include "NptUtils.h"
#include "NptConstants.h"
#include "NptStreams.h"
#include "NptDataBuffer.h"
#include "NptLogging.h"
/*----------------------------------------------------------------------
| logging
+---------------------------------------------------------------------*/
NPT_SET_LOCAL_LOGGER("neptune.file")
/*----------------------------------------------------------------------
| NPT_FilePath::BaseName
+---------------------------------------------------------------------*/
NPT_String
NPT_FilePath::BaseName(const char* path, bool with_extension /* = true */)
{
NPT_String result = path;
int separator = result.ReverseFind(Separator);
if (separator >= 0) {
result = path+separator+NPT_StringLength(Separator);
}
if (!with_extension) {
int dot = result.ReverseFind('.');
if (dot >= 0) {
result.SetLength(dot);
}
}
return result;
}
/*----------------------------------------------------------------------
| NPT_FilePath::DirName
+---------------------------------------------------------------------*/
NPT_String
NPT_FilePath::DirName(const char* path)
{
NPT_String result = path;
int separator = result.ReverseFind(Separator);
if (separator >= 0) {
if (separator == 0) {
result.SetLength(NPT_StringLength(Separator));
} else {
result.SetLength(separator);
}
} else {
result.SetLength(0);
}
return result;
}
/*----------------------------------------------------------------------
| NPT_FilePath::FileExtension
+---------------------------------------------------------------------*/
NPT_String
NPT_FilePath::FileExtension(const char* path)
{
NPT_String result = path;
int separator = result.ReverseFind('.');
if (separator >= 0) {
result = path+separator;
} else {
result.SetLength(0);
}
return result;
}
/*----------------------------------------------------------------------
| NPT_FilePath::Create
+---------------------------------------------------------------------*/
NPT_String
NPT_FilePath::Create(const char* directory, const char* basename)
{
if (!directory || NPT_StringLength(directory) == 0) return basename;
if (!basename || NPT_StringLength(basename) == 0) return directory;
NPT_String result = directory;
if (!result.EndsWith(Separator) && basename[0] != Separator[0]) {
result += Separator;
}
result += basename;
return result;
}
/*----------------------------------------------------------------------
| NPT_File::CreateDir
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::CreateDir(const char* path, bool create_intermediate_dirs)
{
NPT_String full_path = path;
// normalize path separators
full_path.Replace((NPT_FilePath::Separator[0] == '/')?'\\':'/', NPT_FilePath::Separator);
// remove superfluous delimiters at the end
full_path.TrimRight(NPT_FilePath::Separator);
// create intermediate directories if needed
if (create_intermediate_dirs) {
NPT_String dir_path;
// look for the next path separator
int separator = full_path.Find(NPT_FilePath::Separator, 1);
while (separator > 0) {
// copy the path up to the separator
dir_path = full_path.SubString(0, separator);
// create the directory non recursively
NPT_CHECK_WARNING(NPT_File::CreateDir(dir_path, false));
// look for the next delimiter
separator = full_path.Find(NPT_FilePath::Separator, separator + 1);
}
}
// create the final directory
NPT_Result result = NPT_File::CreateDir(full_path);
// return error only if file didn't exist
if (NPT_FAILED(result) && result != NPT_ERROR_FILE_ALREADY_EXISTS) {
return result;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_File::RemoveDir
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::RemoveDir(const char* path, bool force_if_not_empty)
{
NPT_String root_path = path;
// normalize path separators
root_path.Replace((NPT_FilePath::Separator[0] == '/')?'\\':'/', NPT_FilePath::Separator);
// remove superfluous delimiters at the end
root_path.TrimRight(NPT_FilePath::Separator);
// remove all entries in the directory if required
if (force_if_not_empty) {
// enumerate all entries
NPT_File dir(root_path);
NPT_List<NPT_String> entries;
NPT_CHECK_WARNING(dir.ListDir(entries));
for (NPT_List<NPT_String>::Iterator it = entries.GetFirstItem(); it; ++it) {
NPT_File::Remove(NPT_FilePath::Create(root_path, *it), true);
}
}
// remove the (now empty) directory
return NPT_File::RemoveDir(root_path);
}
/*----------------------------------------------------------------------
| NPT_File::Load
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Load(const char* path, NPT_DataBuffer& buffer, NPT_FileInterface::OpenMode mode)
{
// create and open the file
NPT_File file(path);
NPT_Result result = file.Open(mode);
if (NPT_FAILED(result)) return result;
// load the file
result = file.Load(buffer);
// close the file
file.Close();
return result;
}
/*----------------------------------------------------------------------
| NPT_File::Load
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Load(const char* path, NPT_String& data, NPT_FileInterface::OpenMode mode)
{
NPT_DataBuffer buffer;
- // reset ouput params
+ // reset output params
data = "";
// create and open the file
NPT_File file(path);
NPT_Result result = file.Open(mode);
if (NPT_FAILED(result)) return result;
// load the file
result = file.Load(buffer);
if (NPT_SUCCEEDED(result) && buffer.GetDataSize() > 0) {
data.Assign((const char*)buffer.GetData(), buffer.GetDataSize());
data.SetLength(buffer.GetDataSize());
}
// close the file
file.Close();
return result;
}
/*----------------------------------------------------------------------
| NPT_File::Save
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Save(const char* filename, NPT_String& data)
{
NPT_DataBuffer buffer(data.GetChars(), data.GetLength());
return NPT_File::Save(filename, buffer);
}
/*----------------------------------------------------------------------
| NPT_File::Save
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Save(const char* filename, const NPT_DataBuffer& buffer)
{
// create and open the file
NPT_File file(filename);
NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_WRITE | NPT_FILE_OPEN_MODE_CREATE | NPT_FILE_OPEN_MODE_TRUNCATE);
if (NPT_FAILED(result)) return result;
// load the file
result = file.Save(buffer);
// close the file
file.Close();
return result;
}
/*----------------------------------------------------------------------
| NPT_File::Load
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Load(NPT_DataBuffer& buffer)
{
NPT_InputStreamReference input;
// get the input stream for the file
NPT_CHECK_WARNING(GetInputStream(input));
// read the stream
return input->Load(buffer);
}
/*----------------------------------------------------------------------
| NPT_File::Save
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Save(const NPT_DataBuffer& buffer)
{
NPT_OutputStreamReference output;
// get the output stream for the file
NPT_CHECK_WARNING(GetOutputStream(output));
// write to the stream
return output->WriteFully(buffer.GetData(), buffer.GetDataSize());
}
/*----------------------------------------------------------------------
| NPT_File::GetInfo
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::GetInfo(NPT_FileInfo& info)
{
if (m_IsSpecial) {
info.m_Type = NPT_FileInfo::FILE_TYPE_SPECIAL;
info.m_Size = 0;
info.m_Attributes = 0;
info.m_AttributesMask = 0;
return NPT_SUCCESS;
}
return GetInfo(m_Path.GetChars(), &info);
}
/*----------------------------------------------------------------------
| NPT_File::GetSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::GetSize(NPT_LargeSize& size)
{
// default value
size = 0;
// get the file info
NPT_FileInfo info;
GetInfo(info);
switch (info.m_Type) {
case NPT_FileInfo::FILE_TYPE_DIRECTORY: {
NPT_List<NPT_String> entries;
NPT_CHECK_WARNING(ListDir(entries));
size = entries.GetItemCount();
break;
}
case NPT_FileInfo::FILE_TYPE_REGULAR:
case NPT_FileInfo::FILE_TYPE_OTHER:
size = info.m_Size;
return NPT_SUCCESS;
default:
break;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_File::GetSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::GetSize(const char* path, NPT_LargeSize& size)
{
NPT_File file(path);
return file.GetSize(size);
}
/*----------------------------------------------------------------------
| NPT_File::Remove
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Remove(const char* path, bool recurse /* = false */)
{
NPT_FileInfo info;
// make sure the path exists
NPT_CHECK_WARNING(GetInfo(path, &info));
if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) {
return RemoveDir(path, recurse);
} else {
return RemoveFile(path);
}
}
/*----------------------------------------------------------------------
| NPT_File::Rename
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::Rename(const char* path)
{
NPT_Result result = Rename(m_Path.GetChars(), path);
if (NPT_SUCCEEDED(result)) {
m_Path = path;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_File::ListDir
+---------------------------------------------------------------------*/
NPT_Result
NPT_File::ListDir(NPT_List<NPT_String>& entries)
{
entries.Clear();
return ListDir(m_Path.GetChars(), entries);
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.cpp
index af4f6dc949..35d5c45603 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.cpp
@@ -1,3463 +1,3463 @@
/*****************************************************************
|
| Neptune - HTTP Protocol
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptHttp.h"
#include "NptSockets.h"
#include "NptBufferedStreams.h"
#include "NptDebug.h"
#include "NptVersion.h"
#include "NptUtils.h"
#include "NptFile.h"
#include "NptSystem.h"
#include "NptLogging.h"
#include "NptTls.h"
#include "NptStreams.h"
/*----------------------------------------------------------------------
| logging
+---------------------------------------------------------------------*/
NPT_SET_LOCAL_LOGGER("neptune.http")
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
const char* const NPT_HTTP_DEFAULT_403_HTML = "<html><head><title>403 Forbidden</title></head><body><h1>Forbidden</h1><p>Access to this URL is forbidden.</p></html>";
const char* const NPT_HTTP_DEFAULT_404_HTML = "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></html>";
const char* const NPT_HTTP_DEFAULT_500_HTML = "<html><head><title>500 Internal Error</title></head><body><h1>Internal Error</h1><p>The server encountered an unexpected condition which prevented it from fulfilling the request.</p></html>";
/*----------------------------------------------------------------------
| NPT_HttpUrl::NPT_HttpUrl
+---------------------------------------------------------------------*/
NPT_HttpUrl::NPT_HttpUrl(const char* url, bool ignore_scheme) :
NPT_Url(url)
{
if (!ignore_scheme) {
if (GetSchemeId() != NPT_Uri::SCHEME_ID_HTTP &&
GetSchemeId() != NPT_Uri::SCHEME_ID_HTTPS) {
Reset();
}
}
}
/*----------------------------------------------------------------------
| NPT_HttpUrl::NPT_HttpUrl
+---------------------------------------------------------------------*/
NPT_HttpUrl::NPT_HttpUrl(const char* host,
NPT_UInt16 port,
const char* path,
const char* query,
const char* fragment) :
NPT_Url("http", host, port, path, query, fragment)
{
}
/*----------------------------------------------------------------------
| NPT_HttpUrl::ToString
+---------------------------------------------------------------------*/
NPT_String
NPT_HttpUrl::ToString(bool with_fragment) const
{
NPT_UInt16 default_port;
switch (m_SchemeId) {
case SCHEME_ID_HTTP: default_port = NPT_HTTP_DEFAULT_PORT; break;
case SCHEME_ID_HTTPS: default_port = NPT_HTTPS_DEFAULT_PORT; break;
default: default_port = 0;
}
return NPT_Url::ToStringWithDefaultPort(default_port, with_fragment);
}
/*----------------------------------------------------------------------
| NPT_HttpHeader::NPT_HttpHeader
+---------------------------------------------------------------------*/
NPT_HttpHeader::NPT_HttpHeader(const char* name, const char* value):
m_Name(name),
m_Value(value)
{
}
/*----------------------------------------------------------------------
| NPT_HttpHeader::~NPT_HttpHeader
+---------------------------------------------------------------------*/
NPT_HttpHeader::~NPT_HttpHeader()
{
}
/*----------------------------------------------------------------------
| NPT_HttpHeader::Emit
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeader::Emit(NPT_OutputStream& stream) const
{
stream.WriteString(m_Name);
stream.WriteFully(": ", 2);
stream.WriteString(m_Value);
stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
NPT_LOG_FINEST_2("header %s: %s", m_Name.GetChars(), m_Value.GetChars());
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpHeader::SetName
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeader::SetName(const char* name)
{
m_Name = name;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpHeader::~NPT_HttpHeader
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeader::SetValue(const char* value)
{
m_Value = value;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::NPT_HttpHeaders
+---------------------------------------------------------------------*/
NPT_HttpHeaders::NPT_HttpHeaders()
{
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::~NPT_HttpHeaders
+---------------------------------------------------------------------*/
NPT_HttpHeaders::~NPT_HttpHeaders()
{
m_Headers.Apply(NPT_ObjectDeleter<NPT_HttpHeader>());
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::Parse
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeaders::Parse(NPT_BufferedInputStream& stream)
{
NPT_String header_name;
NPT_String header_value;
bool header_pending = false;
NPT_String line;
while (NPT_SUCCEEDED(stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH))) {
if (line.GetLength() == 0) {
// empty line, end of headers
break;
}
if (header_pending && (line[0] == ' ' || line[0] == '\t')) {
// continuation (folded header)
header_value.Append(line.GetChars()+1, line.GetLength()-1);
} else {
// add the pending header to the list
if (header_pending) {
header_value.Trim();
AddHeader(header_name, header_value);
header_pending = false;
NPT_LOG_FINEST_2("header - %s: %s",
header_name.GetChars(),
header_value.GetChars());
}
// find the colon separating the name and the value
int colon_index = line.Find(':');
if (colon_index < 1) {
// invalid syntax, ignore
continue;
}
header_name = line.Left(colon_index);
// the field value starts at the first non-whitespace
const char* value = line.GetChars()+colon_index+1;
while (*value == ' ' || *value == '\t') {
value++;
}
header_value = value;
// the header is pending
header_pending = true;
}
}
// if we have a header pending, add it now
if (header_pending) {
header_value.Trim();
AddHeader(header_name, header_value);
NPT_LOG_FINEST_2("header %s: %s",
header_name.GetChars(),
header_value.GetChars());
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::Emit
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeaders::Emit(NPT_OutputStream& stream) const
{
// for each header in the list
NPT_List<NPT_HttpHeader*>::Iterator header = m_Headers.GetFirstItem();
while (header) {
// emit the header
NPT_CHECK_WARNING((*header)->Emit(stream));
++header;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::GetHeader
+---------------------------------------------------------------------*/
NPT_HttpHeader*
NPT_HttpHeaders::GetHeader(const char* name) const
{
// check args
if (name == NULL) return NULL;
// find a matching header
NPT_List<NPT_HttpHeader*>::Iterator header = m_Headers.GetFirstItem();
while (header) {
if ((*header)->GetName().Compare(name, true) == 0) {
return *header;
}
++header;
}
// not found
return NULL;
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::AddHeader
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeaders::AddHeader(const char* name, const char* value)
{
return m_Headers.Add(new NPT_HttpHeader(name, value));
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::RemoveHeader
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeaders::RemoveHeader(const char* name)
{
bool found = false;
NPT_HttpHeader* header = NULL;
while ((header = GetHeader(name))) {
m_Headers.Remove(header);
delete header;
found = true;
}
return found?NPT_SUCCESS:NPT_ERROR_NO_SUCH_ITEM;
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::SetHeader
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpHeaders::SetHeader(const char* name, const char* value, bool replace)
{
NPT_HttpHeader* header = GetHeader(name);
if (header == NULL) {
return AddHeader(name, value);
} else if (replace) {
return header->SetValue(value);
} else {
return NPT_SUCCESS;
}
}
/*----------------------------------------------------------------------
| NPT_HttpHeaders::GetHeaderValue
+---------------------------------------------------------------------*/
const NPT_String*
NPT_HttpHeaders::GetHeaderValue(const char* name) const
{
NPT_HttpHeader* header = GetHeader(name);
if (header == NULL) {
return NULL;
} else {
return &header->GetValue();
}
}
/*----------------------------------------------------------------------
| NPT_HttpEntityBodyInputStream
+---------------------------------------------------------------------*/
class NPT_HttpEntityBodyInputStream : public NPT_InputStream
{
public:
// constructor and desctructor
NPT_HttpEntityBodyInputStream(NPT_BufferedInputStreamReference& source,
NPT_LargeSize size,
bool size_is_known,
bool chunked,
NPT_HttpClient::Connection* connection,
bool should_persist);
~NPT_HttpEntityBodyInputStream() override;
// methods
bool SizeIsKnown() { return m_SizeIsKnown; }
// NPT_InputStream methods
NPT_Result Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read = NULL) override;
NPT_Result Seek(NPT_Position /*offset*/) override {
return NPT_ERROR_NOT_SUPPORTED;
}
NPT_Result Tell(NPT_Position& offset) override {
offset = m_Position;
return NPT_SUCCESS;
}
NPT_Result GetSize(NPT_LargeSize& size) override {
size = m_Size;
return NPT_SUCCESS;
}
NPT_Result GetAvailable(NPT_LargeSize& available) override;
private:
// methods
virtual void OnFullyRead();
// members
NPT_LargeSize m_Size;
bool m_SizeIsKnown;
bool m_Chunked;
NPT_HttpClient::Connection* m_Connection;
bool m_ShouldPersist;
NPT_Position m_Position;
NPT_InputStreamReference m_Source;
};
/*----------------------------------------------------------------------
| NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream
+---------------------------------------------------------------------*/
NPT_HttpEntityBodyInputStream::NPT_HttpEntityBodyInputStream(
NPT_BufferedInputStreamReference& source,
NPT_LargeSize size,
bool size_is_known,
bool chunked,
NPT_HttpClient::Connection* connection,
bool should_persist) :
m_Size(size),
m_SizeIsKnown(size_is_known),
m_Chunked(chunked),
m_Connection(connection),
m_ShouldPersist(should_persist),
m_Position(0)
{
if (size_is_known && size == 0) {
OnFullyRead();
} else {
if (chunked) {
m_Source = NPT_InputStreamReference(new NPT_HttpChunkedInputStream(source));
} else {
m_Source = source;
}
}
}
/*----------------------------------------------------------------------
| NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream
+---------------------------------------------------------------------*/
NPT_HttpEntityBodyInputStream::~NPT_HttpEntityBodyInputStream()
{
delete m_Connection;
}
/*----------------------------------------------------------------------
| NPT_HttpEntityBodyInputStream::OnFullyRead
+---------------------------------------------------------------------*/
void
NPT_HttpEntityBodyInputStream::OnFullyRead()
{
m_Source = NULL;
if (m_Connection && m_ShouldPersist) {
m_Connection->Recycle();
m_Connection = NULL;
}
}
/*----------------------------------------------------------------------
| NPT_HttpEntityBodyInputStream::Read
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntityBodyInputStream::Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read)
{
if (bytes_read) *bytes_read = 0;
// return now if we've already reached the end
if (m_Source.IsNull()) return NPT_ERROR_EOS;
// clamp to the max possible read size
if (!m_Chunked && m_SizeIsKnown) {
NPT_LargeSize max_can_read = m_Size-m_Position;
if (max_can_read == 0) return NPT_ERROR_EOS;
if (bytes_to_read > max_can_read) bytes_to_read = (NPT_Size)max_can_read;
}
// read from the source
NPT_Size source_bytes_read = 0;
NPT_Result result = m_Source->Read(buffer, bytes_to_read, &source_bytes_read);
if (NPT_SUCCEEDED(result)) {
m_Position += source_bytes_read;
if (bytes_read) *bytes_read = source_bytes_read;
}
// check if we've reached the end
if (result == NPT_ERROR_EOS || (m_SizeIsKnown && (m_Position == m_Size))) {
OnFullyRead();
}
return result;
}
/*----------------------------------------------------------------------
-| NPT_HttpEntityBodyInputStream::GetAvaialble
+| NPT_HttpEntityBodyInputStream::GetAvailable
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntityBodyInputStream::GetAvailable(NPT_LargeSize& available)
{
if (m_Source.IsNull()) {
available = 0;
return NPT_SUCCESS;
}
NPT_Result result = m_Source->GetAvailable(available);
if (NPT_FAILED(result)) {
available = 0;
return result;
}
if (available > m_Size-m_Position) {
available = m_Size-m_Position;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::NPT_HttpEntity
+---------------------------------------------------------------------*/
NPT_HttpEntity::NPT_HttpEntity() :
m_ContentLength(0),
m_ContentLengthIsKnown(false)
{
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::NPT_HttpEntity
+---------------------------------------------------------------------*/
NPT_HttpEntity::NPT_HttpEntity(const NPT_HttpHeaders& headers) :
m_ContentLength(0),
m_ContentLengthIsKnown(false)
{
SetHeaders(headers);
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetHeaders
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetHeaders(const NPT_HttpHeaders& headers)
{
NPT_HttpHeader* header;
// Content-Length
header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH);
if (header != NULL) {
m_ContentLengthIsKnown = true;
NPT_LargeSize length;
if (NPT_SUCCEEDED(header->GetValue().ToInteger64(length))) {
m_ContentLength = length;
} else {
m_ContentLength = 0;
}
}
// Content-Type
header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_TYPE);
if (header != NULL) {
m_ContentType = header->GetValue();
}
// Content-Encoding
header = headers.GetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING);
if (header != NULL) {
m_ContentEncoding = header->GetValue();
}
// Transfer-Encoding
header = headers.GetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING);
if (header != NULL) {
m_TransferEncoding = header->GetValue();
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::~NPT_HttpEntity
+---------------------------------------------------------------------*/
NPT_HttpEntity::~NPT_HttpEntity()
{
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::GetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::GetInputStream(NPT_InputStreamReference& stream)
{
// reset output params first
stream = NULL;
if (m_InputStream.IsNull()) return NPT_FAILURE;
stream = m_InputStream;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetInputStream(const NPT_InputStreamReference& stream,
bool update_content_length /* = false */)
{
m_InputStream = stream;
// get the content length from the stream
if (update_content_length && !stream.IsNull()) {
NPT_LargeSize length;
if (NPT_SUCCEEDED(stream->GetSize(length))) {
return SetContentLength(length);
}
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetInputStream(const void* data, NPT_Size data_size)
{
NPT_MemoryStream* memory_stream = new NPT_MemoryStream(data, data_size);
NPT_InputStreamReference body(memory_stream);
return SetInputStream(body, true);
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetInputStream(const char* string)
{
if (string == NULL) return NPT_ERROR_INVALID_PARAMETERS;
NPT_MemoryStream* memory_stream = new NPT_MemoryStream((const void*)string,
NPT_StringLength(string));
NPT_InputStreamReference body(memory_stream);
return SetInputStream(body, true);
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetInputStream(const NPT_String& string)
{
NPT_MemoryStream* memory_stream = new NPT_MemoryStream((const void*)string.GetChars(),
string.GetLength());
NPT_InputStreamReference body(memory_stream);
return SetInputStream(body, true);
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::Load
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::Load(NPT_DataBuffer& buffer)
{
// check that we have an input stream
if (m_InputStream.IsNull()) return NPT_ERROR_INVALID_STATE;
// load the stream into the buffer
if (m_ContentLength != (NPT_Size)m_ContentLength) return NPT_ERROR_OUT_OF_RANGE;
return m_InputStream->Load(buffer, (NPT_Size)m_ContentLength);
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetContentLength
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetContentLength(NPT_LargeSize length)
{
m_ContentLength = length;
m_ContentLengthIsKnown = true;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetContentType
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetContentType(const char* type)
{
m_ContentType = type;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetContentEncoding
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetContentEncoding(const char* encoding)
{
m_ContentEncoding = encoding;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpEntity::SetTransferEncoding
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEntity::SetTransferEncoding(const char* encoding)
{
m_TransferEncoding = encoding;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpMessage::NPT_HttpMessage
+---------------------------------------------------------------------*/
NPT_HttpMessage::NPT_HttpMessage(const char* protocol) :
m_Protocol(protocol),
m_Entity(NULL)
{
}
/*----------------------------------------------------------------------
| NPT_HttpMessage::NPT_HttpMessage
+---------------------------------------------------------------------*/
NPT_HttpMessage::~NPT_HttpMessage()
{
delete m_Entity;
}
/*----------------------------------------------------------------------
| NPT_HttpMessage::SetEntity
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpMessage::SetEntity(NPT_HttpEntity* entity)
{
if (entity != m_Entity) {
delete m_Entity;
m_Entity = entity;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpMessage::ParseHeaders
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpMessage::ParseHeaders(NPT_BufferedInputStream& stream)
{
return m_Headers.Parse(stream);
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::NPT_HttpRequest
+---------------------------------------------------------------------*/
NPT_HttpRequest::NPT_HttpRequest(const NPT_HttpUrl& url,
const char* method,
const char* protocol) :
NPT_HttpMessage(protocol),
m_Url(url),
m_Method(method)
{
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::NPT_HttpRequest
+---------------------------------------------------------------------*/
NPT_HttpRequest::NPT_HttpRequest(const char* url,
const char* method,
const char* protocol) :
NPT_HttpMessage(protocol),
m_Url(url),
m_Method(method)
{
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::SetUrl
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpRequest::SetUrl(const char* url)
{
m_Url = url;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::SetUrl
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpRequest::SetUrl(const NPT_HttpUrl& url)
{
m_Url = url;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::Parse
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpRequest::Parse(NPT_BufferedInputStream& stream,
const NPT_SocketAddress* endpoint,
NPT_HttpRequest*& request)
{
// default return value
request = NULL;
skip_first_empty_line:
// read the request line
NPT_String line;
NPT_CHECK_FINER(stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH));
NPT_LOG_FINEST_1("http request: %s", line.GetChars());
// when using keep-alive connections, clients such as XBox 360
// incorrectly send a few empty lines as body for GET requests
// so we try to skip them until we find something to parse
if (line.GetLength() == 0) goto skip_first_empty_line;
// check the request line
int first_space = line.Find(' ');
if (first_space < 0) {
NPT_LOG_FINE_1("http request: %s", line.GetChars());
return NPT_ERROR_HTTP_INVALID_REQUEST_LINE;
}
int second_space = line.Find(' ', first_space+1);
if (second_space < 0) {
NPT_LOG_FINE_1("http request: %s", line.GetChars());
return NPT_ERROR_HTTP_INVALID_REQUEST_LINE;
}
// parse the request line
NPT_String method = line.SubString(0, first_space);
NPT_String uri = line.SubString(first_space+1, second_space-first_space-1);
NPT_String protocol = line.SubString(second_space+1);
// create a request
bool proxy_style_request = false;
if (uri.StartsWith("http://", true)) {
// proxy-style request with absolute URI
request = new NPT_HttpRequest(uri, method, protocol);
proxy_style_request = true;
} else {
// normal absolute path request
request = new NPT_HttpRequest("http:", method, protocol);
}
// parse headers
NPT_Result result = request->ParseHeaders(stream);
if (NPT_FAILED(result)) {
delete request;
request = NULL;
return result;
}
// update the URL
if (!proxy_style_request) {
request->m_Url.SetScheme("http");
request->m_Url.ParsePathPlus(uri);
request->m_Url.SetPort(NPT_HTTP_DEFAULT_PORT);
// check for a Host: header
NPT_HttpHeader* host_header = request->GetHeaders().GetHeader(NPT_HTTP_HEADER_HOST);
if (host_header) {
request->m_Url.SetHost(host_header->GetValue());
// host sometimes doesn't contain port
if (endpoint) {
request->m_Url.SetPort(endpoint->GetPort());
}
} else {
// use the endpoint as the host
if (endpoint) {
request->m_Url.SetHost(endpoint->ToString());
} else {
// use defaults
request->m_Url.SetHost("localhost");
}
}
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::~NPT_HttpRequest
+---------------------------------------------------------------------*/
NPT_HttpRequest::~NPT_HttpRequest()
{
}
/*----------------------------------------------------------------------
| NPT_HttpRequest::Emit
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpRequest::Emit(NPT_OutputStream& stream, bool use_proxy) const
{
// write the request line
stream.WriteString(m_Method);
stream.WriteFully(" ", 1);
if (use_proxy) {
stream.WriteString(m_Url.ToString(false));
} else {
stream.WriteString(m_Url.ToRequestString());
}
stream.WriteFully(" ", 1);
stream.WriteString(m_Protocol);
stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
// emit headers
m_Headers.Emit(stream);
// finish with an empty line
stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponse::NPT_HttpResponse
+---------------------------------------------------------------------*/
NPT_HttpResponse::NPT_HttpResponse(NPT_HttpStatusCode status_code,
const char* reason_phrase,
const char* protocol) :
NPT_HttpMessage(protocol),
m_StatusCode(status_code),
m_ReasonPhrase(reason_phrase)
{
}
/*----------------------------------------------------------------------
| NPT_HttpResponse::~NPT_HttpResponse
+---------------------------------------------------------------------*/
NPT_HttpResponse::~NPT_HttpResponse()
{
}
/*----------------------------------------------------------------------
| NPT_HttpResponse::SetStatus
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponse::SetStatus(NPT_HttpStatusCode status_code,
const char* reason_phrase,
const char* protocol)
{
m_StatusCode = status_code;
m_ReasonPhrase = reason_phrase;
if (protocol) m_Protocol = protocol;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponse::SetProtocol
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponse::SetProtocol(const char* protocol)
{
m_Protocol = protocol;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponse::Emit
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponse::Emit(NPT_OutputStream& stream) const
{
// write the request line
stream.WriteString(m_Protocol);
stream.WriteFully(" ", 1);
stream.WriteString(NPT_String::FromInteger(m_StatusCode));
stream.WriteFully(" ", 1);
stream.WriteString(m_ReasonPhrase);
stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
// emit headers
m_Headers.Emit(stream);
// finish with an empty line
stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponse::Parse
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponse::Parse(NPT_BufferedInputStream& stream,
NPT_HttpResponse*& response)
{
// default return value
response = NULL;
// read the response line
NPT_String line;
NPT_Result res = stream.ReadLine(line, NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH);
if (NPT_FAILED(res)) {
if (res != NPT_ERROR_TIMEOUT && res != NPT_ERROR_EOS) NPT_CHECK_WARNING(res);
return res;
}
NPT_LOG_FINER_1("http response: %s", line.GetChars());
// check the response line
// we are lenient here, as we allow the response to deviate slightly from
// strict HTTP (for example, ICY servers response with a method equal to
// ICY insead of HTTP/1.X
int first_space = line.Find(' ');
if (first_space < 1) return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE;
int second_space = line.Find(' ', first_space+1);
if (second_space < 0) {
// some servers omit (incorrectly) the space and Reason-Code
// but we don't fail them just for that. Just check that the
// status code looks ok
if (line.GetLength() != 12) {
return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE;
}
} else if (second_space-first_space != 4) {
// the status code is not of length 3
return NPT_ERROR_HTTP_INVALID_RESPONSE_LINE;
}
// parse the response line
NPT_String protocol = line.SubString(0, first_space);
NPT_String status_code = line.SubString(first_space+1, 3);
NPT_String reason_phrase = line.SubString(first_space+1+3+1,
line.GetLength()-(first_space+1+3+1));
// create a response object
NPT_UInt32 status_code_int = 0;
status_code.ToInteger(status_code_int);
response = new NPT_HttpResponse(status_code_int, reason_phrase, protocol);
// parse headers
NPT_Result result = response->ParseHeaders(stream);
if (NPT_FAILED(result)) {
delete response;
response = NULL;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpEnvProxySelector
+---------------------------------------------------------------------*/
class NPT_HttpEnvProxySelector : public NPT_HttpProxySelector,
public NPT_AutomaticCleaner::Singleton
{
public:
static NPT_HttpEnvProxySelector* GetInstance();
// NPT_HttpProxySelector methods
NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy) override;
private:
// class variables
static NPT_HttpEnvProxySelector* Instance;
// class methods
static void ParseProxyEnv(const NPT_String& env, NPT_HttpProxyAddress& proxy);
// members
NPT_HttpProxyAddress m_HttpProxy;
NPT_HttpProxyAddress m_HttpsProxy;
NPT_List<NPT_String> m_NoProxy;
NPT_HttpProxyAddress m_AllProxy;
};
NPT_HttpEnvProxySelector* NPT_HttpEnvProxySelector::Instance = NULL;
/*----------------------------------------------------------------------
| NPT_HttpEnvProxySelector::GetInstance
+---------------------------------------------------------------------*/
NPT_HttpEnvProxySelector*
NPT_HttpEnvProxySelector::GetInstance()
{
if (Instance) return Instance;
NPT_SingletonLock::GetInstance().Lock();
if (Instance == NULL) {
// create the shared instance
Instance = new NPT_HttpEnvProxySelector();
// prepare for recycling
NPT_AutomaticCleaner::GetInstance()->Register(Instance);
// parse the http proxy settings
NPT_String http_proxy;
NPT_Environment::Get("http_proxy", http_proxy);
ParseProxyEnv(http_proxy, Instance->m_HttpProxy);
NPT_LOG_FINE_2("http_proxy: %s:%d", Instance->m_HttpProxy.GetHostName().GetChars(), Instance->m_HttpProxy.GetPort());
// parse the https proxy settings
NPT_String https_proxy;
if (NPT_FAILED(NPT_Environment::Get("HTTPS_PROXY", https_proxy))) {
NPT_Environment::Get("https_proxy", https_proxy);
}
ParseProxyEnv(https_proxy, Instance->m_HttpsProxy);
NPT_LOG_FINE_2("https_proxy: %s:%d", Instance->m_HttpsProxy.GetHostName().GetChars(), Instance->m_HttpsProxy.GetPort());
// parse the all-proxy settings
NPT_String all_proxy;
if (NPT_FAILED(NPT_Environment::Get("ALL_PROXY", all_proxy))) {
NPT_Environment::Get("all_proxy", all_proxy);
}
ParseProxyEnv(all_proxy, Instance->m_AllProxy);
NPT_LOG_FINE_2("all_proxy: %s:%d", Instance->m_AllProxy.GetHostName().GetChars(), Instance->m_AllProxy.GetPort());
// parse the no-proxy settings
NPT_String no_proxy;
if (NPT_FAILED(NPT_Environment::Get("NO_PROXY", no_proxy))) {
NPT_Environment::Get("no_proxy", no_proxy);
}
if (no_proxy.GetLength()) {
Instance->m_NoProxy = no_proxy.Split(",");
}
}
NPT_SingletonLock::GetInstance().Unlock();
return Instance;
}
/*----------------------------------------------------------------------
| NPT_HttpEnvProxySelector::ParseProxyEnv
+---------------------------------------------------------------------*/
void
NPT_HttpEnvProxySelector::ParseProxyEnv(const NPT_String& env,
NPT_HttpProxyAddress& proxy)
{
// ignore empty strings
if (env.GetLength() == 0) return;
NPT_String proxy_spec;
if (env.Find("://") >= 0) {
proxy_spec = env;
} else {
proxy_spec = "http://"+env;
}
NPT_Url url(proxy_spec);
proxy.SetHostName(url.GetHost());
proxy.SetPort(url.GetPort());
}
/*----------------------------------------------------------------------
| NPT_HttpEnvProxySelector::GetProxyForUrl
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpEnvProxySelector::GetProxyForUrl(const NPT_HttpUrl& url,
NPT_HttpProxyAddress& proxy)
{
NPT_HttpProxyAddress* protocol_proxy = NULL;
switch (url.GetSchemeId()) {
case NPT_Uri::SCHEME_ID_HTTP:
protocol_proxy = &m_HttpProxy;
break;
case NPT_Uri::SCHEME_ID_HTTPS:
protocol_proxy = &m_HttpsProxy;
break;
default:
return NPT_ERROR_HTTP_NO_PROXY;
}
// check for no-proxy first
if (m_NoProxy.GetItemCount()) {
for (NPT_List<NPT_String>::Iterator i = m_NoProxy.GetFirstItem();
i;
++i) {
if ((*i) == "*") {
return NPT_ERROR_HTTP_NO_PROXY;
}
if (url.GetHost().EndsWith(*i, true)) {
if (url.GetHost().GetLength() == (*i).GetLength()) {
// exact match
return NPT_ERROR_HTTP_NO_PROXY;
}
if (url.GetHost().GetChars()[url.GetHost().GetLength()-(*i).GetLength()-1] == '.') {
// subdomain match
return NPT_ERROR_HTTP_NO_PROXY;
}
}
}
}
// check the protocol proxy
if (protocol_proxy->GetHostName().GetLength()) {
proxy = *protocol_proxy;
return NPT_SUCCESS;
}
// use the default proxy
proxy = m_AllProxy;
return proxy.GetHostName().GetLength()?NPT_SUCCESS:NPT_ERROR_HTTP_NO_PROXY;
}
/*----------------------------------------------------------------------
| NPT_HttpProxySelector::GetDefault
+---------------------------------------------------------------------*/
static bool NPT_HttpProxySelector_ConfigChecked = false;
static unsigned int NPT_HttpProxySelector_Config = 0;
const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE = 0;
const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV = 1;
const unsigned int NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM = 2;
NPT_HttpProxySelector*
NPT_HttpProxySelector::GetDefault()
{
if (!NPT_HttpProxySelector_ConfigChecked) {
NPT_String config;
if (NPT_SUCCEEDED(NPT_Environment::Get("NEPTUNE_NET_CONFIG_PROXY_SELECTOR", config))) {
if (config.Compare("noproxy", true) == 0) {
NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE;
} else if (config.Compare("env", true) == 0) {
NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV;
} else if (config.Compare("system", true) == 0) {
NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM;
} else {
NPT_HttpProxySelector_Config = NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE;
}
}
NPT_HttpProxySelector_ConfigChecked = true;
}
switch (NPT_HttpProxySelector_Config) {
case NPT_HTTP_PROXY_SELECTOR_CONFIG_NONE:
// no proxy
return NULL;
case NPT_HTTP_PROXY_SELECTOR_CONFIG_ENV:
// use the shared instance
return NPT_HttpEnvProxySelector::GetInstance();
case NPT_HTTP_PROXY_SELECTOR_CONFIG_SYSTEM:
- // use the sytem proxy selector
+ // use the system proxy selector
return GetSystemSelector();
default:
return NULL;
}
}
/*----------------------------------------------------------------------
| NPT_HttpProxySelector::GetSystemSelector
+---------------------------------------------------------------------*/
#if !defined(NPT_CONFIG_HAVE_SYSTEM_PROXY_SELECTOR)
NPT_HttpProxySelector*
NPT_HttpProxySelector::GetSystemSelector()
{
return NULL;
}
#endif
/*----------------------------------------------------------------------
| NPT_HttpStaticProxySelector
+---------------------------------------------------------------------*/
class NPT_HttpStaticProxySelector : public NPT_HttpProxySelector
{
public:
// constructor
NPT_HttpStaticProxySelector(const char* http_propxy_hostname,
NPT_UInt16 http_proxy_port,
const char* https_proxy_hostname,
NPT_UInt16 htts_proxy_port);
// NPT_HttpProxySelector methods
NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy) override;
private:
// members
NPT_HttpProxyAddress m_HttpProxy;
NPT_HttpProxyAddress m_HttpsProxy;
};
/*----------------------------------------------------------------------
| NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector
+---------------------------------------------------------------------*/
NPT_HttpStaticProxySelector::NPT_HttpStaticProxySelector(const char* http_proxy_hostname,
NPT_UInt16 http_proxy_port,
const char* https_proxy_hostname,
NPT_UInt16 https_proxy_port) :
m_HttpProxy( http_proxy_hostname, http_proxy_port),
m_HttpsProxy(https_proxy_hostname, https_proxy_port)
{
}
/*----------------------------------------------------------------------
| NPT_HttpStaticProxySelector::GetProxyForUrl
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpStaticProxySelector::GetProxyForUrl(const NPT_HttpUrl& url,
NPT_HttpProxyAddress& proxy)
{
switch (url.GetSchemeId()) {
case NPT_Uri::SCHEME_ID_HTTP:
proxy = m_HttpProxy;
break;
case NPT_Uri::SCHEME_ID_HTTPS:
proxy = m_HttpsProxy;
break;
default:
return NPT_ERROR_HTTP_NO_PROXY;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::NPT_HttpConnectionManager
+---------------------------------------------------------------------*/
NPT_HttpConnectionManager::NPT_HttpConnectionManager() :
m_MaxConnections(NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_POOL_SIZE),
m_MaxConnectionAge(NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_AGE)
{
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::~NPT_HttpConnectionManager
+---------------------------------------------------------------------*/
NPT_HttpConnectionManager::~NPT_HttpConnectionManager()
{
// set abort flag and wait for thread to finish
m_Aborted.SetValue(1);
Wait();
m_Connections.Apply(NPT_ObjectDeleter<Connection>());
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::GetInstance
+---------------------------------------------------------------------*/
NPT_HttpConnectionManager*
NPT_HttpConnectionManager::GetInstance()
{
if (Instance) return Instance;
NPT_SingletonLock::GetInstance().Lock();
if (Instance == NULL) {
// create the shared instance
Instance = new NPT_HttpConnectionManager();
// register to for automatic cleanup
NPT_AutomaticCleaner::GetInstance()->RegisterHttpConnectionManager(Instance);
// Start shared instance
Instance->Start();
}
NPT_SingletonLock::GetInstance().Unlock();
return Instance;
}
NPT_HttpConnectionManager* NPT_HttpConnectionManager::Instance = NULL;
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Run
+---------------------------------------------------------------------*/
void
NPT_HttpConnectionManager::Run()
{
// try to cleanup every 5 secs
while (m_Aborted.WaitUntilEquals(1, 5000) == NPT_ERROR_TIMEOUT) {
NPT_AutoLock lock(m_Lock);
Cleanup();
}
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Cleanup
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::Cleanup()
{
NPT_TimeStamp now;
NPT_System::GetCurrentTimeStamp(now);
NPT_TimeStamp delta((float)m_MaxConnectionAge);
NPT_List<Connection*>::Iterator tail = m_Connections.GetLastItem();
while (tail) {
if (now < (*tail)->m_TimeStamp + delta) break;
NPT_LOG_FINE_1("cleaning up connection (%d remain)", m_Connections.GetItemCount());
delete *tail;
m_Connections.Erase(tail);
tail = m_Connections.GetLastItem();
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::FindConnection
+---------------------------------------------------------------------*/
NPT_HttpConnectionManager::Connection*
NPT_HttpConnectionManager::FindConnection(NPT_SocketAddress& address)
{
NPT_AutoLock lock(m_Lock);
Cleanup();
for (NPT_List<Connection*>::Iterator i = m_Connections.GetFirstItem();
i;
++i) {
Connection* connection = *i;
NPT_SocketInfo info;
if (NPT_FAILED(connection->GetInfo(info))) continue;
if (info.remote_address == address) {
m_Connections.Erase(i);
return connection;
}
}
// not found
return NULL;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Track
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::Track(NPT_HttpClient* client, NPT_HttpClient::Connection* connection)
{
NPT_AutoLock lock(m_Lock);
// look if already tracking client connections
ConnectionList* connections = NULL;
if (NPT_SUCCEEDED(m_ClientConnections.Get(client, connections))) {
// return immediately if connection is already associated with client
if (connections->Find(NPT_ObjectComparator<NPT_HttpClient::Connection*>(connection))) {
NPT_LOG_WARNING("Connection already associated to client.");
return NPT_SUCCESS;
}
connections->Add(connection);
return NPT_SUCCESS;
}
// new client connections
ConnectionList new_connections;
// add connection to new client connection list
new_connections.Add(connection);
// track new client connections
m_ClientConnections.Put(client, new_connections);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::UntrackConnection
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::UntrackConnection(NPT_HttpClient::Connection* connection)
{
NPT_AutoLock lock(m_Lock);
// look for connection by enumerating all client connections
NPT_List<NPT_Map<NPT_HttpClient*, ConnectionList>::Entry*>::Iterator entry =
m_ClientConnections.GetEntries().GetFirstItem();
while (entry) {
NPT_HttpClient*& client = (NPT_HttpClient*&)(*entry)->GetKey();
ConnectionList& connections = (ConnectionList&)(*entry)->GetValue();
// look for connection in client connection list
NPT_List<NPT_HttpClient::Connection*>::Iterator i =
connections.Find(NPT_ObjectComparator<NPT_HttpClient::Connection*>(connection));
if (i) {
// remove it
connections.Erase(i);
// untrack client if no more active connections for it
if (connections.GetItemCount() == 0) {
m_ClientConnections.Erase(client);
}
return NPT_SUCCESS;
}
++entry;
}
return NPT_ERROR_NO_SUCH_ITEM;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Untrack
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::Untrack(NPT_HttpClient::Connection* connection)
{
// check first if ConnectionCanceller Instance has not been released already
// with static finalizers
if (Instance == NULL) return NPT_FAILURE;
return GetInstance()->UntrackConnection(connection);
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Recycle
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::Recycle(NPT_HttpConnectionManager::Connection* connection)
{
NPT_AutoLock lock(m_Lock);
Cleanup();
// remove older connections to make room
while (m_Connections.GetItemCount() >= m_MaxConnections) {
NPT_List<Connection*>::Iterator head = m_Connections.GetFirstItem();
if (!head) break;
delete *head;
m_Connections.Erase(head);
NPT_LOG_FINER("removing connection from pool to make some room");
}
if (connection) {
// Untrack connection
UntrackConnection(connection);
// label this connection with the current timestamp and flag
NPT_System::GetCurrentTimeStamp(connection->m_TimeStamp);
connection->m_IsRecycled = true;
// add the connection to the pool
m_Connections.Add(connection);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::AbortConnections
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::AbortConnections(NPT_HttpClient* client)
{
NPT_AutoLock lock(m_Lock);
ConnectionList* connections = NULL;
if (NPT_SUCCEEDED(m_ClientConnections.Get(client, connections))) {
for (NPT_List<NPT_HttpClient::Connection*>::Iterator i = connections->GetFirstItem();
i;
++i) {
(*i)->Abort();
}
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Connection::Connection
+---------------------------------------------------------------------*/
NPT_HttpConnectionManager::Connection::Connection(NPT_HttpConnectionManager& manager,
NPT_SocketReference& socket,
NPT_InputStreamReference input_stream,
NPT_OutputStreamReference output_stream) :
m_Manager(manager),
m_IsRecycled(false),
m_Socket(socket),
m_InputStream(input_stream),
m_OutputStream(output_stream)
{
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Connection::~Connection
+---------------------------------------------------------------------*/
NPT_HttpConnectionManager::Connection::~Connection()
{
NPT_HttpConnectionManager::Untrack(this);
}
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager::Connection::Recycle
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpConnectionManager::Connection::Recycle()
{
return m_Manager.Recycle(this);
}
/*----------------------------------------------------------------------
| NPT_HttpClient::NPT_HttpClient
+---------------------------------------------------------------------*/
NPT_HttpClient::NPT_HttpClient(Connector* connector, bool transfer_ownership) :
m_ProxySelector(NPT_HttpProxySelector::GetDefault()),
m_ProxySelectorIsOwned(false),
m_Connector(connector),
m_ConnectorIsOwned(transfer_ownership),
m_Aborted(false)
{
if (connector == NULL) {
m_Connector = new NPT_HttpTlsConnector();
m_ConnectorIsOwned = true;
}
}
/*----------------------------------------------------------------------
| NPT_HttpClient::~NPT_HttpClient
+---------------------------------------------------------------------*/
NPT_HttpClient::~NPT_HttpClient()
{
if (m_ProxySelectorIsOwned) {
delete m_ProxySelector;
}
if (m_ConnectorIsOwned) {
delete m_Connector;
}
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SetConfig
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SetConfig(const Config& config)
{
m_Config = config;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SetProxy
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SetProxy(const char* http_proxy_hostname,
NPT_UInt16 http_proxy_port,
const char* https_proxy_hostname,
NPT_UInt16 https_proxy_port)
{
if (m_ProxySelectorIsOwned) {
delete m_ProxySelector;
m_ProxySelector = NULL;
m_ProxySelectorIsOwned = false;
}
// use a static proxy to hold on to the settings
m_ProxySelector = new NPT_HttpStaticProxySelector(http_proxy_hostname,
http_proxy_port,
https_proxy_hostname,
https_proxy_port);
m_ProxySelectorIsOwned = true;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SetProxySelector
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SetProxySelector(NPT_HttpProxySelector* selector)
{
if (m_ProxySelectorIsOwned && m_ProxySelector != selector) {
delete m_ProxySelector;
}
m_ProxySelector = selector;
m_ProxySelectorIsOwned = false;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SetConnector
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SetConnector(Connector* connector)
{
if (m_ConnectorIsOwned && m_Connector != connector) {
delete m_Connector;
}
m_Connector = connector;
m_ConnectorIsOwned = false;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SetTimeouts
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SetTimeouts(NPT_Timeout connection_timeout,
NPT_Timeout io_timeout,
NPT_Timeout name_resolver_timeout)
{
m_Config.m_ConnectionTimeout = connection_timeout;
m_Config.m_IoTimeout = io_timeout;
m_Config.m_NameResolverTimeout = name_resolver_timeout;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SetUserAgent
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SetUserAgent(const char* user_agent)
{
m_Config.m_UserAgent = user_agent;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::TrackConnection
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::TrackConnection(Connection* connection)
{
NPT_AutoLock lock(m_AbortLock);
if (m_Aborted) return NPT_ERROR_CANCELLED;
return NPT_HttpConnectionManager::GetInstance()->Track(this, connection);
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SendRequestOnce
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SendRequestOnce(NPT_HttpRequest& request,
NPT_HttpResponse*& response,
NPT_HttpRequestContext* context /* = NULL */)
{
// setup default values
NPT_Result result = NPT_SUCCESS;
response = NULL;
NPT_LOG_FINE_1("requesting URL %s", request.GetUrl().ToString().GetChars());
// get the address and port to which we need to connect
NPT_HttpProxyAddress proxy;
bool use_proxy = false;
if (m_ProxySelector) {
// we have a proxy selector, ask it to select a proxy for this URL
result = m_ProxySelector->GetProxyForUrl(request.GetUrl(), proxy);
if (NPT_FAILED(result) && result != NPT_ERROR_HTTP_NO_PROXY) {
NPT_LOG_WARNING_1("proxy selector failure (%d)", result);
return result;
}
use_proxy = !proxy.GetHostName().IsEmpty();
}
// connect to the server or proxy
Connection* connection = NULL;
bool http_1_1 = (request.GetProtocol() == NPT_HTTP_PROTOCOL_1_1);
NPT_Reference<Connection> cref;
// send the request to the server (in a loop, since we may need to reconnect with 1.1)
bool reconnect = false;
unsigned int watchdog = NPT_HTTP_MAX_RECONNECTS;
do {
cref = NULL;
connection = NULL;
NPT_LOG_FINE_3("calling connector (proxy:%s) (http 1.1:%s) (url:%s)",
use_proxy?"yes":"no", http_1_1?"yes":"no", request.GetUrl().ToStringWithDefaultPort(0).GetChars());
NPT_CHECK_WARNING(m_Connector->Connect(request.GetUrl(),
*this,
use_proxy?&proxy:NULL,
http_1_1,
connection));
NPT_LOG_FINE_1("got connection (reused: %s)", connection->IsRecycled()?"true":"false");
NPT_InputStreamReference input_stream = connection->GetInputStream();
NPT_OutputStreamReference output_stream = connection->GetOutputStream();
cref = connection;
reconnect = connection->IsRecycled();
// update context if any
if (context) {
NPT_SocketInfo info;
cref->GetInfo(info);
context->SetLocalAddress(info.local_address);
context->SetRemoteAddress(info.remote_address);
}
NPT_HttpEntity* entity = request.GetEntity();
NPT_InputStreamReference body_stream;
if (reconnect && entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream)) && NPT_FAILED(body_stream->Seek(0))) {
// if body is not seekable, we can't afford to reuse a connection
// that could fail, so we reconnect a new one instead
NPT_LOG_FINE("rewinding body stream would fail ... create new connection");
continue;
}
// decide if this connection should persist
NPT_HttpHeaders& headers = request.GetHeaders();
bool should_persist = http_1_1;
if (!connection->SupportsPersistence()) {
should_persist = false;
}
if (should_persist) {
const NPT_String* connection_header = headers.GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
if (connection_header && (*connection_header == "close")) {
should_persist = false;
}
}
if (m_Config.m_UserAgent.GetLength()) {
headers.SetHeader(NPT_HTTP_HEADER_USER_AGENT, m_Config.m_UserAgent, false); // set but don't replace
}
result = WriteRequest(*output_stream.AsPointer(), request, should_persist, use_proxy);
if (NPT_FAILED(result)) {
NPT_LOG_FINE_1("failed to write request headers (%d)", result);
if (reconnect && !m_Aborted) {
if (!body_stream.IsNull()) {
// go back to the start of the body so that we can resend
NPT_LOG_FINE("rewinding body stream in order to resend");
result = body_stream->Seek(0);
if (NPT_FAILED(result)) {
NPT_CHECK_FINE(NPT_ERROR_HTTP_CANNOT_RESEND_BODY);
}
}
continue;
} else {
return result;
}
}
result = ReadResponse(input_stream,
should_persist,
request.GetMethod() != NPT_HTTP_METHOD_HEAD,
response,
&cref);
if (NPT_FAILED(result)) {
NPT_LOG_FINE_1("failed to parse the response (%d)", result);
if (reconnect && !m_Aborted /*&&
(result == NPT_ERROR_EOS ||
result == NPT_ERROR_CONNECTION_ABORTED ||
result == NPT_ERROR_CONNECTION_RESET ||
result == NPT_ERROR_READ_FAILED) GBG: don't look for specific error codes */) {
NPT_LOG_FINE("error is not fatal, retrying");
if (!body_stream.IsNull()) {
// go back to the start of the body so that we can resend
NPT_LOG_FINE("rewinding body stream in order to resend");
result = body_stream->Seek(0);
if (NPT_FAILED(result)) {
NPT_CHECK_FINE(NPT_ERROR_HTTP_CANNOT_RESEND_BODY);
}
}
continue;
} else {
// don't retry
return result;
}
}
break;
} while (reconnect && --watchdog && !m_Aborted);
// check that we have a valid connection
if (NPT_FAILED(result) && !m_Aborted) {
NPT_LOG_FINE("failed after max reconnection attempts");
return NPT_ERROR_HTTP_TOO_MANY_RECONNECTS;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::WriteRequest
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::WriteRequest(NPT_OutputStream& output_stream,
NPT_HttpRequest& request,
bool should_persist,
bool use_proxy /* = false */)
{
NPT_Result result = NPT_SUCCESS;
// add any headers that may be missing
NPT_HttpHeaders& headers = request.GetHeaders();
if (!should_persist) {
headers.SetHeader(NPT_HTTP_HEADER_CONNECTION, "close", false); // set but don't replace
}
NPT_String host = request.GetUrl().GetHost();
NPT_UInt16 default_port = 0;
switch (request.GetUrl().GetSchemeId()) {
case NPT_Uri::SCHEME_ID_HTTP: default_port = NPT_HTTP_DEFAULT_PORT; break;
case NPT_Uri::SCHEME_ID_HTTPS: default_port = NPT_HTTPS_DEFAULT_PORT; break;
default: break;
}
if (request.GetUrl().GetPort() != default_port) {
host += ":";
host += NPT_String::FromInteger(request.GetUrl().GetPort());
}
headers.SetHeader(NPT_HTTP_HEADER_HOST, host, false); // set but don't replace
// get the request entity to set additional headers
NPT_InputStreamReference body_stream;
NPT_HttpEntity* entity = request.GetEntity();
if (entity && NPT_SUCCEEDED(entity->GetInputStream(body_stream))) {
// set the content length if known
if (entity->ContentLengthIsKnown()) {
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH,
NPT_String::FromInteger(entity->GetContentLength()));
}
// content type
NPT_String content_type = entity->GetContentType();
if (!content_type.IsEmpty()) {
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
}
// content encoding
NPT_String content_encoding = entity->GetContentEncoding();
if (!content_encoding.IsEmpty()) {
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
}
// transfer encoding
const NPT_String& transfer_encoding = entity->GetTransferEncoding();
if (!transfer_encoding.IsEmpty()) {
headers.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING, transfer_encoding);
}
} else {
//FIXME: We should only set content length of 0 for methods with expected entities.
//headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0");
}
// create a memory stream to buffer the headers
NPT_MemoryStream header_stream;
// emit the request headers into the header buffer
request.Emit(header_stream, use_proxy && request.GetUrl().GetSchemeId()==NPT_Url::SCHEME_ID_HTTP);
// send the headers
NPT_CHECK_WARNING(output_stream.WriteFully(header_stream.GetData(), header_stream.GetDataSize()));
// send request body
if (entity && !body_stream.IsNull()) {
// check for chunked transfer encoding
NPT_OutputStream* dest = &output_stream;
if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
dest = new NPT_HttpChunkedOutputStream(output_stream);
}
NPT_LOG_FINE_1("sending body stream, %lld bytes", entity->GetContentLength()); //FIXME: Would be 0 for chunked encoding
NPT_LargeSize bytes_written = 0;
// content length = 0 means copy until input returns EOS
result = NPT_StreamToStreamCopy(*body_stream.AsPointer(), *dest, 0, entity->GetContentLength(), &bytes_written);
if (NPT_FAILED(result)) {
NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
bytes_written,
result,
NPT_ResultText(result));
}
// flush to write out any buffered data left in chunked output if used
dest->Flush();
// cleanup (this will send zero size chunk followed by CRLF)
if (dest != &output_stream) delete dest;
}
// flush the output stream so that everything is sent to the server
output_stream.Flush();
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::ReadResponse
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::ReadResponse(NPT_InputStreamReference& input_stream,
bool should_persist,
bool expect_entity,
NPT_HttpResponse*& response,
NPT_Reference<Connection>* cref /* = NULL */)
{
NPT_Result result;
// setup default values
response = NULL;
// create a buffered stream for this socket stream
NPT_BufferedInputStreamReference buffered_input_stream(new NPT_BufferedInputStream(input_stream));
// parse the response
for (unsigned int watchcat = 0; watchcat < NPT_HTTP_MAX_100_RESPONSES; watchcat++) {
// parse the response
result = NPT_HttpResponse::Parse(*buffered_input_stream, response);
NPT_CHECK_FINE(result);
if (response->GetStatusCode() >= 100 && response->GetStatusCode() < 200) {
NPT_LOG_FINE_1("got %d response, continuing", response->GetStatusCode());
delete response;
response = NULL;
continue;
}
NPT_LOG_FINER_2("got response, code=%d, msg=%s",
response->GetStatusCode(),
response->GetReasonPhrase().GetChars());
break;
}
// check that we have a valid response
if (response == NULL) {
NPT_LOG_FINE("failed after max continuation attempts");
return NPT_ERROR_HTTP_TOO_MANY_RECONNECTS;
}
// unbuffer the stream
buffered_input_stream->SetBufferSize(0);
// decide if we should still try to reuse this connection later on
if (should_persist) {
const NPT_String* connection_header = response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONNECTION);
if (response->GetProtocol() == NPT_HTTP_PROTOCOL_1_1) {
if (connection_header && (*connection_header == "close")) {
should_persist = false;
}
} else {
if (!connection_header || (*connection_header != "keep-alive")) {
should_persist = false;
}
}
}
// create an entity if one is expected in the response
if (expect_entity) {
NPT_HttpEntity* response_entity = new NPT_HttpEntity(response->GetHeaders());
// check if the content length is known
bool have_content_length = (response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_CONTENT_LENGTH) != NULL);
// check for chunked Transfer-Encoding
bool chunked = false;
if (response_entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
chunked = true;
response_entity->SetTransferEncoding(NULL);
}
// prepare to transfer ownership of the connection if needed
Connection* connection = NULL;
if (cref) {
connection = cref->AsPointer();
cref->Detach(); // release the internal ref
// don't delete connection now so we can abort while readin response body,
// just pass ownership to NPT_HttpEntityBodyInputStream so it can recycle it
// when done if connection should persist
}
// create the body stream wrapper
NPT_InputStream* response_body_stream =
new NPT_HttpEntityBodyInputStream(buffered_input_stream,
response_entity->GetContentLength(),
have_content_length,
chunked,
connection,
should_persist);
response_entity->SetInputStream(NPT_InputStreamReference(response_body_stream));
response->SetEntity(response_entity);
} else {
if (should_persist && cref) {
Connection* connection = cref->AsPointer();
cref->Detach(); // release the internal ref
connection->Recycle();
}
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::SendRequest
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::SendRequest(NPT_HttpRequest& request,
NPT_HttpResponse*& response,
NPT_HttpRequestContext* context /* = NULL */)
{
NPT_Cardinal watchdog = m_Config.m_MaxRedirects+1;
bool keep_going;
NPT_Result result;
// reset aborted flag
m_Aborted = false;
// default value
response = NULL;
// check that for GET requests there is no entity
if (request.GetEntity() != NULL &&
request.GetMethod() == NPT_HTTP_METHOD_GET) {
return NPT_ERROR_HTTP_INVALID_REQUEST;
}
do {
keep_going = false;
result = SendRequestOnce(request, response, context);
if (NPT_FAILED(result)) break;
if (response && m_Config.m_MaxRedirects &&
(request.GetMethod() == NPT_HTTP_METHOD_GET ||
request.GetMethod() == NPT_HTTP_METHOD_HEAD) &&
(response->GetStatusCode() == 301 ||
response->GetStatusCode() == 302 ||
response->GetStatusCode() == 303 ||
response->GetStatusCode() == 307)) {
// handle redirect
const NPT_String* location = response->GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_LOCATION);
if (location) {
// check for location fields that are not absolute URLs
// (this is not allowed by the standard, but many web servers do it
if (location->StartsWith("/") ||
(!location->StartsWith("http://", true) &&
!location->StartsWith("https://", true))) {
NPT_LOG_FINE_1("Location: header (%s) is not an absolute URL, using it as a relative URL", location->GetChars());
if (location->StartsWith("/")) {
NPT_LOG_FINE_1("redirecting to absolute path %s", location->GetChars());
request.GetUrl().ParsePathPlus(*location);
} else {
NPT_String redirect_path = request.GetUrl().GetPath();
int slash_pos = redirect_path.ReverseFind('/');
if (slash_pos >= 0) {
redirect_path.SetLength(slash_pos+1);
} else {
redirect_path = "/";
}
redirect_path += *location;
NPT_LOG_FINE_1("redirecting to absolute path %s", redirect_path.GetChars());
request.GetUrl().ParsePathPlus(redirect_path);
}
} else {
// replace the request url
NPT_LOG_FINE_1("redirecting to %s", location->GetChars());
request.SetUrl(*location);
// remove host header so it is replaced based on new url
request.GetHeaders().RemoveHeader(NPT_HTTP_HEADER_HOST);
}
keep_going = true;
delete response;
response = NULL;
}
}
} while (keep_going && --watchdog && !m_Aborted);
// check if we were bitten by the watchdog
if (watchdog == 0) {
NPT_LOG_WARNING("too many HTTP redirects");
return NPT_ERROR_HTTP_TOO_MANY_REDIRECTS;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpClient::Abort
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpClient::Abort()
{
NPT_AutoLock lock(m_AbortLock);
m_Aborted = true;
NPT_HttpConnectionManager::GetInstance()->AbortConnections(this);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpRequestContext::NPT_HttpRequestContext
+---------------------------------------------------------------------*/
NPT_HttpRequestContext::NPT_HttpRequestContext(const NPT_SocketAddress* local_address,
const NPT_SocketAddress* remote_address)
{
if (local_address) m_LocalAddress = *local_address;
if (remote_address) m_RemoteAddress = *remote_address;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::NPT_HttpServer
+---------------------------------------------------------------------*/
NPT_HttpServer::NPT_HttpServer(NPT_UInt16 listen_port,
bool reuse_address /* = true */) :
m_BoundPort(0),
m_ServerHeader("Neptune/" NPT_NEPTUNE_VERSION_STRING),
m_Run(true)
{
m_Config.m_ListenAddress = NPT_IpAddress::Any;
m_Config.m_ListenPort = listen_port;
m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
m_Config.m_ConnectionTimeout = NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT;
m_Config.m_ReuseAddress = reuse_address;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::NPT_HttpServer
+---------------------------------------------------------------------*/
NPT_HttpServer::NPT_HttpServer(NPT_IpAddress listen_address,
NPT_UInt16 listen_port,
bool reuse_address /* = true */) :
m_BoundPort(0),
m_ServerHeader("Neptune/" NPT_NEPTUNE_VERSION_STRING),
m_Run(true)
{
m_Config.m_ListenAddress = listen_address;
m_Config.m_ListenPort = listen_port;
m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
m_Config.m_ConnectionTimeout = NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT;
m_Config.m_ReuseAddress = reuse_address;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::~NPT_HttpServer
+---------------------------------------------------------------------*/
NPT_HttpServer::~NPT_HttpServer()
{
m_RequestHandlers.Apply(NPT_ObjectDeleter<HandlerConfig>());
}
/*----------------------------------------------------------------------
| NPT_HttpServer::Bind
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::Bind()
{
// check if we're already bound
if (m_BoundPort != 0) return NPT_SUCCESS;
// bind
NPT_Result result = m_Socket.Bind(
NPT_SocketAddress(m_Config.m_ListenAddress, m_Config.m_ListenPort),
m_Config.m_ReuseAddress);
if (NPT_FAILED(result)) return result;
// update the bound port info
NPT_SocketInfo info;
m_Socket.GetInfo(info);
m_BoundPort = info.local_address.GetPort();
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::SetConfig
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::SetConfig(const Config& config)
{
m_Config = config;
// check that we can bind to this listen port
return Bind();
}
/*----------------------------------------------------------------------
| NPT_HttpServer::SetListenPort
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::SetListenPort(NPT_UInt16 port, bool reuse_address)
{
m_Config.m_ListenPort = port;
m_Config.m_ReuseAddress = reuse_address;
return Bind();
}
/*----------------------------------------------------------------------
| NPT_HttpServer::SetTimeouts
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::SetTimeouts(NPT_Timeout connection_timeout,
NPT_Timeout io_timeout)
{
m_Config.m_ConnectionTimeout = connection_timeout;
m_Config.m_IoTimeout = io_timeout;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::SetServerHeader
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::SetServerHeader(const char* server_header)
{
m_ServerHeader = server_header;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::Abort
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::Abort()
{
m_Socket.Cancel();
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::WaitForNewClient
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::WaitForNewClient(NPT_InputStreamReference& input,
NPT_OutputStreamReference& output,
NPT_HttpRequestContext* context,
NPT_Flags socket_flags)
{
// ensure that we're bound
NPT_CHECK_FINE(Bind());
// wait for a connection
NPT_Socket* client;
NPT_LOG_FINE_2("waiting for new connection on %s:%d...",
(const char*)m_Config.m_ListenAddress.ToString(),
m_BoundPort);
NPT_Result result = m_Socket.WaitForNewClient(client, m_Config.m_ConnectionTimeout, socket_flags);
if (result != NPT_ERROR_TIMEOUT) {
NPT_CHECK_WARNING(result);
} else {
NPT_CHECK_FINE(result);
}
if (client == NULL) return NPT_ERROR_INTERNAL;
// get the client info
if (context) {
NPT_SocketInfo client_info;
client->GetInfo(client_info);
context->SetLocalAddress(client_info.local_address);
context->SetRemoteAddress(client_info.remote_address);
NPT_LOG_FINE_2("client connected (%s <- %s)",
client_info.local_address.ToString().GetChars(),
client_info.remote_address.ToString().GetChars());
}
// configure the socket
client->SetReadTimeout(m_Config.m_IoTimeout);
client->SetWriteTimeout(m_Config.m_IoTimeout);
// get the streams
client->GetInputStream(input);
client->GetOutputStream(output);
// we don't need the socket anymore
delete client;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::Loop
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::Loop(bool cancellable_sockets)
{
NPT_InputStreamReference input;
NPT_OutputStreamReference output;
NPT_HttpRequestContext context;
NPT_Result result;
do {
// wait for a client to connect
NPT_Flags flags = cancellable_sockets?NPT_SOCKET_FLAG_CANCELLABLE:0;
result = WaitForNewClient(input, output, &context, flags);
NPT_LOG_FINE_2("WaitForNewClient returned %d (%s)",
result,
NPT_ResultText(result));
if (!m_Run) break;
if (result == NPT_ERROR_TIMEOUT) continue;
// respond to the client
if (NPT_SUCCEEDED(result)) {
// send a response
result = RespondToClient(input, output, context);
NPT_LOG_FINE_2("ResponToClient returned %d (%s)",
result,
NPT_ResultText(result));
} else {
NPT_LOG_FINE_2("WaitForNewClient returned %d (%s)",
result,
NPT_ResultText(result));
// if there was an error, wait a short time to avoid spinning
if (result != NPT_ERROR_TERMINATED) {
NPT_LOG_FINE("sleeping before restarting the loop");
NPT_System::Sleep(1.0);
}
}
// release the stream references so that the socket can be closed
input = NULL;
output = NULL;
} while (m_Run && result != NPT_ERROR_TERMINATED);
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::HandlerConfig::HandlerConfig
+---------------------------------------------------------------------*/
NPT_HttpServer::HandlerConfig::HandlerConfig(NPT_HttpRequestHandler* handler,
const char* path,
bool include_children,
bool transfer_ownership) :
m_Handler(handler),
m_Path(path),
m_IncludeChildren(include_children),
m_HandlerIsOwned(transfer_ownership)
{
}
/*----------------------------------------------------------------------
| NPT_HttpServer::HandlerConfig::~HandlerConfig
+---------------------------------------------------------------------*/
NPT_HttpServer::HandlerConfig::~HandlerConfig()
{
if (m_HandlerIsOwned) delete m_Handler;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::AddRequestHandler
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::AddRequestHandler(NPT_HttpRequestHandler* handler,
const char* path,
bool include_children,
bool transfer_ownership)
{
return m_RequestHandlers.Add(new HandlerConfig(handler, path, include_children, transfer_ownership));
}
/*----------------------------------------------------------------------
| NPT_HttpServer::FindRequestHandler
+---------------------------------------------------------------------*/
NPT_HttpRequestHandler*
NPT_HttpServer::FindRequestHandler(NPT_HttpRequest& request)
{
NPT_String path = NPT_Uri::PercentDecode(request.GetUrl().GetPath());
for (NPT_List<HandlerConfig*>::Iterator it = m_RequestHandlers.GetFirstItem();
it;
++it) {
HandlerConfig* config = *it;
if (config->m_IncludeChildren) {
if (path.StartsWith(config->m_Path)) {
return config->m_Handler;
}
} else {
if (path == config->m_Path) {
return config->m_Handler;
}
}
}
// not found
return NULL;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::FindRequestHandlers
+---------------------------------------------------------------------*/
NPT_List<NPT_HttpRequestHandler*>
NPT_HttpServer::FindRequestHandlers(NPT_HttpRequest& request)
{
NPT_List<NPT_HttpRequestHandler*> handlers;
for (NPT_List<HandlerConfig*>::Iterator it = m_RequestHandlers.GetFirstItem();
it;
++it) {
HandlerConfig* config = *it;
if (config->m_IncludeChildren) {
if (request.GetUrl().GetPath(true).StartsWith(config->m_Path)) {
handlers.Add(config->m_Handler);
}
} else {
if (request.GetUrl().GetPath(true) == config->m_Path) {
handlers.Insert(handlers.GetFirstItem(), config->m_Handler);
}
}
}
return handlers;
}
/*----------------------------------------------------------------------
| NPT_HttpServer::RespondToClient
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpServer::RespondToClient(NPT_InputStreamReference& input,
NPT_OutputStreamReference& output,
const NPT_HttpRequestContext& context)
{
NPT_HttpRequest* request;
NPT_HttpResponse* response = NULL;
NPT_Result result = NPT_ERROR_NO_SUCH_ITEM;
bool terminate_server = false;
NPT_HttpResponder responder(input, output);
NPT_CHECK_WARNING(responder.ParseRequest(request, &context.GetLocalAddress()));
NPT_LOG_FINE_1("request, path=%s", request->GetUrl().ToRequestString(true).GetChars());
// prepare the response body
NPT_HttpEntity* body = new NPT_HttpEntity();
NPT_HttpRequestHandler* handler = FindRequestHandler(*request);
if (handler) {
// create a response object
response = new NPT_HttpResponse(200, "OK", NPT_HTTP_PROTOCOL_1_0);
response->SetEntity(body);
// ask the handler to setup the response
result = handler->SetupResponse(*request, context, *response);
}
if (result == NPT_ERROR_NO_SUCH_ITEM || handler == NULL) {
body->SetInputStream(NPT_HTTP_DEFAULT_404_HTML);
body->SetContentType("text/html");
if (response == NULL) {
response = new NPT_HttpResponse(404, "Not Found", NPT_HTTP_PROTOCOL_1_0);
} else {
response->SetStatus(404, "Not Found");
}
response->SetEntity(body);
handler = NULL;
} else if (result == NPT_ERROR_PERMISSION_DENIED) {
body->SetInputStream(NPT_HTTP_DEFAULT_403_HTML);
body->SetContentType("text/html");
response->SetStatus(403, "Forbidden");
handler = NULL;
} else if (result == NPT_ERROR_TERMINATED) {
// mark that we want to exit
terminate_server = true;
} else if (NPT_FAILED(result)) {
body->SetInputStream(NPT_HTTP_DEFAULT_500_HTML);
body->SetContentType("text/html");
response->SetStatus(500, "Internal Error");
handler = NULL;
}
// augment the headers with server information
if (m_ServerHeader.GetLength()) {
response->GetHeaders().SetHeader(NPT_HTTP_HEADER_SERVER, m_ServerHeader, false);
}
// send the response headers
result = responder.SendResponseHeaders(*response);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_2("SendResponseHeaders failed (%d:%s)", result, NPT_ResultText(result));
goto end;
}
// send the body
if (request->GetMethod() != NPT_HTTP_METHOD_HEAD) {
if (handler) {
result = handler->SendResponseBody(context, *response, *output);
} else {
// send body manually in case there was an error with the handler or no handler was found
NPT_InputStreamReference body_stream;
body->GetInputStream(body_stream);
if (!body_stream.IsNull()) {
result = NPT_StreamToStreamCopy(*body_stream, *output, 0, body->GetContentLength());
if (NPT_FAILED(result)) {
NPT_LOG_INFO_2("NPT_StreamToStreamCopy returned %d (%s)", result, NPT_ResultText(result));
goto end;
}
}
}
}
// flush
output->Flush();
// if we need to die, we return an error code
if (NPT_SUCCEEDED(result) && terminate_server) result = NPT_ERROR_TERMINATED;
end:
// cleanup
delete response;
delete request;
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpResponder::NPT_HttpResponder
+---------------------------------------------------------------------*/
NPT_HttpResponder::NPT_HttpResponder(NPT_InputStreamReference& input,
NPT_OutputStreamReference& output) :
m_Input(new NPT_BufferedInputStream(input)),
m_Output(output)
{
m_Config.m_IoTimeout = NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT;
}
/*----------------------------------------------------------------------
| NPT_HttpResponder::~NPT_HttpResponder
+---------------------------------------------------------------------*/
NPT_HttpResponder::~NPT_HttpResponder()
{
}
/*----------------------------------------------------------------------
| NPT_HttpServer::Terminate
+---------------------------------------------------------------------*/
void NPT_HttpServer::Terminate()
{
m_Run = false;
}
/*----------------------------------------------------------------------
| NPT_HttpResponder::SetConfig
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponder::SetConfig(const Config& config)
{
m_Config = config;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponder::SetTimeout
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponder::SetTimeout(NPT_Timeout io_timeout)
{
m_Config.m_IoTimeout = io_timeout;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponder::ParseRequest
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponder::ParseRequest(NPT_HttpRequest*& request,
const NPT_SocketAddress* local_address)
{
// rebuffer the stream in case we're using a keep-alive connection
m_Input->SetBufferSize(NPT_BUFFERED_BYTE_STREAM_DEFAULT_SIZE);
// parse the request
NPT_CHECK_FINE(NPT_HttpRequest::Parse(*m_Input, local_address, request));
// unbuffer the stream
m_Input->SetBufferSize(0);
// don't create an entity if no body is expected
if (request->GetMethod() == NPT_HTTP_METHOD_GET ||
request->GetMethod() == NPT_HTTP_METHOD_HEAD ||
request->GetMethod() == NPT_HTTP_METHOD_TRACE) {
return NPT_SUCCESS;
}
// set the entity info
NPT_HttpEntity* entity = new NPT_HttpEntity(request->GetHeaders());
if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
entity->SetInputStream(NPT_InputStreamReference(new NPT_HttpChunkedInputStream(m_Input)));
} else {
entity->SetInputStream(m_Input);
}
request->SetEntity(entity);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpResponder::SendResponseHeaders
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpResponder::SendResponseHeaders(NPT_HttpResponse& response)
{
// add default headers
NPT_HttpHeaders& headers = response.GetHeaders();
if (response.GetProtocol() == NPT_HTTP_PROTOCOL_1_0) {
headers.SetHeader(NPT_HTTP_HEADER_CONNECTION,
"close", false); // set but don't replace
}
// add computed headers
NPT_HttpEntity* entity = response.GetEntity();
if (entity) {
// content type
const NPT_String& content_type = entity->GetContentType();
if (!content_type.IsEmpty()) {
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_TYPE, content_type);
}
// content encoding
const NPT_String& content_encoding = entity->GetContentEncoding();
if (!content_encoding.IsEmpty()) {
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_ENCODING, content_encoding);
}
// transfer encoding
const NPT_String& transfer_encoding = entity->GetTransferEncoding();
if (!transfer_encoding.IsEmpty()) {
headers.SetHeader(NPT_HTTP_HEADER_TRANSFER_ENCODING, transfer_encoding);
}
// set the content length if known
if (entity->ContentLengthIsKnown()) {
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH,
NPT_String::FromInteger(entity->GetContentLength()));
} else if (transfer_encoding.IsEmpty() || transfer_encoding.Compare(NPT_HTTP_TRANSFER_ENCODING_CHUNKED, true)) {
// no content length, the only way client will know we're done
// is when we'll close the connection unless it's chunked encoding
headers.SetHeader(NPT_HTTP_HEADER_CONNECTION,
"close", true); // set and replace
}
} else {
// force content length to 0 if there is no message body
// (necessary for 1.1 or 1.0 with keep-alive connections)
headers.SetHeader(NPT_HTTP_HEADER_CONTENT_LENGTH, "0");
}
// create a memory stream to buffer the response line and headers
NPT_MemoryStream buffer;
// emit the response line
NPT_CHECK_WARNING(response.Emit(buffer));
// send the buffer
NPT_CHECK_WARNING(m_Output->WriteFully(buffer.GetData(), buffer.GetDataSize()));
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpRequestHandler Dynamic Cast Anchor
+---------------------------------------------------------------------*/
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(NPT_HttpRequestHandler)
/*----------------------------------------------------------------------
| NPT_HttpRequestHandler::SendResponseBody
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpRequestHandler::SendResponseBody(const NPT_HttpRequestContext& /*context*/,
NPT_HttpResponse& response,
NPT_OutputStream& output)
{
NPT_HttpEntity* entity = response.GetEntity();
if (entity == NULL) return NPT_SUCCESS;
NPT_InputStreamReference body_stream;
entity->GetInputStream(body_stream);
if (body_stream.IsNull()) return NPT_SUCCESS;
// check for chunked transfer encoding
NPT_OutputStream* dest = &output;
if (entity->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED) {
dest = new NPT_HttpChunkedOutputStream(output);
}
// send the body
NPT_LOG_FINE_1("sending body stream, %lld bytes", entity->GetContentLength());
NPT_LargeSize bytes_written = 0;
NPT_Result result = NPT_StreamToStreamCopy(*body_stream, *dest, 0, entity->GetContentLength(), &bytes_written);
if (NPT_FAILED(result)) {
NPT_LOG_FINE_3("body stream only partially sent, %lld bytes (%d:%s)",
bytes_written,
result,
NPT_ResultText(result));
}
// flush to write out any buffered data left in chunked output if used
dest->Flush();
// cleanup (this will send zero size chunk followed by CRLF)
if (dest != &output) delete dest;
return result;
}
/*----------------------------------------------------------------------
| NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
+---------------------------------------------------------------------*/
NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler(const void* data,
NPT_Size size,
const char* mime_type,
bool copy) :
m_MimeType(mime_type),
m_Buffer(data, size, copy)
{}
/*----------------------------------------------------------------------
| NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler
+---------------------------------------------------------------------*/
NPT_HttpStaticRequestHandler::NPT_HttpStaticRequestHandler(const char* document,
const char* mime_type,
bool copy) :
m_MimeType(mime_type),
m_Buffer(document, NPT_StringLength(document), copy)
{}
/*----------------------------------------------------------------------
| NPT_HttpStaticRequestHandler::SetupResponse
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpStaticRequestHandler::SetupResponse(NPT_HttpRequest& /*request*/,
const NPT_HttpRequestContext& /*context*/,
NPT_HttpResponse& response)
{
NPT_HttpEntity* entity = response.GetEntity();
if (entity == NULL) return NPT_ERROR_INVALID_STATE;
entity->SetContentType(m_MimeType);
entity->SetInputStream(m_Buffer.GetData(), m_Buffer.GetDataSize());
return NPT_SUCCESS;
}
const NPT_HttpFileRequestHandler_DefaultFileTypeMapEntry
NPT_HttpFileRequestHandler_DefaultFileTypeMap[] = {
{"xml", "text/xml; charset=\"utf-8\"" },
{"htm", "text/html" },
{"html", "text/html" },
{"c", "text/plain"},
{"h", "text/plain"},
{"txt", "text/plain"},
{"css", "text/css" },
{"manifest", "text/cache-manifest"},
{"gif", "image/gif" },
{"thm", "image/jpeg"},
{"png", "image/png"},
{"tif", "image/tiff"},
{"tiff", "image/tiff"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"jpe", "image/jpeg"},
{"jp2", "image/jp2" },
{"png", "image/png" },
{"bmp", "image/bmp" },
{"aif", "audio/x-aiff"},
{"aifc", "audio/x-aiff"},
{"aiff", "audio/x-aiff"},
{"flac", "audio/x-flac"},
{"mka", "audio/x-matroska"},
{"mpa", "audio/mpeg"},
{"mp2", "audio/mpeg"},
{"mp3", "audio/mpeg"},
{"m4a", "audio/mp4"},
{"wma", "audio/x-ms-wma"},
{"wav", "audio/x-wav"},
{"mkv", "video/x-matroska"},
{"mpeg", "video/mpeg"},
{"mpg", "video/mpeg"},
{"mp4", "video/mp4"},
{"m4v", "video/mp4"},
{"ts", "video/MP2T"}, // RFC 3555
{"mpegts", "video/MP2T"},
{"mov", "video/quicktime"},
{"qt", "video/quicktime"},
{"wmv", "video/x-ms-wmv"},
{"wtv", "video/x-ms-wmv"},
{"asf", "video/x-ms-asf"},
{"mkv", "video/x-matroska"},
{"mk3d", "video/x-matroska-3d"},
{"flv", "video/x-flv"},
{"avi", "video/x-msvideo"},
{"divx", "video/x-msvideo"},
{"xvid", "video/x-msvideo"},
{"doc", "application/msword"},
{"js", "application/javascript"},
{"m3u8", "application/x-mpegURL"},
{"pdf", "application/pdf"},
{"ps", "application/postscript"},
{"eps", "application/postscript"},
{"zip", "application/zip"}
};
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler
+---------------------------------------------------------------------*/
NPT_HttpFileRequestHandler::NPT_HttpFileRequestHandler(const char* url_root,
const char* file_root,
bool auto_dir,
const char* auto_index) :
m_UrlRoot(url_root),
m_FileRoot(file_root),
m_DefaultMimeType("text/html"),
m_UseDefaultFileTypeMap(true),
m_AutoDir(auto_dir),
m_AutoIndex(auto_index)
{
}
/*----------------------------------------------------------------------
| helper functions FIXME: need to move these to a separate module
+---------------------------------------------------------------------*/
static NPT_UInt32
_utf8_decode(const char** str)
{
NPT_UInt32 result;
NPT_UInt32 min_value;
unsigned int bytes_left;
if (**str == 0) {
return ~0;
} else if ((**str & 0x80) == 0x00) {
result = *(*str)++;
bytes_left = 0;
min_value = 0;
} else if ((**str & 0xE0) == 0xC0) {
result = *(*str)++ & 0x1F;
bytes_left = 1;
min_value = 0x80;
} else if ((**str & 0xF0) == 0xE0) {
result = *(*str)++ & 0x0F;
bytes_left = 2;
min_value = 0x800;
} else if ((**str & 0xF8) == 0xF0) {
result = *(*str)++ & 0x07;
bytes_left = 3;
min_value = 0x10000;
} else {
return ~0;
}
while (bytes_left--) {
if (**str == 0 || (**str & 0xC0) != 0x80) return ~0;
result = (result << 6) | (*(*str)++ & 0x3F);
}
if (result < min_value || (result & 0xFFFFF800) == 0xD800 || result > 0x10FFFF) {
return ~0;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_HtmlEncode
+---------------------------------------------------------------------*/
static NPT_String
NPT_HtmlEncode(const char* str, const char* chars)
{
NPT_String encoded;
// check args
if (str == NULL) return encoded;
// reserve at least the size of the current uri
encoded.Reserve(NPT_StringLength(str));
// process each character
while (*str) {
NPT_UInt32 c = _utf8_decode(&str);
bool encode = false;
if (c < ' ' || c > '~') {
encode = true;
} else {
const char* match = chars;
while (*match) {
if (c == (NPT_UInt32)*match) {
encode = true;
break;
}
++match;
}
}
if (encode) {
// encode
char hex[9];
encoded += "&#x";
unsigned int len = 0;
if (c > 0xFFFF) {
NPT_ByteToHex((unsigned char)(c>>24), &hex[0], true);
NPT_ByteToHex((unsigned char)(c>>16), &hex[2], true);
len = 4;
}
NPT_ByteToHex((unsigned char)(c>>8), &hex[len ], true);
NPT_ByteToHex((unsigned char)(c ), &hex[len+2], true);
hex[len+4] = ';';
encoded.Append(hex, len+5);
} else {
// no encoding required
encoded += (char)c;
}
}
return encoded;
}
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler::SetupResponse
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpFileRequestHandler::SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& /* context */,
NPT_HttpResponse& response)
{
NPT_HttpEntity* entity = response.GetEntity();
if (entity == NULL) return NPT_ERROR_INVALID_STATE;
// check the method
if (request.GetMethod() != NPT_HTTP_METHOD_GET &&
request.GetMethod() != NPT_HTTP_METHOD_HEAD) {
response.SetStatus(405, "Method Not Allowed");
return NPT_SUCCESS;
}
// set some default headers
response.GetHeaders().SetHeader(NPT_HTTP_HEADER_ACCEPT_RANGES, "bytes");
// declare HTTP/1.1 if the client asked for it
if (request.GetProtocol() == NPT_HTTP_PROTOCOL_1_1) {
response.SetProtocol(NPT_HTTP_PROTOCOL_1_1);
}
// TODO: we need to normalize the request path
// check that the request's path is an entry under the url root
if (!request.GetUrl().GetPath(true).StartsWith(m_UrlRoot)) {
return NPT_ERROR_INVALID_PARAMETERS;
}
// compute the filename
NPT_String filename = m_FileRoot;
NPT_String relative_path = NPT_Url::PercentDecode(request.GetUrl().GetPath().GetChars()+m_UrlRoot.GetLength());
filename += "/";
filename += relative_path;
NPT_LOG_FINE_1("filename = %s", filename.GetChars());
// get info about the file
NPT_FileInfo info;
NPT_File::GetInfo(filename, &info);
// check if this is a directory
if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) {
NPT_LOG_FINE("file is a DIRECTORY");
if (m_AutoDir) {
if (m_AutoIndex.GetLength()) {
NPT_LOG_FINE("redirecting to auto-index");
filename += NPT_FilePath::Separator;
filename += m_AutoIndex;
if (NPT_File::Exists(filename)) {
NPT_String location = m_UrlRoot+"/"+m_AutoIndex;
response.SetStatus(302, "Found");
response.GetHeaders().SetHeader(NPT_HTTP_HEADER_LOCATION, location);
} else {
return NPT_ERROR_PERMISSION_DENIED;
}
} else {
NPT_LOG_FINE("doing auto-dir");
// get the dir entries
NPT_List<NPT_String> entries;
NPT_File::ListDir(filename, entries);
NPT_String html;
html.Reserve(1024+128*entries.GetItemCount());
NPT_String html_dirname = NPT_HtmlEncode(relative_path, "<>&");
html += "<hmtl><head><title>Directory Listing for /";
html += html_dirname;
html += "</title></head><body>";
html += "<h2>Directory Listing for /";
html += html_dirname;
html += "</h2><hr><ul>\r\n";
NPT_String url_base_path = NPT_HtmlEncode(request.GetUrl().GetPath(), "<>&\"");
for (NPT_List<NPT_String>::Iterator i = entries.GetFirstItem();
i;
++i) {
NPT_String url_filename = NPT_HtmlEncode(*i, "<>&");
html += "<li><a href=\"";
html += url_base_path;
if (!url_base_path.EndsWith("/")) html += "/";
html += url_filename;
html += "\">";
html +=url_filename;
NPT_String full_path = filename;
full_path += "/";
full_path += *i;
NPT_File::GetInfo(full_path, &info);
if (info.m_Type == NPT_FileInfo::FILE_TYPE_DIRECTORY) html += "/";
html += "</a><br>\r\n";
}
html += "</ul></body></html>";
entity->SetContentType("text/html");
entity->SetInputStream(html);
return NPT_SUCCESS;
}
} else {
return NPT_ERROR_PERMISSION_DENIED;
}
}
// open the file
NPT_File file(filename);
NPT_Result result = file.Open(NPT_FILE_OPEN_MODE_READ);
if (NPT_FAILED(result)) {
NPT_LOG_FINE("file not found");
return NPT_ERROR_NO_SUCH_ITEM;
}
NPT_InputStreamReference stream;
file.GetInputStream(stream);
// check for range requests
const NPT_String* range_spec = request.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_RANGE);
// setup entity body
NPT_CHECK(SetupResponseBody(response, stream, range_spec));
// set the response body
entity->SetContentType(GetContentType(filename));
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler::SetupResponseBody
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpFileRequestHandler::SetupResponseBody(NPT_HttpResponse& response,
NPT_InputStreamReference& stream,
const NPT_String* range_spec /* = NULL */)
{
NPT_HttpEntity* entity = response.GetEntity();
if (entity == NULL) return NPT_ERROR_INVALID_STATE;
if (range_spec) {
const NPT_String* accept_range = response.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_ACCEPT_RANGES);
if (response.GetEntity()->GetTransferEncoding() == NPT_HTTP_TRANSFER_ENCODING_CHUNKED ||
(accept_range && accept_range->Compare("bytes"))) {
NPT_LOG_FINE("range request not supported");
response.SetStatus(416, "Requested Range Not Satisfiable");
return NPT_SUCCESS;
}
// measure the stream size
bool has_stream_size = false;
NPT_LargeSize stream_size = 0;
NPT_Result result = stream->GetSize(stream_size);
if (NPT_SUCCEEDED(result)) {
has_stream_size = true;
NPT_LOG_FINE_1("body size=%lld", stream_size);
if (stream_size == 0) return NPT_SUCCESS;
}
if (!range_spec->StartsWith("bytes=")) {
NPT_LOG_FINE("unknown range spec");
response.SetStatus(400, "Bad Request");
return NPT_SUCCESS;
}
NPT_String valid_range;
NPT_String range(range_spec->GetChars()+6);
if (range.Find(',') >= 0) {
NPT_LOG_FINE("multi-range requests not supported");
if (has_stream_size) {
valid_range = "bytes */";
valid_range += NPT_String::FromInteger(stream_size);
response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
}
response.SetStatus(416, "Requested Range Not Satisfiable");
return NPT_SUCCESS;
}
int sep = range.Find('-');
NPT_UInt64 range_start = 0;
NPT_UInt64 range_end = 0;
bool has_start = false;
bool has_end = false;
bool satisfied = false;
if (sep < 0) {
NPT_LOG_FINE("invalid syntax");
response.SetStatus(400, "Bad Request");
return NPT_SUCCESS;
} else {
if ((unsigned int)sep+1 < range.GetLength()) {
result = NPT_ParseInteger64(range.GetChars()+sep+1, range_end);
if (NPT_FAILED(result)) {
NPT_LOG_FINE("failed to parse range end");
return result;
}
range.SetLength(sep);
has_end = true;
}
if (sep > 0) {
result = range.ToInteger64(range_start);
if (NPT_FAILED(result)) {
NPT_LOG_FINE("failed to parse range start");
return result;
}
has_start = true;
}
if (!has_stream_size) {
if (has_start && range_start == 0 && !has_end) {
bool update_content_length = (entity->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED);
// use the whole file stream as a body
return entity->SetInputStream(stream, update_content_length);
} else {
NPT_LOG_WARNING_2("file.GetSize() failed (%d:%s)", result, NPT_ResultText(result));
NPT_LOG_FINE("range request not supported");
response.SetStatus(416, "Requested Range Not Satisfiable");
return NPT_SUCCESS;
}
}
if (has_start) {
// some clients sends incorrect range_end equal to size
// we try to handle it
if (!has_end || range_end == stream_size) range_end = stream_size-1;
} else {
if (has_end) {
if (range_end <= stream_size) {
range_start = stream_size-range_end;
range_end = stream_size-1;
}
}
}
NPT_LOG_FINE_2("final range: start=%lld, end=%lld", range_start, range_end);
if (range_start > range_end) {
NPT_LOG_FINE("invalid range");
response.SetStatus(400, "Bad Request");
satisfied = false;
} else if (range_end >= stream_size) {
response.SetStatus(416, "Requested Range Not Satisfiable");
NPT_LOG_FINE("out of range");
satisfied = false;
} else {
satisfied = true;
}
}
if (satisfied && range_start != 0) {
// seek in the stream
result = stream->Seek(range_start);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_2("stream.Seek() failed (%d:%s)", result, NPT_ResultText(result));
satisfied = false;
}
}
if (!satisfied) {
if (!valid_range.IsEmpty()) response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
response.SetStatus(416, "Requested Range Not Satisfiable");
return NPT_SUCCESS;
}
// use a portion of the file stream as a body
entity->SetInputStream(stream, false);
entity->SetContentLength(range_end-range_start+1);
response.SetStatus(206, "Partial Content");
valid_range = "bytes ";
valid_range += NPT_String::FromInteger(range_start);
valid_range += "-";
valid_range += NPT_String::FromInteger(range_end);
valid_range += "/";
valid_range += NPT_String::FromInteger(stream_size);
response.GetHeaders().SetHeader(NPT_HTTP_HEADER_CONTENT_RANGE, valid_range.GetChars());
} else {
bool update_content_length = (entity->GetTransferEncoding() != NPT_HTTP_TRANSFER_ENCODING_CHUNKED);
// use the whole file stream as a body
entity->SetInputStream(stream, update_content_length);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler::GetContentType
+---------------------------------------------------------------------*/
const char*
NPT_HttpFileRequestHandler::GetDefaultContentType(const char* extension)
{
for (unsigned int i=0; i<NPT_ARRAY_SIZE(NPT_HttpFileRequestHandler_DefaultFileTypeMap); i++) {
if (NPT_String::Compare(extension, NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].extension, true) == 0) {
const char* type = NPT_HttpFileRequestHandler_DefaultFileTypeMap[i].mime_type;
NPT_LOG_FINE_1("using type from default list: %s", type);
return type;
}
}
return NULL;
}
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler::GetContentType
+---------------------------------------------------------------------*/
const char*
NPT_HttpFileRequestHandler::GetContentType(const NPT_String& filename)
{
int last_dot = filename.ReverseFind('.');
if (last_dot > 0) {
NPT_String extension = filename.GetChars()+last_dot+1;
extension.MakeLowercase();
NPT_LOG_FINE_1("extension=%s", extension.GetChars());
NPT_String* mime_type;
if (NPT_SUCCEEDED(m_FileTypeMap.Get(extension, mime_type))) {
NPT_LOG_FINE_1("found mime type in map: %s", mime_type->GetChars());
return mime_type->GetChars();
}
// not found, look in the default map if necessary
if (m_UseDefaultFileTypeMap) {
const char* type = NPT_HttpFileRequestHandler::GetDefaultContentType(extension);
if (type) return type;
}
}
NPT_LOG_FINE("using default mime type");
return m_DefaultMimeType;
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
+---------------------------------------------------------------------*/
NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream(
NPT_BufferedInputStreamReference& stream) :
m_Source(stream),
m_CurrentChunkSize(0),
m_Eos(false)
{
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream
+---------------------------------------------------------------------*/
NPT_HttpChunkedInputStream::~NPT_HttpChunkedInputStream()
{
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::NPT_HttpChunkedInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpChunkedInputStream::Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read /* = NULL */)
{
// set the initial state of return values
if (bytes_read) *bytes_read = 0;
// check for end of stream
if (m_Eos) return NPT_ERROR_EOS;
// shortcut
if (bytes_to_read == 0) return NPT_SUCCESS;
// read next chunk size if needed
if (m_CurrentChunkSize == 0) {
// buffered mode
m_Source->SetBufferSize(4096);
NPT_String size_line;
NPT_CHECK_FINE(m_Source->ReadLine(size_line));
// decode size (in hex)
m_CurrentChunkSize = 0;
if (size_line.GetLength() < 1) {
NPT_LOG_WARNING("empty chunk size line");
return NPT_ERROR_INVALID_FORMAT;
}
const char* size_hex = size_line.GetChars();
while (*size_hex != '\0' &&
*size_hex != ' ' &&
*size_hex != ';' &&
*size_hex != '\r' &&
*size_hex != '\n') {
int nibble = NPT_HexToNibble(*size_hex);
if (nibble < 0) {
NPT_LOG_WARNING_1("invalid chunk size format (%s)", size_line.GetChars());
return NPT_ERROR_INVALID_FORMAT;
}
m_CurrentChunkSize = (m_CurrentChunkSize<<4)|nibble;
++size_hex;
}
NPT_LOG_FINEST_1("start of chunk, size=%d", m_CurrentChunkSize);
// 0 = end of body
if (m_CurrentChunkSize == 0) {
NPT_LOG_FINEST("end of chunked stream, reading trailers");
// read footers until empty line
NPT_String footer;
do {
NPT_CHECK_FINE(m_Source->ReadLine(footer));
} while (!footer.IsEmpty());
m_Eos = true;
NPT_LOG_FINEST("end of chunked stream, done");
return NPT_ERROR_EOS;
}
// unbuffer source
m_Source->SetBufferSize(0);
}
// read no more than what's left in chunk
NPT_Size chunk_bytes_read;
if (bytes_to_read > m_CurrentChunkSize) bytes_to_read = m_CurrentChunkSize;
NPT_CHECK_FINE(m_Source->Read(buffer, bytes_to_read, &chunk_bytes_read));
// ready to go to next chunk?
m_CurrentChunkSize -= chunk_bytes_read;
if (m_CurrentChunkSize == 0) {
NPT_LOG_FINEST("reading end of chunk");
// when a chunk is finished, a \r\n follows
char newline[2];
NPT_CHECK_FINE(m_Source->ReadFully(newline, 2));
if (newline[0] != '\r' || newline[1] != '\n') {
NPT_LOG_WARNING("invalid end of chunk (expected \\r\\n)");
return NPT_ERROR_INVALID_FORMAT;
}
}
// update output params
if (bytes_read) *bytes_read = chunk_bytes_read;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::Seek
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpChunkedInputStream::Seek(NPT_Position /*offset*/)
{
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::Tell
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpChunkedInputStream::Tell(NPT_Position& offset)
{
offset = 0;
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::GetSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpChunkedInputStream::GetSize(NPT_LargeSize& size)
{
return m_Source->GetSize(size);
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream::GetAvailable
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpChunkedInputStream::GetAvailable(NPT_LargeSize& available)
{
return m_Source->GetAvailable(available);
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream
+---------------------------------------------------------------------*/
NPT_HttpChunkedOutputStream::NPT_HttpChunkedOutputStream(NPT_OutputStream& stream) :
m_Stream(stream)
{
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream
+---------------------------------------------------------------------*/
NPT_HttpChunkedOutputStream::~NPT_HttpChunkedOutputStream()
{
// zero size chunk followed by CRLF (no trailer)
m_Stream.WriteFully("0" NPT_HTTP_LINE_TERMINATOR NPT_HTTP_LINE_TERMINATOR, 5);
}
/*----------------------------------------------------------------------
| NPT_HttpChunkedOutputStream::Write
+---------------------------------------------------------------------*/
NPT_Result
NPT_HttpChunkedOutputStream::Write(const void* buffer,
NPT_Size bytes_to_write,
NPT_Size* bytes_written)
{
// default values
if (bytes_written) *bytes_written = 0;
// shortcut
if (bytes_to_write == 0) return NPT_SUCCESS;
// write the chunk header
char size[16];
size[15] = '\n';
size[14] = '\r';
char* c = &size[14];
unsigned int char_count = 2;
unsigned int value = bytes_to_write;
do {
unsigned int digit = (unsigned int)(value%16);
if (digit < 10) {
*--c = '0'+digit;
} else {
*--c = 'A'+digit-10;
}
char_count++;
value /= 16;
} while(value);
NPT_Result result = m_Stream.WriteFully(c, char_count);
if (NPT_FAILED(result)) return result;
// write the chunk data
result = m_Stream.WriteFully(buffer, bytes_to_write);
if (NPT_FAILED(result)) return result;
// finish the chunk
result = m_Stream.WriteFully(NPT_HTTP_LINE_TERMINATOR, 2);
if (NPT_SUCCEEDED(result) && bytes_written) {
*bytes_written = bytes_to_write;
}
return result;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.h b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.h
index fd5bfd66b7..dee10f1f9d 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptHttp.h
@@ -1,861 +1,861 @@
/*****************************************************************
|
| Neptune - HTTP Protocol
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
#ifndef _NPT_HTTP_H_
#define _NPT_HTTP_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptUri.h"
#include "NptTypes.h"
#include "NptList.h"
#include "NptBufferedStreams.h"
#include "NptSockets.h"
#include "NptMap.h"
#include "NptDynamicCast.h"
#include "NptVersion.h"
#include "NptTime.h"
#include "NptThreads.h"
#include "NptAutomaticCleaner.h"
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
const unsigned int NPT_HTTP_DEFAULT_PORT = 80;
const unsigned int NPT_HTTPS_DEFAULT_PORT = 443;
const unsigned int NPT_HTTP_INVALID_PORT = 0;
const NPT_Timeout NPT_HTTP_CLIENT_DEFAULT_CONNECTION_TIMEOUT = 30000;
const NPT_Timeout NPT_HTTP_CLIENT_DEFAULT_IO_TIMEOUT = 30000;
const NPT_Timeout NPT_HTTP_CLIENT_DEFAULT_NAME_RESOLVER_TIMEOUT = 60000;
const unsigned int NPT_HTTP_CLIENT_DEFAULT_MAX_REDIRECTS = 20;
const NPT_Timeout NPT_HTTP_SERVER_DEFAULT_CONNECTION_TIMEOUT = NPT_TIMEOUT_INFINITE;
const NPT_Timeout NPT_HTTP_SERVER_DEFAULT_IO_TIMEOUT = 60000;
const unsigned int NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_POOL_SIZE = 5;
const unsigned int NPT_HTTP_CONNECTION_MANAGER_MAX_CONNECTION_AGE = 50; // seconds
const unsigned int NPT_HTTP_MAX_RECONNECTS = 10;
const unsigned int NPT_HTTP_MAX_100_RESPONSES = 10;
const int NPT_HTTP_PROTOCOL_MAX_LINE_LENGTH = 8192;
const int NPT_HTTP_PROTOCOL_MAX_HEADER_COUNT = 100;
#define NPT_HTTP_PROTOCOL_1_0 "HTTP/1.0"
#define NPT_HTTP_PROTOCOL_1_1 "HTTP/1.1"
#define NPT_HTTP_METHOD_GET "GET"
#define NPT_HTTP_METHOD_HEAD "HEAD"
#define NPT_HTTP_METHOD_POST "POST"
#define NPT_HTTP_METHOD_PUT "PUT"
#define NPT_HTTP_METHOD_OPTIONS "OPTIONS"
#define NPT_HTTP_METHOD_DELETE "DELETE"
#define NPT_HTTP_METHOD_TRACE "TRACE"
#define NPT_HTTP_HEADER_HOST "Host"
#define NPT_HTTP_HEADER_CONNECTION "Connection"
#define NPT_HTTP_HEADER_USER_AGENT "User-Agent"
#define NPT_HTTP_HEADER_SERVER "Server"
#define NPT_HTTP_HEADER_CONTENT_LENGTH "Content-Length"
#define NPT_HTTP_HEADER_CONTENT_TYPE "Content-Type"
#define NPT_HTTP_HEADER_CONTENT_ENCODING "Content-Encoding"
#define NPT_HTTP_HEADER_TRANSFER_ENCODING "Transfer-Encoding"
#define NPT_HTTP_HEADER_LOCATION "Location"
#define NPT_HTTP_HEADER_RANGE "Range"
#define NPT_HTTP_HEADER_CONTENT_RANGE "Content-Range"
#define NPT_HTTP_HEADER_COOKIE "Cookie"
#define NPT_HTTP_HEADER_ACCEPT_RANGES "Accept-Ranges"
#define NPT_HTTP_HEADER_CONTENT_RANGE "Content-Range"
#define NPT_HTTP_HEADER_AUTHORIZATION "Authorization"
#define NPT_HTTP_TRANSFER_ENCODING_CHUNKED "chunked"
const int NPT_ERROR_HTTP_INVALID_RESPONSE_LINE = NPT_ERROR_BASE_HTTP - 0;
const int NPT_ERROR_HTTP_INVALID_REQUEST_LINE = NPT_ERROR_BASE_HTTP - 1;
const int NPT_ERROR_HTTP_NO_PROXY = NPT_ERROR_BASE_HTTP - 2;
const int NPT_ERROR_HTTP_INVALID_REQUEST = NPT_ERROR_BASE_HTTP - 3;
const int NPT_ERROR_HTTP_METHOD_NOT_SUPPORTED = NPT_ERROR_BASE_HTTP - 4;
const int NPT_ERROR_HTTP_TOO_MANY_REDIRECTS = NPT_ERROR_BASE_HTTP - 5;
const int NPT_ERROR_HTTP_TOO_MANY_RECONNECTS = NPT_ERROR_BASE_HTTP - 6;
const int NPT_ERROR_HTTP_CANNOT_RESEND_BODY = NPT_ERROR_BASE_HTTP - 7;
#define NPT_HTTP_LINE_TERMINATOR "\r\n"
#if !defined(NPT_CONFIG_HTTP_DEFAULT_USER_AGENT)
#define NPT_CONFIG_HTTP_DEFAULT_USER_AGENT "Neptune/" NPT_NEPTUNE_VERSION_STRING
#endif
/*----------------------------------------------------------------------
| types
+---------------------------------------------------------------------*/
typedef unsigned int NPT_HttpStatusCode;
typedef NPT_UrlQuery NPT_HttpUrlQuery; // for backward compatibility
/*----------------------------------------------------------------------
| NPT_HttpUrl
+---------------------------------------------------------------------*/
class NPT_HttpUrl : public NPT_Url {
public:
// constructors
NPT_HttpUrl() {}
NPT_HttpUrl(const char* host,
NPT_UInt16 port,
const char* path,
const char* query = NULL,
const char* fragment = NULL);
NPT_HttpUrl(const char* url, bool ignore_scheme = false);
// methods
NPT_String ToString(bool with_fragment = true) const override;
};
/*----------------------------------------------------------------------
| NPT_HttpProtocol
+---------------------------------------------------------------------*/
class NPT_HttpProtocol
{
public:
// class methods
const char* GetStatusCodeString(NPT_HttpStatusCode status_code);
};
/*----------------------------------------------------------------------
| NPT_HttpHeader
+---------------------------------------------------------------------*/
class NPT_HttpHeader {
public:
// constructors and destructor
NPT_HttpHeader(const char* name, const char* value);
~NPT_HttpHeader();
// methods
NPT_Result Emit(NPT_OutputStream& stream) const;
const NPT_String& GetName() const { return m_Name; }
const NPT_String& GetValue() const { return m_Value; }
NPT_Result SetName(const char* name);
NPT_Result SetValue(const char* value);
private:
// members
NPT_String m_Name;
NPT_String m_Value;
};
/*----------------------------------------------------------------------
| NPT_HttpHeaders
+---------------------------------------------------------------------*/
class NPT_HttpHeaders {
public:
// constructors and destructor
NPT_HttpHeaders();
~NPT_HttpHeaders();
// methods
NPT_Result Parse(NPT_BufferedInputStream& stream);
NPT_Result Emit(NPT_OutputStream& stream) const;
const NPT_List<NPT_HttpHeader*>& GetHeaders() const { return m_Headers; }
NPT_HttpHeader* GetHeader(const char* name) const;
const NPT_String* GetHeaderValue(const char* name) const;
NPT_Result SetHeader(const char* name, const char* value, bool replace=true);
NPT_Result AddHeader(const char* name, const char* value);
NPT_Result RemoveHeader(const char* name);
private:
// members
NPT_List<NPT_HttpHeader*> m_Headers;
};
/*----------------------------------------------------------------------
| NPT_HttpEntity
+---------------------------------------------------------------------*/
class NPT_HttpEntity {
public:
// constructors and destructor
NPT_HttpEntity();
NPT_HttpEntity(const NPT_HttpHeaders& headers);
virtual ~NPT_HttpEntity();
// methods
NPT_Result SetInputStream(const NPT_InputStreamReference& stream,
bool update_content_length = false);
NPT_Result SetInputStream(const void* data, NPT_Size size);
NPT_Result SetInputStream(const NPT_String& string);
NPT_Result SetInputStream(const char* string);
NPT_Result GetInputStream(NPT_InputStreamReference& stream);
NPT_Result Load(NPT_DataBuffer& buffer);
NPT_Result SetHeaders(const NPT_HttpHeaders& headers);
// field access
NPT_Result SetContentLength(NPT_LargeSize length);
NPT_Result SetContentType(const char* type);
NPT_Result SetContentEncoding(const char* encoding);
NPT_Result SetTransferEncoding(const char* encoding);
NPT_LargeSize GetContentLength() { return m_ContentLength; }
const NPT_String& GetContentType() { return m_ContentType; }
const NPT_String& GetContentEncoding() { return m_ContentEncoding; }
const NPT_String& GetTransferEncoding() { return m_TransferEncoding;}
bool ContentLengthIsKnown() { return m_ContentLengthIsKnown; }
private:
// members
NPT_InputStreamReference m_InputStream;
NPT_LargeSize m_ContentLength;
NPT_String m_ContentType;
NPT_String m_ContentEncoding;
NPT_String m_TransferEncoding;
bool m_ContentLengthIsKnown;
};
/*----------------------------------------------------------------------
| NPT_HttpMessage
+---------------------------------------------------------------------*/
class NPT_HttpMessage {
public:
// constructors and destructor
virtual ~NPT_HttpMessage();
// methods
const NPT_String& GetProtocol() const {
return m_Protocol;
}
NPT_Result SetProtocol(const char* protocol) {
m_Protocol = protocol;
return NPT_SUCCESS;
}
NPT_HttpHeaders& GetHeaders() {
return m_Headers;
}
const NPT_HttpHeaders& GetHeaders() const {
return m_Headers;
}
NPT_Result SetEntity(NPT_HttpEntity* entity);
NPT_HttpEntity* GetEntity() {
return m_Entity;
}
NPT_HttpEntity* GetEntity() const {
return m_Entity;
}
virtual NPT_Result ParseHeaders(NPT_BufferedInputStream& stream);
protected:
// constructors
NPT_HttpMessage(const char* protocol);
// members
NPT_String m_Protocol;
NPT_HttpHeaders m_Headers;
NPT_HttpEntity* m_Entity;
};
/*----------------------------------------------------------------------
| NPT_HttpRequest
+---------------------------------------------------------------------*/
class NPT_HttpRequest : public NPT_HttpMessage {
public:
// class methods
static NPT_Result Parse(NPT_BufferedInputStream& stream,
const NPT_SocketAddress* endpoint,
NPT_HttpRequest*& request);
// constructors and destructor
NPT_HttpRequest(const NPT_HttpUrl& url,
const char* method,
const char* protocol = NPT_HTTP_PROTOCOL_1_0);
NPT_HttpRequest(const char* url,
const char* method,
const char* protocol = NPT_HTTP_PROTOCOL_1_0);
~NPT_HttpRequest() override;
// methods
const NPT_HttpUrl& GetUrl() const { return m_Url; }
NPT_HttpUrl& GetUrl() { return m_Url; }
NPT_Result SetUrl(const char* url);
NPT_Result SetUrl(const NPT_HttpUrl& url);
const NPT_String& GetMethod() const { return m_Method; }
virtual NPT_Result Emit(NPT_OutputStream& stream, bool use_proxy=false) const;
protected:
// members
NPT_HttpUrl m_Url;
NPT_String m_Method;
};
/*----------------------------------------------------------------------
| NPT_HttpResponse
+---------------------------------------------------------------------*/
class NPT_HttpResponse : public NPT_HttpMessage {
public:
// class methods
static NPT_Result Parse(NPT_BufferedInputStream& stream,
NPT_HttpResponse*& response);
// constructors and destructor
NPT_HttpResponse(NPT_HttpStatusCode status_code,
const char* reason_phrase,
const char* protocol = NPT_HTTP_PROTOCOL_1_0);
~NPT_HttpResponse() override;
// methods
NPT_Result SetStatus(NPT_HttpStatusCode status_code,
const char* reason_phrase,
const char* protocol = NULL);
NPT_Result SetProtocol(const char* protocol);
NPT_HttpStatusCode GetStatusCode() const { return m_StatusCode; }
const NPT_String& GetReasonPhrase() const { return m_ReasonPhrase; }
virtual NPT_Result Emit(NPT_OutputStream& stream) const;
protected:
// members
NPT_HttpStatusCode m_StatusCode;
NPT_String m_ReasonPhrase;
};
/*----------------------------------------------------------------------
| NPT_HttpProxyAddress
+---------------------------------------------------------------------*/
class NPT_HttpProxyAddress
{
public:
NPT_HttpProxyAddress() : m_Port(NPT_HTTP_INVALID_PORT) {}
NPT_HttpProxyAddress(const char* hostname, NPT_UInt16 port) :
m_HostName(hostname), m_Port(port) {}
const NPT_String& GetHostName() const { return m_HostName; }
void SetHostName(const char* hostname) { m_HostName = hostname; }
NPT_UInt16 GetPort() const { return m_Port; }
void SetPort(NPT_UInt16 port) { m_Port = port; }
private:
NPT_String m_HostName;
NPT_UInt16 m_Port;
};
/*----------------------------------------------------------------------
| NPT_HttpProxySelector
+---------------------------------------------------------------------*/
class NPT_HttpProxySelector
{
public:
// class methods
static NPT_HttpProxySelector* GetDefault();
static NPT_HttpProxySelector* GetSystemSelector();
// methods
virtual ~NPT_HttpProxySelector() {};
virtual NPT_Result GetProxyForUrl(const NPT_HttpUrl& url, NPT_HttpProxyAddress& proxy) = 0;
private:
// class members
static NPT_HttpProxySelector* m_SystemDefault;
};
class NPT_HttpRequestContext;
/*----------------------------------------------------------------------
| NPT_HttpClient
+---------------------------------------------------------------------*/
class NPT_HttpClient {
public:
// types
struct Config {
Config() : m_ConnectionTimeout( NPT_HTTP_CLIENT_DEFAULT_CONNECTION_TIMEOUT),
m_IoTimeout( NPT_HTTP_CLIENT_DEFAULT_CONNECTION_TIMEOUT),
m_NameResolverTimeout(NPT_HTTP_CLIENT_DEFAULT_NAME_RESOLVER_TIMEOUT),
m_MaxRedirects( NPT_HTTP_CLIENT_DEFAULT_MAX_REDIRECTS),
m_UserAgent( NPT_CONFIG_HTTP_DEFAULT_USER_AGENT) {}
NPT_Timeout m_ConnectionTimeout;
NPT_Timeout m_IoTimeout;
NPT_Timeout m_NameResolverTimeout;
NPT_Cardinal m_MaxRedirects;
NPT_String m_UserAgent;
};
class Connection {
public:
virtual ~Connection() {}
virtual NPT_InputStreamReference& GetInputStream() = 0;
virtual NPT_OutputStreamReference& GetOutputStream() = 0;
virtual NPT_Result GetInfo(NPT_SocketInfo& info) = 0;
virtual bool SupportsPersistence() { return false; }
virtual bool IsRecycled() { return false; }
virtual NPT_Result Recycle() { delete this; return NPT_SUCCESS; }
virtual NPT_Result Abort() { return NPT_ERROR_NOT_IMPLEMENTED; }
};
class Connector {
public:
virtual ~Connector() {}
virtual NPT_Result Connect(const NPT_HttpUrl& url,
NPT_HttpClient& client,
const NPT_HttpProxyAddress* proxy,
bool reuse, // whether we can reuse a connection or not
Connection*& connection) = 0;
protected:
NPT_Result TrackConnection(NPT_HttpClient& client,
Connection* connection) { return client.TrackConnection(connection); }
Connector() {} // don't instantiate directly
};
// class methods
static NPT_Result WriteRequest(NPT_OutputStream& output_stream,
NPT_HttpRequest& request,
bool should_persist,
bool use_proxy = false);
static NPT_Result ReadResponse(NPT_InputStreamReference& input_stream,
bool should_persist,
bool expect_entity,
NPT_HttpResponse*& response,
NPT_Reference<Connection>* cref = NULL);
/**
* @param connector Pointer to a connector instance, or NULL to use
* the default (TCP) connector.
* @param transfer_ownership Boolean flag. If true, the NPT_HttpClient object
* becomes the owner of the passed Connector and will delete it when it is
* itself deleted. If false, the caller keeps the ownership of the connector.
* This flag is ignored if the connector parameter is NULL.
*/
NPT_HttpClient(Connector* connector = NULL, bool transfer_ownership = true);
virtual ~NPT_HttpClient();
// methods
NPT_Result SendRequest(NPT_HttpRequest& request,
NPT_HttpResponse*& response,
NPT_HttpRequestContext* context = NULL);
NPT_Result Abort();
const Config& GetConfig() const { return m_Config; }
NPT_Result SetConfig(const Config& config);
NPT_Result SetProxy(const char* http_proxy_hostname,
NPT_UInt16 http_proxy_port,
const char* https_proxy_hostname = NULL,
NPT_UInt16 https_proxy_port = 0);
NPT_Result SetProxySelector(NPT_HttpProxySelector* selector);
NPT_Result SetConnector(Connector* connector);
NPT_Result SetTimeouts(NPT_Timeout connection_timeout,
NPT_Timeout io_timeout,
NPT_Timeout name_resolver_timeout);
NPT_Result SetUserAgent(const char* user_agent);
NPT_Result SetOptions(NPT_Flags options, bool on);
protected:
// methods
NPT_Result TrackConnection(Connection* connection);
NPT_Result SendRequestOnce(NPT_HttpRequest& request,
NPT_HttpResponse*& response,
NPT_HttpRequestContext* context = NULL);
// members
Config m_Config;
NPT_HttpProxySelector* m_ProxySelector;
bool m_ProxySelectorIsOwned;
Connector* m_Connector;
bool m_ConnectorIsOwned;
NPT_Mutex m_AbortLock;
bool m_Aborted;
};
/*----------------------------------------------------------------------
| NPT_HttpConnectionManager
+---------------------------------------------------------------------*/
class NPT_HttpConnectionManager : public NPT_Thread,
public NPT_AutomaticCleaner::Singleton
{
public:
// singleton management
static NPT_HttpConnectionManager* GetInstance();
class Connection : public NPT_HttpClient::Connection
{
public:
Connection(NPT_HttpConnectionManager& manager,
NPT_SocketReference& socket,
NPT_InputStreamReference input_stream,
NPT_OutputStreamReference output_stream);
~Connection() override;
// NPT_HttpClient::Connection methods
NPT_InputStreamReference& GetInputStream() override { return m_InputStream; }
NPT_OutputStreamReference& GetOutputStream() override { return m_OutputStream; }
NPT_Result GetInfo(NPT_SocketInfo& info) override { return m_Socket->GetInfo(info); }
bool SupportsPersistence() override { return true; }
bool IsRecycled() override { return m_IsRecycled; }
NPT_Result Recycle() override;
NPT_Result Abort() override { return m_Socket->Cancel(); }
// members
NPT_HttpConnectionManager& m_Manager;
bool m_IsRecycled;
NPT_TimeStamp m_TimeStamp;
NPT_SocketReference m_Socket;
NPT_InputStreamReference m_InputStream;
NPT_OutputStreamReference m_OutputStream;
};
// destructor
~NPT_HttpConnectionManager() override;
// methods
Connection* FindConnection(NPT_SocketAddress& address);
NPT_Result Recycle(Connection* connection);
NPT_Result Track(NPT_HttpClient* client, NPT_HttpClient::Connection* connection);
NPT_Result AbortConnections(NPT_HttpClient* client);
// class methods
static NPT_Result Untrack(NPT_HttpClient::Connection* connection);
private:
typedef NPT_List<NPT_HttpClient::Connection*> ConnectionList;
// class members
static NPT_HttpConnectionManager* Instance;
// constructor
NPT_HttpConnectionManager();
// NPT_Thread methods
void Run() override;
// methods
NPT_Result UntrackConnection(NPT_HttpClient::Connection* connection);
NPT_Result Cleanup();
// members
NPT_Mutex m_Lock;
NPT_Cardinal m_MaxConnections;
NPT_Cardinal m_MaxConnectionAge;
NPT_SharedVariable m_Aborted;
NPT_List<Connection*> m_Connections;
NPT_Map<NPT_HttpClient*, ConnectionList> m_ClientConnections;
};
/*----------------------------------------------------------------------
| NPT_HttpRequestContext
+---------------------------------------------------------------------*/
class NPT_HttpRequestContext
{
public:
// constructor
NPT_HttpRequestContext() {}
NPT_HttpRequestContext(const NPT_SocketAddress* local_address,
const NPT_SocketAddress* remote_address);
// methods
const NPT_SocketAddress& GetLocalAddress() const { return m_LocalAddress; }
const NPT_SocketAddress& GetRemoteAddress() const { return m_RemoteAddress; }
void SetLocalAddress(const NPT_SocketAddress& address) {
m_LocalAddress = address;
}
void SetRemoteAddress(const NPT_SocketAddress& address) {
m_RemoteAddress = address;
}
private:
// members
NPT_SocketAddress m_LocalAddress;
NPT_SocketAddress m_RemoteAddress;
};
/*----------------------------------------------------------------------
| NPT_HttpRequestHandler
+---------------------------------------------------------------------*/
class NPT_HttpRequestHandler
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST(NPT_HttpRequestHandler)
// destructor
virtual ~NPT_HttpRequestHandler() {}
// methods
virtual NPT_Result SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response) = 0;
/**
* Override this method if you want to write the body yourself.
* The default implementation will simply write out the entity's
* input stream.
*/
virtual NPT_Result SendResponseBody(const NPT_HttpRequestContext& context,
NPT_HttpResponse& response,
NPT_OutputStream& output);
};
/*----------------------------------------------------------------------
| NPT_HttpStaticRequestHandler
+---------------------------------------------------------------------*/
class NPT_HttpStaticRequestHandler : public NPT_HttpRequestHandler
{
public:
// constructors
NPT_HttpStaticRequestHandler(const char* document,
const char* mime_type = "text/html",
bool copy = true);
NPT_HttpStaticRequestHandler(const void* data,
NPT_Size size,
const char* mime_type = "text/html",
bool copy = true);
// NPT_HttpRequestHandler methods
NPT_Result SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response) override;
private:
NPT_String m_MimeType;
NPT_DataBuffer m_Buffer;
};
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler_FileTypeMap
+---------------------------------------------------------------------*/
typedef struct NPT_HttpFileRequestHandler_DefaultFileTypeMapEntry {
const char* extension;
const char* mime_type;
} NPT_HttpFileRequestHandler_FileTypeMapEntry;
/*----------------------------------------------------------------------
| NPT_HttpFileRequestHandler
+---------------------------------------------------------------------*/
class NPT_HttpFileRequestHandler : public NPT_HttpRequestHandler
{
public:
// constructors
NPT_HttpFileRequestHandler(const char* url_root,
const char* file_root,
bool auto_dir = false,
const char* auto_index = NULL);
// NPT_HttpRequestHandler methods
NPT_Result SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response) override;
// class methods
static const char* GetDefaultContentType(const char* extension);
// accessors
NPT_Map<NPT_String,NPT_String>& GetFileTypeMap() { return m_FileTypeMap; }
void SetDefaultMimeType(const char* mime_type) {
m_DefaultMimeType = mime_type;
}
void SetUseDefaultFileTypeMap(bool use_default) {
m_UseDefaultFileTypeMap = use_default;
}
static NPT_Result SetupResponseBody(NPT_HttpResponse& response,
NPT_InputStreamReference& stream,
const NPT_String* range_spec = NULL);
protected:
// methods
const char* GetContentType(const NPT_String& filename);
private:
NPT_String m_UrlRoot;
NPT_String m_FileRoot;
NPT_Map<NPT_String, NPT_String> m_FileTypeMap;
NPT_String m_DefaultMimeType;
bool m_UseDefaultFileTypeMap;
bool m_AutoDir;
NPT_String m_AutoIndex;
};
/*----------------------------------------------------------------------
| NPT_HttpServer
+---------------------------------------------------------------------*/
class NPT_HttpServer {
public:
// types
struct Config {
NPT_Timeout m_ConnectionTimeout;
NPT_Timeout m_IoTimeout;
NPT_IpAddress m_ListenAddress;
NPT_UInt16 m_ListenPort;
bool m_ReuseAddress;
};
// constructors and destructor
NPT_HttpServer(NPT_UInt16 listen_port = NPT_HTTP_DEFAULT_PORT,
bool reuse_address = true);
NPT_HttpServer(NPT_IpAddress listen_address,
NPT_UInt16 listen_port = NPT_HTTP_DEFAULT_PORT,
bool reuse_address = true);
virtual ~NPT_HttpServer();
// methods
NPT_Result SetConfig(const Config& config);
const Config& GetConfig() const { return m_Config; }
NPT_Result SetListenPort(NPT_UInt16 port, bool reuse_address = true);
NPT_Result SetTimeouts(NPT_Timeout connection_timeout, NPT_Timeout io_timeout);
NPT_Result SetServerHeader(const char* server_header);
NPT_Result Abort();
NPT_Result WaitForNewClient(NPT_InputStreamReference& input,
NPT_OutputStreamReference& output,
NPT_HttpRequestContext* context,
NPT_Flags socket_flags = 0);
NPT_Result Loop(bool cancellable_sockets=true);
NPT_UInt16 GetPort() { return m_BoundPort; }
void Terminate();
/**
- * Add a request handler. By default the ownership of the handler is NOT transfered to this object,
+ * Add a request handler. By default the ownership of the handler is NOT transferred to this object,
* so the caller is responsible for the lifetime management of the handler object.
*/
virtual NPT_Result AddRequestHandler(NPT_HttpRequestHandler* handler,
const char* path,
bool include_children = false,
bool transfer_ownership = false);
virtual NPT_HttpRequestHandler* FindRequestHandler(NPT_HttpRequest& request);
virtual NPT_List<NPT_HttpRequestHandler*> FindRequestHandlers(NPT_HttpRequest& request);
/**
* Parse the request from a new client, form a response, and send it back.
*/
virtual NPT_Result RespondToClient(NPT_InputStreamReference& input,
NPT_OutputStreamReference& output,
const NPT_HttpRequestContext& context);
protected:
// types
struct HandlerConfig {
HandlerConfig(NPT_HttpRequestHandler* handler,
const char* path,
bool include_children,
bool transfer_ownership = false);
~HandlerConfig();
// methods
bool WillHandle(NPT_HttpRequest& request);
// members
NPT_HttpRequestHandler* m_Handler;
NPT_String m_Path;
bool m_IncludeChildren;
bool m_HandlerIsOwned;
};
// methods
NPT_Result Bind();
// members
NPT_TcpServerSocket m_Socket;
NPT_UInt16 m_BoundPort;
Config m_Config;
NPT_List<HandlerConfig*> m_RequestHandlers;
NPT_String m_ServerHeader;
bool m_Run;
};
/*----------------------------------------------------------------------
| NPT_HttpResponder
+---------------------------------------------------------------------*/
class NPT_HttpResponder {
public:
// types
struct Config {
NPT_Timeout m_IoTimeout;
};
// constructors and destructor
NPT_HttpResponder(NPT_InputStreamReference& input,
NPT_OutputStreamReference& output);
virtual ~NPT_HttpResponder();
// methods
NPT_Result SetConfig(const Config& config);
NPT_Result SetTimeout(NPT_Timeout io_timeout);
NPT_Result ParseRequest(NPT_HttpRequest*& request,
const NPT_SocketAddress* local_address = NULL);
NPT_Result SendResponseHeaders(NPT_HttpResponse& response);
protected:
// members
Config m_Config;
NPT_BufferedInputStreamReference m_Input;
NPT_OutputStreamReference m_Output;
};
/*----------------------------------------------------------------------
| NPT_HttpChunkedInputStream
+---------------------------------------------------------------------*/
class NPT_HttpChunkedInputStream : public NPT_InputStream
{
public:
// constructors and destructor
NPT_HttpChunkedInputStream(NPT_BufferedInputStreamReference& stream);
~NPT_HttpChunkedInputStream() override;
// NPT_InputStream methods
NPT_Result Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read = NULL) override;
NPT_Result Seek(NPT_Position offset) override;
NPT_Result Tell(NPT_Position& offset) override;
NPT_Result GetSize(NPT_LargeSize& size) override;
NPT_Result GetAvailable(NPT_LargeSize& available) override;
protected:
// members
NPT_BufferedInputStreamReference m_Source;
NPT_UInt32 m_CurrentChunkSize;
bool m_Eos;
};
/*----------------------------------------------------------------------
| NPT_HttpChunkedOutputStream
+---------------------------------------------------------------------*/
class NPT_HttpChunkedOutputStream : public NPT_OutputStream
{
public:
// constructors and destructor
NPT_HttpChunkedOutputStream(NPT_OutputStream& stream);
~NPT_HttpChunkedOutputStream() override;
// NPT_OutputStream methods
NPT_Result Write(const void* buffer,
NPT_Size bytes_to_write,
NPT_Size* bytes_written = NULL) override;
NPT_Result Seek(NPT_Position /*offset*/) override { return NPT_ERROR_NOT_SUPPORTED;}
NPT_Result Tell(NPT_Position& offset) override { return m_Stream.Tell(offset); }
NPT_Result Flush() override { return m_Stream.Flush(); }
protected:
// members
NPT_OutputStream& m_Stream;
};
#endif // _NPT_HTTP_H_
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptResults.h b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptResults.h
index 88025f7b9d..fbcdabb76c 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptResults.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptResults.h
@@ -1,163 +1,163 @@
/*****************************************************************
|
| Neptune - Result Codes
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
#ifndef _NPT_RESULTS_H_
#define _NPT_RESULTS_H_
/*----------------------------------------------------------------------
| macros
+---------------------------------------------------------------------*/
#if defined(NPT_DEBUG)
#include "NptDebug.h"
#define NPT_CHECK(_x) \
do { \
NPT_Result _result = (_x); \
if (_result != NPT_SUCCESS) { \
NPT_Debug("%s(%d): @@@ NPT_CHECK failed, result=%d (%s)\n", __FILE__, __LINE__, _result, NPT_ResultText(_result)); \
return _result; \
} \
} while(0)
#define NPT_CHECK_POINTER(_p) \
do { \
if ((_p) == NULL) { \
NPT_Debug("%s(%d): @@@ NULL pointer parameter\n", __FILE__, __LINE__); \
return NPT_ERROR_INVALID_PARAMETERS; \
} \
} while(0)
#define NPT_CHECK_LABEL(_x, label) \
do { \
NPT_Result _result = (_x); \
if (_result != NPT_SUCCESS) { \
NPT_Debug("%s(%d): @@@ NPT_CHECK failed, result=%d (%s)\n", __FILE__, __LINE__, _result, NPT_ResultText(_result)); \
goto label; \
} \
} while(0)
#define NPT_CHECK_POINTER_LABEL(_p, label) \
do { \
if (_p == NULL) { \
NPT_Debug("%s(%d): @@@ NULL pointer parameter\n", __FILE__, __LINE__); \
goto label; \
} \
} while(0)
#else
#define NPT_CHECK(_x) \
do { \
NPT_Result _result = (_x); \
if (_result != NPT_SUCCESS) { \
return _result; \
} \
} while(0)
#define NPT_CHECK_POINTER(_p) \
do { \
if ((_p) == NULL) return NPT_ERROR_INVALID_PARAMETERS; \
} while(0)
#define NPT_CHECK_LABEL(_x, label) \
do { \
NPT_Result _result = (_x); \
if (_result != NPT_SUCCESS) { \
goto label; \
} \
} while(0)
#define NPT_CHECK_POINTER_LABEL(_p, label) \
do { \
if ((_p) == NULL) { \
goto label; \
} \
} while(0)
#endif
#define NPT_FAILED(result) ((result) != NPT_SUCCESS)
#define NPT_SUCCEEDED(result) ((result) == NPT_SUCCESS)
/*----------------------------------------------------------------------
| result codes
+---------------------------------------------------------------------*/
/** Result indicating that the operation or call succeeded */
#define NPT_SUCCESS 0
-/** Result indicating an unspecififed failure condition */
+/** Result indicating an unspecified failure condition */
#define NPT_FAILURE (-1)
#if !defined(NPT_ERROR_BASE)
#define NPT_ERROR_BASE -20000
#endif
// error bases
#define NPT_ERROR_BASE_GENERAL (NPT_ERROR_BASE-0)
#define NPT_ERROR_BASE_LIST (NPT_ERROR_BASE-100)
#define NPT_ERROR_BASE_FILE (NPT_ERROR_BASE-200)
#define NPT_ERROR_BASE_IO (NPT_ERROR_BASE-300)
#define NPT_ERROR_BASE_SOCKET (NPT_ERROR_BASE-400)
#define NPT_ERROR_BASE_INTERFACES (NPT_ERROR_BASE-500)
#define NPT_ERROR_BASE_XML (NPT_ERROR_BASE-600)
#define NPT_ERROR_BASE_UNIX (NPT_ERROR_BASE-700)
#define NPT_ERROR_BASE_HTTP (NPT_ERROR_BASE-800)
#define NPT_ERROR_BASE_THREADS (NPT_ERROR_BASE-900)
#define NPT_ERROR_BASE_SERIAL_PORT (NPT_ERROR_BASE-1000)
#define NPT_ERROR_BASE_TLS (NPT_ERROR_BASE-1100)
// general errors
#define NPT_ERROR_INVALID_PARAMETERS (NPT_ERROR_BASE_GENERAL - 0)
#define NPT_ERROR_PERMISSION_DENIED (NPT_ERROR_BASE_GENERAL - 1)
#define NPT_ERROR_OUT_OF_MEMORY (NPT_ERROR_BASE_GENERAL - 2)
#define NPT_ERROR_NO_SUCH_NAME (NPT_ERROR_BASE_GENERAL - 3)
#define NPT_ERROR_NO_SUCH_PROPERTY (NPT_ERROR_BASE_GENERAL - 4)
#define NPT_ERROR_NO_SUCH_ITEM (NPT_ERROR_BASE_GENERAL - 5)
#define NPT_ERROR_NO_SUCH_CLASS (NPT_ERROR_BASE_GENERAL - 6)
#define NPT_ERROR_OVERFLOW (NPT_ERROR_BASE_GENERAL - 7)
#define NPT_ERROR_INTERNAL (NPT_ERROR_BASE_GENERAL - 8)
#define NPT_ERROR_INVALID_STATE (NPT_ERROR_BASE_GENERAL - 9)
#define NPT_ERROR_INVALID_FORMAT (NPT_ERROR_BASE_GENERAL - 10)
#define NPT_ERROR_INVALID_SYNTAX (NPT_ERROR_BASE_GENERAL - 11)
#define NPT_ERROR_NOT_IMPLEMENTED (NPT_ERROR_BASE_GENERAL - 12)
#define NPT_ERROR_NOT_SUPPORTED (NPT_ERROR_BASE_GENERAL - 13)
#define NPT_ERROR_TIMEOUT (NPT_ERROR_BASE_GENERAL - 14)
#define NPT_ERROR_WOULD_BLOCK (NPT_ERROR_BASE_GENERAL - 15)
#define NPT_ERROR_TERMINATED (NPT_ERROR_BASE_GENERAL - 16)
#define NPT_ERROR_OUT_OF_RANGE (NPT_ERROR_BASE_GENERAL - 17)
#define NPT_ERROR_OUT_OF_RESOURCES (NPT_ERROR_BASE_GENERAL - 18)
#define NPT_ERROR_NOT_ENOUGH_SPACE (NPT_ERROR_BASE_GENERAL - 19)
#define NPT_ERROR_INTERRUPTED (NPT_ERROR_BASE_GENERAL - 20)
#define NPT_ERROR_CANCELLED (NPT_ERROR_BASE_GENERAL - 21)
/* standard error codes */
/* these are special codes to convey an errno */
/* the error code is (SHI_ERROR_BASE_ERRNO - errno) */
/* where errno is the positive integer from errno.h */
#define NPT_ERROR_BASE_ERRNO (NPT_ERROR_BASE-2000)
#define NPT_ERROR_ERRNO(e) (NPT_ERROR_BASE_ERRNO - (e))
/*----------------------------------------------------------------------
| functions
+---------------------------------------------------------------------*/
const char* NPT_ResultText(int result);
#endif // _NPT_RESULTS_H_
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptStrings.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptStrings.cpp
index 2a04854a96..cdfe3b0913 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptStrings.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptStrings.cpp
@@ -1,1205 +1,1205 @@
/*****************************************************************
|
| Neptune - String Objects
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptConfig.h"
#include "NptTypes.h"
#include "NptConstants.h"
#include "NptStrings.h"
#include "NptResults.h"
#include "NptUtils.h"
#include "NptDebug.h"
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
#define NPT_STRINGS_WHITESPACE_CHARS "\r\n\t "
const unsigned int NPT_STRING_FORMAT_BUFFER_DEFAULT_SIZE = 256;
const unsigned int NPT_STRING_FORMAT_BUFFER_MAX_SIZE = 0x80000; // 512k
/*----------------------------------------------------------------------
| helpers
+---------------------------------------------------------------------*/
inline char NPT_Uppercase(char x) {
return (x >= 'a' && x <= 'z') ? x&0xdf : x;
}
inline char NPT_Lowercase(char x) {
return (x >= 'A' && x <= 'Z') ? x^32 : x;
}
/*----------------------------------------------------------------------
| NPT_String::EmptyString
+---------------------------------------------------------------------*/
char NPT_String::EmptyString = '\0';
/*----------------------------------------------------------------------
| NPT_String::FromInteger
+---------------------------------------------------------------------*/
NPT_String
NPT_String::FromInteger(NPT_Int64 value)
{
char str[32];
char* c = &str[31];
*c-- = '\0';
// handle the sign
bool negative = false;
if (value < 0) {
negative = true;
value = -value;
}
// process the digits
do {
int digit = (int)(value%10);
*c-- = '0'+digit;
value /= 10;
} while(value);
if (negative) {
*c = '-';
} else {
++c;
}
return NPT_String(c);
}
/*----------------------------------------------------------------------
| NPT_String::FromIntegerU
+---------------------------------------------------------------------*/
NPT_String
NPT_String::FromIntegerU(NPT_UInt64 value)
{
char str[32];
char* c = &str[31];
*c = '\0';
// process the digits
do {
int digit = (int)(value%10);
*--c = '0'+digit;
value /= 10;
} while(value);
return NPT_String(c);
}
/*----------------------------------------------------------------------
| NPT_String::Format
+---------------------------------------------------------------------*/
NPT_String
NPT_String::Format(const char* format, ...)
{
NPT_String result;
NPT_Size buffer_size = NPT_STRING_FORMAT_BUFFER_DEFAULT_SIZE; // default value
va_list args;
for(;;) {
/* try to format (it might not fit) */
result.Reserve(buffer_size);
char* buffer = result.UseChars();
va_start(args, format);
int f_result = NPT_FormatStringVN(buffer, buffer_size, format, args);
va_end(args);
if (f_result >= (int)(buffer_size)) f_result = -1;
if (f_result >= 0) {
result.SetLength(f_result);
break;
}
/* the buffer was too small, try something bigger */
/* (we don't trust the return value of NPT_FormatStringVN */
/* for the actual size needed) */
buffer_size *= 2;
if (buffer_size > NPT_STRING_FORMAT_BUFFER_MAX_SIZE) break;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_String::NPT_String
+---------------------------------------------------------------------*/
NPT_String::NPT_String(const char* str)
{
if (str == NULL) {
m_Chars = NULL;
} else {
m_Chars = Buffer::Create(str);
}
}
/*----------------------------------------------------------------------
| NPT_String::NPT_String
+---------------------------------------------------------------------*/
NPT_String::NPT_String(const char* str, NPT_Size length)
{
if (str == NULL || length == 0) {
m_Chars = NULL;
} else {
for (unsigned int i=0; i<length-1; i++) {
if (str[i] == '\0') {
if (i == 0) {
m_Chars = NULL;
return;
}
length = i;
break;
}
}
m_Chars = Buffer::Create(str, length);
}
}
/*----------------------------------------------------------------------
| NPT_String::NPT_String
+---------------------------------------------------------------------*/
NPT_String::NPT_String(const NPT_String& str)
{
if (str.GetLength() == 0) {
m_Chars = NULL;
} else {
m_Chars = Buffer::Create(str.GetChars(), str.GetLength());
}
}
/*----------------------------------------------------------------------
| NPT_String::NPT_String
+---------------------------------------------------------------------*/
NPT_String::NPT_String(char c, NPT_Cardinal repeat)
{
if (repeat != 0) {
m_Chars = Buffer::Create(c, repeat);
} else {
m_Chars = NULL;
}
}
/*----------------------------------------------------------------------
| NPT_String::SetLength
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::SetLength(NPT_Size length, bool pad)
{
// special case for 0
if (length == 0) {
Reset();
return NPT_SUCCESS;
}
// reserve the space
Reserve(length);
// pad with spaces if necessary
char* chars = UseChars();
if (pad) {
unsigned int current_length = GetLength();
if (length > current_length) {
unsigned int pad_length = length-current_length;
NPT_SetMemory(chars+current_length, ' ', pad_length);
}
}
// update the length and terminate the buffer
GetBuffer()->SetLength(length);
chars[length] = '\0';
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_String::PrepareToWrite
+---------------------------------------------------------------------*/
inline char*
NPT_String::PrepareToWrite(NPT_Size length)
{
NPT_ASSERT(length != 0);
if (m_Chars == NULL || GetBuffer()->GetAllocated() < length) {
// the buffer is too small, we need to allocate a new one.
NPT_Size needed = length;
if (m_Chars != NULL) {
NPT_Size grow = GetBuffer()->GetAllocated()*2;
if (grow > length) needed = grow;
delete GetBuffer();
}
m_Chars = Buffer::Create(needed);
}
GetBuffer()->SetLength(length);
return m_Chars;
}
/*----------------------------------------------------------------------
| NPT_String::Reserve
+---------------------------------------------------------------------*/
void
NPT_String::Reserve(NPT_Size allocate)
{
if (m_Chars == NULL || GetBuffer()->GetAllocated() < allocate) {
// the buffer is too small, we need to allocate a new one.
NPT_Size needed = allocate;
if (m_Chars != NULL) {
NPT_Size grow = GetBuffer()->GetAllocated()*2;
if (grow > allocate) needed = grow;
}
NPT_Size length = GetLength();
char* copy = Buffer::Create(needed, length);
if (m_Chars != NULL) {
CopyString(copy, m_Chars);
delete GetBuffer();
} else {
copy[0] = '\0';
}
m_Chars = copy;
}
}
/*----------------------------------------------------------------------
| NPT_String::Assign
+---------------------------------------------------------------------*/
void
NPT_String::Assign(const char* str, NPT_Size length)
{
if (str == NULL || length == 0) {
Reset();
} else {
for (unsigned int i=0; i<length-1; i++) {
if (str[i] == '\0') {
if (i == 0) {
Reset();
return;
} else {
length = i;
break;
}
}
}
PrepareToWrite(length);
CopyBuffer(m_Chars, str, length);
m_Chars[length] = '\0';
}
}
/*----------------------------------------------------------------------
| NPT_String::operator=
+---------------------------------------------------------------------*/
NPT_String&
NPT_String::operator=(const char* str)
{
if (str == NULL) {
Reset();
} else {
NPT_Size length = StringLength(str);
if (length == 0) {
Reset();
} else {
CopyString(PrepareToWrite(length), str);
}
}
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::operator=
+---------------------------------------------------------------------*/
NPT_String&
NPT_String::operator=(const NPT_String& str)
{
// do nothing if we're assigning to ourselves
if (this != &str) {
Assign(str.GetChars(), str.GetLength());
}
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::GetHash32
+---------------------------------------------------------------------*/
NPT_UInt32
NPT_String::GetHash32() const
{
return NPT_Fnv1aHashStr32(GetChars());
}
/*----------------------------------------------------------------------
| NPT_String::GetHash64
+---------------------------------------------------------------------*/
NPT_UInt64
NPT_String::GetHash64() const
{
return NPT_Fnv1aHashStr64(GetChars());
}
/*----------------------------------------------------------------------
| NPT_String::Append
+---------------------------------------------------------------------*/
void
NPT_String::Append(const char* str, NPT_Size length)
{
// shortcut
if (str == NULL || length == 0) return;
// compute the new length
NPT_Size old_length = GetLength();
NPT_Size new_length = old_length + length;
// allocate enough space
Reserve(new_length);
// append the new string at the end of the current one
CopyBuffer(m_Chars+old_length, str, length);
m_Chars[new_length] = '\0';
// update the length
GetBuffer()->SetLength(new_length);
}
/*----------------------------------------------------------------------
| NPT_String::Compare
+---------------------------------------------------------------------*/
int
NPT_String::Compare(const char *s, bool ignore_case) const
{
return NPT_String::Compare(GetChars(), s, ignore_case);
}
/*----------------------------------------------------------------------
| NPT_String::Compare
+---------------------------------------------------------------------*/
int
NPT_String::Compare(const char *s1, const char *s2, bool ignore_case)
{
const char *r1 = s1;
const char *r2 = s2;
if (ignore_case) {
while (NPT_Uppercase(*r1) == NPT_Uppercase(*r2)) {
if (*r1++ == '\0') {
return 0;
}
r2++;
}
return NPT_Uppercase(*r1) - NPT_Uppercase(*r2);
} else {
while (*r1 == *r2) {
if (*r1++ == '\0') {
return 0;
}
r2++;
}
return (*r1 - *r2);
}
}
/*----------------------------------------------------------------------
| NPT_String::CompareN
+---------------------------------------------------------------------*/
int
NPT_String::CompareN(const char *s, NPT_Size count, bool ignore_case) const
{
return NPT_String::CompareN(GetChars(), s, count, ignore_case);
}
/*----------------------------------------------------------------------
| NPT_String::CompareN
+---------------------------------------------------------------------*/
int
NPT_String::CompareN(const char* s1, const char *s2, NPT_Size count, bool ignore_case)
{
const char* me = s1;
if (ignore_case) {
for (unsigned int i=0; i<count; i++) {
if (NPT_Uppercase(me[i]) != NPT_Uppercase(s2[i])) {
return NPT_Uppercase(me[i]) - NPT_Uppercase(s2[i]);
}
}
return 0;
} else {
for (unsigned int i=0; i<count; i++) {
if (me[i] != s2[i]) {
return (me[i] - s2[i]);
}
}
return 0;
}
}
/*----------------------------------------------------------------------
| NPT_String::Split
+---------------------------------------------------------------------*/
NPT_List<NPT_String>
NPT_String::Split(const char* separator) const
{
NPT_List<NPT_String> result;
NPT_Size separator_length = NPT_StringLength(separator);
- // sepcial case for empty separators
+ // special case for empty separators
if (separator_length == 0) {
result.Add(*this);
return result;
}
int current = 0;
int next;
do {
next = Find(separator, current);
unsigned int end = (next>=0?(unsigned int)next:GetLength());
result.Add(SubString(current, end-current));
current = next+separator_length;
} while (next >= 0);
return result;
}
/*----------------------------------------------------------------------
| NPT_String::SplitAny
+---------------------------------------------------------------------*/
NPT_Array<NPT_String>
NPT_String::SplitAny(const char* separator) const
{
NPT_Array<NPT_String> result((GetLength()>>1)+1);
- // sepcial case for empty separators
+ // special case for empty separators
if (NPT_StringLength(separator) == 0) {
result.Add(*this);
return result;
}
int current = 0;
int next;
do {
next = FindAny(separator, current);
unsigned int end = (next>=0?(unsigned int)next:GetLength());
result.Add(SubString(current, end-current));
current = next+1;
} while (next >= 0);
return result;
}
/*----------------------------------------------------------------------
| NPT_String::Join
+---------------------------------------------------------------------*/
NPT_String
NPT_String::Join(NPT_List<NPT_String>& args, const char* separator)
{
NPT_String output;
NPT_List<NPT_String>::Iterator arg = args.GetFirstItem();
while (arg) {
output += *arg;
if (++arg) output += separator;
}
return output;
}
/*----------------------------------------------------------------------
| NPT_String::SubString
+---------------------------------------------------------------------*/
NPT_String
NPT_String::SubString(NPT_Ordinal first, NPT_Size length) const
{
if (first >= GetLength()) {
first = GetLength();
length = 0;
} else if (first+length >= GetLength()) {
length = GetLength()-first;
}
return NPT_String(GetChars()+first, length);
}
/*----------------------------------------------------------------------
| NPT_StringStartsWith
|
| returns:
| 1 if str starts with sub,
| 0 if str is large enough but does not start with sub
| -1 if str is too short to start with sub
+---------------------------------------------------------------------*/
static inline int
NPT_StringStartsWith(const char* str, const char* sub, bool ignore_case)
{
if (ignore_case) {
while (NPT_Uppercase(*str) == NPT_Uppercase(*sub)) {
if (*str++ == '\0') {
return 1;
}
sub++;
}
} else {
while (*str == *sub) {
if (*str++ == '\0') {
return 1;
}
sub++;
}
}
return (*sub == '\0') ? 1 : (*str == '\0' ? -1 : 0);
}
/*----------------------------------------------------------------------
| NPT_String::StartsWith
+---------------------------------------------------------------------*/
bool
NPT_String::StartsWith(const char *s, bool ignore_case) const
{
if (s == NULL) return false;
return NPT_StringStartsWith(GetChars(), s, ignore_case) == 1;
}
/*----------------------------------------------------------------------
| NPT_String::EndsWith
+---------------------------------------------------------------------*/
bool
NPT_String::EndsWith(const char *s, bool ignore_case) const
{
if (s == NULL) return false;
NPT_Size str_length = NPT_StringLength(s);
if (str_length > GetLength()) return false;
return NPT_StringStartsWith(GetChars()+GetLength()-str_length, s, ignore_case) == 1;
}
/*----------------------------------------------------------------------
| NPT_String::Find
+---------------------------------------------------------------------*/
int
NPT_String::Find(const char* str, NPT_Ordinal start, bool ignore_case) const
{
// check args
if (str == NULL || start >= GetLength()) return -1;
// skip to start position
const char* src = m_Chars + start;
// look for a substring
while (*src) {
int cmp = NPT_StringStartsWith(src, str, ignore_case);
switch (cmp) {
case -1:
// ref is too short, abort
return -1;
case 1:
// match
return (int)(src-m_Chars);
}
src++;
}
return -1;
}
/*----------------------------------------------------------------------
| NPT_String::Find
+---------------------------------------------------------------------*/
int
NPT_String::Find(char c, NPT_Ordinal start, bool ignore_case) const
{
// check args
if (start >= GetLength()) return -1;
// skip to start position
const char* src = m_Chars + start;
// look for the character
if (ignore_case) {
while (*src) {
if (NPT_Uppercase(*src) == NPT_Uppercase(c)) {
return (int)(src-m_Chars);
}
src++;
}
} else {
while (*src) {
if (*src == c) return (int)(src-m_Chars);
src++;
}
}
return -1;
}
/*----------------------------------------------------------------------
| NPT_String::FindAny
+---------------------------------------------------------------------*/
int
NPT_String::FindAny(const char* s, NPT_Ordinal start, bool ignore_case) const
{
// check args
if (start >= GetLength()) return -1;
// skip to start position
const char* src = m_Chars + start;
// look for the character
if (ignore_case) {
while (*src) {
for (NPT_Size i=0; i<NPT_StringLength(s); i++) {
if (NPT_Uppercase(*src) == NPT_Uppercase(s[i])) {
return (int)(src-m_Chars);
}
}
src++;
}
} else {
while (*src) {
for (NPT_Size i=0; i<NPT_StringLength(s); i++) {
if (*src == s[i]) return (int)(src-m_Chars);
}
src++;
}
}
return -1;
}
/*----------------------------------------------------------------------
| NPT_String::ReverseFind
+---------------------------------------------------------------------*/
int
NPT_String::ReverseFind(const char* str, NPT_Ordinal start, bool ignore_case) const
{
// check args
if (str == NULL || *str == '\0') return -1;
// look for a substring
NPT_Size my_length = GetLength();
NPT_Size str_length = NPT_StringLength(str);
int i=my_length-start-str_length;
const char* src = GetChars();
if (i<0) return -1;
for (;i>=0; i--) {
int cmp = NPT_StringStartsWith(src+i, str, ignore_case);
if (cmp == 1) {
// match
return i;
}
}
return -1;
}
/*----------------------------------------------------------------------
| NPT_String::ReverseFind
+---------------------------------------------------------------------*/
int
NPT_String::ReverseFind(char c, NPT_Ordinal start, bool ignore_case) const
{
// check args
NPT_Size length = GetLength();
int i = length-start-1;
if (i < 0) return -1;
// look for the character
const char* src = GetChars();
if (ignore_case) {
for (;i>=0;i--) {
if (NPT_Uppercase(src[i]) == NPT_Uppercase(c)) {
return i;
}
}
} else {
for (;i>=0;i--) {
if (src[i] == c) return i;
}
}
return -1;
}
/*----------------------------------------------------------------------
| NPT_String::MakeLowercase
+---------------------------------------------------------------------*/
void
NPT_String::MakeLowercase()
{
// the source is the current buffer
const char* src = GetChars();
// convert all the characters of the existing buffer
char* dst = const_cast<char*>(src);
while (*dst != '\0') {
*dst = NPT_Lowercase(*dst);
dst++;
}
}
/*----------------------------------------------------------------------
| NPT_String::MakeUppercase
+---------------------------------------------------------------------*/
void
NPT_String::MakeUppercase()
{
// the source is the current buffer
const char* src = GetChars();
// convert all the characters of the existing buffer
char* dst = const_cast<char*>(src);
while (*dst != '\0') {
*dst = NPT_Uppercase(*dst);
dst++;
}
}
/*----------------------------------------------------------------------
| NPT_String::ToLowercase
+---------------------------------------------------------------------*/
NPT_String
NPT_String::ToLowercase() const
{
NPT_String result(*this);
result.MakeLowercase();
return result;
}
/*----------------------------------------------------------------------
| NPT_String::ToUppercase
+---------------------------------------------------------------------*/
NPT_String
NPT_String::ToUppercase() const
{
NPT_String result(*this);
result.MakeUppercase();
return result;
}
/*----------------------------------------------------------------------
| NPT_String::Replace
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Replace(char a, char b)
{
// check args
if (m_Chars == NULL || a == '\0' || b == '\0') return *this;
// we are going to modify the characters
char* src = m_Chars;
// process the buffer in place
while (*src) {
if (*src == a) *src = b;
src++;
}
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::Replace
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Replace(char a, const char* str)
{
// check args
if (m_Chars == NULL || a == '\0' || str == NULL) return *this;
// optimization
if (NPT_StringLength(str) == 1) return Replace(a, str[0]);
// we are going to create a new string
NPT_String dst;
char* src = m_Chars;
// reserve at least as much as input
dst.Reserve(GetLength());
// process the buffer
while (*src) {
if (*src == a) {
dst += str;
} else {
dst += *src;
}
src++;
}
Assign(dst.GetChars(), dst.GetLength());
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::Replace
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Replace(const char* before, const char* after)
{
NPT_Size size_before = NPT_StringLength(before);
NPT_Size size_after = NPT_StringLength(after);
int index = Find(before);
while (index != NPT_STRING_SEARCH_FAILED) {
Erase(index, size_before);
Insert(after, index);
index = Find(before, index+size_after);
}
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::Insert
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Insert(const char* str, NPT_Ordinal where)
{
// check args
if (str == NULL || where > GetLength()) return *this;
// measure the string to insert
NPT_Size str_length = StringLength(str);
if (str_length == 0) return *this;
// compute the size of the new string
NPT_Size old_length = GetLength();
NPT_Size new_length = str_length + GetLength();
// prepare to write the new string
char* src = m_Chars;
char* nst = Buffer::Create(new_length, new_length);
char* dst = nst;
// copy the beginning of the old string
if (where > 0) {
CopyBuffer(dst, src, where);
src += where;
dst += where;
}
// copy the inserted string
CopyString(dst, str);
dst += str_length;
// copy the end of the old string
if (old_length > where) {
CopyString(dst, src);
}
// use the new string
if (m_Chars) delete GetBuffer();
m_Chars = nst;
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::Erase
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Erase(NPT_Ordinal start, NPT_Cardinal count /* = 1 */)
{
// check bounds
NPT_Size length = GetLength();
if (start+count > length) {
if (start >= length) return *this;
count = length-start;
}
if (count == 0) return *this;
CopyString(m_Chars+start, m_Chars+start+count);
GetBuffer()->SetLength(length-count);
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger(int& value, bool relaxed) const
{
return NPT_ParseInteger(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger(unsigned int& value, bool relaxed) const
{
return NPT_ParseInteger(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger(long& value, bool relaxed) const
{
return NPT_ParseInteger(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger(unsigned long& value, bool relaxed) const
{
return NPT_ParseInteger(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger32
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger32(NPT_Int32& value, bool relaxed) const
{
return NPT_ParseInteger32(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger32
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger32(NPT_UInt32& value, bool relaxed) const
{
return NPT_ParseInteger32(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger64
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger64(NPT_Int64& value, bool relaxed) const
{
return NPT_ParseInteger64(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToInteger64
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToInteger64(NPT_UInt64& value, bool relaxed) const
{
return NPT_ParseInteger64(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::ToFloat
+---------------------------------------------------------------------*/
NPT_Result
NPT_String::ToFloat(float& value, bool relaxed) const
{
return NPT_ParseFloat(GetChars(), value, relaxed);
}
/*----------------------------------------------------------------------
| NPT_String::TrimLeft
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::TrimLeft()
{
return TrimLeft(NPT_STRINGS_WHITESPACE_CHARS);
}
/*----------------------------------------------------------------------
| NPT_String::TrimLeft
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::TrimLeft(char c)
{
char s[2] = {c, 0};
return TrimLeft((const char*)s);
}
/*----------------------------------------------------------------------
| NPT_String::TrimLeft
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::TrimLeft(const char* chars)
{
if (m_Chars == NULL) return *this;
const char* s = m_Chars;
while (char c = *s) {
const char* x = chars;
while (*x) {
if (*x == c) break;
x++;
}
if (*x == 0) break; // not found
s++;
}
if (s == m_Chars) {
// nothing was trimmed
return *this;
}
// shift chars to the left
char* d = m_Chars;
GetBuffer()->SetLength(GetLength()-(NPT_Size)(s-d));
while ((*d++ = *s++)) {};
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::TrimRight
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::TrimRight()
{
return TrimRight(NPT_STRINGS_WHITESPACE_CHARS);
}
/*----------------------------------------------------------------------
| NPT_String::TrimRight
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::TrimRight(char c)
{
char s[2] = {c, 0};
return TrimRight((const char*)s);
}
/*----------------------------------------------------------------------
| NPT_String::TrimRight
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::TrimRight(const char* chars)
{
if (m_Chars == NULL || m_Chars[0] == '\0') return *this;
char* tail = m_Chars+GetLength()-1;
char* s = tail;
while (s != m_Chars-1) {
const char* x = chars;
while (*x) {
if (*x == *s) {
*s = '\0';
break;
}
x++;
}
if (*x == 0) break; // not found
s--;
}
if (s == tail) {
// nothing was trimmed
return *this;
}
GetBuffer()->SetLength(1+(int)(s-m_Chars));
return *this;
}
/*----------------------------------------------------------------------
| NPT_String::Trim
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Trim()
{
TrimLeft();
return TrimRight();
}
/*----------------------------------------------------------------------
| NPT_String::Trim
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Trim(char c)
{
char s[2] = {c, 0};
TrimLeft((const char*)s);
return TrimRight((const char*)s);
}
/*----------------------------------------------------------------------
| NPT_String::Trim
+---------------------------------------------------------------------*/
const NPT_String&
NPT_String::Trim(const char* chars)
{
TrimLeft(chars);
return TrimRight(chars);
}
/*----------------------------------------------------------------------
| NPT_String::operator+(const NPT_String&, const char*)
+---------------------------------------------------------------------*/
NPT_String
operator+(const NPT_String& s1, const char* s2)
{
// shortcut
if (s2 == NULL) return NPT_String(s1);
// measure strings
NPT_Size s1_length = s1.GetLength();
NPT_Size s2_length = NPT_String::StringLength(s2);
// allocate space for the new string
NPT_String result;
char* start = result.PrepareToWrite(s1_length+s2_length);
// concatenate the two strings into the result
NPT_String::CopyBuffer(start, s1, s1_length);
NPT_String::CopyString(start+s1_length, s2);
return result;
}
/*----------------------------------------------------------------------
| NPT_String::operator+(const NPT_String& , const char*)
+---------------------------------------------------------------------*/
NPT_String
operator+(const char* s1, const NPT_String& s2)
{
// shortcut
if (s1 == NULL) return NPT_String(s2);
// measure strings
NPT_Size s1_length = NPT_String::StringLength(s1);
NPT_Size s2_length = s2.GetLength();
// allocate space for the new string
NPT_String result;
char* start = result.PrepareToWrite(s1_length+s2_length);
// concatenate the two strings into the result
NPT_String::CopyBuffer(start, s1, s1_length);
NPT_String::CopyString(start+s1_length, s2.GetChars());
return result;
}
/*----------------------------------------------------------------------
| NPT_String::operator+(const NPT_String& , char)
+---------------------------------------------------------------------*/
NPT_String
operator+(const NPT_String& s1, char c)
{
// allocate space for the new string
NPT_String result;
result.Reserve(s1.GetLength()+1);
// append
result = s1;
result += c;
return result;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptTime.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptTime.cpp
index 7bb57df6ed..ff77dde77c 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptTime.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptTime.cpp
@@ -1,714 +1,714 @@
/*****************************************************************
|
| Neptune - Time
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptTime.h"
#include "NptUtils.h"
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
const char* const NPT_TIME_DAYS_SHORT[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
const char* const NPT_TIME_DAYS_LONG[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
const char* const NPT_TIME_MONTHS[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
static const NPT_Int32 NPT_TIME_MONTH_DAY[] = {-1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 };
static const NPT_Int32 NPT_TIME_MONTH_DAY_LEAP[] = {-1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
static const NPT_Int32 NPT_TIME_ELAPSED_DAYS_AT_MONTH[13] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};
const NPT_Int32 NPT_SECONDS_PER_DAY = (24L * 60L * 60L);
const NPT_Int32 NPT_SECONDS_PER_YEAR = (365L * NPT_SECONDS_PER_DAY);
/*----------------------------------------------------------------------
| macros
+---------------------------------------------------------------------*/
#define NPT_TIME_YEAR_IS_LEAP(_y) ((((_y)%4 == 0) && ((_y)%100 != 0)) || ((_y)%400 == 0))
#define NPT_TIME_CHECK_BOUNDS(_var, _low, _high) do { \
if (((_var)<(_low)) || ((_var)>(_high))) { \
return NPT_ERROR_OUT_OF_RANGE; \
} \
} while (0)
/*----------------------------------------------------------------------
| NPT_TimeStamp::NPT_TimeStamp
+---------------------------------------------------------------------*/
NPT_TimeStamp::NPT_TimeStamp(const NPT_TimeStamp& timestamp)
{
m_NanoSeconds = timestamp.m_NanoSeconds;
}
/*----------------------------------------------------------------------
| NPT_TimeStamp::NPT_TimeStamp
+---------------------------------------------------------------------*/
NPT_TimeStamp::NPT_TimeStamp(double seconds)
{
m_NanoSeconds = (NPT_Int64)(seconds * 1e9);
}
/*----------------------------------------------------------------------
| NPT_TimeStamp::operator+=
+---------------------------------------------------------------------*/
NPT_TimeStamp&
NPT_TimeStamp::operator+=(const NPT_TimeStamp& t)
{
m_NanoSeconds += t.m_NanoSeconds;
return *this;
}
/*----------------------------------------------------------------------
| NPT_TimeStamp::operator-=
+---------------------------------------------------------------------*/
NPT_TimeStamp&
NPT_TimeStamp::operator-=(const NPT_TimeStamp& t)
{
m_NanoSeconds -= t.m_NanoSeconds;
return *this;
}
/*----------------------------------------------------------------------
| MatchString
+---------------------------------------------------------------------*/
static int
MatchString(const char* string, const char* const* list, unsigned int list_length)
{
for (unsigned int i=0; i<list_length; i++) {
if (NPT_StringsEqual(string, list[i])) return i;
}
return -1;
}
/*----------------------------------------------------------------------
| ElapsedLeapYearsSince1900
+---------------------------------------------------------------------*/
static NPT_UInt32
ElapsedLeapYearsSince1900(NPT_UInt32 year)
{
if (year < 1901) return 0;
NPT_UInt32 years_since_1900 = year-1-1900; // not including the current year
return years_since_1900/4 -
years_since_1900/100 +
(years_since_1900+300)/400;
}
/*----------------------------------------------------------------------
| ElapsedDaysSince1900
+---------------------------------------------------------------------*/
static NPT_UInt32
ElapsedDaysSince1900(const NPT_DateTime& date)
{
// compute the number of days elapsed in the year
NPT_UInt32 day_count = NPT_TIME_ELAPSED_DAYS_AT_MONTH[date.m_Month-1] + date.m_Day - 1;
// adjust for leap years after february
if (NPT_TIME_YEAR_IS_LEAP(date.m_Year) && (date.m_Month > 2)) ++day_count;
// compute the total number of elapsed days
NPT_UInt32 leap_year_count = ElapsedLeapYearsSince1900(date.m_Year);
day_count += (date.m_Year-1900)*365 + leap_year_count;
return day_count;
}
/*----------------------------------------------------------------------
| NPT_DateTime::NPT_DateTime
+---------------------------------------------------------------------*/
NPT_DateTime::NPT_DateTime() :
m_Year(1970),
m_Month(1),
m_Day(1),
m_Hours(0),
m_Minutes(0),
m_Seconds(0),
m_NanoSeconds(0),
m_TimeZone(0)
{
}
/*----------------------------------------------------------------------
| NPT_DateTime::NPT_DateTime
+---------------------------------------------------------------------*/
NPT_DateTime::NPT_DateTime(const NPT_TimeStamp& timestamp, bool local)
{
FromTimeStamp(timestamp, local);
}
/*----------------------------------------------------------------------
| NPT_DateTime::ChangeTimeZone
+---------------------------------------------------------------------*/
NPT_Result
NPT_DateTime::ChangeTimeZone(NPT_Int32 timezone)
{
if (timezone < -12*60 || timezone > 12*60) {
return NPT_ERROR_OUT_OF_RANGE;
}
NPT_TimeStamp ts;
NPT_Result result = ToTimeStamp(ts);
if (NPT_FAILED(result)) return result;
ts.SetNanos(ts.ToNanos()+(NPT_Int64)timezone*(NPT_Int64)60*(NPT_Int64)1000000000);
result = FromTimeStamp(ts);
m_TimeZone = timezone;
return result;
}
/*----------------------------------------------------------------------
| NPT_DateTime::FromTimeStamp
+---------------------------------------------------------------------*/
NPT_Result
NPT_DateTime::FromTimeStamp(const NPT_TimeStamp& ts, bool local)
{
// number of seconds from the epoch (positive or negative)
NPT_Int64 seconds = ts.ToSeconds();
// check the range (we only allow up to 31 bits of negative range for seconds
// in order to have the same lower bound as the 32-bit gmtime() function)
if (seconds < 0 && (NPT_Int32)seconds != seconds) return NPT_ERROR_OUT_OF_RANGE;
// adjust for the timezone if necessary
NPT_Int32 timezone = 0;
if (local) {
timezone = GetLocalTimeZone();
seconds += timezone*60;
}
// adjust to the number of seconds since 1900
seconds += (NPT_Int64)NPT_SECONDS_PER_YEAR*70 +
(NPT_Int64)(17*NPT_SECONDS_PER_DAY); // 17 leap year between 1900 and 1970
// compute the years since 1900, not adjusting for leap years
NPT_UInt32 years_since_1900 = (NPT_UInt32)(seconds/NPT_SECONDS_PER_YEAR);
// compute the number of seconds elapsed in the current year
seconds -= (NPT_Int64)years_since_1900 * NPT_SECONDS_PER_YEAR;
// adjust for leap years
bool is_leap_year = false;
NPT_UInt32 leap_years_since_1900 = ElapsedLeapYearsSince1900(years_since_1900+1900);
if (seconds < (leap_years_since_1900 * NPT_SECONDS_PER_DAY)) {
// not enough seconds in the current year to compensate, move one year back
seconds += NPT_SECONDS_PER_YEAR;
seconds -= leap_years_since_1900 * NPT_SECONDS_PER_DAY;
--years_since_1900;
if (NPT_TIME_YEAR_IS_LEAP(years_since_1900+1900) ) {
seconds += NPT_SECONDS_PER_DAY;
is_leap_year = true;
}
} else {
seconds -= leap_years_since_1900 * NPT_SECONDS_PER_DAY;
if (NPT_TIME_YEAR_IS_LEAP(years_since_1900+1900) ) {
is_leap_year = true;
}
}
// now we know the year
m_Year = years_since_1900+1900;
// compute the number of days since January 1 (0 - 365)
NPT_UInt32 day_of_the_year = (NPT_UInt32)(seconds/NPT_SECONDS_PER_DAY);
// compute the number of seconds in the current day
seconds -= day_of_the_year * NPT_SECONDS_PER_DAY;
// compute the number of months since January (0 - 11) and the day of month (1 - 31) */
const NPT_Int32* month_day = is_leap_year?NPT_TIME_MONTH_DAY_LEAP:NPT_TIME_MONTH_DAY;
NPT_UInt32 month;
for (month = 1; month_day[month] < (NPT_Int32)day_of_the_year ; month++) {}
// now we know the month and day
m_Month = month;
m_Day = day_of_the_year - month_day[month-1];
// compute the number of hours since midnight (0 - 23), minutes after the hour
// (0 - 59), seconds after the minute (0 - 59) and nanoseconds
m_Hours = (NPT_Int32)seconds/3600;
seconds -= m_Hours * 3600L;
m_Minutes = (NPT_Int32)seconds / 60;
m_Seconds = (NPT_Int32)seconds - m_Minutes * 60;
m_NanoSeconds = (NPT_Int32)(ts.ToNanos()%1000000000);
if (local) {
m_TimeZone = timezone;
} else {
m_TimeZone = 0;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| CheckDate
+---------------------------------------------------------------------*/
static NPT_Result
CheckDate(const NPT_DateTime& date)
{
NPT_TIME_CHECK_BOUNDS(date.m_Year, NPT_DATETIME_YEAR_MIN, NPT_DATETIME_YEAR_MAX);
NPT_TIME_CHECK_BOUNDS(date.m_Month, 1, 12);
NPT_TIME_CHECK_BOUNDS(date.m_Day, 1, 31);
NPT_TIME_CHECK_BOUNDS(date.m_Hours, 0, 23);
NPT_TIME_CHECK_BOUNDS(date.m_Minutes, 0, 59);
NPT_TIME_CHECK_BOUNDS(date.m_Seconds, 0, 59);
NPT_TIME_CHECK_BOUNDS(date.m_NanoSeconds, 0, 999999999);
NPT_TIME_CHECK_BOUNDS(date.m_TimeZone, -12*60, 12*60);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_DateTime::ToTimeStamp
+---------------------------------------------------------------------*/
NPT_Result
NPT_DateTime::ToTimeStamp(NPT_TimeStamp& timestamp) const
{
// default value
timestamp.SetNanos(0);
// check bounds
NPT_Result result = CheckDate(*this);
if (NPT_FAILED(result)) return result;
// compute the number of days elapsed since 1900
NPT_UInt32 days = ElapsedDaysSince1900(*this);
// compute the number of nanoseconds
NPT_Int64 seconds = (NPT_Int64)days * (24*60*60) +
(NPT_Int64)m_Hours * (60*60) +
(NPT_Int64)m_Minutes * (60) +
(NPT_Int64)m_Seconds;
seconds -= (NPT_Int64)m_TimeZone*60;
// adjust to the number of seconds since 1900
seconds -= (NPT_Int64)NPT_SECONDS_PER_YEAR*70 +
(NPT_Int64)(17*NPT_SECONDS_PER_DAY); // 17 leap year between 1900 and 1970
timestamp.FromNanos(seconds * 1000000000 + m_NanoSeconds);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| AppendNumber
+---------------------------------------------------------------------*/
static void
AppendNumber(NPT_String& output, NPT_UInt32 number, unsigned int digit_count)
{
NPT_Size new_length = output.GetLength()+digit_count;
output.SetLength(new_length);
char* dest = output.UseChars()+new_length;
while (digit_count--) {
*--dest = '0'+(number%10);
number /= 10;
}
}
/*----------------------------------------------------------------------
| NPT_DateTime::ToString
+---------------------------------------------------------------------*/
NPT_String
NPT_DateTime::ToString(Format format, NPT_Flags flags) const
{
NPT_String result;
if (NPT_FAILED(CheckDate(*this))) return result;
switch (format) {
case FORMAT_W3C:
AppendNumber(result, m_Year, 4);
result += '-';
AppendNumber(result, m_Month, 2);
result += '-';
AppendNumber(result, m_Day, 2);
result += 'T';
AppendNumber(result, m_Hours, 2);
result += ':';
AppendNumber(result, m_Minutes, 2);
result += ':';
AppendNumber(result, m_Seconds, 2);
if (flags & FLAG_EMIT_FRACTION) {
result += '.';
if (flags & FLAG_EXTENDED_PRECISION) {
// nanoseconds precision
AppendNumber(result, m_NanoSeconds, 9);
} else {
- // only miliseconds precision
+ // only milliseconds precision
AppendNumber(result, m_NanoSeconds/1000000, 3);
}
}
if (m_TimeZone) {
NPT_UInt32 tz;
if (m_TimeZone > 0) {
result += '+';
tz = m_TimeZone;
} else {
result += '-';
tz = -m_TimeZone;
}
AppendNumber(result, tz/60, 2);
result += ':';
AppendNumber(result, tz%60, 2);
} else {
result += 'Z';
}
break;
case FORMAT_ANSI: {
// compute the number of days elapsed since 1900
NPT_UInt32 days = ElapsedDaysSince1900(*this);
// format the result
result.SetLength(24);
NPT_FormatString(result.UseChars(), result.GetLength()+1,
"%.3s %.3s%3d %.2d:%.2d:%.2d %d",
NPT_TIME_DAYS_SHORT[(days+1)%7],
NPT_TIME_MONTHS[m_Month-1],
m_Day,
m_Hours,
m_Minutes,
m_Seconds,
m_Year);
break;
}
case FORMAT_RFC_1036:
case FORMAT_RFC_1123: {
// compute the number of days elapsed since 1900
NPT_UInt32 days = ElapsedDaysSince1900(*this);
if (format == FORMAT_RFC_1036) {
result += NPT_TIME_DAYS_LONG[(days+1)%7];
result += ", ";
AppendNumber(result, m_Day, 2);
result += '-';
result += NPT_TIME_MONTHS[m_Month-1];
result += '-';
AppendNumber(result, m_Year%100, 2);
} else {
result += NPT_TIME_DAYS_SHORT[(days+1)%7];
result += ", ";
AppendNumber(result, m_Day, 2);
result += ' ';
result += NPT_TIME_MONTHS[m_Month-1];
result += ' ';
AppendNumber(result, m_Year, 4);
}
result += ' ';
AppendNumber(result, m_Hours, 2);
result += ':';
AppendNumber(result, m_Minutes, 2);
result += ':';
AppendNumber(result, m_Seconds, 2);
if (m_TimeZone) {
if (m_TimeZone > 0) {
result += " +";
AppendNumber(result, m_TimeZone/60, 2);
AppendNumber(result, m_TimeZone%60, 2);
} else {
result += " -";
AppendNumber(result, -m_TimeZone/60, 2);
AppendNumber(result, -m_TimeZone%60, 2);
}
} else {
result += " GMT";
}
break;
}
}
return result;
}
/*----------------------------------------------------------------------
| NPT_DateTime::FromString
+--------------------------------------------------------------------*/
NPT_Result
NPT_DateTime::FromString(const char* date, Format format)
{
if (date == NULL || date[0] == '\0') return NPT_ERROR_INVALID_PARAMETERS;
// create a local copy to work with
NPT_String workspace(date);
char* input = workspace.UseChars();
NPT_Size input_size = workspace.GetLength();
switch (format) {
case FORMAT_W3C: {
if (input_size < 17 && input_size != 10) return NPT_ERROR_INVALID_SYNTAX;
// check separators
if (input[4] != '-' ||
input[7] != '-') {
return NPT_ERROR_INVALID_SYNTAX;
}
// replace separators with terminators
input[4] = input[7] = '\0';
bool no_seconds = true;
if (input_size > 10) {
if (input[10] != 'T' ||
input[13] != ':') {
return NPT_ERROR_INVALID_SYNTAX;
}
input[10] = input[13] = '\0';
if (input[16] == ':') {
input[16] = '\0';
no_seconds = false;
if (input_size < 20) return NPT_ERROR_INVALID_SYNTAX;
} else {
m_Seconds = 0;
}
}
// parse CCYY-MM-DD fields
if (NPT_FAILED(NPT_ParseInteger(input, m_Year, false)) ||
NPT_FAILED(NPT_ParseInteger(input+5, m_Month, false)) ||
NPT_FAILED(NPT_ParseInteger(input+8, m_Day, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
// parse remaining fields if any
if (input_size > 10) {
// parse the timezone part
if (input[input_size-1] == 'Z') {
m_TimeZone = 0;
input[input_size-1] = '\0';
} else if (input[input_size-6] == '+' || input[input_size-6] == '-') {
if (input[input_size-3] != ':') return NPT_ERROR_INVALID_SYNTAX;
input[input_size-3] = '\0';
unsigned int hh, mm;
if (NPT_FAILED(NPT_ParseInteger(input+input_size-5, hh, false)) ||
NPT_FAILED(NPT_ParseInteger(input+input_size-2, mm, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
if (hh > 59 || mm > 59) return NPT_ERROR_INVALID_SYNTAX;
m_TimeZone = hh*60+mm;
if (input[input_size-6] == '-') m_TimeZone = -m_TimeZone;
input[input_size-6] = '\0';
}
// parse fields
if (NPT_FAILED(NPT_ParseInteger(input+11, m_Hours, false)) ||
NPT_FAILED(NPT_ParseInteger(input+14, m_Minutes, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
if (!no_seconds && input[19] == '.') {
char fraction[10];
fraction[9] = '\0';
unsigned int fraction_size = NPT_StringLength(input+20);
if (fraction_size == 0) return NPT_ERROR_INVALID_SYNTAX;
for (unsigned int i=0; i<9; i++) {
if (i < fraction_size) {
fraction[i] = input[20+i];
} else {
fraction[i] = '0';
}
}
if (NPT_FAILED(NPT_ParseInteger(fraction, m_NanoSeconds, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
input[19] = '\0';
} else {
m_NanoSeconds = 0;
}
if (!no_seconds) {
if (NPT_FAILED(NPT_ParseInteger(input+17, m_Seconds, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
}
}
break;
}
case FORMAT_RFC_1036:
case FORMAT_RFC_1123: {
if (input_size < 26) return NPT_ERROR_INVALID_SYNTAX;
// look for the weekday and separtor
const char* wday = input;
while (*input && *input != ',') {
++input;
--input_size;
}
if (*input == '\0' || *wday == ',') return NPT_ERROR_INVALID_SYNTAX;
*input++ = '\0';
--input_size;
// look for the timezone
char* timezone = input+input_size-1;
unsigned int timezone_size = 0;
while (input_size && *timezone != ' ') {
--timezone;
++timezone_size;
--input_size;
}
if (input_size == 0) return NPT_ERROR_INVALID_SYNTAX;
*timezone++ = '\0';
// check separators
if (input_size < 20) return NPT_ERROR_INVALID_SYNTAX;
unsigned int yl = input_size-18;
if (yl != 2 && yl != 4) return NPT_ERROR_INVALID_SYNTAX;
char sep;
int wday_index;
if (format == FORMAT_RFC_1036) {
sep = '-';
wday_index = MatchString(wday, NPT_TIME_DAYS_LONG, 7);
} else {
sep = ' ';
wday_index = MatchString(wday, NPT_TIME_DAYS_SHORT, 7);
}
if (input[0] != ' ' ||
input[3] != sep ||
input[7] != sep ||
input[8+yl] != ' ' ||
input[11+yl] != ':' ||
input[14+yl] != ':') {
return NPT_ERROR_INVALID_SYNTAX;
}
input[3] = input[7] = input[8+yl] = input[11+yl] = input[14+yl] = '\0';
// parse fields
m_Month = 1+MatchString(input+4, NPT_TIME_MONTHS, 12);
if (NPT_FAILED(NPT_ParseInteger(input+1, m_Day, false)) ||
NPT_FAILED(NPT_ParseInteger(input+8, m_Year, false)) ||
NPT_FAILED(NPT_ParseInteger(input+9+yl, m_Hours, false)) ||
NPT_FAILED(NPT_ParseInteger(input+12+yl, m_Minutes, false)) ||
NPT_FAILED(NPT_ParseInteger(input+15+yl, m_Seconds, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
// adjust short year lengths
if (yl == 2) m_Year += 1900;
// parse the timezone
if (NPT_StringsEqual(timezone, "GMT") ||
NPT_StringsEqual(timezone, "UT") ||
NPT_StringsEqual(timezone, "Z")) {
m_TimeZone = 0;
} else if (NPT_StringsEqual(timezone, "EDT")) {
m_TimeZone = -4*60;
} else if (NPT_StringsEqual(timezone, "EST") ||
NPT_StringsEqual(timezone, "CDT")) {
m_TimeZone = -5*60;
} else if (NPT_StringsEqual(timezone, "CST") ||
NPT_StringsEqual(timezone, "MDT")) {
m_TimeZone = -6*60;
} else if (NPT_StringsEqual(timezone, "MST") ||
NPT_StringsEqual(timezone, "PDT")) {
m_TimeZone = -7*60;
} else if (NPT_StringsEqual(timezone, "PST")) {
m_TimeZone = -8*60;
} else if (timezone_size == 1) {
if (timezone[0] >= 'A' && timezone[0] <= 'I') {
m_TimeZone = -60*(1+timezone[0]-'A');
} else if (timezone[0] >= 'K' && timezone[0] <= 'M') {
m_TimeZone = -60*(timezone[0]-'A');
} else if (timezone[0] >= 'N' && timezone[0] <= 'Y') {
m_TimeZone = 60*(1+timezone[0]-'N');
} else {
return NPT_ERROR_INVALID_SYNTAX;
}
} else if (timezone_size == 5) {
int sign;
if (timezone[0] == '-') {
sign = -1;
} else if (timezone[0] == '+') {
sign = 1;
} else {
return NPT_ERROR_INVALID_SYNTAX;
}
NPT_UInt32 tz;
if (NPT_FAILED(NPT_ParseInteger(timezone+1, tz, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
unsigned int hh = (tz/100);
unsigned int mm = (tz%100);
if (hh > 59 || mm > 59) return NPT_ERROR_INVALID_SYNTAX;
m_TimeZone = sign*(hh*60+mm);
} else {
return NPT_ERROR_INVALID_SYNTAX;
}
// compute the number of days elapsed since 1900
NPT_UInt32 days = ElapsedDaysSince1900(*this);
if ((int)((days+1)%7) != wday_index) {
return NPT_ERROR_INVALID_PARAMETERS;
}
m_NanoSeconds = 0;
break;
}
case FORMAT_ANSI: {
if (input_size != 24) return NPT_ERROR_INVALID_SYNTAX;
// check separators
if (input[3] != ' ' ||
input[7] != ' ' ||
input[10] != ' ' ||
input[13] != ':' ||
input[16] != ':' ||
input[19] != ' ') {
return NPT_ERROR_INVALID_SYNTAX;
}
input[3] = input[7] = input[10] = input[13] = input[16] = input[19] = '\0';
if (input[8] == ' ') input[8] = '0';
m_Month = 1+MatchString(input+4, NPT_TIME_MONTHS, 12);
if (NPT_FAILED(NPT_ParseInteger(input+8, m_Day, false)) ||
NPT_FAILED(NPT_ParseInteger(input+11, m_Hours, false)) ||
NPT_FAILED(NPT_ParseInteger(input+14, m_Minutes, false)) ||
NPT_FAILED(NPT_ParseInteger(input+17, m_Seconds, false)) ||
NPT_FAILED(NPT_ParseInteger(input+20, m_Year, false))) {
return NPT_ERROR_INVALID_SYNTAX;
}
// compute the number of days elapsed since 1900
NPT_UInt32 days = ElapsedDaysSince1900(*this);
if ((int)((days+1)%7) != MatchString(input, NPT_TIME_DAYS_SHORT, 7)) {
return NPT_ERROR_INVALID_PARAMETERS;
}
m_TimeZone = 0;
m_NanoSeconds = 0;
break;
}
default:
return NPT_ERROR_INVALID_PARAMETERS;
}
return CheckDate(*this);
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.cpp
index dfb145c495..6522ac0a52 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.cpp
@@ -1,848 +1,848 @@
/*****************************************************************
|
| Neptune - URI
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
***************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptUri.h"
#include "NptUtils.h"
#include "NptResults.h"
/*----------------------------------------------------------------------
| NPT_Uri::ParseScheme
+---------------------------------------------------------------------*/
NPT_Uri::SchemeId
NPT_Uri::ParseScheme(const NPT_String& scheme)
{
if (scheme == "http") {
return SCHEME_ID_HTTP;
} else if (scheme == "https") {
return SCHEME_ID_HTTPS;
} else {
return SCHEME_ID_UNKNOWN;
}
}
/*----------------------------------------------------------------------
| NPT_Uri::SetScheme
+---------------------------------------------------------------------*/
void
NPT_Uri::SetScheme(const char* scheme)
{
m_Scheme = scheme;
m_Scheme.MakeLowercase();
m_SchemeId = ParseScheme(m_Scheme);
}
/*----------------------------------------------------------------------
| NPT_Uri::SetSchemeFromUri
+---------------------------------------------------------------------*/
NPT_Result
NPT_Uri::SetSchemeFromUri(const char* uri)
{
const char* start = uri;
char c;
while ((c =*uri++)) {
if (c == ':') {
m_Scheme.Assign(start, (NPT_Size)(uri-start-1));
m_Scheme.MakeLowercase();
m_SchemeId = ParseScheme(m_Scheme);
return NPT_SUCCESS;
} else if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
(c == '+') ||
(c == '.') ||
(c == '-')) {
continue;
} else {
break;
}
}
return NPT_ERROR_INVALID_SYNTAX;
}
/*----------------------------------------------------------------------
Appendix A. Collected ABNF for URI
URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
hier-part = "//" authority path-abempty
/ path-absolute
/ path-rootless
/ path-empty
URI-reference = URI / relative-ref
absolute-URI = scheme ":" hier-part [ "?" query ]
relative-ref = relative-part [ "?" query ] [ "#" fragment ]
relative-part = "//" authority path-abempty
/ path-absolute
/ path-noscheme
/ path-empty
scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
authority = [ userinfo "@" ] host [ ":" port ]
userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
host = IP-literal / IPv4address / reg-name
port = *DIGIT
IP-literal = "[" ( IPv6address / IPvFuture ) "]"
IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
IPv6address = 6( h16 ":" ) ls32
/ "::" 5( h16 ":" ) ls32
/ [ h16 ] "::" 4( h16 ":" ) ls32
/ [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
/ [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
/ [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
/ [ *4( h16 ":" ) h16 ] "::" ls32
/ [ *5( h16 ":" ) h16 ] "::" h16
/ [ *6( h16 ":" ) h16 ] "::"
h16 = 1*4HEXDIG
ls32 = ( h16 ":" h16 ) / IPv4address
IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
dec-octet = DIGIT ; 0-9
/ %x31-39 DIGIT ; 10-99
/ "1" 2DIGIT ; 100-199
/ "2" %x30-34 DIGIT ; 200-249
/ "25" %x30-35 ; 250-255
reg-name = *( unreserved / pct-encoded / sub-delims )
path = path-abempty ; begins with "/" or is empty
/ path-absolute ; begins with "/" but not "//"
/ path-noscheme ; begins with a non-colon segment
/ path-rootless ; begins with a segment
/ path-empty ; zero characters
path-abempty = *( "/" segment )
path-absolute = "/" [ segment-nz *( "/" segment ) ]
path-noscheme = segment-nz-nc *( "/" segment )
path-rootless = segment-nz *( "/" segment )
path-empty = 0<pchar>
segment = *pchar
segment-nz = 1*pchar
segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
; non-zero-length segment without any colon ":"
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
query = *( pchar / "/" / "?" )
fragment = *( pchar / "/" / "?" )
pct-encoded = "%" HEXDIG HEXDIG
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
reserved = gen-delims / sub-delims
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
/ "*" / "+" / "," / ";" / "="
---------------------------------------------------------------------*/
#define NPT_URI_ALWAYS_ENCODE " !\"<>\\^`{|}"
/*----------------------------------------------------------------------
| NPT_Uri::PathCharsToEncode
+---------------------------------------------------------------------*/
const char* const
NPT_Uri::PathCharsToEncode = NPT_URI_ALWAYS_ENCODE "?#[]";
//NPT_Uri::PathCharsToEncode = NPT_URI_ALWAYS_ENCODE "?#[]:/";
/*----------------------------------------------------------------------
| NPT_Uri::QueryCharsToEncode
+---------------------------------------------------------------------*/
const char* const
NPT_Uri::QueryCharsToEncode = NPT_URI_ALWAYS_ENCODE "#[]";
/*----------------------------------------------------------------------
| NPT_Uri::FragmentCharsToEncode
+---------------------------------------------------------------------*/
const char* const
NPT_Uri::FragmentCharsToEncode = NPT_URI_ALWAYS_ENCODE "[]";
/*----------------------------------------------------------------------
| NPT_Uri::UnsafeCharsToEncode
+---------------------------------------------------------------------*/
const char* const
NPT_Uri::UnsafeCharsToEncode = NPT_URI_ALWAYS_ENCODE; // and ' ?
/*----------------------------------------------------------------------
| NPT_Uri::PercentEncode
+---------------------------------------------------------------------*/
NPT_String
NPT_Uri::PercentEncode(const char* str, const char* chars, bool encode_percents)
{
NPT_String encoded;
// check args
if (str == NULL) return encoded;
// reserve at least the size of the current uri
encoded.Reserve(NPT_StringLength(str));
// process each character
char escaped[3];
escaped[0] = '%';
while (unsigned char c = *str++) {
bool encode = false;
if (encode_percents && c == '%') {
encode = true;
} else if (c < ' ' || c > '~') {
encode = true;
} else {
const char* match = chars;
while (*match) {
if (c == *match) {
encode = true;
break;
}
++match;
}
}
if (encode) {
// encode
NPT_ByteToHex(c, &escaped[1], true);
encoded.Append(escaped, 3);
} else {
// no encoding required
encoded += c;
}
}
return encoded;
}
/*----------------------------------------------------------------------
| NPT_Uri::PercentDecode
+---------------------------------------------------------------------*/
NPT_String
NPT_Uri::PercentDecode(const char* str)
{
NPT_String decoded;
// check args
if (str == NULL) return decoded;
// reserve at least the size of the current uri
decoded.Reserve(NPT_StringLength(str));
// process each character
while (unsigned char c = *str++) {
if (c == '%') {
// needs to be unescaped
unsigned char unescaped;
if (NPT_SUCCEEDED(NPT_HexToByte(str, unescaped))) {
decoded += unescaped;
str += 2;
} else {
// not a valid escape sequence, just keep the %
decoded += c;
}
} else {
// no unescaping required
decoded += c;
}
}
return decoded;
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::NPT_UrlQuery
+---------------------------------------------------------------------*/
NPT_UrlQuery::NPT_UrlQuery(const char* query)
{
Parse(query);
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::UrlEncode
+---------------------------------------------------------------------*/
NPT_String
NPT_UrlQuery::UrlEncode(const char* str, bool encode_percents)
{
NPT_String encoded = NPT_Uri::PercentEncode(
str,
";/?:@&=+$," /* reserved as defined in RFC 2396 */
"\"#<>\\^`{|}", /* other unsafe chars */
encode_percents);
encoded.Replace(' ','+');
return encoded;
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::UrlDecode
+---------------------------------------------------------------------*/
NPT_String
NPT_UrlQuery::UrlDecode(const char* str)
{
NPT_String decoded = NPT_Uri::PercentDecode(str);
decoded.Replace('+', ' ');
return decoded;
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::Field::Field
+---------------------------------------------------------------------*/
NPT_UrlQuery::Field::Field(const char* name, const char* value, bool encoded)
{
if (encoded) {
m_Name = name;
m_Value = value;
} else {
m_Name = UrlEncode(name);
m_Value = UrlEncode(value);
}
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::ToString
+---------------------------------------------------------------------*/
NPT_String
NPT_UrlQuery::ToString()
{
NPT_String encoded;
bool separator = false;
for (NPT_List<Field>::Iterator it = m_Fields.GetFirstItem();
it;
++it) {
Field& field = *it;
if (separator) encoded += "&";
separator = true;
encoded += field.m_Name;
encoded += "=";
encoded += field.m_Value;
}
return encoded;
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::Parse
+---------------------------------------------------------------------*/
NPT_Result
NPT_UrlQuery::Parse(const char* query)
{
const char* cursor = query;
NPT_String name;
NPT_String value;
bool in_name = true;
do {
if (*cursor == '\0' || *cursor == '&') {
if (!name.IsEmpty()) {
AddField(name, value, true);
}
name.SetLength(0);
value.SetLength(0);
in_name = true;
} else if (*cursor == '=' && in_name) {
in_name = false;
} else {
if (in_name) {
name += *cursor;
} else {
value += *cursor;
}
}
} while (*cursor++);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::AddField
+---------------------------------------------------------------------*/
NPT_Result
NPT_UrlQuery::AddField(const char* name, const char* value, bool encoded)
{
return m_Fields.Add(Field(name, value, encoded));
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::SetField
+---------------------------------------------------------------------*/
NPT_Result
NPT_UrlQuery::SetField(const char* name, const char* value, bool encoded)
{
NPT_String ename;
if (encoded) {
ename = name;
} else {
ename = UrlEncode(name);
}
for (NPT_List<Field>::Iterator it = m_Fields.GetFirstItem();
it;
++it) {
Field& field = *it;
if (field.m_Name == ename) {
if (encoded) {
field.m_Value = value;
} else {
field.m_Value = UrlEncode(value);
}
return NPT_SUCCESS;
}
}
// field not found, add it
return AddField(name, value, encoded);
}
/*----------------------------------------------------------------------
| NPT_UrlQuery::GetField
+---------------------------------------------------------------------*/
const char*
NPT_UrlQuery::GetField(const char* name)
{
NPT_String ename = UrlEncode(name);
for (NPT_List<Field>::Iterator it = m_Fields.GetFirstItem();
it;
++it) {
Field& field = *it;
if (field.m_Name == ename) return field.m_Value;
}
// field not found
return NULL;
}
/*----------------------------------------------------------------------
| types
+---------------------------------------------------------------------*/
typedef enum {
NPT_URL_PARSER_STATE_START,
NPT_URL_PARSER_STATE_SCHEME,
NPT_URL_PARSER_STATE_LEADING_SLASH,
NPT_URL_PARSER_STATE_HOST,
NPT_URL_PARSER_STATE_PORT,
NPT_URL_PARSER_STATE_PATH,
NPT_URL_PARSER_STATE_QUERY
} NPT_UrlParserState;
/*----------------------------------------------------------------------
| NPT_Url::NPT_Url
+---------------------------------------------------------------------*/
NPT_Url::NPT_Url() :
m_Port(NPT_URL_INVALID_PORT),
m_Path("/"),
m_HasQuery(false),
m_HasFragment(false)
{
}
/*----------------------------------------------------------------------
| NPT_Url::NPT_Url
+---------------------------------------------------------------------*/
NPT_Url::NPT_Url(const char* url, NPT_UInt16 default_port) :
m_Port(NPT_URL_INVALID_PORT),
m_HasQuery(false),
m_HasFragment(false)
{
// try to parse
if (NPT_FAILED(Parse(url, default_port))) {
Reset();
}
}
/*----------------------------------------------------------------------
| NPT_Url::Parse
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::Parse(const char* url, NPT_UInt16 default_port)
{
// check parameters
if (url == NULL) return NPT_ERROR_INVALID_PARAMETERS;
// set the uri scheme
NPT_Result result = SetSchemeFromUri(url);
if (NPT_FAILED(result)) return result;
// set the default port
if (default_port) {
m_Port = default_port;
} else {
switch (m_SchemeId) {
case SCHEME_ID_HTTP: m_Port = NPT_URL_DEFAULT_HTTP_PORT; break;
case SCHEME_ID_HTTPS: m_Port = NPT_URL_DEFAULT_HTTPS_PORT; break;
default: break;
}
}
// move to the scheme-specific part
url += m_Scheme.GetLength()+1;
- // intialize the parser
+ // initialize the parser
NPT_UrlParserState state = NPT_URL_PARSER_STATE_START;
const char* mark = url;
// parse the URL
char c;
do {
c = *url++;
switch (state) {
case NPT_URL_PARSER_STATE_START:
if (c == '/') {
state = NPT_URL_PARSER_STATE_LEADING_SLASH;
} else {
return NPT_ERROR_INVALID_SYNTAX;
}
break;
case NPT_URL_PARSER_STATE_LEADING_SLASH:
if (c == '/') {
state = NPT_URL_PARSER_STATE_HOST;
mark = url;
} else {
return NPT_ERROR_INVALID_SYNTAX;
}
break;
case NPT_URL_PARSER_STATE_HOST:
if (c == ':' || c == '/' || c == '\0' || c == '?' || c == '#') {
m_Host.Assign(mark, (NPT_Size)(url-1-mark));
if (c == ':') {
mark = url;
m_Port = 0;
state = NPT_URL_PARSER_STATE_PORT;
} else {
mark = url-1;
state = NPT_URL_PARSER_STATE_PATH;
}
}
break;
case NPT_URL_PARSER_STATE_PORT:
if (c >= '0' && c <= '9') {
unsigned int val = m_Port*10+(c-'0');
if (val > 65535) {
m_Port = NPT_URL_INVALID_PORT;
return NPT_ERROR_INVALID_SYNTAX;
}
m_Port = val;
} else if (c == '/' || c == '\0') {
mark = url-1;
state = NPT_URL_PARSER_STATE_PATH;
} else {
// invalid character
m_Port = NPT_URL_INVALID_PORT;
return NPT_ERROR_INVALID_SYNTAX;
}
break;
case NPT_URL_PARSER_STATE_PATH:
if (*mark) {
return ParsePathPlus(mark);
}
break;
default:
break;
}
} while (c);
// if we get here, the path is implicit
m_Path = "/";
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::NPT_Url
+---------------------------------------------------------------------*/
NPT_Url::NPT_Url(const char* scheme,
const char* host,
NPT_UInt16 port,
const char* path,
const char* query,
const char* fragment) :
m_Host(host),
m_Port(port),
m_Path(path),
m_HasQuery(query != NULL),
m_Query(query),
m_HasFragment(fragment != NULL),
m_Fragment(fragment)
{
SetScheme(scheme);
}
/*----------------------------------------------------------------------
| NPT_Url::Reset
+---------------------------------------------------------------------*/
void
NPT_Url::Reset()
{
m_Host.SetLength(0);
m_Port = 0;
m_Path.SetLength(0);
m_HasQuery = false;
m_Query.SetLength(0);
m_HasFragment = false;
m_Fragment.SetLength(0);
}
/*----------------------------------------------------------------------
| NPT_Url::IsValid
+---------------------------------------------------------------------*/
bool
NPT_Url::IsValid() const
{
switch (m_SchemeId) {
case SCHEME_ID_HTTP:
case SCHEME_ID_HTTPS:
return m_Port != NPT_URL_INVALID_PORT && !m_Host.IsEmpty();
break;
default:
return !m_Scheme.IsEmpty();
}
}
/*----------------------------------------------------------------------
| NPT_Url::SetHost
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::SetHost(const char* host)
{
const char* port = host;
while (*port && *port != ':') port++;
if (*port) {
m_Host.Assign(host, (NPT_Size)(port-host));
unsigned int port_number;
if (NPT_SUCCEEDED(NPT_ParseInteger(port+1, port_number, false))) {
m_Port = (short)port_number;
}
} else {
m_Host = host;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::SetHost
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::SetPort(NPT_UInt16 port)
{
m_Port = port;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::SetPath
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::SetPath(const char* path, bool encoded)
{
if (encoded) {
m_Path = path;
} else {
m_Path = PercentEncode(path, PathCharsToEncode);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::ParsePathPlus
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::ParsePathPlus(const char* path_plus)
{
// check parameters
if (path_plus == NULL) return NPT_ERROR_INVALID_PARAMETERS;
// reset any existing values
m_Path.SetLength(0);
m_Query.SetLength(0);
m_Fragment.SetLength(0);
m_HasQuery = false;
m_HasFragment = false;
#ifdef _WIN32
// Skip the leading '/' if there is an absolute path starting with
// a drive letter on Windows.
if (path_plus[0] == '/' &&
((path_plus[1] >= 'a' && path_plus[1] <= 'z') ||
(path_plus[1] >= 'A' && path_plus[1] <= 'Z')) &&
path_plus[2] == ':')
{
++path_plus;
}
#endif
- // intialize the parser
+ // initialize the parser
NPT_UrlParserState state = NPT_URL_PARSER_STATE_PATH;
const char* mark = path_plus;
// parse the path+
char c;
do {
c = *path_plus++;
switch (state) {
case NPT_URL_PARSER_STATE_PATH:
if (c == '\0' || c == '?' || c == '#') {
if (path_plus-1 > mark) {
m_Path.Append(mark, (NPT_Size)(path_plus-1-mark));
}
if (c == '?') {
m_HasQuery = true;
state = NPT_URL_PARSER_STATE_QUERY;
mark = path_plus;
} else if (c == '#') {
m_HasFragment = true;
m_Fragment = path_plus;
return NPT_SUCCESS;
}
}
break;
case NPT_URL_PARSER_STATE_QUERY:
if (c == '\0' || c == '#') {
m_Query.Assign(mark, (NPT_Size)(path_plus-1-mark));
if (c == '#') {
m_HasFragment = true;
m_Fragment = path_plus;
}
return NPT_SUCCESS;
}
break;
default:
break;
}
} while (c);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::SetQuery
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::SetQuery(const char* query, bool encoded)
{
if (encoded) {
m_Query = query;
} else {
m_Query = PercentEncode(query, QueryCharsToEncode);
}
m_HasQuery = query!=NULL && NPT_StringLength(query)>0;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::SetFragment
+---------------------------------------------------------------------*/
NPT_Result
NPT_Url::SetFragment(const char* fragment, bool encoded)
{
if (encoded) {
m_Fragment = fragment;
} else {
m_Fragment = PercentEncode(fragment, FragmentCharsToEncode);
}
m_HasFragment = fragment!=NULL;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Url::ToRequestString
+---------------------------------------------------------------------*/
NPT_String
NPT_Url::ToRequestString(bool with_fragment) const
{
NPT_String result;
NPT_Size length = m_Path.GetLength()+1;
if (m_HasQuery) length += 1+m_Query.GetLength();
if (with_fragment) length += 1+m_Fragment.GetLength();
result.Reserve(length);
if (m_Path.IsEmpty()) {
result += "/";
} else {
result += m_Path;
}
if (m_HasQuery) {
result += "?";
result += m_Query;
}
if (with_fragment && m_HasFragment) {
result += "#";
result += m_Fragment;
}
return result;
}
/*----------------------------------------------------------------------
| NPT_Url::ToStringWithDefaultPort
+---------------------------------------------------------------------*/
NPT_String
NPT_Url::ToStringWithDefaultPort(NPT_UInt16 default_port, bool with_fragment) const
{
NPT_String result;
NPT_String request = ToRequestString(with_fragment);
NPT_Size length = m_Scheme.GetLength()+3+m_Host.GetLength()+6+request.GetLength();
result.Reserve(length);
result += m_Scheme;
result += "://";
result += m_Host;
if (m_Port != default_port) {
NPT_String port = NPT_String::FromInteger(m_Port);
result += ":";
result += port;
}
result += request;
return result;
}
/*----------------------------------------------------------------------
| NPT_Url::ToString
+---------------------------------------------------------------------*/
NPT_String
NPT_Url::ToString(bool with_fragment) const
{
return ToStringWithDefaultPort(0, with_fragment);
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.h b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.h
index 8ef46cb1f5..cb73c13adb 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptUri.h
@@ -1,320 +1,320 @@
/*****************************************************************
|
| Neptune - URI
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
#ifndef _NPT_URI_H_
#define _NPT_URI_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptStrings.h"
#include "NptList.h"
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
const NPT_UInt16 NPT_URL_INVALID_PORT = 0;
const NPT_UInt16 NPT_URL_DEFAULT_HTTP_PORT = 80;
const NPT_UInt16 NPT_URL_DEFAULT_HTTPS_PORT = 443;
/*----------------------------------------------------------------------
| NPT_Uri
+---------------------------------------------------------------------*/
class NPT_Uri {
public:
// types
typedef enum {
SCHEME_ID_UNKNOWN,
SCHEME_ID_HTTP,
SCHEME_ID_HTTPS
} SchemeId;
// constants. use as a parameter to Encode()
static const char* const PathCharsToEncode;
static const char* const QueryCharsToEncode;
static const char* const FragmentCharsToEncode;
static const char* const UnsafeCharsToEncode;
// class methods
static NPT_String PercentEncode(const char* str, const char* chars, bool encode_percents=true);
static NPT_String PercentDecode(const char* str);
static SchemeId ParseScheme(const NPT_String& scheme);
// methods
NPT_Uri() : m_SchemeId(SCHEME_ID_UNKNOWN) {}
virtual ~NPT_Uri() {}
const NPT_String& GetScheme() const {
return m_Scheme;
}
void SetScheme(const char* scheme);
NPT_Result SetSchemeFromUri(const char* uri);
SchemeId GetSchemeId() const {
return m_SchemeId;
}
protected:
// members
NPT_String m_Scheme;
SchemeId m_SchemeId;
};
/*----------------------------------------------------------------------
| NPT_UrlQuery
+---------------------------------------------------------------------*/
class NPT_UrlQuery
{
public:
// class methods
static NPT_String UrlEncode(const char* str, bool encode_percents=true);
static NPT_String UrlDecode(const char* str);
// types
struct Field {
Field(const char* name, const char* value, bool encoded);
NPT_String m_Name;
NPT_String m_Value;
};
// constructor
NPT_UrlQuery() {}
NPT_UrlQuery(const char* query);
// accessors
NPT_List<Field>& GetFields() { return m_Fields; }
// methods
NPT_Result Parse(const char* query);
NPT_Result SetField(const char* name, const char* value, bool encoded=false);
NPT_Result AddField(const char* name, const char* value, bool encoded=false);
const char* GetField(const char* name);
NPT_String ToString();
private:
// members
NPT_List<Field> m_Fields;
};
/*----------------------------------------------------------------------
| NPT_Url
+---------------------------------------------------------------------*/
class NPT_Url : public NPT_Uri {
public:
/**
* Default constructor. This does not construct a valid URL, but an
* uninitialized one that can later be initialized to a valid URL by
* parsing or setting some of its fields.
*/
NPT_Url();
/**
* Construct a URL by parsing an input string in its fully encoded form.
* If an error occurs during parsing (such as an invalid syntax), the
* URL will be in an invalid state (a call to IsValid() will return false).
*
* @param url The URL string in its encoded form
* @param default_port The default port number, or 0 if not specified
*/
NPT_Url(const char* url, NPT_UInt16 default_port = 0);
/**
* Construct a URL from its components. When constructing a URL from
* components, the components are assumed to be passed in their non-encoded
* form, and will thus be encoded automatically.
*
* @param scheme The URL scheme
* @param port The port number
* @param path The path
* @param query The query, if any, or NULL
* @param fragment The fragment, if any, or NULL
*/
NPT_Url(const char* scheme,
const char* host,
NPT_UInt16 port,
const char* path,
const char* query = NULL,
const char* fragment = NULL);
/**
* Parse a URL from its fully encoded form.
*
* @param url The URL string in its encoded form
- * @param default port The defautl port number, or 0 if not specified
+ * @param default port The default port number, or 0 if not specified
*/
NPT_Result Parse(const char* url, NPT_UInt16 default_port = 0);
/**
* Parse just the path plus optional query and fragment from a fully encoded form.
*
* @param path_plus The URL path plus optional query and fragment
*/
NPT_Result ParsePathPlus(const char* path_plus);
/**
* Returns the host part of the URL, in its encoded form
*/
const NPT_String& GetHost() const { return m_Host; }
/**
* Returns the port number of the URL.
*/
NPT_UInt16 GetPort() const { return m_Port; }
/**
* Returns the path part of the URL, in its encoded form
*/
const NPT_String& GetPath() const { return m_Path; }
/**
* Returns the path part of the URL, in its encoded or decoded form
*/
NPT_String GetPath(bool decoded) const { return decoded?NPT_Uri::PercentDecode(m_Path):m_Path;}
/**
* Returns the query part of the URL, in its encoded form
*/
const NPT_String& GetQuery() const { return m_Query; }
/**
* Returns the fragment part of the URL, in its encoded form
*/
const NPT_String& GetFragment() const { return m_Fragment; }
/**
* Returns whether the URL is valid or not. Invalid URLs are uninitialized or
* not fully initialized URLs.
*
* @return true if the URL is valid, false if it is not.
*/
virtual bool IsValid() const;
/**
* Resets a URL to an uninitialized state.
*/
void Reset();
/**
* Returns whether the URL has a query part or not.
*
* @return true if the URL has a query part, false if it does not.
*/
bool HasQuery() const { return m_HasQuery; }
/**
* Returns whether the URL has a fragment part or not.
*
* @return true if the URL has a fragment part, false if it does not.
*/
bool HasFragment() const { return m_HasFragment; }
/**
* Sets the host part of the URL.
*
* @param host The host part of the URL
*/
NPT_Result SetHost(const char* host);
/**
* Sets the port number of the URL.
*
* @param port The port number of the URL
*/
NPT_Result SetPort(NPT_UInt16 port);
/**
* Sets the path part of the URL.
*
* @param path The path part of the URL
* @param encoded Boolean flag indicating whether the path parameter is
* already encoded or not. If it is not already encoded, it will be
* automatically encoded.
*/
NPT_Result SetPath(const char* path, bool encoded=false);
/**
* Sets the query part of the URL.
*
* @param query The query part of the URL
* @param encoded Boolean flag indicating whether the query parameter is
* already encoded or not. If it is not already encoded, it will be
* automatically encoded.
*/
NPT_Result SetQuery(const char* query, bool encoded=false);
/**
* Sets the fragment part of the URL.
*
* @param query The fragment part of the URL
* @param encoded Boolean flag indicating whether the fragment parameter is
* already encoded or not. If it is not already encoded, it will be
* automatically encoded.
*/
NPT_Result SetFragment(const char* fragment, bool encoded=false);
/**
* Return the string representation of the URL in a way that can be used in
* an HTTP request (i.e just the portion of the URL starting with the path)
*
* @param with_fragment Boolean flag specifiying whether the fragment part of
* the URL should be included in the returned string or not.
*/
virtual NPT_String ToRequestString(bool with_fragment = false) const;
/**
* Return the string representation of the URL.
*
* @param default_port default port number for the scheme. If the port number of
- * the URL is not equal to the default port, then port number is explicitely
+ * the URL is not equal to the default port, then port number is explicitly
* included in the string representation of the URL.
* @param with_fragment Boolean flag specifiying whether the fragment part of
* the URL should be included in the returned string or not.
*/
virtual NPT_String ToStringWithDefaultPort(NPT_UInt16 default_port, bool with_fragment = true) const;
/**
* Return the string representation of the URL.
*
* @param with_fragment Boolean flag specifiying whether the fragment part of
* the URL should be included in the returned string or not.
*/
virtual NPT_String ToString(bool with_fragment = true) const;
protected:
// members
NPT_String m_Host;
NPT_UInt16 m_Port;
NPT_String m_Path;
bool m_HasQuery;
NPT_String m_Query;
bool m_HasFragment;
NPT_String m_Fragment;
};
#endif // _NPT_URI_H_
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptZip.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptZip.cpp
index bed1e559be..753e3adbd6 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptZip.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Core/NptZip.cpp
@@ -1,861 +1,861 @@
/*****************************************************************
|
| Neptune - Zip Support
|
| Copyright (c) 2002-2008, Axiomatic Systems, LLC.
| All rights reserved.
|
| Redistribution and use in source and binary forms, with or without
| modification, are permitted provided that the following conditions are met:
| * Redistributions of source code must retain the above copyright
| notice, this list of conditions and the following disclaimer.
| * Redistributions in binary form must reproduce the above copyright
| notice, this list of conditions and the following disclaimer in the
| documentation and/or other materials provided with the distribution.
| * Neither the name of Axiomatic Systems nor the
| names of its contributors may be used to endorse or promote products
| derived from this software without specific prior written permission.
|
| THIS SOFTWARE IS PROVIDED BY AXIOMATIC SYSTEMS ''AS IS'' AND ANY
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
| DISCLAIMED. IN NO EVENT SHALL AXIOMATIC SYSTEMS BE LIABLE FOR ANY
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptConfig.h"
#include "NptZip.h"
#include "NptLogging.h"
#include "NptUtils.h"
#include "zlib.h"
/*----------------------------------------------------------------------
| logging
+---------------------------------------------------------------------*/
NPT_SET_LOCAL_LOGGER("neptune.zip")
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
static const NPT_UInt32 NPT_ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
static const NPT_UInt32 NPT_ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06064b50;
static const NPT_UInt32 NPT_ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE = 0x07064b50;
static const NPT_UInt32 NPT_ZIP_CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
static const NPT_UInt32 NPT_ZIP_LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
static const NPT_UInt16 NPT_ZIP_EXT_DATA_TYPE_ZIP64 = 0x0001;
static const NPT_UInt32 NPT_ZIP_MAX_DIRECTORY_SIZE = 0x1000000; // 16 MB
static const NPT_UInt32 NPT_ZIP_MAX_ENTRY_COUNT = 0x100000; // 1M entries
/*----------------------------------------------------------------------
| NPT_ZipFile::NPT_ZipFile
+---------------------------------------------------------------------*/
NPT_ZipFile::NPT_ZipFile()
{
}
/*----------------------------------------------------------------------
| NPT_ZipFile::Parse
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipFile::Parse(NPT_InputStream& stream, NPT_ZipFile*& file)
{
- // defautl return value
+ // default return value
file = NULL;
// check that we know the size of the stream
NPT_LargeSize stream_size = 0;
NPT_Result result = stream.GetSize(stream_size);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("cannot get stream size (%d)", result);
return result;
}
if (stream_size < 22) {
NPT_LOG_WARNING("input stream too short");
return NPT_ERROR_INVALID_FORMAT;
}
// seek to the most likely start of the end of central directory record
unsigned int max_eocdr_size = 22+65536;
if (max_eocdr_size > stream_size) {
max_eocdr_size = (unsigned int)stream_size;
}
unsigned char eocdr[22];
bool record_found = false;
NPT_Position position = 0;
for (unsigned int i=0; i<max_eocdr_size; i++) {
position = stream_size-22-i;
result = stream.Seek(position);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("seek failed (%d)", result);
return result;
}
result = stream.ReadFully(eocdr, 22);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("read failed (%d)", result);
return result;
}
NPT_UInt32 signature = NPT_BytesToInt32Le(eocdr);
if (signature == NPT_ZIP_END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
record_found = true;
break;
}
}
if (!record_found) {
NPT_LOG_WARNING("eocd record not found at end of stream");
return NPT_ERROR_INVALID_FORMAT;
}
// parse the eocdr
NPT_UInt32 this_disk = NPT_BytesToInt16Le(&eocdr[ 4]);
NPT_UInt32 start_disk = NPT_BytesToInt16Le(&eocdr[ 6]);
NPT_UInt64 this_disk_entry_count = NPT_BytesToInt16Le(&eocdr[ 8]);
NPT_UInt64 total_entry_count = NPT_BytesToInt16Le(&eocdr[10]);
NPT_UInt64 central_directory_size = NPT_BytesToInt32Le(&eocdr[12]);
NPT_Position central_directory_offset = NPT_BytesToInt32Le(&eocdr[16]);
// format check
if (this_disk != 0 || start_disk != 0) {
return NPT_ERROR_NOT_SUPPORTED;
}
if (this_disk_entry_count != total_entry_count) {
return NPT_ERROR_NOT_SUPPORTED;
}
// check if this is a zip64 file
if (central_directory_offset == 0xFFFFFFFF) {
unsigned char zip64_locator[20];
result = stream.Seek(position-20);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("seek failed (%d)", result);
return result;
}
result = stream.ReadFully(zip64_locator, 20);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("read failed (%d)", result);
return result;
}
NPT_UInt32 signature = NPT_BytesToInt32Le(&zip64_locator[0]);
if (signature != NPT_ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE) {
NPT_LOG_WARNING("zip64 directory locator signature not found");
return NPT_ERROR_INVALID_FORMAT;
}
NPT_UInt32 zip64_disk_start = NPT_BytesToInt32Le(&zip64_locator[ 4]);
NPT_UInt64 zip64_directory_offset = NPT_BytesToInt64Le(&zip64_locator[ 8]);
NPT_UInt32 zip64_disk_count = NPT_BytesToInt32Le(&zip64_locator[16]);
// format check
if (zip64_disk_start != 0 || zip64_disk_count != 1) {
return NPT_ERROR_NOT_SUPPORTED;
}
// size check
if (zip64_directory_offset > stream_size) {
NPT_LOG_WARNING("zip64 directory offset too large");
return NPT_ERROR_INVALID_FORMAT;
}
// load and parse the eocdr64
unsigned char eocdr64[56];
result = stream.Seek(zip64_directory_offset);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("seek failed (%d)", result);
return result;
}
result = stream.ReadFully(eocdr64, 56);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("read failed (%d)", result);
return result;
}
signature = NPT_BytesToInt32Le(&eocdr64[0]);
if (signature != NPT_ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
NPT_LOG_WARNING("zip64 directory signature not found");
return NPT_ERROR_INVALID_FORMAT;
}
this_disk = NPT_BytesToInt32Le(&eocdr64[16]);
start_disk = NPT_BytesToInt32Le(&eocdr64[20]);
this_disk_entry_count = NPT_BytesToInt64Le(&eocdr64[24]);
total_entry_count = NPT_BytesToInt64Le(&eocdr64[32]);
central_directory_size = NPT_BytesToInt64Le(&eocdr64[40]);
central_directory_offset = NPT_BytesToInt64Le(&eocdr64[48]);
}
// format check
if (this_disk != 0 || start_disk != 0) {
return NPT_ERROR_NOT_SUPPORTED;
}
if (this_disk_entry_count != total_entry_count) {
return NPT_ERROR_NOT_SUPPORTED;
}
// check that the size looks reasonable
if (central_directory_size > NPT_ZIP_MAX_DIRECTORY_SIZE) {
NPT_LOG_WARNING("central directory larger than max supported");
return NPT_ERROR_OUT_OF_RANGE;
}
if (total_entry_count > NPT_ZIP_MAX_ENTRY_COUNT) {
NPT_LOG_WARNING("central directory larger than max supported");
return NPT_ERROR_OUT_OF_RANGE;
}
// read the central directory
NPT_DataBuffer central_directory_buffer;
result = central_directory_buffer.SetDataSize((NPT_Size)central_directory_size);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("central directory too large (%lld)", central_directory_size);
return result;
}
result = stream.Seek(central_directory_offset);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("seek failed (%d)", result);
return result;
}
result = stream.ReadFully(central_directory_buffer.UseData(), (NPT_Size)central_directory_size);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("failed to read central directory (%d)", result);
return result;
}
// create a new file object
file = new NPT_ZipFile();
file->m_Entries.Reserve((NPT_Cardinal)total_entry_count);
// parse all entries
const unsigned char* buffer = (const unsigned char*)central_directory_buffer.GetData();
for (unsigned int i=0; i<total_entry_count; i++) {
NPT_UInt32 signature = NPT_BytesToInt32Le(buffer);
if (signature != NPT_ZIP_CENTRAL_FILE_HEADER_SIGNATURE) {
NPT_LOG_WARNING("unexpected signature in central directory");
break;
}
NPT_ZipFile::Entry entry(buffer);
if (entry.m_DirectoryEntrySize > central_directory_size) {
NPT_LOG_WARNING_1("entry size too large (%d)", entry.m_DirectoryEntrySize);
break;
}
file->GetEntries().Add(entry);
central_directory_size -= entry.m_DirectoryEntrySize;
buffer += entry.m_DirectoryEntrySize;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipFile::GetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipFile::GetInputStream(Entry& entry, NPT_InputStreamReference& zip_stream, NPT_InputStream*& file_stream)
{
// default return value
file_stream = NULL;
// we don't support encrypted files
if (entry.m_Flags & NPT_ZIP_FILE_FLAG_ENCRYPTED) {
return NPT_ERROR_NOT_SUPPORTED;
}
// check that we support the compression method
#if NPT_CONFIG_ENABLE_ZIP
if (entry.m_CompressionMethod != NPT_ZIP_FILE_COMPRESSION_METHOD_NONE &&
entry.m_CompressionMethod != NPT_ZIP_FILE_COMPRESSION_METHOD_DEFLATE) {
return NPT_ERROR_NOT_SUPPORTED;
}
#else
if (entry.m_CompressionMethod != NPT_ZIP_FILE_COMPRESSION_METHOD_NONE) {
return NPT_ERROR_NOT_SUPPORTED;
}
#endif
// seek to the start of the file entry
NPT_Result result = zip_stream->Seek(entry.m_RelativeOffset);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("seek failed (%d)", result);
return result;
}
// read the fixed part of the header
unsigned char header[30];
result = zip_stream->ReadFully(header, 30);
if (NPT_FAILED(result)) {
NPT_LOG_WARNING_1("read failed (%d)", result);
return result;
}
NPT_UInt16 file_name_length = NPT_BytesToInt16Le(&header[26]);
NPT_UInt16 extra_field_length = NPT_BytesToInt16Le(&header[28]);
unsigned int header_size = 30+file_name_length+extra_field_length;
NPT_LargeSize zip_stream_size = 0;
zip_stream->GetSize(zip_stream_size);
if (entry.m_RelativeOffset+header_size+entry.m_CompressedSize > zip_stream_size) {
// something's wrong here
return NPT_ERROR_INVALID_FORMAT;
}
file_stream = new NPT_SubInputStream(zip_stream, entry.m_RelativeOffset+header_size, entry.m_CompressedSize);
#if NPT_CONFIG_ENABLE_ZIP
if (entry.m_CompressionMethod == NPT_ZIP_FILE_COMPRESSION_METHOD_DEFLATE) {
NPT_InputStreamReference file_stream_ref(file_stream);
file_stream = new NPT_ZipInflatingInputStream(file_stream_ref, true);
}
#endif
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipFile::Entry::Entry
+---------------------------------------------------------------------*/
NPT_ZipFile::Entry::Entry(const unsigned char* data)
{
m_Flags = NPT_BytesToInt16Le(data+ 8);
m_CompressionMethod = NPT_BytesToInt16Le(data+10);
m_Crc32 = NPT_BytesToInt32Le(data+16);
m_CompressedSize = NPT_BytesToInt32Le(data+20);
m_UncompressedSize = NPT_BytesToInt32Le(data+24);
m_DiskNumber = NPT_BytesToInt16Le(data+34);
m_InternalFileAttributes = NPT_BytesToInt32Le(data+36);
m_ExternalFileAttributes = NPT_BytesToInt32Le(data+38);
m_RelativeOffset = NPT_BytesToInt32Le(data+42);
NPT_UInt16 file_name_length = NPT_BytesToInt16Le(data+28);
NPT_UInt16 extra_field_length = NPT_BytesToInt16Le(data+30);
NPT_UInt16 file_comment_length = NPT_BytesToInt16Le(data+32);
// extract the file name
m_Name.Assign((const char*)data+46, file_name_length);
m_DirectoryEntrySize = 46+file_name_length+extra_field_length+file_comment_length;
// check for a zip64 extension
const unsigned char* ext_data = data+46+file_name_length;
unsigned int ext_data_size = extra_field_length;
while (ext_data_size >= 4) {
unsigned int ext_id = NPT_BytesToInt16Le(ext_data);
unsigned int ext_size = NPT_BytesToInt16Le(ext_data+2);
if (ext_id == NPT_ZIP_EXT_DATA_TYPE_ZIP64) {
const unsigned char* local_data = ext_data+4;
if (m_UncompressedSize == 0xFFFFFFFF) {
m_UncompressedSize = NPT_BytesToInt64Le(local_data);
local_data += 8;
}
if (m_CompressedSize == 0xFFFFFFFF) {
m_CompressedSize = NPT_BytesToInt64Le(local_data);
local_data += 8;
}
if (m_RelativeOffset == 0xFFFFFFFF) {
m_RelativeOffset = NPT_BytesToInt64Le(local_data);
local_data += 8;
}
if (m_DiskNumber == 0xFFFF) {
m_DiskNumber = NPT_BytesToInt32Le(local_data);
local_data += 4;
}
}
ext_data += 4+ext_size;
if (ext_data_size >= 4+ext_size) {
ext_data_size -= 4+ext_size;
} else {
ext_data_size = 0;
}
}
}
#if defined(NPT_CONFIG_ENABLE_ZIP)
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
const unsigned int NPT_ZIP_DEFAULT_BUFFER_SIZE = 4096;
/*----------------------------------------------------------------------
| NPT_Zip::MapError
+---------------------------------------------------------------------*/
NPT_Result
NPT_Zip::MapError(int err)
{
switch (err) {
case Z_OK: return NPT_SUCCESS;
case Z_STREAM_END: return NPT_ERROR_EOS;
case Z_DATA_ERROR:
case Z_STREAM_ERROR: return NPT_ERROR_INVALID_FORMAT;
case Z_MEM_ERROR: return NPT_ERROR_OUT_OF_MEMORY;
case Z_VERSION_ERROR: return NPT_ERROR_INTERNAL;
case Z_NEED_DICT: return NPT_ERROR_NOT_SUPPORTED;
default: return NPT_FAILURE;
}
}
/*----------------------------------------------------------------------
| NPT_ZipInflateState
+---------------------------------------------------------------------*/
class NPT_ZipInflateState {
public:
NPT_ZipInflateState(bool raw = false);
~NPT_ZipInflateState();
z_stream m_Stream;
};
/*----------------------------------------------------------------------
| NPT_ZipInflateState::NPT_ZipInflateState
+---------------------------------------------------------------------*/
NPT_ZipInflateState::NPT_ZipInflateState(bool raw)
{
// initialize the state
NPT_SetMemory(&m_Stream, 0, sizeof(m_Stream));
// initialize the decompressor
inflateInit2(&m_Stream, raw?-15:15+32); // 15 = default window bits, +32 = automatic header
}
/*----------------------------------------------------------------------
| NPT_ZipInflateState::~NPT_ZipInflateState
+---------------------------------------------------------------------*/
NPT_ZipInflateState::~NPT_ZipInflateState()
{
inflateEnd(&m_Stream);
}
/*----------------------------------------------------------------------
| NPT_ZipDeflateState
+---------------------------------------------------------------------*/
class NPT_ZipDeflateState {
public:
NPT_ZipDeflateState(int compression_level,
NPT_Zip::Format format);
~NPT_ZipDeflateState();
z_stream m_Stream;
};
/*----------------------------------------------------------------------
| NPT_ZipDeflateState::NPT_ZipDeflateState
+---------------------------------------------------------------------*/
NPT_ZipDeflateState::NPT_ZipDeflateState(int compression_level,
NPT_Zip::Format format)
{
// check parameters
if (compression_level < NPT_ZIP_COMPRESSION_LEVEL_DEFAULT ||
compression_level > NPT_ZIP_COMPRESSION_LEVEL_MAX) {
compression_level = NPT_ZIP_COMPRESSION_LEVEL_DEFAULT;
}
// initialize the state
NPT_SetMemory(&m_Stream, 0, sizeof(m_Stream));
// initialize the compressor
deflateInit2(&m_Stream,
compression_level,
Z_DEFLATED,
15 + (format == NPT_Zip::GZIP ? 16 : 0),
8,
Z_DEFAULT_STRATEGY);
}
/*----------------------------------------------------------------------
| NPT_ZipDeflateState::~NPT_ZipDeflateState
+---------------------------------------------------------------------*/
NPT_ZipDeflateState::~NPT_ZipDeflateState()
{
deflateEnd(&m_Stream);
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::NPT_ZipInflatingInputStream
+---------------------------------------------------------------------*/
NPT_ZipInflatingInputStream::NPT_ZipInflatingInputStream(NPT_InputStreamReference& source, bool raw) :
m_Source(source),
m_Position(0),
m_State(new NPT_ZipInflateState(raw)),
m_Buffer(NPT_ZIP_DEFAULT_BUFFER_SIZE)
{
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::~NPT_ZipInflatingInputStream
+---------------------------------------------------------------------*/
NPT_ZipInflatingInputStream::~NPT_ZipInflatingInputStream()
{
delete m_State;
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::Read
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipInflatingInputStream::Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read)
{
// check state and parameters
if (m_State == NULL) return NPT_ERROR_INVALID_STATE;
if (buffer == NULL) return NPT_ERROR_INVALID_PARAMETERS;
if (bytes_to_read == 0) return NPT_SUCCESS;
// default values
if (bytes_read) *bytes_read = 0;
// setup the output buffer
m_State->m_Stream.next_out = (Bytef*)buffer;
m_State->m_Stream.avail_out = (uInt)bytes_to_read;
while (m_State->m_Stream.avail_out) {
// decompress what we can
int err = inflate(&m_State->m_Stream, Z_NO_FLUSH);
if (err == Z_STREAM_END) {
// we decompressed everything
break;
} else if (err == Z_OK) {
// we got something
continue;
} else if (err == Z_BUF_ERROR) {
// we need more input data
NPT_Size input_bytes_read = 0;
NPT_Result result = m_Source->Read(m_Buffer.UseData(), m_Buffer.GetBufferSize(), &input_bytes_read);
if (NPT_FAILED(result)) return result;
// setup the input buffer
m_Buffer.SetDataSize(input_bytes_read);
m_State->m_Stream.next_in = m_Buffer.UseData();
m_State->m_Stream.avail_in = m_Buffer.GetDataSize();
} else {
return NPT_Zip::MapError(err);
}
}
// report how much we could decompress
NPT_Size progress = bytes_to_read - m_State->m_Stream.avail_out;
if (bytes_read) {
*bytes_read = progress;
}
m_Position += progress;
return progress == 0 ? NPT_ERROR_EOS:NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::Seek
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipInflatingInputStream::Seek(NPT_Position /* offset */)
{
// we can't seek
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::Tell
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipInflatingInputStream::Tell(NPT_Position& offset)
{
offset = m_Position;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::GetSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipInflatingInputStream::GetSize(NPT_LargeSize& size)
{
// the size is not predictable
size = 0;
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_ZipInflatingInputStream::GetAvailable
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipInflatingInputStream::GetAvailable(NPT_LargeSize& available)
{
// we don't know
available = 0;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::NPT_ZipDeflatingInputStream
+---------------------------------------------------------------------*/
NPT_ZipDeflatingInputStream::NPT_ZipDeflatingInputStream(
NPT_InputStreamReference& source,
int compression_level,
NPT_Zip::Format format) :
m_Source(source),
m_Position(0),
m_Eos(false),
m_State(new NPT_ZipDeflateState(compression_level, format)),
m_Buffer(NPT_ZIP_DEFAULT_BUFFER_SIZE)
{
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::~NPT_ZipDeflatingInputStream
+---------------------------------------------------------------------*/
NPT_ZipDeflatingInputStream::~NPT_ZipDeflatingInputStream()
{
delete m_State;
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::Read
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipDeflatingInputStream::Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read)
{
// check state and parameters
if (m_State == NULL) return NPT_ERROR_INVALID_STATE;
if (buffer == NULL) return NPT_ERROR_INVALID_PARAMETERS;
if (bytes_to_read == 0) return NPT_SUCCESS;
// default values
if (bytes_read) *bytes_read = 0;
// setup the output buffer
m_State->m_Stream.next_out = (Bytef*)buffer;
m_State->m_Stream.avail_out = (uInt)bytes_to_read;
while (m_State->m_Stream.avail_out) {
// compress what we can
int err = deflate(&m_State->m_Stream, m_Eos?Z_FINISH:Z_NO_FLUSH);
if (err == Z_STREAM_END) {
// we compressed everything
break;
} else if (err == Z_OK) {
// we got something
continue;
} else if (err == Z_BUF_ERROR) {
// we need more input data
NPT_Size input_bytes_read = 0;
NPT_Result result = m_Source->Read(m_Buffer.UseData(), m_Buffer.GetBufferSize(), &input_bytes_read);
if (result == NPT_ERROR_EOS) {
m_Eos = true;
} else {
if (NPT_FAILED(result)) return result;
}
// setup the input buffer
m_Buffer.SetDataSize(input_bytes_read);
m_State->m_Stream.next_in = m_Buffer.UseData();
m_State->m_Stream.avail_in = m_Buffer.GetDataSize();
} else {
return NPT_Zip::MapError(err);
}
}
// report how much we could compress
NPT_Size progress = bytes_to_read - m_State->m_Stream.avail_out;
if (bytes_read) {
*bytes_read = progress;
}
m_Position += progress;
return progress == 0 ? NPT_ERROR_EOS:NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::Seek
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipDeflatingInputStream::Seek(NPT_Position /* offset */)
{
// we can't seek
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::Tell
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipDeflatingInputStream::Tell(NPT_Position& offset)
{
offset = m_Position;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::GetSize
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipDeflatingInputStream::GetSize(NPT_LargeSize& size)
{
// the size is not predictable
size = 0;
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_ZipDeflatingInputStream::GetAvailable
+---------------------------------------------------------------------*/
NPT_Result
NPT_ZipDeflatingInputStream::GetAvailable(NPT_LargeSize& available)
{
// we don't know
available = 0;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Zip::Deflate
+---------------------------------------------------------------------*/
NPT_Result
NPT_Zip::Deflate(const NPT_DataBuffer& in,
NPT_DataBuffer& out,
int compression_level,
Format format /* = ZLIB */)
{
// default return state
out.SetDataSize(0);
// check parameters
if (compression_level < NPT_ZIP_COMPRESSION_LEVEL_DEFAULT ||
compression_level > NPT_ZIP_COMPRESSION_LEVEL_MAX) {
return NPT_ERROR_INVALID_PARAMETERS;
}
// setup the stream
z_stream stream;
NPT_SetMemory(&stream, 0, sizeof(stream));
stream.next_in = (Bytef*)in.GetData();
stream.avail_in = (uInt)in.GetDataSize();
// setup the memory functions
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;
// initialize the compressor
int err = deflateInit2(&stream,
compression_level,
Z_DEFLATED,
15 + (format == GZIP ? 16 : 0),
8,
Z_DEFAULT_STRATEGY);
if (err != Z_OK) return MapError(err);
// reserve an output buffer known to be large enough
out.Reserve((NPT_Size)deflateBound(&stream, stream.avail_in) + (format==GZIP?10:0));
stream.next_out = out.UseData();
stream.avail_out = out.GetBufferSize();
// decompress
err = deflate(&stream, Z_FINISH);
if (err != Z_STREAM_END) {
deflateEnd(&stream);
return MapError(err);
}
// update the output size
out.SetDataSize((NPT_Size)stream.total_out);
// cleanup
err = deflateEnd(&stream);
return MapError(err);
}
/*----------------------------------------------------------------------
| NPT_Zip::Inflate
+---------------------------------------------------------------------*/
NPT_Result
NPT_Zip::Inflate(const NPT_DataBuffer& in,
NPT_DataBuffer& out,
bool raw)
{
// assume an output buffer twice the size of the input plus a bit
NPT_CHECK_WARNING(out.Reserve(32+2*in.GetDataSize()));
// setup the stream
z_stream stream;
stream.next_in = (Bytef*)in.GetData();
stream.avail_in = (uInt)in.GetDataSize();
stream.next_out = out.UseData();
stream.avail_out = (uInt)out.GetBufferSize();
// setup the memory functions
stream.zalloc = (alloc_func)0;
stream.zfree = (free_func)0;
stream.opaque = (voidpf)0;
// initialize the decompressor
int err = inflateInit2(&stream, raw?-15:15+32); // 15 = default window bits, +32 = automatic header
if (err != Z_OK) return MapError(err);
// decompress until the end
do {
err = inflate(&stream, Z_SYNC_FLUSH);
if (err == Z_STREAM_END || err == Z_OK || err == Z_BUF_ERROR) {
out.SetDataSize((NPT_Size)stream.total_out);
if ((err == Z_OK && stream.avail_out == 0) || err == Z_BUF_ERROR) {
// grow the output buffer
out.Reserve(out.GetBufferSize()*2);
stream.next_out = out.UseData()+stream.total_out;
stream.avail_out = out.GetBufferSize()-(NPT_Size)stream.total_out;
}
}
} while (err == Z_OK);
// check for errors
if (err != Z_STREAM_END) {
inflateEnd(&stream);
return MapError(err);
}
// cleanup
err = inflateEnd(&stream);
return MapError(err);
}
/*----------------------------------------------------------------------
| NPT_Zip::Deflate
+---------------------------------------------------------------------*/
NPT_Result
NPT_Zip::Deflate(NPT_File& in,
NPT_File& out,
int compression_level,
Format format /* = ZLIB */)
{
// check parameters
if (compression_level < NPT_ZIP_COMPRESSION_LEVEL_DEFAULT ||
compression_level > NPT_ZIP_COMPRESSION_LEVEL_MAX) {
return NPT_ERROR_INVALID_PARAMETERS;
}
NPT_InputStreamReference input;
NPT_CHECK(in.GetInputStream(input));
NPT_OutputStreamReference output;
NPT_CHECK(out.GetOutputStream(output));
NPT_ZipDeflatingInputStream deflating_stream(input, compression_level, format);
return NPT_StreamToStreamCopy(deflating_stream, *output.AsPointer());
}
#endif // NPT_CONFIG_ENABLE_ZIP
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32MessageQueue.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32MessageQueue.cpp
index f415b851d5..ce910f681b 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32MessageQueue.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32MessageQueue.cpp
@@ -1,183 +1,183 @@
/*****************************************************************
|
| Neptune - Win32 Message Queue
|
| (c) 2001-2008 Gilles Boccon-Gibod
| Author: Gilles Boccon-Gibod (bok@bok.net)
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "NptWin32MessageQueue.h"
/*----------------------------------------------------------------------
| platform adaptation
+---------------------------------------------------------------------*/
#if defined(_WIN32_WCE)
#define GetWindowLongPtr GetWindowLong
#define SetWindowLongPtr SetWindowLong
#endif
/*----------------------------------------------------------------------
| constants
+---------------------------------------------------------------------*/
const int NPT_WIN32_MESSAGE_ID_BASE = WM_USER + 9200;
/*----------------------------------------------------------------------
| NPT_Win32WindowMessageQueue
+---------------------------------------------------------------------*/
NPT_Win32WindowMessageQueue::NPT_Win32WindowMessageQueue()
{
// create a hidden window to process our incoming messages
WNDCLASS wclass;
// compute a unique class name
m_ClassName[0] = 'N';
m_ClassName[1] = 'P';
m_ClassName[2] = 'T';
m_ClassName[3] = 'H';
m_ClassName[4] = 'W';
NPT_String tid = NPT_String::FromInteger(GetCurrentThreadId());
for (unsigned int i=0; i<=tid.GetLength(); i++) {
m_ClassName[5+i] = tid.GetChars()[i];
}
// register a window class
wclass.style = 0;
wclass.lpfnWndProc = NPT_Win32WindowMessageQueue::WindowProcedure;
wclass.cbClsExtra = 0;
wclass.cbWndExtra = 0;
wclass.hInstance = GetModuleHandle(NULL);
wclass.hIcon = NULL;
wclass.hCursor = NULL;
wclass.hbrBackground = NULL;
wclass.lpszMenuName = NULL;
wclass.lpszClassName = m_ClassName;
// register the class and ignore any error because we might
// be registering the class more than once
RegisterClass(&wclass);
// create the hidden window
m_WindowHandle = CreateWindow(
wclass.lpszClassName, // pointer to registered class name
TEXT(""), // pointer to window name
0, // window style
0, // horizontal position of window
0, // vertical position of window
0, // window width
0, // window height
NULL, // handle to parent or owner window
NULL, // handle to menu or child-window identifier
wclass.hInstance, // handle to application instance
NULL);
- // set a pointer to ourself as user data */
+ // set a pointer to ourselves as user data */
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable: 4244) // we have to test for this because SetWindowLongPtr
// is incorrectly defined, so we'll get a C4244 warning
#endif // _MSC_VER
if (m_WindowHandle) {
SetWindowLongPtr(m_WindowHandle, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
#if defined(_MSC_VER)
#pragma warning( pop )
#endif // _MSC_VER
m_hInstance = wclass.hInstance;
}
/*----------------------------------------------------------------------
| NPT_Win32WindowMessageQueue
+---------------------------------------------------------------------*/
NPT_Win32WindowMessageQueue::~NPT_Win32WindowMessageQueue()
{
- // remove ourself as user data to ensure we're not called anymore
+ // remove ourselves as user data to ensure we're not called anymore
SetWindowLongPtr(m_WindowHandle, GWLP_USERDATA, 0);
// destroy the hidden window
DestroyWindow(m_WindowHandle);
// unregister the window class
UnregisterClass(m_ClassName, m_hInstance);
}
/*----------------------------------------------------------------------
| NPT_Win32WindowMessageQueue::WindowProcedure
+---------------------------------------------------------------------*/
LRESULT CALLBACK
NPT_Win32WindowMessageQueue::WindowProcedure(HWND window,
UINT message,
WPARAM wparam,
LPARAM lparam)
{
// if it is a windows message, just pass it along
if (message != (UINT) NPT_WIN32_MESSAGE_ID_BASE) {
return DefWindowProc(window, message, wparam, lparam);
}
// dispatch the message to the handler
#if defined(_MSC_VER)
#pragma warning( push )
#pragma warning( disable: 4312) // we have to test for this because GetWindowLongPtr
// is incorrectly defined, so we'll get a C4244 warning
#endif // _MSC_VER
NPT_Win32WindowMessageQueue* queue = reinterpret_cast<NPT_Win32WindowMessageQueue *>(GetWindowLongPtr(window, GWLP_USERDATA));
#if defined(_MSC_VER)
#pragma warning( pop )
#endif // _MSC_VER
if (queue == NULL) {
return 0;
}
queue->HandleMessage(reinterpret_cast<NPT_Message*>(lparam),
reinterpret_cast<NPT_MessageHandler*>(wparam));
return 0;
}
/*----------------------------------------------------------------------
| NPT_Win32WindowMessageQueue::PumpMessage
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32WindowMessageQueue::PumpMessage(NPT_Timeout)
{
// you cannot pump messages on this type of queue, since they will
// be pumped by the main windows message loop
return NPT_ERROR_NOT_SUPPORTED;
}
/*----------------------------------------------------------------------
| NPT_Win32WindowMessageQueue::QueueMessage
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32WindowMessageQueue::QueueMessage(NPT_Message* message,
NPT_MessageHandler* handler)
{
int result;
result = ::PostMessage(m_WindowHandle,
NPT_WIN32_MESSAGE_ID_BASE,
(WPARAM)handler,
(LPARAM)message);
if (result == 0) return NPT_FAILURE;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Win32WindowMessageQueue::HandleMessage
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32WindowMessageQueue::HandleMessage(NPT_Message* message,
NPT_MessageHandler* handler)
{
NPT_Result result = NPT_FAILURE;
if (message && handler) {
result = handler->HandleMessage(message);
}
delete message;
return result;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32SerialPort.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32SerialPort.cpp
index 9428648bd7..94114f2e4f 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32SerialPort.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/System/Win32/NptWin32SerialPort.cpp
@@ -1,340 +1,340 @@
/*****************************************************************
|
| Neptune - Serial Ports :: Win32 Implementation
|
| (c) 2001-2007 Gilles Boccon-Gibod
| Author: Gilles Boccon-Gibod (bok@bok.net)
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include <windows.h>
#include "NptUtils.h"
#include "NptSerialPort.h"
#include "NptStrings.h"
#include "NptLogging.h"
/*----------------------------------------------------------------------
| NPT_Win32HandletWrapper
+---------------------------------------------------------------------*/
class NPT_Win32HandleWrapper
{
public:
// constructors and destructor
NPT_Win32HandleWrapper(HANDLE handle) : m_Handle(handle) {}
~NPT_Win32HandleWrapper() {
CloseHandle(m_Handle);
}
// methods
HANDLE GetHandle() { return m_Handle; }
private:
// members
HANDLE m_Handle;
};
typedef NPT_Reference<NPT_Win32HandleWrapper> NPT_Win32HandleReference;
/*----------------------------------------------------------------------
| NPT_Win32SerialPortStream
+---------------------------------------------------------------------*/
class NPT_Win32SerialPortStream
{
public:
// constructors and destructor
NPT_Win32SerialPortStream(NPT_Win32HandleReference handle) :
m_HandleReference(handle) {}
protected:
// constructors and destructors
virtual ~NPT_Win32SerialPortStream() {}
// members
NPT_Win32HandleReference m_HandleReference;
};
/*----------------------------------------------------------------------
| NPT_Win32SerialPortInputStream
+---------------------------------------------------------------------*/
class NPT_Win32SerialPortInputStream : public NPT_InputStream,
private NPT_Win32SerialPortStream
{
public:
// constructors and destructor
NPT_Win32SerialPortInputStream(NPT_Win32HandleReference& handle) :
NPT_Win32SerialPortStream(handle) {}
// NPT_InputStream methods
NPT_Result Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read);
NPT_Result Seek(NPT_Position /* offset */) {
return NPT_ERROR_NOT_SUPPORTED;
}
NPT_Result Tell(NPT_Position& /* offset */) {
return NPT_ERROR_NOT_SUPPORTED;
}
NPT_Result GetSize(NPT_LargeSize& /* size */) {
return NPT_ERROR_NOT_SUPPORTED;
}
NPT_Result GetAvailable(NPT_LargeSize& /* available */) {
return NPT_ERROR_NOT_SUPPORTED;
}
};
/*----------------------------------------------------------------------
| NPT_Win32SerialPortInputStream::Read
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32SerialPortInputStream::Read(void* buffer,
NPT_Size bytes_to_read,
NPT_Size* bytes_read)
{
DWORD nb_read = 0;
BOOL result = ReadFile(m_HandleReference->GetHandle(),
buffer,
bytes_to_read,
&nb_read,
NULL);
if (result == TRUE) {
if (bytes_read) *bytes_read = nb_read;
return NPT_SUCCESS;
} else {
if (bytes_read) *bytes_read = 0;
return NPT_FAILURE;
}
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPortOutputStream
+---------------------------------------------------------------------*/
class NPT_Win32SerialPortOutputStream : public NPT_OutputStream,
private NPT_Win32SerialPortStream
{
public:
// constructors and destructor
NPT_Win32SerialPortOutputStream(NPT_Win32HandleReference& handle) :
NPT_Win32SerialPortStream(handle) {}
// NPT_InputStream methods
NPT_Result Write(const void* buffer,
NPT_Size bytes_to_write,
NPT_Size* bytes_written);
NPT_Result Seek(NPT_Position /* offset */) {
return NPT_ERROR_NOT_SUPPORTED;
}
NPT_Result Tell(NPT_Position& /* offset */) {
return NPT_ERROR_NOT_SUPPORTED;
}
};
/*----------------------------------------------------------------------
| NPT_Win32SerialPortOutputStream::Write
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32SerialPortOutputStream::Write(const void* buffer,
NPT_Size bytes_to_write,
NPT_Size* bytes_written)
{
DWORD nb_written = 0;
BOOL result = WriteFile(m_HandleReference->GetHandle(),
buffer,
bytes_to_write,
&nb_written,
NULL);
if (result == TRUE) {
if (bytes_written) *bytes_written = nb_written;
return NPT_SUCCESS;
} else {
if (bytes_written) *bytes_written = 0;
return NPT_FAILURE;
}
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPort
+---------------------------------------------------------------------*/
class NPT_Win32SerialPort: public NPT_SerialPortInterface
{
public:
// constructors and destructor
NPT_Win32SerialPort(const char* name);
~NPT_Win32SerialPort();
// NPT_SerialPortInterface methods
NPT_Result Open(unsigned int speed,
NPT_SerialPortStopBits stop_bits = NPT_SERIAL_PORT_STOP_BITS_1,
NPT_SerialPortFlowControl flow_control = NPT_SERIAL_PORT_FLOW_CONTROL_NONE,
NPT_SerialPortParity parity = NPT_SERIAL_PORT_PARITY_NONE);
NPT_Result Close();
NPT_Result GetInputStream(NPT_InputStreamReference& stream);
NPT_Result GetOutputStream(NPT_OutputStreamReference& stream);
private:
// members
NPT_String m_Name;
NPT_Win32HandleReference m_HandleReference;
};
/*----------------------------------------------------------------------
| NPT_Win32SerialPort::NPT_Win32SerialPort
+---------------------------------------------------------------------*/
NPT_Win32SerialPort::NPT_Win32SerialPort(const char* name) :
m_Name(name)
{
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPort::~NPT_Win32SerialPort
+---------------------------------------------------------------------*/
NPT_Win32SerialPort::~NPT_Win32SerialPort()
{
Close();
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPort::Open
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32SerialPort::Open(unsigned int speed,
NPT_SerialPortStopBits stop_bits,
NPT_SerialPortFlowControl flow_control,
NPT_SerialPortParity parity)
{
- return NPT_FAILURE; // We don't need serial port suppurt
+ return NPT_FAILURE; // We don't need serial port support
#if 0
// check if we're already open
if (!m_HandleReference.IsNull()) {
return NPT_ERROR_SERIAL_PORT_ALREADY_OPEN;
}
HANDLE handle = CreateFile(m_Name,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0);
if (handle == INVALID_HANDLE_VALUE) {
return NPT_ERROR_NO_SUCH_SERIAL_PORT;
}
// set the parameters
DCB dcb;
NPT_SetMemory(&dcb, 0, sizeof(dcb));
dcb.DCBlength = sizeof(DCB);
if (!GetCommState(handle, &dcb)) {
CloseHandle(handle);
return NPT_FAILURE;
}
dcb.fBinary = TRUE;
dcb.BaudRate = speed;
switch (stop_bits) {
case NPT_SERIAL_PORT_STOP_BITS_1: dcb.StopBits = ONESTOPBIT; break;
case NPT_SERIAL_PORT_STOP_BITS_1_5: dcb.StopBits = ONE5STOPBITS; break;
case NPT_SERIAL_PORT_STOP_BITS_2: dcb.StopBits = TWOSTOPBITS; break;
}
switch (flow_control) {
case NPT_SERIAL_PORT_FLOW_CONTROL_NONE:
dcb.fOutX = dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = FALSE;
dcb.fInX = dcb.fDsrSensitivity = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
break;
case NPT_SERIAL_PORT_FLOW_CONTROL_HARDWARE:
dcb.fOutX = dcb.fOutxDsrFlow = FALSE;
dcb.fOutxCtsFlow = TRUE;
dcb.fInX = dcb.fDsrSensitivity = FALSE;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
break;
case NPT_SERIAL_PORT_FLOW_CONTROL_XON_XOFF:
dcb.fOutX = TRUE;
dcb.fOutxCtsFlow = dcb.fOutxDsrFlow = FALSE;
dcb.fInX = TRUE;
dcb.fDsrSensitivity = FALSE;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fDtrControl = DTR_CONTROL_DISABLE;
break;
}
switch (parity) {
case NPT_SERIAL_PORT_PARITY_NONE: dcb.fParity = FALSE; dcb.Parity = NOPARITY; break;
case NPT_SERIAL_PORT_PARITY_EVEN: dcb.fParity = TRUE; dcb.Parity = EVENPARITY; break;
case NPT_SERIAL_PORT_PARITY_ODD: dcb.fParity = TRUE; dcb.Parity = ODDPARITY; break;
case NPT_SERIAL_PORT_PARITY_MARK: dcb.fParity = TRUE; dcb.Parity = MARKPARITY; break;
}
if (!SetCommState(handle, &dcb)) {
CloseHandle(handle);
return NPT_FAILURE;
}
// create a reference to the FILE object
m_HandleReference = new NPT_Win32HandleWrapper(handle);
return NPT_SUCCESS;
#endif
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPort::Close
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32SerialPort::Close()
{
// release the file reference
m_HandleReference = NULL;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPort::GetInputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32SerialPort::GetInputStream(NPT_InputStreamReference& stream)
{
// default value
stream = NULL;
// check that the file is open
if (m_HandleReference.IsNull()) return NPT_ERROR_SERIAL_PORT_NOT_OPEN;
// create a stream
stream = new NPT_Win32SerialPortInputStream(m_HandleReference);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_Win32SerialPort::GetOutputStream
+---------------------------------------------------------------------*/
NPT_Result
NPT_Win32SerialPort::GetOutputStream(NPT_OutputStreamReference& stream)
{
// default value
stream = NULL;
// check that the file is open
if (m_HandleReference.IsNull()) return NPT_ERROR_SERIAL_PORT_NOT_OPEN;
// create a stream
stream = new NPT_Win32SerialPortOutputStream(m_HandleReference);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| NPT_SerialPort::NPT_SerialPort
+---------------------------------------------------------------------*/
NPT_SerialPort::NPT_SerialPort(const char* name)
{
m_Delegate = new NPT_Win32SerialPort(name);
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages1/MessagesTest1.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages1/MessagesTest1.cpp
index 4e768b0bc6..6876e6afbc 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages1/MessagesTest1.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages1/MessagesTest1.cpp
@@ -1,408 +1,408 @@
/*****************************************************************
|
| Messages Test Program 1
|
| (c) 2001-2008 Gilles Boccon-Gibod
| Author: Gilles Boccon-Gibod (bok@bok.net)
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include <stdio.h>
#include "Neptune.h"
/*----------------------------------------------------------------------
| FooServerMessageHandler
+---------------------------------------------------------------------*/
class FooServerMessageHandler
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST(FooServerMessageHandler)
// destructor
virtual ~FooServerMessageHandler() {}
// methods
virtual void OnBarCmd1(NPT_MessageReceiver* /*receiver*/, int /*info*/) {}
virtual void OnBarCmd2(NPT_MessageReceiver* /*receiver*/,
int /*info1*/, int /*info2*/) { }
virtual void OnBarCmd3(NPT_MessageReceiver* /*receiver*/,
int /*info1*/, int /*info2*/, int /*info3*/) {}
};
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(FooServerMessageHandler)
/*----------------------------------------------------------------------
| FooServerMessage
+---------------------------------------------------------------------*/
class FooServerMessage : public NPT_Message
{
public:
static NPT_Message::Type MessageType;
NPT_Message::Type GetType() {
return MessageType;
}
virtual NPT_Result Deliver(FooServerMessageHandler* handler) = 0;
virtual NPT_Result Dispatch(NPT_MessageHandler* handler) {
FooServerMessageHandler* specific = NPT_DYNAMIC_CAST(FooServerMessageHandler, handler);
if (specific) {
return Deliver(specific);
} else {
return DefaultDeliver(handler);
}
}
};
NPT_Message::Type FooServerMessage::MessageType = "FooServer Message";
/*----------------------------------------------------------------------
| FooServerBarCmd1Message
+---------------------------------------------------------------------*/
class FooServerBarCmd1Message : public FooServerMessage
{
public:
FooServerBarCmd1Message(NPT_MessageReceiver* receiver, int info) :
m_Receiver(receiver), m_Info(info) {}
NPT_Result Deliver(FooServerMessageHandler* handler) {
handler->OnBarCmd1(m_Receiver, m_Info);
return NPT_SUCCESS;
}
private:
NPT_MessageReceiver* m_Receiver;
int m_Info;
};
/*----------------------------------------------------------------------
| FooServerBarCmd2Message
+---------------------------------------------------------------------*/
class FooServerBarCmd2Message : public FooServerMessage
{
public:
FooServerBarCmd2Message(NPT_MessageReceiver* receiver,
int info1, int info2) :
m_Receiver(receiver), m_Info1(info1), m_Info2(info2) {}
NPT_Result Deliver(FooServerMessageHandler* handler) {
handler->OnBarCmd2(m_Receiver, m_Info1, m_Info2);
return NPT_SUCCESS;
}
private:
NPT_MessageReceiver* m_Receiver;
int m_Info1;
int m_Info2;
};
/*----------------------------------------------------------------------
| FooServerBarCmd3Message
+---------------------------------------------------------------------*/
class FooServerBarCmd3Message : public FooServerMessage
{
public:
FooServerBarCmd3Message(NPT_MessageReceiver* receiver,
int info1, int info2, int info3) :
m_Receiver(receiver), m_Info1(info1), m_Info2(info2), m_Info3(info3) {}
NPT_Result Deliver(FooServerMessageHandler* handler) {
handler->OnBarCmd3(m_Receiver, m_Info1, m_Info2, m_Info3);
return NPT_SUCCESS;
}
private:
NPT_MessageReceiver* m_Receiver;
int m_Info1;
int m_Info2;
int m_Info3;
};
/*----------------------------------------------------------------------
| FooServerBarCmd4Message
+---------------------------------------------------------------------*/
class FooServerBarCmd4Message : public NPT_Message
{
public:
static NPT_Message::Type MessageType;
NPT_Message::Type GetType() {
return MessageType;
}
FooServerBarCmd4Message() {}
};
NPT_Message::Type FooServerBarCmd4Message::MessageType = "FooServerBarCmd4 Message";
/*----------------------------------------------------------------------
| FooClientMessageHandler
+---------------------------------------------------------------------*/
class FooClientMessageHandler
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST(FooClientMessageHandler)
// destructor
virtual ~FooClientMessageHandler() {}
// methods
virtual void OnBarNotification1(int /*info*/) {}
virtual void OnBarNotification2(int /*info1*/, int /*info2*/) {}
};
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(FooClientMessageHandler)
/*----------------------------------------------------------------------
| FooClientMessage
+---------------------------------------------------------------------*/
class FooClientMessage : public NPT_Message
{
public:
static NPT_Message::Type MessageType;
NPT_Message::Type GetType() {
return MessageType;
}
virtual NPT_Result Deliver(FooClientMessageHandler* handler) = 0;
virtual NPT_Result Dispatch(NPT_MessageHandler* handler) {
FooClientMessageHandler* specific = NPT_DYNAMIC_CAST(FooClientMessageHandler, handler);
if (specific) {
return Deliver(specific);
} else {
return DefaultDeliver(handler);
}
}
};
NPT_Message::Type FooClientMessage::MessageType = "FooClient Message";
/*----------------------------------------------------------------------
| FooClientBarNotification1Message
+---------------------------------------------------------------------*/
class FooClientBarNotification1Message : public FooClientMessage
{
public:
FooClientBarNotification1Message(int info) : m_Info(info) {}
NPT_Result Deliver(FooClientMessageHandler* handler) {
handler->OnBarNotification1(m_Info);
return NPT_SUCCESS;
}
private:
int m_Info;
};
/*----------------------------------------------------------------------
| FooServer
+---------------------------------------------------------------------*/
class FooServer : public NPT_Thread,
public NPT_MessageReceiver,
public NPT_MessageHandler,
public FooServerMessageHandler
{
public:
FooServer();
// message posting wrappers
NPT_Result DoBarCmd1(NPT_MessageReceiver* receiver, int info);
NPT_Result DoBarCmd2(NPT_MessageReceiver* receiver, int info1, int info2);
NPT_Result DoBarCmd3(NPT_MessageReceiver* receiver,
int info1, int info2, int info3);
NPT_Result DoBarCmd4();
// NPT_Runnable methods (from NPT_Thread)
void Run();
// NPT_MessageHandler methods
void OnMessage(NPT_Message* message);
NPT_Result HandleMessage(NPT_Message* message);
// NPT_FooServerMessageHandler methods
void OnBarCmd1(NPT_MessageReceiver* receiver, int info);
void OnBarCmd2(NPT_MessageReceiver* receiver, int info1, int info2);
private:
// members
NPT_SimpleMessageQueue* m_MessageQueue;
};
/*----------------------------------------------------------------------
| FooServer::FooServer
+---------------------------------------------------------------------*/
FooServer::FooServer()
{
// create the message queue
m_MessageQueue = new NPT_SimpleMessageQueue();
// attach to the message queue
SetQueue(m_MessageQueue);
SetHandler(this);
// start the thread
Start();
}
/*----------------------------------------------------------------------
| FooServer::Run
+---------------------------------------------------------------------*/
void
FooServer::Run()
{
printf("FooServer::Run - begin\n");
while (m_MessageQueue->PumpMessage() == NPT_SUCCESS) {};
printf("FooServer::Run - end\n");
}
/*----------------------------------------------------------------------
| FooServer::HandleMessage
+---------------------------------------------------------------------*/
NPT_Result
FooServer::HandleMessage(NPT_Message* message)
{
// a handler typically does not implement this method unless it
// needs to catch all messages before they are dispatched
printf("FooServer::HandleMessage (%s)\n", message->GetType());
return NPT_MessageHandler::HandleMessage(message);
}
/*----------------------------------------------------------------------
| FooServer::OnMessage
+---------------------------------------------------------------------*/
void
FooServer::OnMessage(NPT_Message* message)
{
printf("FooServer::OnMessage (%s)\n", message->GetType());
}
/*----------------------------------------------------------------------
| FooServer::OnBarCmd1
+---------------------------------------------------------------------*/
void
FooServer::OnBarCmd1(NPT_MessageReceiver* receiver, int info)
{
printf("FooServer::OnBarCmd1 %d\n", info);
receiver->PostMessage(new FooClientBarNotification1Message(7));
}
/*----------------------------------------------------------------------
| FooServer::OnBarCmd2
+---------------------------------------------------------------------*/
void
FooServer::OnBarCmd2(NPT_MessageReceiver* /*receiver*/, int info1, int info2)
{
printf("FooServer::OnBarCmd2 %d %d\n", info1, info2);
}
/*----------------------------------------------------------------------
| FooServer::DoBarCmd1
+---------------------------------------------------------------------*/
NPT_Result
FooServer::DoBarCmd1(NPT_MessageReceiver* receiver, int info)
{
return PostMessage(new FooServerBarCmd1Message(receiver, info));
}
/*----------------------------------------------------------------------
| FooServer::DoBarCmd2
+---------------------------------------------------------------------*/
NPT_Result
FooServer::DoBarCmd2(NPT_MessageReceiver* receiver, int info1, int info2)
{
return PostMessage(new FooServerBarCmd2Message(receiver, info1,info2));
}
/*----------------------------------------------------------------------
| FooServer::DoBarCmd3
+---------------------------------------------------------------------*/
NPT_Result
FooServer::DoBarCmd3(NPT_MessageReceiver* receiver,
int info1, int info2, int info3)
{
return PostMessage(new FooServerBarCmd3Message(receiver,
info1, info2, info3));
}
/*----------------------------------------------------------------------
| FooServer::DoBarCmd4
+---------------------------------------------------------------------*/
NPT_Result
FooServer::DoBarCmd4()
{
return PostMessage(new FooServerBarCmd4Message());
}
/*----------------------------------------------------------------------
| FooClient
+---------------------------------------------------------------------*/
class FooClient : public NPT_MessageReceiver,
public NPT_MessageHandler,
public FooClientMessageHandler
{
public:
FooClient(FooServer* server, int id);
// NPT_MessageHandler methods
//void OnMessage(NPT_Message* message);
// NPT_FooServerMessageHandler methods
void OnBarNotification1(int info);
void OnBarNotification2(int info1, int info2);
private:
// members
FooServer* m_Server;
int m_Id;
};
/*----------------------------------------------------------------------
| FooClient::FooClient
+---------------------------------------------------------------------*/
FooClient::FooClient(FooServer* server, int id) :
m_Server(server), m_Id(id)
{
- // set ourself as the message handler
+ // set ourselves as the message handler
SetHandler(this);
// send commands to server
server->DoBarCmd1(this, 1);
server->DoBarCmd2(this, 1, 2);
server->DoBarCmd3(this, 1, 2, 3);
server->DoBarCmd4();
}
/*----------------------------------------------------------------------
| FooClient::OnBarNotification1
+---------------------------------------------------------------------*/
void
FooClient::OnBarNotification1(int info)
{
printf("FooClient::OnBarNotification1 (client=%d) %d\n", m_Id, info);
}
/*----------------------------------------------------------------------
| FooClient::OnBarNotification2
+---------------------------------------------------------------------*/
void
FooClient::OnBarNotification2(int info1, int info2)
{
printf("FooClient::OnBarNotification2 (client=%d) %d %d\n", m_Id, info1, info2);
}
/*----------------------------------------------------------------------
| main
+---------------------------------------------------------------------*/
int
main(int /*argc*/, char** /*argv*/)
{
printf("MessagesTest1:: start\n");
FooServer* server = new FooServer();
FooClient* client1 = new FooClient(server, 1);
FooClient* client2 = new FooClient(server, 2);
NPT_MessageQueue* queue = new NPT_SimpleMessageQueue();
client1->SetQueue(queue);
client2->SetQueue(queue);
while (queue->PumpMessage() == NPT_SUCCESS) {}
delete client1;
delete client2;
delete server;
delete queue;
printf("MessagesTest1:: end\n");
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages2/MessagesTest2.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages2/MessagesTest2.cpp
index 8ec72fada6..a41d09adaa 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages2/MessagesTest2.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Messages2/MessagesTest2.cpp
@@ -1,243 +1,243 @@
/*****************************************************************
|
| Messages Test Program 1
|
| (c) 2001-2008 Gilles Boccon-Gibod
| Author: Gilles Boccon-Gibod (bok@bok.net)
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
/*----------------------------------------------------------------------
| TestServerMessageHandler
+---------------------------------------------------------------------*/
class TestServerMessageHandler
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST(TestServerMessageHandler)
// destructor
virtual ~TestServerMessageHandler() {}
// methods
virtual void OnTestCommand(NPT_MessageReceiver* /*receiver*/, int /*id*/) {}
};
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(TestServerMessageHandler)
/*----------------------------------------------------------------------
| TestServerMessage
+---------------------------------------------------------------------*/
class TestServerMessage : public NPT_Message
{
public:
static NPT_Message::Type MessageType;
NPT_Message::Type GetType() {
return MessageType;
}
virtual NPT_Result Deliver(TestServerMessageHandler* handler) = 0;
virtual NPT_Result Dispatch(NPT_MessageHandler* handler) {
TestServerMessageHandler* specific = NPT_DYNAMIC_CAST(TestServerMessageHandler, handler);
if (specific) {
return Deliver(specific);
} else {
return DefaultDeliver(handler);
}
}
};
NPT_Message::Type TestServerMessage::MessageType = "TestServer Message";
/*----------------------------------------------------------------------
| TestServerTestCommandMessage
+---------------------------------------------------------------------*/
class TestServerTestCommandMessage : public TestServerMessage
{
public:
TestServerTestCommandMessage(NPT_MessageReceiver* receiver, int id) :
m_Receiver(receiver), m_Id(id) {}
NPT_Result Deliver(TestServerMessageHandler* handler) {
handler->OnTestCommand(m_Receiver, m_Id);
return NPT_SUCCESS;
}
private:
NPT_MessageReceiver* m_Receiver;
int m_Id;
};
/*----------------------------------------------------------------------
| TestClientMessageHandler
+---------------------------------------------------------------------*/
class TestClientMessageHandler
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST(TestClientMessageHandler)
// destructor
virtual ~TestClientMessageHandler() {}
// methods
virtual void OnReply(int /*id*/) {}
};
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(TestClientMessageHandler)
/*----------------------------------------------------------------------
| TestClientMessage
+---------------------------------------------------------------------*/
class TestClientMessage : public NPT_Message
{
public:
static NPT_Message::Type MessageType;
NPT_Message::Type GetType() {
return MessageType;
}
virtual NPT_Result Deliver(TestClientMessageHandler* handler) = 0;
virtual NPT_Result Dispatch(NPT_MessageHandler* handler) {
TestClientMessageHandler* specific = NPT_DYNAMIC_CAST(TestClientMessageHandler, handler);
if (specific) {
return Deliver(specific);
} else {
return DefaultDeliver(handler);
}
}
};
NPT_Message::Type TestClientMessage::MessageType = "TestClient Message";
/*----------------------------------------------------------------------
| TestClientReplyMessage
+---------------------------------------------------------------------*/
class TestClientReplyMessage : public TestClientMessage
{
public:
TestClientReplyMessage(int id) : m_Id(id) {}
NPT_Result Deliver(TestClientMessageHandler* handler) {
handler->OnReply(m_Id);
return NPT_SUCCESS;
}
private:
int m_Id;
};
/*----------------------------------------------------------------------
| TestServer
+---------------------------------------------------------------------*/
class TestServer : public NPT_Thread,
public NPT_MessageReceiver,
public NPT_MessageHandler,
public TestServerMessageHandler
{
public:
TestServer();
// message posting wrappers
NPT_Result DoTestCommand(NPT_MessageReceiver* receiver, int id);
// NPT_Runnable methods (from NPT_Thread)
void Run();
// NPT_TestServerMessageHandler methods
void OnTestCommand(NPT_MessageReceiver* receiver, int id);
private:
// members
NPT_SimpleMessageQueue* m_MessageQueue;
};
/*----------------------------------------------------------------------
| TestServer::TestServer
+---------------------------------------------------------------------*/
TestServer::TestServer()
{
// create the message queue
m_MessageQueue = new NPT_SimpleMessageQueue();
// attach to the message queue
SetQueue(m_MessageQueue);
SetHandler(this);
// start the thread
Start();
}
/*----------------------------------------------------------------------
| TestServer::Run
+---------------------------------------------------------------------*/
void
TestServer::Run()
{
printf("TestServer::Run - begin\n");
while (m_MessageQueue->PumpMessage() == NPT_SUCCESS) {};
printf("TestServer::Run - end\n");
}
/*----------------------------------------------------------------------
| TestServer::DoTestCommand
+---------------------------------------------------------------------*/
NPT_Result
TestServer::DoTestCommand(NPT_MessageReceiver* receiver, int id)
{
return this->PostMessage(new TestServerTestCommandMessage(receiver, id));
}
/*----------------------------------------------------------------------
| TestServer::OnTestCommand
+---------------------------------------------------------------------*/
void
TestServer::OnTestCommand(NPT_MessageReceiver* receiver, int id)
{
printf("TestServer::OnTestCommand %d\n", id);
receiver->PostMessage(new TestClientReplyMessage(id+10000));
}
/*----------------------------------------------------------------------
| TestClient
+---------------------------------------------------------------------*/
class TestClient : public NPT_MessageReceiver,
public NPT_MessageHandler,
public TestClientMessageHandler
{
public:
TestClient(TestServer* server, int id);
// NPT_TestServerMessageHandler methods
void OnReply(int id);
private:
// members
TestServer* m_Server;
int m_Id;
};
/*----------------------------------------------------------------------
| TestClient::TestClient
+---------------------------------------------------------------------*/
TestClient::TestClient(TestServer* server, int id) :
m_Server(server), m_Id(id)
{
- // set ourself as the message handler
+ // set ourselves as the message handler
SetHandler(this);
// send commands to server
server->DoTestCommand(this, 1);
server->DoTestCommand(this, 2);
server->DoTestCommand(this, 3);
server->DoTestCommand(this, 4);
}
/*----------------------------------------------------------------------
| TestClient::OnReply
+---------------------------------------------------------------------*/
void
TestClient::OnReply(int id)
{
printf("TestClient::OnReply (client=%d) %d\n", m_Id, id);
}
/*----------------------------------------------------------------------
| main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
NPT_COMPILER_UNUSED(argc);
NPT_COMPILER_UNUSED(argv);
printf("MessagesTest2:: start\n");
TestServer* server = new TestServer();
TestClient* client1 = new TestClient(server, 1);
TestClient* client2 = new TestClient(server, 2);
NPT_MessageQueue* queue = new NPT_SimpleMessageQueue();
client1->SetQueue(queue);
client2->SetQueue(queue);
server->Wait();
delete client1;
delete client2;
delete server;
delete queue;
printf("MessagesTest2:: end\n");
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Time1/TimeTest1.cpp b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Time1/TimeTest1.cpp
index 1655e4ddbe..3937dc9829 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Time1/TimeTest1.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/Source/Tests/Time1/TimeTest1.cpp
@@ -1,559 +1,559 @@
/*****************************************************************
|
| Time Test Program 1
|
| (c) 2005-2006 Gilles Boccon-Gibod
| Author: Gilles Boccon-Gibod (bok@bok.net)
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include "Neptune.h"
#include "NptResults.h"
/*----------------------------------------------------------------------
| macros
+---------------------------------------------------------------------*/
#define SHOULD_SUCCEED(r) \
do { \
if (NPT_FAILED(r)) { \
fprintf(stderr, "FAILED: line %d\n", __LINE__); \
NPT_ASSERT(0); \
} \
} while(0)
#define SHOULD_FAIL(r) \
do { \
if (NPT_SUCCEEDED(r)) { \
fprintf(stderr, "should have failed line %d (%d)\n", \
__LINE__, r); \
NPT_ASSERT(0); \
} \
} while(0)
#define SHOULD_EQUAL(a, b) \
do { \
if ((a) != (b)) { \
fprintf(stderr, "not equal, line %d\n", __LINE__); \
NPT_ASSERT(0); \
} \
} while(0)
#define SHOULD_EQUAL_I(a, b) \
do { \
if ((a) != (b)) { \
fprintf(stderr, "got %d, expected %d line %d\n", \
a, b, __LINE__); \
NPT_ASSERT(0); \
} \
} while(0)
#define SHOULD_EQUAL_F(a, b) \
do { \
if ((a) != (b)) { \
fprintf(stderr, "got %f, expected %f line %d\n", \
(float)a, (float)b, __LINE__); \
NPT_ASSERT(0); \
} \
} while(0)
#define SHOULD_EQUAL_S(a, b) \
do { \
if (!NPT_StringsEqual(a,b)) { \
fprintf(stderr, "got %s, expected %s line %d\n", \
a, b, __LINE__); \
NPT_ASSERT(0); \
} \
} while(0)
/*----------------------------------------------------------------------
| TestMisc
+---------------------------------------------------------------------*/
static void
TestMisc()
{
NPT_DateTime date;
NPT_TimeStamp ts;
NPT_String s;
NPT_System::GetCurrentTimeStamp(ts);
SHOULD_SUCCEED(date.FromTimeStamp(ts, false));
s = date.ToString(NPT_DateTime::FORMAT_W3C);
NPT_Console::OutputF("%s\n", s.GetChars());
s = date.ToString(NPT_DateTime::FORMAT_ANSI);
NPT_Console::OutputF("%s\n", s.GetChars());
s = date.ToString(NPT_DateTime::FORMAT_RFC_1036);
NPT_Console::OutputF("%s\n", s.GetChars());
s = date.ToString(NPT_DateTime::FORMAT_RFC_1123);
NPT_Console::OutputF("%s\n", s.GetChars());
SHOULD_SUCCEED(date.FromTimeStamp(ts, true));
s = date.ToString(NPT_DateTime::FORMAT_W3C);
NPT_Console::OutputF("%s\n", s.GetChars());
s = date.ToString(NPT_DateTime::FORMAT_ANSI);
NPT_Console::OutputF("%s\n", s.GetChars());
s = date.ToString(NPT_DateTime::FORMAT_RFC_1036);
NPT_Console::OutputF("%s\n", s.GetChars());
s = date.ToString(NPT_DateTime::FORMAT_RFC_1123);
NPT_Console::OutputF("%s\n", s.GetChars());
ts = 0.0;
SHOULD_SUCCEED(date.FromTimeStamp(ts, false));
s = date.ToString(NPT_DateTime::FORMAT_W3C);
SHOULD_EQUAL_S(s.GetChars(), "1970-01-01T00:00:00Z");
s = date.ToString(NPT_DateTime::FORMAT_ANSI);
SHOULD_EQUAL_S(s.GetChars(), "Thu Jan 1 00:00:00 1970");
s = date.ToString(NPT_DateTime::FORMAT_RFC_1036);
SHOULD_EQUAL_S(s.GetChars(), "Thursday, 01-Jan-70 00:00:00 GMT");
s = date.ToString(NPT_DateTime::FORMAT_RFC_1123);
SHOULD_EQUAL_S(s.GetChars(), "Thu, 01 Jan 1970 00:00:00 GMT");
ts.SetSeconds(0xFFFFFFFF);
SHOULD_SUCCEED(date.FromTimeStamp(ts, false));
s = date.ToString(NPT_DateTime::FORMAT_W3C, false);
SHOULD_EQUAL_S(s.GetChars(), "2106-02-07T06:28:15Z");
NPT_TimeStamp now;
NPT_System::GetCurrentTimeStamp(now);
NPT_DateTime now_local(now, true);
NPT_DateTime now_utc(now, false);
SHOULD_EQUAL_I(now_utc.m_TimeZone, 0);
NPT_TimeStamp ts1, ts2;
now_local.ToTimeStamp(ts1);
now_utc.ToTimeStamp(ts2);
SHOULD_EQUAL_I((int)ts1.ToSeconds(), (int)ts2.ToSeconds());
ts.SetSeconds(0);
NPT_DateTime d1(ts);
ts.SetSeconds(ts.ToSeconds()-3600);
NPT_DateTime d2(ts);
d1.ToTimeStamp(ts1);
d2.ToTimeStamp(ts2);
SHOULD_EQUAL_I((int)ts1.ToSeconds(), (int)ts2.ToSeconds()+3600);
}
/*----------------------------------------------------------------------
| TestDateFromTimeStringW3C
+---------------------------------------------------------------------*/
static void
TestDateFromTimeStringW3C()
{
NPT_DateTime date;
/* Valid date */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10.003Z", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 3000000);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date, 2 characters milliseconds */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10.02Z", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 20000000);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date, 1 character milliseconds */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10.9Z", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 900000000);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date, no seconds, Z */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01Z", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 0);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date, no seconds, timezone */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01+03:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 0);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 180);
- /* Valid date, no millimseconds */
+ /* Valid date, no milliseconds */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10Z", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date with microseconds, 'Z' */
SHOULD_SUCCEED(date.FromString("2005-09-06T17:16:10.003498Z", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2005);
SHOULD_EQUAL_I(date.m_Month , 9);
SHOULD_EQUAL_I(date.m_Day , 6);
SHOULD_EQUAL_I(date.m_Hours , 17);
SHOULD_EQUAL_I(date.m_Minutes , 16);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 3498000);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date, no milliseconds, with timezone offset */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10+03:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 180);
/* Valid date, no milliseconds, with negative m_TimeZone offset */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10-05:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , -300);
/* Valid date, with milliseconds, with positive m_TimeZone offset */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10.200+03:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 200000000);
SHOULD_EQUAL_I(date.m_TimeZone , 180);
/* Valid date, with milliseconds, with negative m_TimeZone offset */
SHOULD_SUCCEED(date.FromString("2006-04-14T12:01:10.030-05:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 30000000);
SHOULD_EQUAL_I(date.m_TimeZone , -300);
/* Valid date with microseconds and negative m_TimeZone offset */
SHOULD_SUCCEED(date.FromString("2005-09-06T17:16:10.001822-05:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2005);
SHOULD_EQUAL_I(date.m_Month , 9);
SHOULD_EQUAL_I(date.m_Day , 6);
SHOULD_EQUAL_I(date.m_Hours , 17);
SHOULD_EQUAL_I(date.m_Minutes , 16);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 1822000);
SHOULD_EQUAL_I(date.m_TimeZone , -300);
/* Valid date with microseconds and positive m_TimeZone offset */
SHOULD_SUCCEED(date.FromString("2005-09-06T17:16:10.001822+05:00", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2005);
SHOULD_EQUAL_I(date.m_Month , 9);
SHOULD_EQUAL_I(date.m_Day , 6);
SHOULD_EQUAL_I(date.m_Hours , 17);
SHOULD_EQUAL_I(date.m_Minutes , 16);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 1822000);
SHOULD_EQUAL_I(date.m_TimeZone , 300);
/* Valid date with no time and m_TimeZone offset */
SHOULD_SUCCEED(date.FromString("2006-10-05", NPT_DateTime::FORMAT_W3C));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 10);
SHOULD_EQUAL_I(date.m_Day , 5);
/* Invalid date with 3 digit year */
SHOULD_FAIL(date.FromString("206-04-14T12:01:10.003Z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with 5 digit year */
SHOULD_FAIL(date.FromString("20076-04-14T12:01:10.003Z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with 5 digit year */
SHOULD_FAIL(date.FromString("20076-04-14T12:01:10.003Z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with garbage in the end */
SHOULD_FAIL(date.FromString("2006-04-14T12:01:10.003+69:696", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad month */
SHOULD_FAIL(date.FromString("2006-010-14T12:01:10.003", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad month, right overall length */
SHOULD_FAIL(date.FromString("2063-0--14T12:01:10.003", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad year-month separator */
SHOULD_FAIL(date.FromString("2063Y08-14T12:01:10.003", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad time separator */
SHOULD_FAIL(date.FromString("2063-08-14t12:01:10.003", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad hour */
SHOULD_FAIL(date.FromString("2063-08-14T012:01:10.003", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad GMT indicator */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.003z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad GMT indicator */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.003g", NPT_DateTime::FORMAT_W3C));
/* Invalid date with millisecond separator but no digits */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.", NPT_DateTime::FORMAT_W3C));
/* Invalid date with millisecond separator but no digits */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.Z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with millisecond separator but no digits */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.+10:38", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad m_TimeZone offset */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10+10:338", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad m_TimeZone offset */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10+001:38", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad m_TimeZone offset */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10+10:33Z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad m_TimeZone offset */
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.08+10:33Z", NPT_DateTime::FORMAT_W3C));
/* Invalid date with bad m_TimeZone offset with m_Seconds*/
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.08+10:33:30", NPT_DateTime::FORMAT_W3C));
/* Invalid date with m_TimeZone offset too big*/
SHOULD_FAIL(date.FromString("2063-08-14T12:01:10.08+14:33", NPT_DateTime::FORMAT_W3C));
}
/*----------------------------------------------------------------------
| TestDateFromTimeStringANSI
+---------------------------------------------------------------------*/
static void
TestDateFromTimeStringANSI()
{
NPT_DateTime date;
/* Valid date */
SHOULD_SUCCEED(date.FromString("Fri Apr 14 12:01:10 2006", NPT_DateTime::FORMAT_ANSI));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date with space in the days */
SHOULD_SUCCEED(date.FromString("Fri Apr 7 12:01:10 2006", NPT_DateTime::FORMAT_ANSI));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 7);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Wrong weekday */
SHOULD_FAIL(date.FromString("Wed Apr 14 12:01:10 2006", NPT_DateTime::FORMAT_ANSI));
/* Wrong year length */
SHOULD_FAIL(date.FromString("Mon Apr 14 12:01:10 95", NPT_DateTime::FORMAT_ANSI));
}
/*----------------------------------------------------------------------
| TestDateFromTimeStringRFC_1036
+---------------------------------------------------------------------*/
static void
TestDateFromTimeStringRFC_1036()
{
NPT_DateTime date;
/* Valid date */
SHOULD_SUCCEED(date.FromString("Friday, 14-Apr-2006 12:01:10 UT", NPT_DateTime::FORMAT_RFC_1036));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date with timezone */
SHOULD_SUCCEED(date.FromString("Friday, 14-Apr-95 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1036));
SHOULD_EQUAL_I(date.m_Year , 1995);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date with timezone */
SHOULD_SUCCEED(date.FromString("Friday, 14-Apr-95 12:01:10 -0800", NPT_DateTime::FORMAT_RFC_1036));
SHOULD_EQUAL_I(date.m_Year , 1995);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , -8*60);
/* Wrong day name */
SHOULD_FAIL(date.FromString("Fri, 14-Apr-95 12:01:10 -0800", NPT_DateTime::FORMAT_RFC_1036));
/* Wrong weekday */
SHOULD_FAIL(date.FromString("Wednesday, 14-Apr-95 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1036));
/* Wrong year length */
SHOULD_FAIL(date.FromString("Monday, 14-Apr-1995 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1036));
}
/*----------------------------------------------------------------------
| TestDateFromTimeStringRFC_1123
+---------------------------------------------------------------------*/
static void
TestDateFromTimeStringRFC_1123()
{
NPT_DateTime date;
/* Valid date */
SHOULD_SUCCEED(date.FromString("Fri, 14 Apr 2006 12:01:10 UT", NPT_DateTime::FORMAT_RFC_1123));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date with timezone*/
SHOULD_SUCCEED(date.FromString("Fri, 14 Apr 2006 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1123));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Valid date with timezone*/
SHOULD_SUCCEED(date.FromString("Fri, 14 Apr 2006 12:01:10 +0800", NPT_DateTime::FORMAT_RFC_1123));
SHOULD_EQUAL_I(date.m_Year , 2006);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 8*60);
/* Valid date, short year */
SHOULD_SUCCEED(date.FromString("Fri, 14 Apr 95 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1123));
SHOULD_EQUAL_I(date.m_Year , 1995);
SHOULD_EQUAL_I(date.m_Month , 4);
SHOULD_EQUAL_I(date.m_Day , 14);
SHOULD_EQUAL_I(date.m_Hours , 12);
SHOULD_EQUAL_I(date.m_Minutes , 1);
SHOULD_EQUAL_I(date.m_Seconds , 10);
SHOULD_EQUAL_I(date.m_NanoSeconds , 0);
SHOULD_EQUAL_I(date.m_TimeZone , 0);
/* Wrong day name */
SHOULD_FAIL(date.FromString("Friday, 14 Apr 95 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1123));
/* Wrong weekday */
SHOULD_FAIL(date.FromString("Wed, 14 Apr 2006 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1123));
/* Wrong year length */
SHOULD_FAIL(date.FromString("Mon, 14 Apr 95 12:01:10 GMT", NPT_DateTime::FORMAT_RFC_1123));
}
/*----------------------------------------------------------------------
| TestRandom
+---------------------------------------------------------------------*/
static void
TestRandom()
{
for (unsigned int i=0; i<10000; i++) {
NPT_TimeStamp ts((double)NPT_System::GetRandomInteger());
NPT_TimeStamp ts2;
NPT_DateTime date;
SHOULD_SUCCEED(date.FromTimeStamp(ts, false));
SHOULD_SUCCEED(date.ToTimeStamp(ts2));
NPT_String ds;
NPT_DateTime ndate;
ds = date.ToString(NPT_DateTime::FORMAT_ANSI);
ndate.FromString(ds);
//SHOULD_EQUAL(date, ndate);
SHOULD_SUCCEED(ndate.ToTimeStamp(ts2));
SHOULD_EQUAL_F((double)ts2.ToSeconds(), (double)ts.ToSeconds());
ds = date.ToString(NPT_DateTime::FORMAT_W3C);
ndate.FromString(ds);
//SHOULD_EQUAL(date, ndate);
SHOULD_SUCCEED(ndate.ToTimeStamp(ts2));
SHOULD_EQUAL_F((double)ts2.ToSeconds(), (double)ts.ToSeconds());
ds = date.ToString(NPT_DateTime::FORMAT_RFC_1123);
ndate.FromString(ds);
//SHOULD_EQUAL(date, ndate);
SHOULD_SUCCEED(ndate.ToTimeStamp(ts2));
SHOULD_EQUAL_F((double)ts2.ToSeconds(), (double)ts.ToSeconds());
ds = date.ToString(NPT_DateTime::FORMAT_RFC_1036);
ndate.FromString(ds);
//SHOULD_EQUAL(date, ndate);
SHOULD_SUCCEED(ndate.ToTimeStamp(ts2));
SHOULD_EQUAL_F((double)ts2.ToSeconds(), (double)ts.ToSeconds());
}
}
/*----------------------------------------------------------------------
| main
+---------------------------------------------------------------------*/
int
main(int /*argc*/, char** /*argv*/)
{
TestMisc();
TestDateFromTimeStringW3C();
TestDateFromTimeStringANSI();
TestDateFromTimeStringRFC_1036();
TestDateFromTimeStringRFC_1123();
TestRandom();
return 0;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/ssl.h b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/ssl.h
index c14744d48d..1552353ca7 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/ssl.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/ssl.h
@@ -1,511 +1,511 @@
/*
* Copyright (c) 2007, Cameron Rich
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the axTLS project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* axTLS API
*
* @image html axolotl.jpg
*
* The axTLS library has features such as:
* - The TLSv1 SSL client/server protocol
* - No requirement to use any openssl libraries.
* - A choice between AES block (128/256 bit) and RC4 (128 bit) stream ciphers.
* - RSA encryption/decryption with variable sized keys (up to 4096 bits).
* - Certificate chaining and peer authentication.
* - Session resumption, session renegotiation.
* - ASN.1, X.509, PKCS#8, PKCS#12 keys/certificates with DER/PEM encoding.
* - Highly configurable compile time options.
* - Portable across many platforms (written in ANSI C), and has language
* bindings in C, C#, VB.NET, Java, Perl and Lua.
* - Partial openssl API compatibility (via a wrapper).
* - A very small footprint (around 50-60kB for the library in 'server-only'
* mode).
* - No dependencies on sockets - can use serial connections for example.
* - A very simple API - ~ 20 functions/methods.
*
* A list of these functions/methods are described below.
*
* @ref c_api
*
* @ref bigint_api
*
* @ref csharp_api
*
* @ref java_api
*/
#ifndef HEADER_SSL_H
#define HEADER_SSL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <time.h>
#include "os_port.h"
/* need to predefine before ssl_lib.h gets to it */
#define SSL_SESSION_ID_SIZE 32
#include "tls1.h"
/* The optional parameters that can be given to the client/server SSL engine */
#define SSL_CLIENT_AUTHENTICATION 0x00010000
#define SSL_SERVER_VERIFY_LATER 0x00020000
#define SSL_NO_DEFAULT_KEY 0x00040000
#define SSL_DISPLAY_STATES 0x00080000
#define SSL_DISPLAY_BYTES 0x00100000
#define SSL_DISPLAY_CERTS 0x00200000
#define SSL_DISPLAY_RSA 0x00400000
#define SSL_CONNECT_IN_PARTS 0x00800000
/* errors that can be generated */
#define SSL_OK 0
#define SSL_NOT_OK -1
#define SSL_ERROR_DEAD -2
#define SSL_CLOSE_NOTIFY -3
#define SSL_ERROR_CONN_LOST -256
#define SSL_ERROR_SOCK_SETUP_FAILURE -258
#define SSL_ERROR_INVALID_HANDSHAKE -260
#define SSL_ERROR_INVALID_PROT_MSG -261
#define SSL_ERROR_INVALID_HMAC -262
#define SSL_ERROR_INVALID_VERSION -263
#define SSL_ERROR_INVALID_SESSION -265
#define SSL_ERROR_NO_CIPHER -266
#define SSL_ERROR_BAD_CERTIFICATE -268
#define SSL_ERROR_INVALID_KEY -269
#define SSL_ERROR_FINISHED_INVALID -271
#define SSL_ERROR_NO_CERT_DEFINED -272
#define SSL_ERROR_NO_CLIENT_RENOG -273
#define SSL_ERROR_NOT_SUPPORTED -274
#define SSL_ERROR_TIMEOUT -275 /* GBG */
#define SSL_ERROR_EOS -276 /* GBG */
#define SSL_X509_OFFSET -512
#define SSL_X509_ERROR(A) (SSL_X509_OFFSET+A)
/* alert types that are recognized */
#define SSL_ALERT_TYPE_WARNING 1
#define SLL_ALERT_TYPE_FATAL 2
/* these are all the alerts that are recognized */
#define SSL_ALERT_CLOSE_NOTIFY 0
#define SSL_ALERT_UNEXPECTED_MESSAGE 10
#define SSL_ALERT_BAD_RECORD_MAC 20
#define SSL_ALERT_HANDSHAKE_FAILURE 40
#define SSL_ALERT_BAD_CERTIFICATE 42
#define SSL_ALERT_ILLEGAL_PARAMETER 47
#define SSL_ALERT_DECODE_ERROR 50
#define SSL_ALERT_DECRYPT_ERROR 51
#define SSL_ALERT_INVALID_VERSION 70
#define SSL_ALERT_NO_RENEGOTIATION 100
/* The ciphers that are supported */
#define SSL_AES128_SHA 0x2f
#define SSL_AES256_SHA 0x35
#define SSL_RC4_128_SHA 0x05
#define SSL_RC4_128_MD5 0x04
/* build mode ids' */
#define SSL_BUILD_SKELETON_MODE 0x01
#define SSL_BUILD_SERVER_ONLY 0x02
#define SSL_BUILD_ENABLE_VERIFICATION 0x03
#define SSL_BUILD_ENABLE_CLIENT 0x04
#define SSL_BUILD_FULL_MODE 0x05
/* offsets to retrieve configuration information */
#define SSL_BUILD_MODE 0
#define SSL_MAX_CERT_CFG_OFFSET 1
#define SSL_MAX_CA_CERT_CFG_OFFSET 2
#define SSL_HAS_PEM 3
/* default session sizes */
#define SSL_DEFAULT_SVR_SESS 5
#define SSL_DEFAULT_CLNT_SESS 1
/* X.509/X.520 distinguished name types */
#define SSL_X509_CERT_COMMON_NAME 0
#define SSL_X509_CERT_ORGANIZATION 1
#define SSL_X509_CERT_ORGANIZATIONAL_NAME 2
#define SSL_X509_CA_CERT_COMMON_NAME 3
#define SSL_X509_CA_CERT_ORGANIZATION 4
#define SSL_X509_CA_CERT_ORGANIZATIONAL_NAME 5
/* SSL object loader types */
#define SSL_OBJ_X509_CERT 1
#define SSL_OBJ_X509_CACERT 2
#define SSL_OBJ_RSA_KEY 3
#define SSL_OBJ_PKCS8 4
#define SSL_OBJ_PKCS12 5
/**
* @defgroup c_api Standard C API
* @brief The standard interface in C.
* @{
*/
/**
* @brief Establish a new client/server context.
*
* This function is called before any client/server SSL connections are made.
*
* Each new connection will use the this context's private key and
* certificate chain. If a different certificate chain is required, then a
* different context needs to be be used.
*
* There are two threading models supported - a single thread with one
* SSL_CTX can support any number of SSL connections - and multiple threads can
* support one SSL_CTX object each (the default). But if a single SSL_CTX
* object uses many SSL objects in individual threads, then the
* CONFIG_SSL_CTX_MUTEXING option needs to be configured.
*
* @param options [in] Any particular options. At present the options
* supported are:
* - SSL_SERVER_VERIFY_LATER (client only): Don't stop a handshake if the server
* authentication fails. The certificate can be authenticated later with a
* call to ssl_verify_cert().
* - SSL_CLIENT_AUTHENTICATION (server only): Enforce client authentication
* i.e. each handshake will include a "certificate request" message from the
* server. Only available if verification has been enabled.
* - SSL_DISPLAY_BYTES (full mode build only): Display the byte sequences
* during the handshake.
* - SSL_DISPLAY_STATES (full mode build only): Display the state changes
* during the handshake.
* - SSL_DISPLAY_CERTS (full mode build only): Display the certificates that
* are passed during a handshake.
* - SSL_DISPLAY_RSA (full mode build only): Display the RSA key details that
* are passed during a handshake.
* - SSL_CONNECT_IN_PARTS (client only): To use a non-blocking version of
* ssl_client_new().
* @param num_sessions [in] The number of sessions to be used for session
* caching. If this value is 0, then there is no session caching. This option
* is not used in skeleton mode.
* @return A client/server context.
*/
EXP_FUNC SSL_CTX * STDCALL ssl_ctx_new(uint32_t options, int num_sessions);
/**
* @brief Remove a client/server context.
*
* Frees any used resources used by this context. Each connection will be
* sent a "Close Notify" alert (if possible).
* @param ssl_ctx [in] The client/server context.
*/
EXP_FUNC void STDCALL ssl_ctx_free(SSL_CTX *ssl_ctx);
/**
* @brief (server only) Establish a new SSL connection to an SSL client.
*
* It is up to the application to establish the logical connection (whether it
* is a socket, serial connection etc).
* @param ssl_ctx [in] The server context.
* @param client_fd [in] The client's file descriptor.
* @return An SSL object reference.
*/
EXP_FUNC SSL * STDCALL ssl_server_new(SSL_CTX *ssl_ctx, SSL_SOCKET* client_fd);
/**
* @brief (client only) Establish a new SSL connection to an SSL server.
*
* It is up to the application to establish the initial logical connection
* (whether it is a socket, serial connection etc).
*
* This is a normally a blocking call - it will finish when the handshake is
* complete (or has failed). To use in non-blocking mode, set
* SSL_CONNECT_IN_PARTS in ssl_ctx_new().
* @param ssl_ctx [in] The client context.
* @param client_fd [in] The client's file descriptor.
* @param session_id [in] A 32 byte session id for session resumption. This
* can be null if no session resumption is being used or required. This option
* is not used in skeleton mode.
* @param sess_id_size The size of the session id (max 32)
* @return An SSL object reference. Use ssl_handshake_status() to check
* if a handshake succeeded.
*/
EXP_FUNC SSL * STDCALL ssl_client_new(SSL_CTX *ssl_ctx, SSL_SOCKET* client_fd, const uint8_t *session_id, uint8_t sess_id_size);
/**
* @brief Free any used resources on this connection.
* A "Close Notify" message is sent on this connection (if possible). It is up
* to the application to close the socket or file descriptor.
* @param ssl [in] The ssl object reference.
*/
EXP_FUNC void STDCALL ssl_free(SSL *ssl);
/**
* @brief Read the SSL data stream.
* If the socket is non-blocking and data is blocked then SSO_OK will be
* returned.
* @param ssl [in] An SSL object reference.
* @param in_data [out] If the read was successful, a pointer to the read
* buffer will be here. Do NOT ever free this memory as this buffer is used in
- * sucessive calls. If the call was unsuccessful, this value will be null.
+ * successive calls. If the call was unsuccessful, this value will be null.
* @return The number of decrypted bytes:
* - if > 0, then the handshaking is complete and we are returning the number
* of decrypted bytes.
* - SSL_OK if the handshaking stage is successful (but not yet complete).
* - < 0 if an error.
* @see ssl.h for the error code list.
* @note Use in_data before doing any successive ssl calls.
*/
EXP_FUNC int STDCALL ssl_read(SSL *ssl, uint8_t **in_data);
/**
* @brief Write to the SSL data stream.
* if the socket is non-blocking and data is blocked then a check is made
* to ensure that all data is sent (i.e. blocked mode is forced).
* @param ssl [in] An SSL obect reference.
* @param out_data [in] The data to be written
* @param out_len [in] The number of bytes to be written.
* @return The number of bytes sent, or if < 0 if an error.
* @see ssl.h for the error code list.
*/
EXP_FUNC int STDCALL ssl_write(SSL *ssl, const uint8_t *out_data, int out_len);
/**
* @brief Find an ssl object based on a file descriptor.
*
* Goes through the list of SSL objects maintained in a client/server context
* to look for a file descriptor match.
* @param ssl_ctx [in] The client/server context.
* @param client_fd [in] The file descriptor.
* @return A reference to the SSL object. Returns null if the object could not
* be found.
*/
EXP_FUNC SSL * STDCALL ssl_find(SSL_CTX *ssl_ctx, SSL_SOCKET* client_fd);
/**
* @brief Get the session id for a handshake.
*
* This will be a 32 byte sequence and is available after the first
* handshaking messages are sent.
* @param ssl [in] An SSL object reference.
* @return The session id as a 32 byte sequence.
* @note A SSLv23 handshake may have only 16 valid bytes.
*/
EXP_FUNC const uint8_t * STDCALL ssl_get_session_id(const SSL *ssl);
/**
* @brief Get the session id size for a handshake.
*
* This will normally be 32 but could be 0 (no session id) or something else.
* @param ssl [in] An SSL object reference.
* @return The size of the session id.
*/
EXP_FUNC uint8_t STDCALL ssl_get_session_id_size(const SSL *ssl);
/**
* @brief Return the cipher id (in the SSL form).
* @param ssl [in] An SSL object reference.
* @return The cipher id. This will be one of the following:
* - SSL_AES128_SHA (0x2f)
* - SSL_AES256_SHA (0x35)
* - SSL_RC4_128_SHA (0x05)
* - SSL_RC4_128_MD5 (0x04)
*/
EXP_FUNC uint8_t STDCALL ssl_get_cipher_id(const SSL *ssl);
/**
* @brief Return the status of the handshake.
* @param ssl [in] An SSL object reference.
* @return SSL_OK if the handshake is complete and ok.
* @see ssl.h for the error code list.
*/
EXP_FUNC int STDCALL ssl_handshake_status(const SSL *ssl);
/**
* @brief Retrieve various parameters about the axTLS engine.
* @param offset [in] The configuration offset. It will be one of the following:
* - SSL_BUILD_MODE The build mode. This will be one of the following:
* - SSL_BUILD_SERVER_ONLY (basic server mode)
* - SSL_BUILD_ENABLE_VERIFICATION (server can do client authentication)
* - SSL_BUILD_ENABLE_CLIENT (client/server capabilties)
* - SSL_BUILD_FULL_MODE (client/server with diagnostics)
* - SSL_BUILD_SKELETON_MODE (skeleton mode)
* - SSL_MAX_CERT_CFG_OFFSET The maximum number of certificates allowed.
* - SSL_MAX_CA_CERT_CFG_OFFSET The maximum number of CA certificates allowed.
* - SSL_HAS_PEM 1 if supported
* @return The value of the requested parameter.
*/
EXP_FUNC int STDCALL ssl_get_config(int offset);
/**
* @brief Display why the handshake failed.
*
* This call is only useful in a 'full mode' build. The output is to stdout.
* @param error_code [in] An error code.
* @see ssl.h for the error code list.
*/
EXP_FUNC void STDCALL ssl_display_error(int error_code);
/**
* @brief Authenticate a received certificate.
*
* This call is usually made by a client after a handshake is complete and the
* context is in SSL_SERVER_VERIFY_LATER mode.
* @param ssl [in] An SSL object reference.
* @return SSL_OK if the certificate is verified.
*/
EXP_FUNC int STDCALL ssl_verify_cert(const SSL *ssl);
/**
* @brief Retrieve an X.509 distinguished name component.
*
* When a handshake is complete and a certificate has been exchanged, then the
* details of the remote certificate can be retrieved.
*
* This will usually be used by a client to check that the server's common
* name matches the URL.
*
* @param ssl [in] An SSL_X509_CERT object reference. [GBG: modified]
* @param component [in] one of:
* - SSL_X509_CERT_COMMON_NAME
* - SSL_X509_CERT_ORGANIZATION
* - SSL_X509_CERT_ORGANIZATIONAL_NAME
* - SSL_X509_CA_CERT_COMMON_NAME
* - SSL_X509_CA_CERT_ORGANIZATION
* - SSL_X509_CA_CERT_ORGANIZATIONAL_NAME
* @return The appropriate string (or null if not defined)
* @note Verification build mode must be enabled.
*/
/* GBG: modified */
EXP_FUNC const char * STDCALL ssl_cert_get_dn(const SSL_X509_CERT *cert, int component);
/**
* @brief Retrieve a Subject Alternative DNSName
*
* When a handshake is complete and a certificate has been exchanged, then the
* details of the remote certificate can be retrieved.
*
* This will usually be used by a client to check that the server's DNS
* name matches the URL.
*
* @param ssl [in] An SSL_X509_CERT object reference. [GBG: modified]
* @param dnsindex [in] The index of the DNS name to retrieve.
* @return The appropriate string (or null if not defined)
* @note Verification build mode must be enabled.
*/
/* GBG: modified */
EXP_FUNC const char * STDCALL ssl_cert_get_subject_alt_dnsname(const SSL_X509_CERT *cert, int dnsindex);
/* GBG added */
EXP_FUNC const SSL_X509_CERT* ssl_get_peer_cert(const SSL* ssl, unsigned int position);
EXP_FUNC void ssl_cert_get_fingerprints(const SSL_X509_CERT *cert, unsigned char* md5, unsigned char* sha1);
EXP_FUNC void ssl_cert_get_validity_dates(const SSL_X509_CERT *cert, SSL_DateTime* not_before, SSL_DateTime* not_after);
/**
* @brief Force the client to perform its handshake again.
*
* For a client this involves sending another "client hello" message.
* For the server is means sending a "hello request" message.
*
* This is a blocking call on the client (until the handshake completes).
*
* @param ssl [in] An SSL object reference.
* @return SSL_OK if renegotiation instantiation was ok
*/
EXP_FUNC int STDCALL ssl_renegotiate(SSL *ssl);
/**
* @brief Process a file that is in binary DER or ASCII PEM format.
*
* These are temporary objects that are used to load private keys,
* certificates etc into memory.
* @param ssl_ctx [in] The client/server context.
* @param obj_type [in] The format of the file. Can be one of:
* - SSL_OBJ_X509_CERT (no password required)
* - SSL_OBJ_X509_CACERT (no password required)
* - SSL_OBJ_RSA_KEY (AES128/AES256 PEM encryption supported)
* - SSL_OBJ_PKCS8 (RC4-128 encrypted data supported)
* - SSL_OBJ_PKCS12 (RC4-128 encrypted data supported)
*
* PEM files are automatically detected (if supported). The object type is
* also detected, and so is not relevant for these types of files.
* @param filename [in] The location of a file in DER/PEM format.
* @param password [in] The password used. Can be null if not required.
* @return SSL_OK if all ok
* @note Not available in skeleton build mode.
*/
EXP_FUNC int STDCALL ssl_obj_load(SSL_CTX *ssl_ctx, int obj_type, const char *filename, const char *password);
/**
* @brief Process binary data.
*
* These are temporary objects that are used to load private keys,
* certificates etc into memory.
* @param ssl_ctx [in] The client/server context.
* @param obj_type [in] The format of the memory data.
* @param data [in] The binary data to be loaded.
* @param len [in] The amount of data to be loaded.
* @param password [in] The password used. Can be null if not required.
* @return SSL_OK if all ok
* @see ssl_obj_load for more details on obj_type.
*/
EXP_FUNC int STDCALL ssl_obj_memory_load(SSL_CTX *ssl_ctx, int obj_type, const uint8_t *data, int len, const char *password);
#ifdef CONFIG_SSL_GENERATE_X509_CERT
/**
* @brief Create an X.509 certificate.
*
* This certificate is a self-signed v1 cert with a fixed start/stop validity
* times. It is signed with an internal private key in ssl_ctx.
*
* @param ssl_ctx [in] The client/server context.
* @param options [in] Not used yet.
* @param dn [in] An array of distinguished name strings. The array is defined
* by:
* - SSL_X509_CERT_COMMON_NAME (0)
* - If SSL_X509_CERT_COMMON_NAME is empty or not defined, then the
* hostname will be used.
* - SSL_X509_CERT_ORGANIZATION (1)
* - If SSL_X509_CERT_ORGANIZATION is empty or not defined, then $USERNAME
* will be used.
* - SSL_X509_CERT_ORGANIZATIONAL_NAME (2)
* - SSL_X509_CERT_ORGANIZATIONAL_NAME is optional.
* @param cert_data [out] The certificate as a sequence of bytes.
* @return < 0 if an error, or the size of the certificate in bytes.
* @note cert_data must be freed when there is no more need for it.
*/
EXP_FUNC int STDCALL ssl_x509_create(SSL_CTX *ssl_ctx, uint32_t options, const char * dn[], uint8_t **cert_data);
#endif
/**
* @brief Return the axTLS library version as a string.
*/
EXP_FUNC const char * STDCALL ssl_version(void);
EXP_FUNC void ssl_mem_free(void* mem); /* GBG */
/** @} */
#ifdef __cplusplus
}
#endif
#endif
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/tls1.c b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/tls1.c
index d8eaf94972..f3edb67acf 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/tls1.c
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/axTLS/ssl/tls1.c
@@ -1,2271 +1,2271 @@
/*
* Copyright (c) 2007, Cameron Rich
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of the axTLS project nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Common ssl/tlsv1 code to both the client and server implementations.
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include "os_port.h"
#include "ssl.h"
/* The session expiry time */
#define SSL_EXPIRY_TIME (CONFIG_SSL_EXPIRY_TIME*3600)
static const uint8_t g_hello_request[] = { HS_HELLO_REQUEST, 0, 0, 0 };
static const uint8_t g_chg_cipher_spec_pkt[] = { 1 };
static const char * server_finished = "server finished";
static const char * client_finished = "client finished";
static int do_handshake(SSL *ssl, uint8_t *buf, int read_len);
static int set_key_block(SSL *ssl, int is_write);
static int verify_digest(SSL *ssl, int mode, const uint8_t *buf, int read_len);
static void *crypt_new(SSL *ssl, uint8_t *key, uint8_t *iv, int is_decrypt);
static int send_raw_packet(SSL *ssl, uint8_t protocol);
/**
* The server will pick the cipher based on the order that the order that the
* ciphers are listed. This order is defined at compile time.
*/
#ifdef CONFIG_SSL_SKELETON_MODE
const uint8_t ssl_prot_prefs[NUM_PROTOCOLS] =
{ SSL_RC4_128_SHA };
#else
static void session_free(SSL_SESSION *ssl_sessions[], int sess_index);
const uint8_t ssl_prot_prefs[NUM_PROTOCOLS] =
#ifdef CONFIG_SSL_PROT_LOW /* low security, fast speed */
{ SSL_RC4_128_SHA, SSL_AES128_SHA, SSL_AES256_SHA, SSL_RC4_128_MD5 };
#elif CONFIG_SSL_PROT_MEDIUM /* medium security, medium speed */
{ SSL_AES128_SHA, SSL_AES256_SHA, SSL_RC4_128_SHA, SSL_RC4_128_MD5 };
#else /* CONFIG_SSL_PROT_HIGH */ /* high security, low speed */
{ SSL_AES256_SHA, SSL_AES128_SHA, SSL_RC4_128_SHA, SSL_RC4_128_MD5 };
#endif
#endif /* CONFIG_SSL_SKELETON_MODE */
/**
* The cipher map containing all the essentials for each cipher.
*/
#ifdef CONFIG_SSL_SKELETON_MODE
static const cipher_info_t cipher_info[NUM_PROTOCOLS] =
{
{ /* RC4-SHA */
SSL_RC4_128_SHA, /* RC4-SHA */
16, /* key size */
0, /* iv size */
2*(SHA1_SIZE+16), /* key block size */
0, /* no padding */
SHA1_SIZE, /* digest size */
hmac_sha1, /* hmac algorithm */
(crypt_func)RC4_crypt, /* encrypt */
(crypt_func)RC4_crypt /* decrypt */
},
};
#else
static const cipher_info_t cipher_info[NUM_PROTOCOLS] =
{
{ /* AES128-SHA */
SSL_AES128_SHA, /* AES128-SHA */
16, /* key size */
16, /* iv size */
2*(SHA1_SIZE+16+16), /* key block size */
16, /* block padding size */
SHA1_SIZE, /* digest size */
hmac_sha1, /* hmac algorithm */
(crypt_func)AES_cbc_encrypt, /* encrypt */
(crypt_func)AES_cbc_decrypt /* decrypt */
},
{ /* AES256-SHA */
SSL_AES256_SHA, /* AES256-SHA */
32, /* key size */
16, /* iv size */
2*(SHA1_SIZE+32+16), /* key block size */
16, /* block padding size */
SHA1_SIZE, /* digest size */
hmac_sha1, /* hmac algorithm */
(crypt_func)AES_cbc_encrypt, /* encrypt */
(crypt_func)AES_cbc_decrypt /* decrypt */
},
{ /* RC4-SHA */
SSL_RC4_128_SHA, /* RC4-SHA */
16, /* key size */
0, /* iv size */
2*(SHA1_SIZE+16), /* key block size */
0, /* no padding */
SHA1_SIZE, /* digest size */
hmac_sha1, /* hmac algorithm */
(crypt_func)RC4_crypt, /* encrypt */
(crypt_func)RC4_crypt /* decrypt */
},
/*
* This protocol is from SSLv2 days and is unlikely to be used - but was
* useful for testing different possible digest algorithms.
*/
{ /* RC4-MD5 */
SSL_RC4_128_MD5, /* RC4-MD5 */
16, /* key size */
0, /* iv size */
2*(MD5_SIZE+16), /* key block size */
0, /* no padding */
MD5_SIZE, /* digest size */
hmac_md5, /* hmac algorithm */
(crypt_func)RC4_crypt, /* encrypt */
(crypt_func)RC4_crypt /* decrypt */
},
};
#endif
static void prf(const uint8_t *sec, int sec_len, uint8_t *seed, int seed_len,
uint8_t *out, int olen);
static const cipher_info_t *get_cipher_info(uint8_t cipher);
static void increment_read_sequence(SSL *ssl);
static void increment_write_sequence(SSL *ssl);
static void add_hmac_digest(SSL *ssl, int snd, uint8_t *hmac_header,
const uint8_t *buf, int buf_len, uint8_t *hmac_buf);
/* win32 VC6.0 doesn't have variadic macros */
/* GBG added
//#if defined(WIN32) && !defined(CONFIG_SSL_FULL_MODE)
//void DISPLAY_BYTES(SSL *ssl, const char *format,
// const uint8_t *data, int size, ...) {}
//#endif */
void DISPLAY_BYTES(SSL *ssl, const char *format,
const uint8_t *data, int size, ...) {
(void)ssl;
(void)format;
(void)data;
(void)size;
}
/* /GBG */
/**
* Establish a new client/server context.
*/
EXP_FUNC SSL_CTX *STDCALL ssl_ctx_new(uint32_t options, int num_sessions)
{
SSL_CTX *ssl_ctx = (SSL_CTX *)calloc(1, sizeof (SSL_CTX));
ssl_ctx->options = options;
RNG_initialize();
#if 0 /* GBG: no automatic cert loading */
if (load_key_certs(ssl_ctx) < 0)
{
free(ssl_ctx); /* can't load our key/certificate pair, so die */
return NULL;
}
#endif
#ifndef CONFIG_SSL_SKELETON_MODE
ssl_ctx->num_sessions = num_sessions;
#endif
SSL_CTX_MUTEX_INIT(ssl_ctx->mutex);
#ifndef CONFIG_SSL_SKELETON_MODE
if (num_sessions)
{
ssl_ctx->ssl_sessions = (SSL_SESSION **)
calloc(1, num_sessions*sizeof(SSL_SESSION *));
}
#endif
return ssl_ctx;
}
/*
* Remove a client/server context.
*/
EXP_FUNC void STDCALL ssl_ctx_free(SSL_CTX *ssl_ctx)
{
SSL *ssl;
int i;
if (ssl_ctx == NULL)
return;
ssl = ssl_ctx->head;
/* clear out all the ssl entries */
while (ssl)
{
SSL *next = ssl->next;
ssl_free(ssl);
ssl = next;
}
#ifndef CONFIG_SSL_SKELETON_MODE
/* clear out all the sessions */
for (i = 0; i < ssl_ctx->num_sessions; i++)
session_free(ssl_ctx->ssl_sessions, i);
free(ssl_ctx->ssl_sessions);
#endif
/* GBG: changed */
{
SSL_CERT* cert = ssl_ctx->certs;
while (cert) {
SSL_CERT* next = cert->next;
free(cert->buf);
free(cert);
cert = next;
}
}
#ifdef CONFIG_SSL_CERT_VERIFICATION
/* GBG: remove - remove_ca_certs(ssl_ctx->ca_cert_ctx); */
/* GBG: added */
if (ssl_ctx->ca_certs) x509_free(ssl_ctx->ca_certs);
/* GBG */
#endif
/* GBG: removed - ssl_ctx->chain_length = 0; */
SSL_CTX_MUTEX_DESTROY(ssl_ctx->mutex);
RSA_free(ssl_ctx->rsa_ctx);
RNG_terminate();
free(ssl_ctx);
}
/*
* Free any used resources used by this connection.
*/
EXP_FUNC void STDCALL ssl_free(SSL *ssl)
{
SSL_CTX *ssl_ctx;
if (ssl == NULL) /* just ignore null pointers */
return;
/* only notify if we weren't notified first */
/* spec says we must notify when we are dying */
if (!IS_SET_SSL_FLAG(SSL_SENT_CLOSE_NOTIFY))
send_alert(ssl, SSL_ALERT_CLOSE_NOTIFY);
ssl_ctx = ssl->ssl_ctx;
SSL_CTX_LOCK(ssl_ctx->mutex);
/* adjust the server SSL list */
if (ssl->prev)
ssl->prev->next = ssl->next;
else
ssl_ctx->head = ssl->next;
if (ssl->next)
ssl->next->prev = ssl->prev;
else
ssl_ctx->tail = ssl->prev;
SSL_CTX_UNLOCK(ssl_ctx->mutex);
/* may already be free - but be sure */
free(ssl->encrypt_ctx);
free(ssl->decrypt_ctx);
disposable_free(ssl);
#ifdef CONFIG_SSL_CERT_VERIFICATION
x509_free(ssl->x509_ctx);
#endif
free(ssl);
}
/*
* Read the SSL connection and send any alerts for various errors.
*/
EXP_FUNC int STDCALL ssl_read(SSL *ssl, uint8_t **in_data)
{
int ret = basic_read(ssl, in_data);
/* check for return code so we can send an alert */
if (ret < SSL_OK && ret != SSL_CLOSE_NOTIFY)
{
if (ret != SSL_ERROR_CONN_LOST &&
ret != SSL_ERROR_TIMEOUT && /* GBG */
ret != SSL_ERROR_EOS /* GBG */ )
{
send_alert(ssl, ret);
#ifndef CONFIG_SSL_SKELETON_MODE
/* something nasty happened, so get rid of this session */
kill_ssl_session(ssl->ssl_ctx->ssl_sessions, ssl);
#endif
}
}
return ret;
}
/*
* Write application data to the client
*/
EXP_FUNC int STDCALL ssl_write(SSL *ssl, const uint8_t *out_data, int out_len)
{
int n = out_len, nw, i, tot = 0;
/* maximum size of a TLS packet is around 16kB, so fragment */
do
{
nw = n;
if (nw > RT_MAX_PLAIN_LENGTH) /* fragment if necessary */
nw = RT_MAX_PLAIN_LENGTH;
if ((i = send_packet(ssl, PT_APP_PROTOCOL_DATA,
&out_data[tot], nw)) <= 0)
{
out_len = i; /* an error */
break;
}
tot += i;
n -= i;
} while (n > 0);
return out_len;
}
/**
* Add a certificate to the certificate chain.
*/
int add_cert(SSL_CTX *ssl_ctx, const uint8_t *buf, int len)
{
int ret = SSL_ERROR_NO_CERT_DEFINED;
SSL_CERT *ssl_cert;
SSL_CERT *ssl_cert_tail;
X509_CTX *cert = NULL;
int offset;
if ((ret = x509_new(buf, &offset, &cert)))
goto error;
#if defined (CONFIG_SSL_FULL_MODE)
if (ssl_ctx->options & SSL_DISPLAY_CERTS)
x509_print(cert, NULL);
#endif
/* GBG: modified */
ssl_cert = (SSL_CERT*)malloc(sizeof(SSL_CERT));
if (ssl_cert == NULL) {
ret = SSL_NOT_OK;
goto error;
}
ssl_cert_tail = ssl_ctx->certs;
while (ssl_cert_tail && ssl_cert_tail->next) {
ssl_cert_tail = ssl_cert_tail->next;
}
if (ssl_cert_tail == NULL) {
ssl_ctx->certs = ssl_cert;
} else {
ssl_cert_tail->next = ssl_cert;
}
ssl_cert->next = NULL;
ssl_cert->size = len;
ssl_cert->buf = (uint8_t *)malloc(len);
memcpy(ssl_cert->buf, buf, len);
/* GBG: removed - ssl_ctx->chain_length++; */
len -= offset;
ret = SSL_OK; /* ok so far */
/* recurse? */
if (len > 0)
{
ret = add_cert(ssl_ctx, &buf[offset], len);
}
error:
if (cert) x509_free(cert); /* don't need anymore */
return ret;
}
#ifdef CONFIG_SSL_CERT_VERIFICATION
#if 0 /* GBG: removed */
/**
* Add a certificate authority.
*/
int add_cert_auth(SSL_CTX *ssl_ctx, const uint8_t *buf, int len)
{
int ret = SSL_OK; /* ignore errors for now */
int i = 0;
CA_CERT_CTX *ca_cert_ctx;
if (ssl_ctx->ca_cert_ctx == NULL)
ssl_ctx->ca_cert_ctx = (CA_CERT_CTX *)calloc(1, sizeof(CA_CERT_CTX));
ca_cert_ctx = ssl_ctx->ca_cert_ctx;
while (i < CONFIG_X509_MAX_CA_CERTS && ca_cert_ctx->cert[i])
i++;
while (len > 0)
{
int offset;
if (i >= CONFIG_X509_MAX_CA_CERTS)
{
#ifdef CONFIG_SSL_FULL_MODE
printf("Error: maximum number of CA certs added (%d) - change of "
"compile-time configuration required\n",
CONFIG_X509_MAX_CA_CERTS);
#endif
break;
}
/* ignore the return code */
if (x509_new(buf, &offset, &ca_cert_ctx->cert[i]) == X509_OK)
{
#if defined (CONFIG_SSL_FULL_MODE)
if (ssl_ctx->options & SSL_DISPLAY_CERTS)
x509_print(ca_cert_ctx->cert[i], NULL);
#endif
}
i++;
len -= offset;
}
return ret;
}
#else /* GBG */
/* GBG: added */
int add_cert_auth(SSL_CTX *ssl_ctx, const uint8_t *cert_data, int cert_data_size)
{
X509_CTX* ca_cert = NULL;
int x509_size = cert_data_size;
int result;
result = x509_new(cert_data, &x509_size, &ca_cert);
if (result != SSL_OK) return SSL_X509_ERROR(result);
ca_cert->next = ssl_ctx->ca_certs;
ssl_ctx->ca_certs = ca_cert;
return SSL_OK;
}
#endif /* GBG */
/* GBG added */
EXP_FUNC const X509_CTX* ssl_get_peer_cert(const SSL* ssl, unsigned int position)
{
X509_CTX* cert = ssl->x509_ctx;
/* look for the cert at the requested position */
while (position && cert) {
cert = cert->next;
--position;
}
if (position) return NULL;
/* past the last cert, check for a cert in the list of trust anchors */
if (cert == NULL && ssl->x509_ctx && ssl->ssl_ctx) {
X509_CTX* ca_cert = ssl->ssl_ctx->ca_certs;
cert = ssl->x509_ctx;
while (cert->next) cert = cert->next;
while (ca_cert) {
if (asn1_compare_dn(cert->ca_cert_dn, ca_cert->cert_dn) == 0) {
return ca_cert;
}
ca_cert = ca_cert->next;
}
}
return cert;
}
/*
* Retrieve an X.509 distinguished name component
*/
EXP_FUNC const char * STDCALL ssl_cert_get_dn(const X509_CTX* cert, int component)
{
if (cert == NULL) return NULL;
switch (component)
{
case SSL_X509_CERT_COMMON_NAME:
return cert->cert_dn[X509_COMMON_NAME];
case SSL_X509_CERT_ORGANIZATION:
return cert->cert_dn[X509_ORGANIZATION];
case SSL_X509_CERT_ORGANIZATIONAL_NAME:
return cert->cert_dn[X509_ORGANIZATIONAL_UNIT];
case SSL_X509_CA_CERT_COMMON_NAME:
return cert->ca_cert_dn[X509_COMMON_NAME];
case SSL_X509_CA_CERT_ORGANIZATION:
return cert->ca_cert_dn[X509_ORGANIZATION];
case SSL_X509_CA_CERT_ORGANIZATIONAL_NAME:
return cert->ca_cert_dn[X509_ORGANIZATIONAL_UNIT];
default:
return NULL;
}
}
/*
* Retrieve a "Subject Alternative Name" from a v3 certificate
*/
EXP_FUNC const char * STDCALL ssl_cert_get_subject_alt_dnsname(const X509_CTX* cert,
int dnsindex)
{
int i;
if (cert == NULL || cert->subject_alt_dnsnames == NULL)
return NULL;
for (i = 0; i < dnsindex; ++i)
{
if (cert->subject_alt_dnsnames[i] == NULL)
return NULL;
}
return cert->subject_alt_dnsnames[dnsindex];
}
/* GBG added */
EXP_FUNC void ssl_cert_get_fingerprints(const X509_CTX* cert, unsigned char* md5, unsigned char* sha1)
{
if (cert == NULL) {
memset(md5, 0, MD5_SIZE);
memset(sha1, 0, SHA1_SIZE);
return;
}
memcpy(md5, cert->fingerprint.md5, MD5_SIZE);
memcpy(sha1, cert->fingerprint.sha1, SHA1_SIZE);
}
EXP_FUNC void ssl_cert_get_validity_dates(const X509_CTX* cert, SSL_DateTime* not_before, SSL_DateTime* not_after)
{
if (cert == NULL) {
memset(not_before, 0, sizeof(SSL_DateTime));
memset(not_after, 0, sizeof(SSL_DateTime));
return;
}
*not_before = cert->not_before;
*not_after = cert->not_after;
}
/* /GBG added */
#endif /* CONFIG_SSL_CERT_VERIFICATION */
/*
* Find an ssl object based on the client's file descriptor.
*/
EXP_FUNC SSL * STDCALL ssl_find(SSL_CTX *ssl_ctx, SSL_SOCKET* client_fd)
{
SSL *ssl;
SSL_CTX_LOCK(ssl_ctx->mutex);
ssl = ssl_ctx->head;
/* search through all the ssl entries */
while (ssl)
{
if (ssl->client_fd == client_fd)
{
SSL_CTX_UNLOCK(ssl_ctx->mutex);
return ssl;
}
ssl = ssl->next;
}
SSL_CTX_UNLOCK(ssl_ctx->mutex);
return NULL;
}
/*
* Force the client to perform its handshake again.
*/
EXP_FUNC int STDCALL ssl_renegotiate(SSL *ssl)
{
int ret = SSL_OK;
disposable_new(ssl);
#ifdef CONFIG_SSL_ENABLE_CLIENT
if (IS_SET_SSL_FLAG(SSL_IS_CLIENT))
{
ret = do_client_connect(ssl);
}
else
#endif
{
send_packet(ssl, PT_HANDSHAKE_PROTOCOL,
g_hello_request, sizeof(g_hello_request));
SET_SSL_FLAG(SSL_NEED_RECORD);
}
return ret;
}
/**
* @brief Get what we need for key info.
* @param cipher [in] The cipher information we are after
* @param key_size [out] The key size for the cipher
* @param iv_size [out] The iv size for the cipher
* @return The amount of key information we need.
*/
static const cipher_info_t *get_cipher_info(uint8_t cipher)
{
int i;
for (i = 0; i < NUM_PROTOCOLS; i++)
{
if (cipher_info[i].cipher == cipher)
{
return &cipher_info[i];
}
}
return NULL; /* error */
}
/*
* Get a new ssl context for a new connection.
*/
SSL *ssl_new(SSL_CTX *ssl_ctx, void* client_fd)
{
SSL *ssl = (SSL *)calloc(1, sizeof(SSL));
ssl->ssl_ctx = ssl_ctx;
ssl->need_bytes = SSL_RECORD_SIZE; /* need a record */
ssl->client_fd = client_fd;
ssl->flag = SSL_NEED_RECORD;
ssl->bm_data = ssl->bm_all_data+BM_RECORD_OFFSET; /* space at the start */
ssl->hs_status = SSL_NOT_OK; /* not connected */
#ifdef CONFIG_ENABLE_VERIFICATION
ssl->ca_cert_ctx = ssl_ctx->ca_cert_ctx;
#endif
disposable_new(ssl);
/* a bit hacky but saves a few bytes of memory */
ssl->flag |= ssl_ctx->options;
SSL_CTX_LOCK(ssl_ctx->mutex);
if (ssl_ctx->head == NULL)
{
ssl_ctx->head = ssl;
ssl_ctx->tail = ssl;
}
else
{
ssl->prev = ssl_ctx->tail;
ssl_ctx->tail->next = ssl;
ssl_ctx->tail = ssl;
}
SSL_CTX_UNLOCK(ssl_ctx->mutex);
return ssl;
}
/*
* Add a private key to a context.
*/
int add_private_key(SSL_CTX *ssl_ctx, SSLObjLoader *ssl_obj)
{
int ret = SSL_OK;
/* get the private key details */
if (asn1_get_private_key(ssl_obj->buf, ssl_obj->len, &ssl_ctx->rsa_ctx))
{
ret = SSL_ERROR_INVALID_KEY;
goto error;
}
error:
return ret;
}
/**
* Increment the read sequence number (as a 64 bit endian indepenent #)
*/
static void increment_read_sequence(SSL *ssl)
{
int i;
for (i = 7; i >= 0; i--)
{
if (++ssl->read_sequence[i])
break;
}
}
/**
* Increment the read sequence number (as a 64 bit endian indepenent #)
*/
static void increment_write_sequence(SSL *ssl)
{
int i;
for (i = 7; i >= 0; i--)
{
if (++ssl->write_sequence[i])
break;
}
}
/**
* Work out the HMAC digest in a packet.
*/
static void add_hmac_digest(SSL *ssl, int mode, uint8_t *hmac_header,
const uint8_t *buf, int buf_len, uint8_t *hmac_buf)
{
int hmac_len = buf_len + 8 + SSL_RECORD_SIZE;
uint8_t *t_buf = (uint8_t *)alloca(hmac_len+10);
memcpy(t_buf, (mode == SSL_SERVER_WRITE || mode == SSL_CLIENT_WRITE) ?
ssl->write_sequence : ssl->read_sequence, 8);
memcpy(&t_buf[8], hmac_header, SSL_RECORD_SIZE);
memcpy(&t_buf[8+SSL_RECORD_SIZE], buf, buf_len);
ssl->cipher_info->hmac(t_buf, hmac_len,
(mode == SSL_SERVER_WRITE || mode == SSL_CLIENT_READ) ?
ssl->server_mac : ssl->client_mac,
ssl->cipher_info->digest_size, hmac_buf);
#if 0
print_blob("record", hmac_header, SSL_RECORD_SIZE);
print_blob("buf", buf, buf_len);
if (mode == SSL_SERVER_WRITE || mode == SSL_CLIENT_WRITE)
{
print_blob("write seq", ssl->write_sequence, 8);
}
else
{
print_blob("read seq", ssl->read_sequence, 8);
}
if (mode == SSL_SERVER_WRITE || mode == SSL_CLIENT_READ)
{
print_blob("server mac",
ssl->server_mac, ssl->cipher_info->digest_size);
}
else
{
print_blob("client mac",
ssl->client_mac, ssl->cipher_info->digest_size);
}
print_blob("hmac", hmac_buf, SHA1_SIZE);
#endif
}
/**
* Verify that the digest of a packet is correct.
*/
static int verify_digest(SSL *ssl, int mode, const uint8_t *buf, int read_len)
{
uint8_t hmac_buf[SHA1_SIZE];
int hmac_offset;
if (ssl->cipher_info->padding_size)
{
int last_blk_size = buf[read_len-1], i;
hmac_offset = read_len-last_blk_size-ssl->cipher_info->digest_size-1;
/* guard against a timing attack - make sure we do the digest */
if (hmac_offset < 0)
{
hmac_offset = 0;
}
else
{
/* already looked at last byte */
for (i = 1; i < last_blk_size; i++)
{
if (buf[read_len-i] != last_blk_size)
{
hmac_offset = 0;
break;
}
}
}
}
else /* stream cipher */
{
hmac_offset = read_len - ssl->cipher_info->digest_size;
if (hmac_offset < 0)
{
hmac_offset = 0;
}
}
/* sanity check the offset */
ssl->hmac_header[3] = hmac_offset >> 8; /* insert size */
ssl->hmac_header[4] = hmac_offset & 0xff;
add_hmac_digest(ssl, mode, ssl->hmac_header, buf, hmac_offset, hmac_buf);
if (memcmp(hmac_buf, &buf[hmac_offset], ssl->cipher_info->digest_size))
{
return SSL_ERROR_INVALID_HMAC;
}
return hmac_offset;
}
/**
* Add a packet to the end of our sent and received packets, so that we may use
* it to calculate the hash at the end.
*/
void add_packet(SSL *ssl, const uint8_t *pkt, int len)
{
MD5_Update(&ssl->dc->md5_ctx, pkt, len);
SHA1_Update(&ssl->dc->sha1_ctx, pkt, len);
}
/**
* Work out the MD5 PRF.
*/
static void p_hash_md5(const uint8_t *sec, int sec_len,
uint8_t *seed, int seed_len, uint8_t *out, int olen)
{
uint8_t a1[128];
/* A(1) */
hmac_md5(seed, seed_len, sec, sec_len, a1);
memcpy(&a1[MD5_SIZE], seed, seed_len);
hmac_md5(a1, MD5_SIZE+seed_len, sec, sec_len, out);
while (olen > MD5_SIZE)
{
uint8_t a2[MD5_SIZE];
out += MD5_SIZE;
olen -= MD5_SIZE;
/* A(N) */
hmac_md5(a1, MD5_SIZE, sec, sec_len, a2);
memcpy(a1, a2, MD5_SIZE);
/* work out the actual hash */
hmac_md5(a1, MD5_SIZE+seed_len, sec, sec_len, out);
}
}
/**
* Work out the SHA1 PRF.
*/
static void p_hash_sha1(const uint8_t *sec, int sec_len,
uint8_t *seed, int seed_len, uint8_t *out, int olen)
{
uint8_t a1[128];
/* A(1) */
hmac_sha1(seed, seed_len, sec, sec_len, a1);
memcpy(&a1[SHA1_SIZE], seed, seed_len);
hmac_sha1(a1, SHA1_SIZE+seed_len, sec, sec_len, out);
while (olen > SHA1_SIZE)
{
uint8_t a2[SHA1_SIZE];
out += SHA1_SIZE;
olen -= SHA1_SIZE;
/* A(N) */
hmac_sha1(a1, SHA1_SIZE, sec, sec_len, a2);
memcpy(a1, a2, SHA1_SIZE);
/* work out the actual hash */
hmac_sha1(a1, SHA1_SIZE+seed_len, sec, sec_len, out);
}
}
/**
* Work out the PRF.
*/
static void prf(const uint8_t *sec, int sec_len, uint8_t *seed, int seed_len,
uint8_t *out, int olen)
{
int len, i;
const uint8_t *S1, *S2;
uint8_t xbuf[256]; /* needs to be > the amount of key data */
uint8_t ybuf[256]; /* needs to be > the amount of key data */
len = sec_len/2;
S1 = sec;
S2 = &sec[len];
len += (sec_len & 1); /* add for odd, make longer */
p_hash_md5(S1, len, seed, seed_len, xbuf, olen);
p_hash_sha1(S2, len, seed, seed_len, ybuf, olen);
for (i = 0; i < olen; i++)
out[i] = xbuf[i] ^ ybuf[i];
}
/**
* Generate a master secret based on the client/server random data and the
* premaster secret.
*/
void generate_master_secret(SSL *ssl, const uint8_t *premaster_secret)
{
uint8_t buf[128]; /* needs to be > 13+32+32 in size */
memcpy(buf, "master secret", 13);
memcpy(&buf[13], ssl->dc->client_random, SSL_RANDOM_SIZE);
memcpy(&buf[45], ssl->dc->server_random, SSL_RANDOM_SIZE);
prf(premaster_secret, SSL_SECRET_SIZE, buf, 77, ssl->dc->master_secret,
SSL_SECRET_SIZE);
}
/**
* Generate a 'random' blob of data used for the generation of keys.
*/
static void generate_key_block(uint8_t *client_random, uint8_t *server_random,
uint8_t *master_secret, uint8_t *key_block, int key_block_size)
{
uint8_t buf[128];
memcpy(buf, "key expansion", 13);
memcpy(&buf[13], server_random, SSL_RANDOM_SIZE);
memcpy(&buf[45], client_random, SSL_RANDOM_SIZE);
prf(master_secret, SSL_SECRET_SIZE, buf, 77, key_block, key_block_size);
}
/**
* Calculate the digest used in the finished message. This function also
* doubles up as a certificate verify function.
*/
void finished_digest(SSL *ssl, const char *label, uint8_t *digest)
{
uint8_t mac_buf[128];
uint8_t *q = mac_buf;
MD5_CTX md5_ctx = ssl->dc->md5_ctx;
SHA1_CTX sha1_ctx = ssl->dc->sha1_ctx;
if (label)
{
memcpy(q, label, (int)strlen(label));
q += strlen(label);
}
MD5_Final(q, &md5_ctx);
q += MD5_SIZE;
SHA1_Final(q, &sha1_ctx);
q += SHA1_SIZE;
if (label)
{
prf(ssl->dc->master_secret, SSL_SECRET_SIZE, mac_buf, (int)(q-mac_buf),
digest, SSL_FINISHED_HASH_SIZE);
}
else /* for use in a certificate verify */
{
memcpy(digest, mac_buf, MD5_SIZE + SHA1_SIZE);
}
#if 0
printf("label: %s\n", label);
print_blob("master secret", ssl->dc->master_secret, 48);
print_blob("mac_buf", mac_buf, q-mac_buf);
print_blob("finished digest", digest, SSL_FINISHED_HASH_SIZE);
#endif
}
/**
* Retrieve (and initialise) the context of a cipher.
*/
static void *crypt_new(SSL *ssl, uint8_t *key, uint8_t *iv, int is_decrypt)
{
switch (ssl->cipher)
{
#ifndef CONFIG_SSL_SKELETON_MODE
case SSL_AES128_SHA:
{
AES_CTX *aes_ctx = (AES_CTX *)malloc(sizeof(AES_CTX));
AES_set_key(aes_ctx, key, iv, AES_MODE_128);
if (is_decrypt)
{
AES_convert_key(aes_ctx);
}
return (void *)aes_ctx;
}
case SSL_AES256_SHA:
{
AES_CTX *aes_ctx = (AES_CTX *)malloc(sizeof(AES_CTX));
AES_set_key(aes_ctx, key, iv, AES_MODE_256);
if (is_decrypt)
{
AES_convert_key(aes_ctx);
}
return (void *)aes_ctx;
}
case SSL_RC4_128_MD5:
#endif
case SSL_RC4_128_SHA:
{
RC4_CTX *rc4_ctx = (RC4_CTX *)malloc(sizeof(RC4_CTX));
RC4_setup(rc4_ctx, key, 16);
return (void *)rc4_ctx;
}
}
return NULL; /* its all gone wrong */
}
/**
* Send a packet over the socket.
*/
static int send_raw_packet(SSL *ssl, uint8_t protocol)
{
uint8_t *rec_buf = ssl->bm_all_data;
int pkt_size = SSL_RECORD_SIZE+ssl->bm_index;
int sent = 0;
int ret = SSL_OK;
rec_buf[0] = protocol;
rec_buf[1] = 0x03; /* version = 3.1 or higher */
rec_buf[2] = ssl->version & 0x0f;
rec_buf[3] = ssl->bm_index >> 8;
rec_buf[4] = ssl->bm_index & 0xff;
DISPLAY_BYTES(ssl, "sending %d bytes", ssl->bm_all_data,
pkt_size, pkt_size);
while (sent < pkt_size)
{
ret = SOCKET_WRITE(ssl->client_fd,
&ssl->bm_all_data[sent], pkt_size-sent);
if (ret >= 0)
sent += ret;
else
{
/*ret = SSL_ERROR_CONN_LOST*/;
break;
}
}
SET_SSL_FLAG(SSL_NEED_RECORD); /* reset for next time */
ssl->bm_index = 0;
if (protocol != PT_APP_PROTOCOL_DATA)
{
/* always return SSL_OK during handshake */
ret = SSL_OK;
}
return ret;
}
/**
* Send an encrypted packet with padding bytes if necessary.
*/
int send_packet(SSL *ssl, uint8_t protocol, const uint8_t *in, int length)
{
int ret, msg_length = 0;
/* if our state is bad, don't bother */
if (ssl->hs_status == SSL_ERROR_DEAD)
return SSL_ERROR_CONN_LOST;
if (in) /* has the buffer already been initialised? */
{
memcpy(ssl->bm_data, in, length);
}
msg_length += length;
if (IS_SET_SSL_FLAG(SSL_TX_ENCRYPTED))
{
int mode = IS_SET_SSL_FLAG(SSL_IS_CLIENT) ?
SSL_CLIENT_WRITE : SSL_SERVER_WRITE;
- uint8_t hmac_header[SSL_RECORD_SIZE]; /* GBG: modified intializer */
+ uint8_t hmac_header[SSL_RECORD_SIZE]; /* GBG: modified initializer */
hmac_header[0] = protocol;
hmac_header[1] = 0x03; /* version = 3.1 or higher */
hmac_header[2] = ssl->version & 0x0f;
hmac_header[3] = msg_length >> 8;
hmac_header[4] = msg_length & 0xff;
if (protocol == PT_HANDSHAKE_PROTOCOL)
{
DISPLAY_STATE(ssl, 1, ssl->bm_data[0], 0);
if (ssl->bm_data[0] != HS_HELLO_REQUEST)
{
add_packet(ssl, ssl->bm_data, msg_length);
}
}
/* add the packet digest */
add_hmac_digest(ssl, mode, hmac_header, ssl->bm_data, msg_length,
&ssl->bm_data[msg_length]);
msg_length += ssl->cipher_info->digest_size;
/* add padding? */
if (ssl->cipher_info->padding_size)
{
int last_blk_size = msg_length%ssl->cipher_info->padding_size;
int pad_bytes = ssl->cipher_info->padding_size - last_blk_size;
/* ensure we always have at least 1 padding byte */
if (pad_bytes == 0)
pad_bytes += ssl->cipher_info->padding_size;
memset(&ssl->bm_data[msg_length], pad_bytes-1, pad_bytes);
msg_length += pad_bytes;
}
DISPLAY_BYTES(ssl, "unencrypted write", ssl->bm_data, msg_length);
increment_write_sequence(ssl);
/* add the explicit IV for TLS1.1 */
if (ssl->version >= SSL_PROTOCOL_VERSION1_1 &&
ssl->cipher_info->iv_size)
{
uint8_t iv_size = ssl->cipher_info->iv_size;
uint8_t *t_buf = alloca(msg_length + iv_size);
memcpy(t_buf + iv_size, ssl->bm_data, msg_length);
get_random(iv_size, t_buf);
msg_length += iv_size;
memcpy(ssl->bm_data, t_buf, msg_length);
}
/* now encrypt the packet */
ssl->cipher_info->encrypt(ssl->encrypt_ctx, ssl->bm_data,
ssl->bm_data, msg_length);
}
else if (protocol == PT_HANDSHAKE_PROTOCOL)
{
DISPLAY_STATE(ssl, 1, ssl->bm_data[0], 0);
if (ssl->bm_data[0] != HS_HELLO_REQUEST)
{
add_packet(ssl, ssl->bm_data, length);
}
}
ssl->bm_index = msg_length;
if ((ret = send_raw_packet(ssl, protocol)) <= 0)
return ret;
return length; /* just return what we wanted to send */
}
/**
* Work out the cipher keys we are going to use for this session based on the
* master secret.
*/
static int set_key_block(SSL *ssl, int is_write)
{
const cipher_info_t *ciph_info = get_cipher_info(ssl->cipher);
uint8_t *q;
uint8_t client_key[32], server_key[32]; /* big enough for AES256 */
uint8_t client_iv[16], server_iv[16]; /* big enough for AES128/256 */
int is_client = IS_SET_SSL_FLAG(SSL_IS_CLIENT);
if (ciph_info == NULL)
return -1;
/* only do once in a handshake */
if (ssl->dc->key_block == NULL)
{
ssl->dc->key_block = (uint8_t *)malloc(ciph_info->key_block_size);
#if 0
print_blob("client", ssl->dc->client_random, 32);
print_blob("server", ssl->dc->server_random, 32);
print_blob("master", ssl->dc->master_secret, SSL_SECRET_SIZE);
#endif
generate_key_block(ssl->dc->client_random, ssl->dc->server_random,
ssl->dc->master_secret, ssl->dc->key_block,
ciph_info->key_block_size);
#if 0
print_blob("keyblock", ssl->dc->key_block, ciph_info->key_block_size);
#endif
}
q = ssl->dc->key_block;
if ((is_client && is_write) || (!is_client && !is_write))
{
memcpy(ssl->client_mac, q, ciph_info->digest_size);
}
q += ciph_info->digest_size;
if ((!is_client && is_write) || (is_client && !is_write))
{
memcpy(ssl->server_mac, q, ciph_info->digest_size);
}
q += ciph_info->digest_size;
memcpy(client_key, q, ciph_info->key_size);
q += ciph_info->key_size;
memcpy(server_key, q, ciph_info->key_size);
q += ciph_info->key_size;
#ifndef CONFIG_SSL_SKELETON_MODE
if (ciph_info->iv_size) /* RC4 has no IV, AES does */
{
memcpy(client_iv, q, ciph_info->iv_size);
q += ciph_info->iv_size;
memcpy(server_iv, q, ciph_info->iv_size);
q += ciph_info->iv_size;
}
#endif
free(is_write ? ssl->encrypt_ctx : ssl->decrypt_ctx);
/* now initialise the ciphers */
if (is_client)
{
finished_digest(ssl, server_finished, ssl->dc->final_finish_mac);
if (is_write)
ssl->encrypt_ctx = crypt_new(ssl, client_key, client_iv, 0);
else
ssl->decrypt_ctx = crypt_new(ssl, server_key, server_iv, 1);
}
else
{
finished_digest(ssl, client_finished, ssl->dc->final_finish_mac);
if (is_write)
ssl->encrypt_ctx = crypt_new(ssl, server_key, server_iv, 0);
else
ssl->decrypt_ctx = crypt_new(ssl, client_key, client_iv, 1);
}
ssl->cipher_info = ciph_info;
return 0;
}
/**
* Read the SSL connection.
*/
int basic_read(SSL *ssl, uint8_t **in_data)
{
int ret = SSL_OK;
int read_len, is_client = IS_SET_SSL_FLAG(SSL_IS_CLIENT);
uint8_t *buf = ssl->bm_data;
read_len = SOCKET_READ(ssl->client_fd, &buf[ssl->bm_read_index],
ssl->need_bytes-ssl->got_bytes);
/* connection has gone, so die */
if (read_len <= 0)
{
ret = read_len;
ssl->hs_status = SSL_ERROR_DEAD; /* make sure it stays dead */
goto error;
}
DISPLAY_BYTES(ssl, "received %d bytes",
&ssl->bm_data[ssl->bm_read_index], read_len, read_len);
ssl->got_bytes += read_len;
ssl->bm_read_index += read_len;
/* haven't quite got what we want, so try again later */
if (ssl->got_bytes < ssl->need_bytes)
return SSL_OK;
read_len = ssl->got_bytes;
ssl->got_bytes = 0;
if (IS_SET_SSL_FLAG(SSL_NEED_RECORD))
{
/* check for sslv2 "client hello" */
if (buf[0] & 0x80 && buf[2] == 1)
{
#ifdef CONFIG_SSL_ENABLE_V23_HANDSHAKE
uint8_t version = (buf[3] << 4) + buf[4];
DISPLAY_BYTES(ssl, "ssl2 record", buf, 5);
/* should be v3.1 (TLSv1) or better */
ssl->version = ssl->client_version = version;
if (version > SSL_PROTOCOL_VERSION_MAX)
{
/* use client's version */
ssl->version = SSL_PROTOCOL_VERSION_MAX;
}
else if (version < SSL_PROTOCOL_MIN_VERSION)
{
ret = SSL_ERROR_INVALID_VERSION;
ssl_display_error(ret);
return ret;
}
add_packet(ssl, &buf[2], 3);
ret = process_sslv23_client_hello(ssl);
#else
printf("Error: no SSLv23 handshaking allowed\n"); TTY_FLUSH();
ret = SSL_ERROR_NOT_SUPPORTED;
#endif
goto error; /* not an error - just get out of here */
}
/* GBG: check the header values */
if ((buf[0] != PT_HANDSHAKE_PROTOCOL &&
buf[0] != PT_CHANGE_CIPHER_SPEC &&
buf[0] != PT_APP_PROTOCOL_DATA &&
buf[0] != PT_ALERT_PROTOCOL) ||
(buf[1] != 3 /* version major */)) {
ret = SSL_ERROR_INVALID_PROT_MSG;
goto error;
}
ssl->need_bytes = (buf[3] << 8) + buf[4];
/* do we violate the spec with the message size? */
if (ssl->need_bytes > RT_MAX_PLAIN_LENGTH+RT_EXTRA-BM_RECORD_OFFSET)
{
ret = SSL_ERROR_INVALID_PROT_MSG;
goto error;
}
CLR_SSL_FLAG(SSL_NEED_RECORD);
memcpy(ssl->hmac_header, buf, 3); /* store for hmac */
ssl->record_type = buf[0];
goto error; /* no error, we're done */
}
/* for next time - just do it now in case of an error */
SET_SSL_FLAG(SSL_NEED_RECORD);
ssl->need_bytes = SSL_RECORD_SIZE;
/* decrypt if we need to */
if (IS_SET_SSL_FLAG(SSL_RX_ENCRYPTED))
{
ssl->cipher_info->decrypt(ssl->decrypt_ctx, buf, buf, read_len);
if (ssl->version >= SSL_PROTOCOL_VERSION1_1 &&
ssl->cipher_info->iv_size)
{
buf += ssl->cipher_info->iv_size;
read_len -= ssl->cipher_info->iv_size;
}
read_len = verify_digest(ssl,
is_client ? SSL_CLIENT_READ : SSL_SERVER_READ, buf, read_len);
/* does the hmac work? */
if (read_len < 0)
{
ret = read_len;
goto error;
}
DISPLAY_BYTES(ssl, "decrypted", buf, read_len);
increment_read_sequence(ssl);
}
/* The main part of the SSL packet */
switch (ssl->record_type)
{
case PT_HANDSHAKE_PROTOCOL:
if (ssl->dc != NULL)
{
ssl->dc->bm_proc_index = 0;
ret = do_handshake(ssl, buf, read_len);
}
else /* no client renegotiation allowed */
{
ret = SSL_ERROR_NO_CLIENT_RENOG;
goto error;
}
break;
case PT_CHANGE_CIPHER_SPEC:
if (ssl->next_state != HS_FINISHED)
{
ret = SSL_ERROR_INVALID_HANDSHAKE;
goto error;
}
/* all encrypted from now on */
SET_SSL_FLAG(SSL_RX_ENCRYPTED);
if (set_key_block(ssl, 0) < 0)
{
ret = SSL_ERROR_INVALID_HANDSHAKE;
goto error;
}
memset(ssl->read_sequence, 0, 8);
break;
case PT_APP_PROTOCOL_DATA:
if (in_data)
{
*in_data = buf; /* point to the work buffer */
(*in_data)[read_len] = 0; /* null terminate just in case */
}
ret = read_len;
break;
case PT_ALERT_PROTOCOL:
/* return the alert # with alert bit set */
if(buf[0] == SSL_ALERT_TYPE_WARNING &&
buf[1] == SSL_ALERT_CLOSE_NOTIFY)
{
ret = SSL_CLOSE_NOTIFY;
send_alert(ssl, SSL_ALERT_CLOSE_NOTIFY);
SET_SSL_FLAG(SSL_SENT_CLOSE_NOTIFY);
}
else
{
ret = -buf[1];
DISPLAY_ALERT(ssl, buf[1]);
}
break;
default:
ret = SSL_ERROR_INVALID_PROT_MSG;
break;
}
error:
ssl->bm_read_index = 0; /* reset to go again */
if (ret < SSL_OK && in_data)/* if all wrong, then clear this buffer ptr */
*in_data = NULL;
return ret;
}
/**
* Do some basic checking of data and then perform the appropriate handshaking.
*/
static int do_handshake(SSL *ssl, uint8_t *buf, int read_len)
{
int hs_len = (buf[2]<<8) + buf[3];
uint8_t handshake_type = buf[0];
int ret = SSL_OK;
int is_client = IS_SET_SSL_FLAG(SSL_IS_CLIENT);
/* some integrity checking on the handshake */
PARANOIA_CHECK(read_len-SSL_HS_HDR_SIZE, hs_len);
if (handshake_type != ssl->next_state)
{
/* handle a special case on the client */
if (!is_client || handshake_type != HS_CERT_REQ ||
ssl->next_state != HS_SERVER_HELLO_DONE)
{
ret = SSL_ERROR_INVALID_HANDSHAKE;
goto error;
}
}
hs_len += SSL_HS_HDR_SIZE; /* adjust for when adding packets */
ssl->bm_index = hs_len; /* store the size and check later */
DISPLAY_STATE(ssl, 0, handshake_type, 0);
if (handshake_type != HS_CERT_VERIFY && handshake_type != HS_HELLO_REQUEST)
add_packet(ssl, buf, hs_len);
#if defined(CONFIG_SSL_ENABLE_CLIENT)
ret = is_client ?
do_clnt_handshake(ssl, handshake_type, buf, hs_len) :
do_svr_handshake(ssl, handshake_type, buf, hs_len);
#else
ret = do_svr_handshake(ssl, handshake_type, buf, hs_len);
#endif
/* just use recursion to get the rest */
if (hs_len < read_len && ret == SSL_OK)
ret = do_handshake(ssl, &buf[hs_len], read_len-hs_len);
error:
return ret;
}
/**
* Sends the change cipher spec message. We have just read a finished message
* from the client.
*/
int send_change_cipher_spec(SSL *ssl)
{
int ret = send_packet(ssl, PT_CHANGE_CIPHER_SPEC,
g_chg_cipher_spec_pkt, sizeof(g_chg_cipher_spec_pkt));
SET_SSL_FLAG(SSL_TX_ENCRYPTED);
if (ret >= 0 && set_key_block(ssl, 1) < 0)
ret = SSL_ERROR_INVALID_HANDSHAKE;
memset(ssl->write_sequence, 0, 8);
return ret;
}
/**
* Send a "finished" message
*/
int send_finished(SSL *ssl)
{
uint8_t buf[SSL_FINISHED_HASH_SIZE+4] = {
HS_FINISHED, 0, 0, SSL_FINISHED_HASH_SIZE };
/* now add the finished digest mac (12 bytes) */
finished_digest(ssl,
IS_SET_SSL_FLAG(SSL_IS_CLIENT) ?
client_finished : server_finished, &buf[4]);
#ifndef CONFIG_SSL_SKELETON_MODE
/* store in the session cache */
if (!IS_SET_SSL_FLAG(SSL_SESSION_RESUME) && ssl->ssl_ctx->num_sessions)
{
memcpy(ssl->session->master_secret,
ssl->dc->master_secret, SSL_SECRET_SIZE);
}
#endif
return send_packet(ssl, PT_HANDSHAKE_PROTOCOL,
buf, SSL_FINISHED_HASH_SIZE+4);
}
/**
* Send an alert message.
* Return 1 if the alert was an "error".
*/
int send_alert(SSL *ssl, int error_code)
{
int alert_num = 0;
int is_warning = 0;
uint8_t buf[2];
/* Don't bother we're already dead */
if (ssl->hs_status == SSL_ERROR_DEAD)
{
return SSL_ERROR_CONN_LOST;
}
#ifdef CONFIG_SSL_FULL_MODE
if (IS_SET_SSL_FLAG(SSL_DISPLAY_STATES))
ssl_display_error(error_code);
#endif
switch (error_code)
{
case SSL_ALERT_CLOSE_NOTIFY:
is_warning = 1;
alert_num = SSL_ALERT_CLOSE_NOTIFY;
break;
case SSL_ERROR_CONN_LOST: /* don't send alert just yet */
is_warning = 1;
break;
case SSL_ERROR_INVALID_HANDSHAKE:
case SSL_ERROR_INVALID_PROT_MSG:
alert_num = SSL_ALERT_HANDSHAKE_FAILURE;
break;
case SSL_ERROR_INVALID_HMAC:
case SSL_ERROR_FINISHED_INVALID:
alert_num = SSL_ALERT_BAD_RECORD_MAC;
break;
case SSL_ERROR_INVALID_VERSION:
alert_num = SSL_ALERT_INVALID_VERSION;
break;
case SSL_ERROR_INVALID_SESSION:
case SSL_ERROR_NO_CIPHER:
case SSL_ERROR_INVALID_KEY:
alert_num = SSL_ALERT_ILLEGAL_PARAMETER;
break;
case SSL_ERROR_BAD_CERTIFICATE:
alert_num = SSL_ALERT_BAD_CERTIFICATE;
break;
case SSL_ERROR_NO_CLIENT_RENOG:
alert_num = SSL_ALERT_NO_RENEGOTIATION;
break;
default:
/* a catch-all for any badly verified certificates */
alert_num = (error_code <= SSL_X509_OFFSET) ?
SSL_ALERT_BAD_CERTIFICATE : SSL_ALERT_UNEXPECTED_MESSAGE;
break;
}
buf[0] = is_warning ? 1 : 2;
buf[1] = alert_num;
send_packet(ssl, PT_ALERT_PROTOCOL, buf, sizeof(buf));
DISPLAY_ALERT(ssl, alert_num);
return is_warning ? 0 : 1;
}
/**
* Process a client finished message.
*/
int process_finished(SSL *ssl, uint8_t *buf, int hs_len)
{
int ret = SSL_OK;
int is_client = IS_SET_SSL_FLAG(SSL_IS_CLIENT);
int resume = IS_SET_SSL_FLAG(SSL_SESSION_RESUME);
(void)hs_len; /* GBG: unused */
PARANOIA_CHECK(ssl->bm_index, SSL_FINISHED_HASH_SIZE+4);
/* check that we all work before we continue */
if (memcmp(ssl->dc->final_finish_mac, &buf[4], SSL_FINISHED_HASH_SIZE))
return SSL_ERROR_FINISHED_INVALID;
if ((!is_client && !resume) || (is_client && resume))
{
if ((ret = send_change_cipher_spec(ssl)) == SSL_OK)
ret = send_finished(ssl);
}
/* if we ever renegotiate */
ssl->next_state = is_client ? HS_HELLO_REQUEST : HS_CLIENT_HELLO;
ssl->hs_status = ret; /* set the final handshake status */
error:
return ret;
}
/**
* Send a certificate.
*/
int send_certificate(SSL *ssl)
{
uint8_t *buf = ssl->bm_data;
int offset = 7;
int chain_length;
SSL_CERT* cert = ssl->ssl_ctx->certs; /* GBG: added */
buf[0] = HS_CERTIFICATE;
buf[1] = 0;
buf[4] = 0;
while (cert)
{
buf[offset++] = 0;
buf[offset++] = cert->size >> 8; /* cert 1 length */
buf[offset++] = cert->size & 0xff;
memcpy(&buf[offset], cert->buf, cert->size);
offset += cert->size;
cert = cert->next;
}
chain_length = offset - 7;
buf[5] = chain_length >> 8; /* cert chain length */
buf[6] = chain_length & 0xff;
chain_length += 3;
buf[2] = chain_length >> 8; /* handshake length */
buf[3] = chain_length & 0xff;
ssl->bm_index = offset;
return send_packet(ssl, PT_HANDSHAKE_PROTOCOL, NULL, offset);
}
/**
* Create a blob of memory that we'll get rid of once the handshake is
* complete.
*/
void disposable_new(SSL *ssl)
{
if (ssl->dc == NULL)
{
ssl->dc = (DISPOSABLE_CTX *)calloc(1, sizeof(DISPOSABLE_CTX));
MD5_Init(&ssl->dc->md5_ctx);
SHA1_Init(&ssl->dc->sha1_ctx);
}
}
/**
* Remove the temporary blob of memory.
*/
void disposable_free(SSL *ssl)
{
if (ssl->dc)
{
free(ssl->dc->key_block);
memset(ssl->dc, 0, sizeof(DISPOSABLE_CTX));
free(ssl->dc);
ssl->dc = NULL;
}
}
#ifndef CONFIG_SSL_SKELETON_MODE /* no session resumption in this mode */
/**
* Find if an existing session has the same session id. If so, use the
* master secret from this session for session resumption.
*/
SSL_SESSION *ssl_session_update(int max_sessions, SSL_SESSION *ssl_sessions[],
SSL *ssl, const uint8_t *session_id)
{
time_t tm = time(NULL);
time_t oldest_sess_time = tm;
SSL_SESSION *oldest_sess = NULL;
int i;
/* no sessions? Then bail */
if (max_sessions == 0)
return NULL;
SSL_CTX_LOCK(ssl->ssl_ctx->mutex);
if (session_id)
{
for (i = 0; i < max_sessions; i++)
{
if (ssl_sessions[i])
{
/* kill off any expired sessions (including those in
the future) */
if ((tm > ssl_sessions[i]->conn_time + SSL_EXPIRY_TIME) ||
(tm < ssl_sessions[i]->conn_time))
{
session_free(ssl_sessions, i);
continue;
}
/* if the session id matches, it must still be less than
the expiry time */
if (memcmp(ssl_sessions[i]->session_id, session_id,
SSL_SESSION_ID_SIZE) == 0)
{
ssl->session_index = i;
memcpy(ssl->dc->master_secret,
ssl_sessions[i]->master_secret, SSL_SECRET_SIZE);
SET_SSL_FLAG(SSL_SESSION_RESUME);
SSL_CTX_UNLOCK(ssl->ssl_ctx->mutex);
return ssl_sessions[i]; /* a session was found */
}
}
}
}
/* If we've got here, no matching session was found - so create one */
for (i = 0; i < max_sessions; i++)
{
if (ssl_sessions[i] == NULL)
{
/* perfect, this will do */
ssl_sessions[i] = (SSL_SESSION *)calloc(1, sizeof(SSL_SESSION));
ssl_sessions[i]->conn_time = tm;
ssl->session_index = i;
SSL_CTX_UNLOCK(ssl->ssl_ctx->mutex);
return ssl_sessions[i]; /* return the session object */
}
else if (ssl_sessions[i]->conn_time <= oldest_sess_time)
{
/* find the oldest session */
oldest_sess_time = ssl_sessions[i]->conn_time;
oldest_sess = ssl_sessions[i];
ssl->session_index = i;
}
}
/* ok, we've used up all of our sessions. So blow the oldest session away */
oldest_sess->conn_time = tm;
memset(oldest_sess->session_id, 0, sizeof(SSL_SESSION_ID_SIZE));
memset(oldest_sess->master_secret, 0, sizeof(SSL_SECRET_SIZE));
SSL_CTX_UNLOCK(ssl->ssl_ctx->mutex);
return oldest_sess;
}
/**
* Free an existing session.
*/
static void session_free(SSL_SESSION *ssl_sessions[], int sess_index)
{
if (ssl_sessions[sess_index])
{
free(ssl_sessions[sess_index]);
ssl_sessions[sess_index] = NULL;
}
}
/**
* This ssl object doesn't want this session anymore.
*/
void kill_ssl_session(SSL_SESSION **ssl_sessions, SSL *ssl)
{
SSL_CTX_LOCK(ssl->ssl_ctx->mutex);
if (ssl->ssl_ctx->num_sessions)
{
session_free(ssl_sessions, ssl->session_index);
ssl->session = NULL;
}
SSL_CTX_UNLOCK(ssl->ssl_ctx->mutex);
}
#endif /* CONFIG_SSL_SKELETON_MODE */
/*
* Get the session id for a handshake. This will be a 32 byte sequence.
*/
EXP_FUNC const uint8_t * STDCALL ssl_get_session_id(const SSL *ssl)
{
return ssl->session_id;
}
/*
* Get the session id size for a handshake.
*/
EXP_FUNC uint8_t STDCALL ssl_get_session_id_size(const SSL *ssl)
{
return ssl->sess_id_size;
}
/*
* Return the cipher id (in the SSL form).
*/
EXP_FUNC uint8_t STDCALL ssl_get_cipher_id(const SSL *ssl)
{
return ssl->cipher;
}
/*
* Return the status of the handshake.
*/
EXP_FUNC int STDCALL ssl_handshake_status(const SSL *ssl)
{
return ssl->hs_status;
}
#if 0 /* GBG: removed */
/*
* Retrieve various parameters about the SSL engine.
*/
EXP_FUNC int STDCALL ssl_get_config(int offset)
{
switch (offset)
{
/* return the appropriate build mode */
case SSL_BUILD_MODE:
#if defined(CONFIG_SSL_FULL_MODE)
return SSL_BUILD_FULL_MODE;
#elif defined(CONFIG_SSL_ENABLE_CLIENT)
return SSL_BUILD_ENABLE_CLIENT;
#elif defined(CONFIG_ENABLE_VERIFICATION)
return SSL_BUILD_ENABLE_VERIFICATION;
#elif defined(CONFIG_SSL_SERVER_ONLY )
return SSL_BUILD_SERVER_ONLY;
#else
return SSL_BUILD_SKELETON_MODE;
#endif
#if 0 /* GBG: removed */
case SSL_MAX_CERT_CFG_OFFSET:
return CONFIG_SSL_MAX_CERTS;
#endif
#ifdef CONFIG_SSL_CERT_VERIFICATION
case SSL_MAX_CA_CERT_CFG_OFFSET:
return CONFIG_X509_MAX_CA_CERTS;
#endif
#ifdef CONFIG_SSL_HAS_PEM
case SSL_HAS_PEM:
return 1;
#endif
default:
return 0;
}
}
#endif /* GBG */
#ifdef CONFIG_SSL_CERT_VERIFICATION
/**
* Authenticate a received certificate.
*/
EXP_FUNC int STDCALL ssl_verify_cert(const SSL *ssl)
{
int ret;
SSL_CTX_LOCK(ssl->ssl_ctx->mutex);
ret = x509_verify(ssl->ssl_ctx->ca_certs /* GBG: modified */, ssl->x509_ctx, NULL);
SSL_CTX_UNLOCK(ssl->ssl_ctx->mutex);
if (ret) /* modify into an SSL error type */
{
ret = SSL_X509_ERROR(ret);
}
return ret;
}
/**
* Process a certificate message.
*/
int process_certificate(SSL *ssl, X509_CTX **x509_ctx)
{
int ret = SSL_OK;
uint8_t *buf = &ssl->bm_data[ssl->dc->bm_proc_index];
int pkt_size = ssl->bm_index;
int cert_size, offset = 5;
int total_cert_size = (buf[offset]<<8) + buf[offset+1];
int is_client = IS_SET_SSL_FLAG(SSL_IS_CLIENT);
X509_CTX **chain = x509_ctx;
offset += 2;
PARANOIA_CHECK(total_cert_size, offset);
while (offset < total_cert_size)
{
offset++; /* skip empty char */
cert_size = (buf[offset]<<8) + buf[offset+1];
offset += 2;
if (x509_new(&buf[offset], NULL, chain))
{
ret = SSL_ERROR_BAD_CERTIFICATE;
goto error;
}
chain = &((*chain)->next);
offset += cert_size;
}
PARANOIA_CHECK(pkt_size, offset);
/* GBG: modif: verify for server and client (was: if we are client we can do the verify now or later) */
if (!IS_SET_SSL_FLAG(SSL_SERVER_VERIFY_LATER))
{
ret = ssl_verify_cert(ssl);
}
ssl->next_state = is_client ? HS_SERVER_HELLO_DONE : HS_CLIENT_KEY_XCHG;
ssl->dc->bm_proc_index += offset;
error:
return ret;
}
#endif /* CONFIG_SSL_CERT_VERIFICATION */
/**
* Debugging routine to display SSL handshaking stuff.
*/
#ifdef CONFIG_SSL_FULL_MODE
/**
* Debugging routine to display SSL states.
*/
void DISPLAY_STATE(SSL *ssl, int is_send, uint8_t state, int not_ok)
{
const char *str;
if (!IS_SET_SSL_FLAG(SSL_DISPLAY_STATES))
return;
printf(not_ok ? "Error - invalid State:\t" : "State:\t");
printf(is_send ? "sending " : "receiving ");
switch (state)
{
case HS_HELLO_REQUEST:
str = "Hello Request (0)";
break;
case HS_CLIENT_HELLO:
str = "Client Hello (1)";
break;
case HS_SERVER_HELLO:
str = "Server Hello (2)";
break;
case HS_CERTIFICATE:
str = "Certificate (11)";
break;
case HS_SERVER_KEY_XCHG:
str = "Certificate Request (12)";
break;
case HS_CERT_REQ:
str = "Certificate Request (13)";
break;
case HS_SERVER_HELLO_DONE:
str = "Server Hello Done (14)";
break;
case HS_CERT_VERIFY:
str = "Certificate Verify (15)";
break;
case HS_CLIENT_KEY_XCHG:
str = "Client Key Exchange (16)";
break;
case HS_FINISHED:
str = "Finished (16)";
break;
default:
str = "Error (Unknown)";
break;
}
printf("%s\n", str);
TTY_FLUSH();
}
/**
* Debugging routine to display RSA objects
*/
void DISPLAY_RSA(SSL *ssl, const RSA_CTX *rsa_ctx)
{
if (!IS_SET_SSL_FLAG(SSL_DISPLAY_RSA))
return;
RSA_print(rsa_ctx);
TTY_FLUSH();
}
/**
* Debugging routine to display SSL handshaking bytes.
*/
void DISPLAY_BYTES(SSL *ssl, const char *format,
const uint8_t *data, int size, ...)
{
va_list(ap);
if (!IS_SET_SSL_FLAG(SSL_DISPLAY_BYTES))
return;
va_start(ap, size);
print_blob(format, data, size, va_arg(ap, char *));
va_end(ap);
TTY_FLUSH();
}
/**
* Debugging routine to display SSL handshaking errors.
*/
EXP_FUNC void STDCALL ssl_display_error(int error_code)
{
if (error_code == SSL_OK)
return;
printf("Error: ");
/* X509 error? */
if (error_code < SSL_X509_OFFSET)
{
printf("%s\n", x509_display_error(error_code - SSL_X509_OFFSET));
return;
}
/* SSL alert error code */
if (error_code > SSL_ERROR_CONN_LOST)
{
printf("SSL error %d\n", -error_code);
return;
}
switch (error_code)
{
case SSL_ERROR_DEAD:
printf("connection dead");
break;
case SSL_ERROR_INVALID_HANDSHAKE:
printf("invalid handshake");
break;
case SSL_ERROR_INVALID_PROT_MSG:
printf("invalid protocol message");
break;
case SSL_ERROR_INVALID_HMAC:
printf("invalid mac");
break;
case SSL_ERROR_INVALID_VERSION:
printf("invalid version");
break;
case SSL_ERROR_INVALID_SESSION:
printf("invalid session");
break;
case SSL_ERROR_NO_CIPHER:
printf("no cipher");
break;
case SSL_ERROR_CONN_LOST:
printf("connection lost");
break;
case SSL_ERROR_BAD_CERTIFICATE:
printf("bad certificate");
break;
case SSL_ERROR_INVALID_KEY:
printf("invalid key");
break;
case SSL_ERROR_FINISHED_INVALID:
printf("finished invalid");
break;
case SSL_ERROR_NO_CERT_DEFINED:
printf("no certificate defined");
break;
case SSL_ERROR_NO_CLIENT_RENOG:
printf("client renegotiation not supported");
break;
case SSL_ERROR_NOT_SUPPORTED:
printf("Option not supported");
break;
default:
printf("undefined as yet - %d", error_code);
break;
}
printf("\n");
TTY_FLUSH();
}
/**
* Debugging routine to display alerts.
*/
void DISPLAY_ALERT(SSL *ssl, int alert)
{
if (!IS_SET_SSL_FLAG(SSL_DISPLAY_STATES))
return;
printf("Alert: ");
switch (alert)
{
case SSL_ALERT_CLOSE_NOTIFY:
printf("close notify");
break;
case SSL_ALERT_INVALID_VERSION:
printf("invalid version");
break;
case SSL_ALERT_BAD_CERTIFICATE:
printf("bad certificate");
break;
case SSL_ALERT_UNEXPECTED_MESSAGE:
printf("unexpected message");
break;
case SSL_ALERT_BAD_RECORD_MAC:
printf("bad record mac");
break;
case SSL_ALERT_HANDSHAKE_FAILURE:
printf("handshake failure");
break;
case SSL_ALERT_ILLEGAL_PARAMETER:
printf("illegal parameter");
break;
case SSL_ALERT_DECODE_ERROR:
printf("decode error");
break;
case SSL_ALERT_DECRYPT_ERROR:
printf("decrypt error");
break;
case SSL_ALERT_NO_RENEGOTIATION:
printf("no renegotiation");
break;
default:
printf("alert - (unknown %d)", alert);
break;
}
printf("\n");
TTY_FLUSH();
}
#endif /* CONFIG_SSL_FULL_MODE */
/**
* Return the version of this library.
*/
EXP_FUNC const char * STDCALL ssl_version()
{
static const char * axtls_version = AXTLS_VERSION;
return axtls_version;
}
EXP_FUNC void ssl_mem_free(void* mem) /* GBG */
{
if (mem) free(mem);
}
/**
* Enable the various language bindings to work regardless of the
* configuration - they just return an error statement and a bad return code.
*/
#if !defined(CONFIG_SSL_FULL_MODE)
EXP_FUNC void STDCALL ssl_display_error(int error_code) { (void)error_code; }
#endif
#ifdef CONFIG_BINDINGS
#if !defined(CONFIG_SSL_ENABLE_CLIENT)
EXP_FUNC SSL * STDCALL ssl_client_new(SSL_CTX *ssl_ctx, int client_fd, const
uint8_t *session_id, uint8_t sess_id_size)
{
printf(unsupported_str);
return NULL;
}
#endif
#if !defined(CONFIG_SSL_CERT_VERIFICATION)
EXP_FUNC int STDCALL ssl_verify_cert(const SSL *ssl)
{
printf(unsupported_str);
return -1;
}
EXP_FUNC const char * STDCALL ssl_get_cert_dn(const SSL *ssl, int component)
{
printf(unsupported_str);
return NULL;
}
EXP_FUNC const char * STDCALL ssl_get_cert_subject_alt_dnsname(const SSL *ssl, int index)
{
printf(unsupported_str);
return NULL;
}
#endif /* CONFIG_SSL_CERT_VERIFICATION */
#endif /* CONFIG_BINDINGS */
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.3/zlib.h b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.3/zlib.h
index 022817927c..1e2a910233 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.3/zlib.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.3/zlib.h
@@ -1,1357 +1,1357 @@
/* zlib.h -- interface of the 'zlib' general purpose compression library
version 1.2.3, July 18th, 2005
Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
The data format used by the zlib library is described by RFCs (Request for
Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt
(zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format).
*/
#ifndef ZLIB_H
#define ZLIB_H
#include "zconf.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ZLIB_VERSION "1.2.3"
#define ZLIB_VERNUM 0x1230
/*
The 'zlib' compression library provides in-memory compression and
decompression functions, including integrity checks of the uncompressed
data. This version of the library supports only one compression method
(deflation) but other algorithms will be added later and will have the same
stream interface.
Compression can be done in a single step if the buffers are large
enough (for example if an input file is mmap'ed), or can be done by
repeated calls of the compression function. In the latter case, the
application must provide more input and/or consume the output
(providing more output space) before each call.
The compressed data format used by default by the in-memory functions is
the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
around a deflate stream, which is itself documented in RFC 1951.
The library also supports reading and writing files in gzip (.gz) format
with an interface similar to that of stdio using the functions that start
with "gz". The gzip format is different from the zlib format. gzip is a
gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
This library can optionally read and write gzip streams in memory as well.
The zlib format was designed to be compact and fast for use in memory
and on communications channels. The gzip format was designed for single-
file compression on file systems, has a larger header than zlib to maintain
directory information, and uses a different, slower check method than zlib.
The library does not install any signal handler. The decoder checks
the consistency of the compressed data, so the library should never
crash even in case of corrupted input.
*/
typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
typedef void (*free_func) OF((voidpf opaque, voidpf address));
struct internal_state;
typedef struct z_stream_s {
Bytef *next_in; /* next input byte */
uInt avail_in; /* number of bytes available at next_in */
uLong total_in; /* total nb of input bytes read so far */
Bytef *next_out; /* next output byte should be put there */
uInt avail_out; /* remaining free space at next_out */
uLong total_out; /* total nb of bytes output so far */
char *msg; /* last error message, NULL if no error */
struct internal_state FAR *state; /* not visible by applications */
alloc_func zalloc; /* used to allocate the internal state */
free_func zfree; /* used to free the internal state */
voidpf opaque; /* private data object passed to zalloc and zfree */
int data_type; /* best guess about the data type: binary or text */
uLong adler; /* adler32 value of the uncompressed data */
uLong reserved; /* reserved for future use */
} z_stream;
typedef z_stream FAR *z_streamp;
/*
gzip header information passed to and from zlib routines. See RFC 1952
for more details on the meanings of these fields.
*/
typedef struct gz_header_s {
int text; /* true if compressed data believed to be text */
uLong time; /* modification time */
int xflags; /* extra flags (not used when writing a gzip file) */
int os; /* operating system */
Bytef *extra; /* pointer to extra field or Z_NULL if none */
uInt extra_len; /* extra field length (valid if extra != Z_NULL) */
uInt extra_max; /* space at extra (only when reading header) */
Bytef *name; /* pointer to zero-terminated file name or Z_NULL */
uInt name_max; /* space at name (only when reading header) */
Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */
uInt comm_max; /* space at comment (only when reading header) */
int hcrc; /* true if there was or will be a header crc */
int done; /* true when done reading gzip header (not used
when writing a gzip file) */
} gz_header;
typedef gz_header FAR *gz_headerp;
/*
The application must update next_in and avail_in when avail_in has
dropped to zero. It must update next_out and avail_out when avail_out
has dropped to zero. The application must initialize zalloc, zfree and
opaque before calling the init function. All other fields are set by the
compression library and must not be updated by the application.
The opaque value provided by the application will be passed as the first
parameter for calls of zalloc and zfree. This can be useful for custom
memory management. The compression library attaches no meaning to the
opaque value.
zalloc must return Z_NULL if there is not enough memory for the object.
If zlib is used in a multi-threaded application, zalloc and zfree must be
thread safe.
On 16-bit systems, the functions zalloc and zfree must be able to allocate
exactly 65536 bytes, but will not be required to allocate more than this
if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS,
pointers returned by zalloc for objects of exactly 65536 bytes *must*
have their offset normalized to zero. The default allocation function
provided by this library ensures this (see zutil.c). To reduce memory
requirements and avoid any allocation of 64K objects, at the expense of
compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h).
The fields total_in and total_out can be used for statistics or
progress reports. After compression, total_in holds the total size of
the uncompressed data and may be saved for use in the decompressor
(particularly if the decompressor wants to decompress everything in
a single step).
*/
/* constants */
#define Z_NO_FLUSH 0
#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */
#define Z_SYNC_FLUSH 2
#define Z_FULL_FLUSH 3
#define Z_FINISH 4
#define Z_BLOCK 5
/* Allowed flush values; see deflate() and inflate() below for details */
#define Z_OK 0
#define Z_STREAM_END 1
#define Z_NEED_DICT 2
#define Z_ERRNO (-1)
#define Z_STREAM_ERROR (-2)
#define Z_DATA_ERROR (-3)
#define Z_MEM_ERROR (-4)
#define Z_BUF_ERROR (-5)
#define Z_VERSION_ERROR (-6)
/* Return codes for the compression/decompression functions. Negative
* values are errors, positive values are used for special but normal events.
*/
#define Z_NO_COMPRESSION 0
#define Z_BEST_SPEED 1
#define Z_BEST_COMPRESSION 9
#define Z_DEFAULT_COMPRESSION (-1)
/* compression levels */
#define Z_FILTERED 1
#define Z_HUFFMAN_ONLY 2
#define Z_RLE 3
#define Z_FIXED 4
#define Z_DEFAULT_STRATEGY 0
/* compression strategy; see deflateInit2() below for details */
#define Z_BINARY 0
#define Z_TEXT 1
#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */
#define Z_UNKNOWN 2
/* Possible values of the data_type field (though see inflate()) */
#define Z_DEFLATED 8
/* The deflate compression method (the only one supported in this version) */
#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */
#define zlib_version zlibVersion()
/* for compatibility with versions < 1.0.2 */
/* basic functions */
ZEXTERN const char * ZEXPORT zlibVersion OF((void));
/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
If the first character differs, the library code actually used is
not compatible with the zlib.h header file used by the application.
This check is automatically made by deflateInit and inflateInit.
*/
/*
ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
Initializes the internal stream state for compression. The fields
zalloc, zfree and opaque must be initialized before by the caller.
If zalloc and zfree are set to Z_NULL, deflateInit updates them to
use default allocation functions.
The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
1 gives best speed, 9 gives best compression, 0 gives no compression at
all (the input data is simply copied a block at a time).
Z_DEFAULT_COMPRESSION requests a default compromise between speed and
compression (currently equivalent to level 6).
deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_STREAM_ERROR if level is not a valid compression level,
Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
with the version assumed by the caller (ZLIB_VERSION).
msg is set to null if there is no error message. deflateInit does not
perform any compression: this will be done by deflate().
*/
ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
/*
deflate compresses as much data as possible, and stops when the input
buffer becomes empty or the output buffer becomes full. It may introduce some
output latency (reading input without producing any output) except when
forced to flush.
The detailed semantics are as follows. deflate performs one or both of the
following actions:
- Compress more input starting at next_in and update next_in and avail_in
accordingly. If not all input can be processed (because there is not
enough room in the output buffer), next_in and avail_in are updated and
processing will resume at this point for the next call of deflate().
- Provide more output starting at next_out and update next_out and avail_out
accordingly. This action is forced if the parameter flush is non zero.
Forcing flush frequently degrades the compression ratio, so this parameter
should be set only when necessary (in interactive applications).
Some output may be provided even if flush is not set.
Before the call of deflate(), the application should ensure that at least
one of the actions is possible, by providing more input and/or consuming
more output, and updating avail_in or avail_out accordingly; avail_out
should never be zero before the call. The application can consume the
compressed output when it wants, for example when the output buffer is full
(avail_out == 0), or after each call of deflate(). If deflate returns Z_OK
and with zero avail_out, it must be called again after making room in the
output buffer because there might be more output pending.
Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
- decide how much data to accumualte before producing output, in order to
+ decide how much data to accumulate before producing output, in order to
maximize compression.
If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
flushed to the output buffer and the output is aligned on a byte boundary, so
that the decompressor can get all input data available so far. (In particular
avail_in is zero after the call if enough output space has been provided
before the call.) Flushing may degrade compression for some compression
algorithms and so it should be used only when necessary.
If flush is set to Z_FULL_FLUSH, all output is flushed as with
Z_SYNC_FLUSH, and the compression state is reset so that decompression can
restart from this point if previous compressed data has been damaged or if
random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
compression.
If deflate returns with avail_out == 0, this function must be called again
with the same value of the flush parameter and more output space (updated
avail_out), until the flush is complete (deflate returns with non-zero
avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
avail_out is greater than six to avoid repeated flush markers due to
avail_out == 0 on return.
If the parameter flush is set to Z_FINISH, pending input is processed,
pending output is flushed and deflate returns with Z_STREAM_END if there
was enough output space; if deflate returns with Z_OK, this function must be
called again with Z_FINISH and more output space (updated avail_out) but no
more input data, until it returns with Z_STREAM_END or an error. After
deflate has returned Z_STREAM_END, the only possible operations on the
stream are deflateReset or deflateEnd.
Z_FINISH can be used immediately after deflateInit if all the compression
is to be done in a single step. In this case, avail_out must be at least
the value returned by deflateBound (see below). If deflate does not return
Z_STREAM_END, then it must be called again as described above.
deflate() sets strm->adler to the adler32 checksum of all input read
so far (that is, total_in bytes).
deflate() may update strm->data_type if it can make a good guess about
the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered
binary. This field is only for information purposes and does not affect
the compression algorithm in any manner.
deflate() returns Z_OK if some progress has been made (more input
processed or more output produced), Z_STREAM_END if all input has been
consumed and all output has been produced (only when flush is set to
Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible
(for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not
fatal, and deflate() can be called again with more input and more output
space to continue compressing.
*/
ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
/*
All dynamically allocated data structures for this stream are freed.
This function discards any unprocessed input and does not flush any
pending output.
deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
stream state was inconsistent, Z_DATA_ERROR if the stream was freed
prematurely (some input or output was discarded). In the error case,
msg may be set but then points to a static string (which must not be
deallocated).
*/
/*
ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
Initializes the internal stream state for decompression. The fields
next_in, avail_in, zalloc, zfree and opaque must be initialized before by
the caller. If next_in is not Z_NULL and avail_in is large enough (the exact
value depends on the compression method), inflateInit determines the
compression method from the zlib header and allocates all data structures
accordingly; otherwise the allocation will be deferred to the first call of
inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to
use default allocation functions.
inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
version assumed by the caller. msg is set to null if there is no error
message. inflateInit does not perform any decompression apart from reading
the zlib header if present: this will be done by inflate(). (So next_in and
avail_in may be modified, but next_out and avail_out are unchanged.)
*/
ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
/*
inflate decompresses as much data as possible, and stops when the input
buffer becomes empty or the output buffer becomes full. It may introduce
some output latency (reading input without producing any output) except when
forced to flush.
The detailed semantics are as follows. inflate performs one or both of the
following actions:
- Decompress more input starting at next_in and update next_in and avail_in
accordingly. If not all input can be processed (because there is not
enough room in the output buffer), next_in is updated and processing
will resume at this point for the next call of inflate().
- Provide more output starting at next_out and update next_out and avail_out
accordingly. inflate() provides as much output as possible, until there
is no more input data or no more space in the output buffer (see below
about the flush parameter).
Before the call of inflate(), the application should ensure that at least
one of the actions is possible, by providing more input and/or consuming
more output, and updating the next_* and avail_* values accordingly.
The application can consume the uncompressed output when it wants, for
example when the output buffer is full (avail_out == 0), or after each
call of inflate(). If inflate returns Z_OK and with zero avail_out, it
must be called again after making room in the output buffer because there
might be more output pending.
The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH,
Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much
output as possible to the output buffer. Z_BLOCK requests that inflate() stop
if and when it gets to the next deflate block boundary. When decoding the
zlib or gzip format, this will cause inflate() to return immediately after
the header and before the first block. When doing a raw inflate, inflate()
will go ahead and process the first block, and will return when it gets to
the end of that block, or when it runs out of data.
The Z_BLOCK option assists in appending to or combining deflate streams.
Also to assist in this, on return inflate() will set strm->data_type to the
number of unused bits in the last byte taken from strm->next_in, plus 64
if inflate() is currently decoding the last block in the deflate stream,
plus 128 if inflate() returned immediately after decoding an end-of-block
code or decoding the complete header up to just before the first byte of the
deflate stream. The end-of-block will not be indicated until all of the
uncompressed data from that block has been written to strm->next_out. The
number of unused bits may in general be greater than seven, except when
bit 7 of data_type is set, in which case the number of unused bits will be
less than eight.
inflate() should normally be called until it returns Z_STREAM_END or an
error. However if all decompression is to be performed in a single step
(a single call of inflate), the parameter flush should be set to
Z_FINISH. In this case all pending input is processed and all pending
output is flushed; avail_out must be large enough to hold all the
uncompressed data. (The size of the uncompressed data may have been saved
by the compressor for this purpose.) The next operation on this stream must
be inflateEnd to deallocate the decompression state. The use of Z_FINISH
is never required, but can be used to inform inflate that a faster approach
may be used for the single inflate() call.
In this implementation, inflate() always flushes as much output as
possible to the output buffer, and always uses the faster approach on the
first call. So the only effect of the flush parameter in this implementation
is on the return value of inflate(), as noted below, or when it returns early
because Z_BLOCK is used.
If a preset dictionary is needed after this call (see inflateSetDictionary
below), inflate sets strm->adler to the adler32 checksum of the dictionary
chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
strm->adler to the adler32 checksum of all output produced so far (that is,
total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
below. At the end of the stream, inflate() checks that its computed adler32
checksum is equal to that saved by the compressor and returns Z_STREAM_END
only if the checksum is correct.
inflate() will decompress and check either zlib-wrapped or gzip-wrapped
deflate data. The header type is detected automatically. Any information
contained in the gzip header is not retained, so applications that need that
information should instead use raw inflate, see inflateInit2() below, or
inflateBack() and perform their own processing of the gzip header and
trailer.
inflate() returns Z_OK if some progress has been made (more input processed
or more output produced), Z_STREAM_END if the end of the compressed data has
been reached and all uncompressed output has been produced, Z_NEED_DICT if a
preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
corrupted (input stream not conforming to the zlib format or incorrect check
value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory,
Z_BUF_ERROR if no progress is possible or if there was not enough room in the
output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
inflate() can be called again with more input and more output space to
continue decompressing. If Z_DATA_ERROR is returned, the application may then
call inflateSync() to look for a good compression block if a partial recovery
of the data is desired.
*/
ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
/*
All dynamically allocated data structures for this stream are freed.
This function discards any unprocessed input and does not flush any
pending output.
inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
was inconsistent. In the error case, msg may be set but then points to a
static string (which must not be deallocated).
*/
/* Advanced functions */
/*
The following functions are needed only in some special applications.
*/
/*
ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
int level,
int method,
int windowBits,
int memLevel,
int strategy));
This is another version of deflateInit with more compression options. The
fields next_in, zalloc, zfree and opaque must be initialized before by
the caller.
The method parameter is the compression method. It must be Z_DEFLATED in
this version of the library.
The windowBits parameter is the base two logarithm of the window size
(the size of the history buffer). It should be in the range 8..15 for this
version of the library. Larger values of this parameter result in better
compression at the expense of memory usage. The default value is 15 if
deflateInit is used instead.
windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
determines the window size. deflate() will then generate raw deflate data
with no zlib header or trailer, and will not compute an adler32 check value.
windowBits can also be greater than 15 for optional gzip encoding. Add
16 to windowBits to write a simple gzip header and trailer around the
compressed data instead of a zlib wrapper. The gzip header will have no
file name, no extra data, no comment, no modification time (set to zero),
no header crc, and the operating system will be set to 255 (unknown). If a
gzip stream is being written, strm->adler is a crc32 instead of an adler32.
The memLevel parameter specifies how much memory should be allocated
for the internal compression state. memLevel=1 uses minimum memory but
is slow and reduces compression ratio; memLevel=9 uses maximum memory
for optimal speed. The default value is 8. See zconf.h for total memory
usage as a function of windowBits and memLevel.
The strategy parameter is used to tune the compression algorithm. Use the
value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
string match), or Z_RLE to limit match distances to one (run-length
encoding). Filtered data consists mostly of small values with a somewhat
random distribution. In this case, the compression algorithm is tuned to
compress them better. The effect of Z_FILTERED is to force more Huffman
coding and less string matching; it is somewhat intermediate between
Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as
Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy
parameter only affects the compression ratio but not the correctness of the
compressed output even if it is not set appropriately. Z_FIXED prevents the
use of dynamic Huffman codes, allowing for a simpler decoder for special
applications.
deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid
method). msg is set to null if there is no error message. deflateInit2 does
not perform any compression: this will be done by deflate().
*/
ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
const Bytef *dictionary,
uInt dictLength));
/*
Initializes the compression dictionary from the given byte sequence
without producing any compressed output. This function must be called
immediately after deflateInit, deflateInit2 or deflateReset, before any
call of deflate. The compressor and decompressor must use exactly the same
dictionary (see inflateSetDictionary).
The dictionary should consist of strings (byte sequences) that are likely
to be encountered later in the data to be compressed, with the most commonly
used strings preferably put towards the end of the dictionary. Using a
dictionary is most useful when the data to be compressed is short and can be
predicted with good accuracy; the data can then be compressed better than
with the default empty dictionary.
Depending on the size of the compression data structures selected by
deflateInit or deflateInit2, a part of the dictionary may in effect be
discarded, for example if the dictionary is larger than the window size in
deflate or deflate2. Thus the strings most likely to be useful should be
put at the end of the dictionary, not at the front. In addition, the
current implementation of deflate will use at most the window size minus
262 bytes of the provided dictionary.
Upon return of this function, strm->adler is set to the adler32 value
of the dictionary; the decompressor may later use this value to determine
which dictionary has been used by the compressor. (The adler32 value
applies to the whole dictionary even if only a subset of the dictionary is
actually used by the compressor.) If a raw deflate was requested, then the
adler32 value is not computed and strm->adler is not set.
deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
parameter is invalid (such as NULL dictionary) or the stream state is
inconsistent (for example if deflate has already been called for this stream
or if the compression method is bsort). deflateSetDictionary does not
perform any compression: this will be done by deflate().
*/
ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
z_streamp source));
/*
Sets the destination stream as a complete copy of the source stream.
This function can be useful when several compression strategies will be
tried, for example when there are several ways of pre-processing the input
data with a filter. The streams that will be discarded should then be freed
by calling deflateEnd. Note that deflateCopy duplicates the internal
compression state which can be quite large, so this strategy is slow and
can consume lots of memory.
deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
(such as zalloc being NULL). msg is left unchanged in both source and
destination.
*/
ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
/*
This function is equivalent to deflateEnd followed by deflateInit,
but does not free and reallocate all the internal compression state.
The stream will keep the same compression level and any other attributes
that may have been set by deflateInit2.
deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being NULL).
*/
ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
int level,
int strategy));
/*
Dynamically update the compression level and compression strategy. The
interpretation of level and strategy is as in deflateInit2. This can be
used to switch between compression and straight copy of the input data, or
to switch to a different kind of input data requiring a different
strategy. If the compression level is changed, the input available so far
is compressed with the old level (and may be flushed); the new level will
take effect only at the next call of deflate().
Before the call of deflateParams, the stream state must be set as for
a call of deflate(), since the currently available input may have to
be compressed and flushed. In particular, strm->avail_out must be non-zero.
deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR
if strm->avail_out was zero.
*/
ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
int good_length,
int max_lazy,
int nice_length,
int max_chain));
/*
Fine tune deflate's internal compression parameters. This should only be
used by someone who understands the algorithm used by zlib's deflate for
searching for the best matching string, and even then only by the most
fanatic optimizer trying to squeeze out the last compressed bit for their
specific input data. Read the deflate.c source code for the meaning of the
max_lazy, good_length, nice_length, and max_chain parameters.
deflateTune() can be called after deflateInit() or deflateInit2(), and
returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
*/
ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm,
uLong sourceLen));
/*
deflateBound() returns an upper bound on the compressed size after
deflation of sourceLen bytes. It must be called after deflateInit()
or deflateInit2(). This would be used to allocate an output buffer
for deflation in a single pass, and so would be called before deflate().
*/
ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm,
int bits,
int value));
/*
deflatePrime() inserts bits in the deflate output stream. The intent
is that this function is used to start off the deflate output with the
bits leftover from a previous deflate stream when appending to it. As such,
this function can only be used for raw deflate, and must be used before the
first deflate() call after a deflateInit2() or deflateReset(). bits must be
less than or equal to 16, and that many of the least significant bits of
value will be inserted in the output.
deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm,
gz_headerp head));
/*
deflateSetHeader() provides gzip header information for when a gzip
stream is requested by deflateInit2(). deflateSetHeader() may be called
after deflateInit2() or deflateReset() and before the first call of
deflate(). The text, time, os, extra field, name, and comment information
in the provided gz_header structure are written to the gzip header (xflag is
ignored -- the extra flags are set according to the compression level). The
caller must assure that, if not Z_NULL, name and comment are terminated with
a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
available there. If hcrc is true, a gzip header crc is included. Note that
the current versions of the command-line version of gzip (up through version
1.3.x) do not support header crc's, and will report that it is a "multi-part
gzip file" and give up.
If deflateSetHeader is not used, the default gzip header has text false,
the time set to zero, and os set to 255, with no extra, name, or comment
fields. The gzip header is returned to the default state by deflateReset().
deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
/*
ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
int windowBits));
This is another version of inflateInit with an extra parameter. The
fields next_in, avail_in, zalloc, zfree and opaque must be initialized
before by the caller.
The windowBits parameter is the base two logarithm of the maximum window
size (the size of the history buffer). It should be in the range 8..15 for
this version of the library. The default value is 15 if inflateInit is used
instead. windowBits must be greater than or equal to the windowBits value
provided to deflateInit2() while compressing, or it must be equal to 15 if
deflateInit2() was not used. If a compressed stream with a larger window
size is given as input, inflate() will return with the error code
Z_DATA_ERROR instead of trying to allocate a larger window.
windowBits can also be -8..-15 for raw inflate. In this case, -windowBits
determines the window size. inflate() will then process raw deflate data,
not looking for a zlib or gzip header, not generating a check value, and not
looking for any check values for comparison at the end of the stream. This
is for use with other formats that use the deflate compressed data format
such as zip. Those formats provide their own check values. If a custom
format is developed using the raw deflate format for compressed data, it is
recommended that a check value such as an adler32 or a crc32 be applied to
the uncompressed data as is done in the zlib, gzip, and zip formats. For
most applications, the zlib format should be used as is. Note that comments
above on the use in deflateInit2() applies to the magnitude of windowBits.
windowBits can also be greater than 15 for optional gzip decoding. Add
32 to windowBits to enable zlib and gzip decoding with automatic header
detection, or add 16 to decode only the gzip format (the zlib format will
return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is
a crc32 instead of an adler32.
inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg
is set to null if there is no error message. inflateInit2 does not perform
any decompression apart from reading the zlib header if present: this will
be done by inflate(). (So next_in and avail_in may be modified, but next_out
and avail_out are unchanged.)
*/
ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
const Bytef *dictionary,
uInt dictLength));
/*
Initializes the decompression dictionary from the given uncompressed byte
sequence. This function must be called immediately after a call of inflate,
if that call returned Z_NEED_DICT. The dictionary chosen by the compressor
can be determined from the adler32 value returned by that call of inflate.
The compressor and decompressor must use exactly the same dictionary (see
deflateSetDictionary). For raw inflate, this function can be called
immediately after inflateInit2() or inflateReset() and before any call of
inflate() to set the dictionary. The application must insure that the
dictionary that was used for compression is provided.
inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
parameter is invalid (such as NULL dictionary) or the stream state is
inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
expected one (incorrect adler32 value). inflateSetDictionary does not
perform any decompression: this will be done by subsequent calls of
inflate().
*/
ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm));
/*
Skips invalid compressed data until a full flush point (see above the
description of deflate with Z_FULL_FLUSH) can be found, or until all
available input is skipped. No output is provided.
inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR
if no more input was provided, Z_DATA_ERROR if no flush point has been found,
or Z_STREAM_ERROR if the stream structure was inconsistent. In the success
case, the application may save the current current value of total_in which
indicates where valid compressed data was found. In the error case, the
application may repeatedly call inflateSync, providing more input each time,
until success or end of the input data.
*/
ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
z_streamp source));
/*
Sets the destination stream as a complete copy of the source stream.
This function can be useful when randomly accessing a large stream. The
first pass through the stream can periodically record the inflate state,
allowing restarting inflate at those points when randomly accessing the
stream.
inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
(such as zalloc being NULL). msg is left unchanged in both source and
destination.
*/
ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
/*
This function is equivalent to inflateEnd followed by inflateInit,
but does not free and reallocate all the internal decompression state.
The stream will keep attributes that may have been set by inflateInit2.
inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being NULL).
*/
ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm,
int bits,
int value));
/*
This function inserts bits in the inflate input stream. The intent is
that this function is used to start inflating at a bit position in the
middle of a byte. The provided bits will be used before any bytes are used
from next_in. This function should only be used with raw inflate, and
should be used before the first inflate() call after inflateInit2() or
inflateReset(). bits must be less than or equal to 16, and that many of the
least significant bits of value will be inserted in the input.
inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm,
gz_headerp head));
/*
inflateGetHeader() requests that gzip header information be stored in the
provided gz_header structure. inflateGetHeader() may be called after
inflateInit2() or inflateReset(), and before the first call of inflate().
As inflate() processes the gzip stream, head->done is zero until the header
is completed, at which time head->done is set to one. If a zlib stream is
being decoded, then head->done is set to -1 to indicate that there will be
no gzip header information forthcoming. Note that Z_BLOCK can be used to
force inflate() to return immediately after header processing is complete
and before any actual data is decompressed.
The text, time, xflags, and os fields are filled in with the gzip header
contents. hcrc is set to true if there is a header CRC. (The header CRC
was valid if done is set to one.) If extra is not Z_NULL, then extra_max
contains the maximum number of bytes to write to extra. Once done is true,
extra_len contains the actual extra field length, and extra contains the
extra field, or that field truncated if extra_max is less than extra_len.
If name is not Z_NULL, then up to name_max characters are written there,
terminated with a zero unless the length is greater than name_max. If
comment is not Z_NULL, then up to comm_max characters are written there,
terminated with a zero unless the length is greater than comm_max. When
any of extra, name, or comment are not Z_NULL and the respective field is
not present in the header, then that field is set to Z_NULL to signal its
absence. This allows the use of deflateSetHeader() with the returned
structure to duplicate the header. However if those fields are set to
allocated memory, then the application will need to save those pointers
elsewhere so that they can be eventually freed.
If inflateGetHeader is not used, then the header information is simply
discarded. The header is always checked for validity, including the header
CRC if present. inflateReset() will reset the process to discard the header
information. The application would need to call inflateGetHeader() again to
retrieve the header from the next gzip stream.
inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
/*
ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits,
unsigned char FAR *window));
Initialize the internal stream state for decompression using inflateBack()
calls. The fields zalloc, zfree and opaque in strm must be initialized
before the call. If zalloc and zfree are Z_NULL, then the default library-
derived memory allocation routines are used. windowBits is the base two
logarithm of the window size, in the range 8..15. window is a caller
supplied buffer of that size. Except for special applications where it is
assured that deflate was used with small window sizes, windowBits must be 15
and a 32K byte window must be supplied to be able to decompress general
deflate streams.
See inflateBack() for the usage of these routines.
inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
- the paramaters are invalid, Z_MEM_ERROR if the internal state could not
+ the parameters are invalid, Z_MEM_ERROR if the internal state could not
be allocated, or Z_VERSION_ERROR if the version of the library does not
match the version of the header file.
*/
typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *));
typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned));
ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
in_func in, void FAR *in_desc,
out_func out, void FAR *out_desc));
/*
inflateBack() does a raw inflate with a single call using a call-back
interface for input and output. This is more efficient than inflate() for
file i/o applications in that it avoids copying between the output and the
sliding window by simply making the window itself the output buffer. This
function trusts the application to not change the output buffer passed by
the output function, at least until inflateBack() returns.
inflateBackInit() must be called first to allocate the internal state
and to initialize the state with the user-provided window buffer.
inflateBack() may then be used multiple times to inflate a complete, raw
deflate stream with each call. inflateBackEnd() is then called to free
the allocated state.
A raw deflate stream is one with no zlib or gzip header or trailer.
This routine would normally be used in a utility that reads zip or gzip
files and writes out uncompressed files. The utility would decode the
header and process the trailer on its own, hence this routine expects
only the raw deflate stream to decompress. This is different from the
normal behavior of inflate(), which expects either a zlib or gzip header and
trailer around the deflate stream.
inflateBack() uses two subroutines supplied by the caller that are then
called by inflateBack() for input and output. inflateBack() calls those
routines until it reads a complete deflate stream and writes out all of the
uncompressed data, or until it encounters an error. The function's
parameters and return types are defined above in the in_func and out_func
typedefs. inflateBack() will call in(in_desc, &buf) which should return the
number of bytes of provided input, and a pointer to that input in buf. If
there is no input available, in() must return zero--buf is ignored in that
case--and inflateBack() will return a buffer error. inflateBack() will call
out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out()
should return zero on success, or non-zero on failure. If out() returns
non-zero, inflateBack() will return with an error. Neither in() nor out()
are permitted to change the contents of the window provided to
inflateBackInit(), which is also the buffer that out() uses to write from.
The length written by out() will be at most the window size. Any non-zero
amount of input may be provided by in().
For convenience, inflateBack() can be provided input on the first call by
setting strm->next_in and strm->avail_in. If that input is exhausted, then
in() will be called. Therefore strm->next_in must be initialized before
calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called
immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in
must also be initialized, and then if strm->avail_in is not zero, input will
initially be taken from strm->next_in[0 .. strm->avail_in - 1].
The in_desc and out_desc parameters of inflateBack() is passed as the
first parameter of in() and out() respectively when they are called. These
descriptors can be optionally used to pass any information that the caller-
supplied in() and out() functions need to do their job.
On return, inflateBack() will set strm->next_in and strm->avail_in to
pass back any unused input that was provided by the last in() call. The
return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
if in() or out() returned an error, Z_DATA_ERROR if there was a format
error in the deflate stream (in which case strm->msg is set to indicate the
nature of the error), or Z_STREAM_ERROR if the stream was not properly
initialized. In the case of Z_BUF_ERROR, an input or output error can be
distinguished using strm->next_in which will be Z_NULL only if in() returned
an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to
out() returning non-zero. (in() will always be called before out(), so
strm->next_in is assured to be defined if out() returns non-zero.) Note
that inflateBack() cannot return Z_OK.
*/
ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm));
/*
All memory allocated by inflateBackInit() is freed.
inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
state was inconsistent.
*/
ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
/* Return flags indicating compile-time options.
Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
1.0: size of uInt
3.2: size of uLong
5.4: size of voidpf (pointer)
7.6: size of z_off_t
Compiler, assembler, and debug options:
8: DEBUG
9: ASMV or ASMINF -- use ASM code
10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
11: 0 (reserved)
One-time table building (smaller code, but not thread-safe if true):
12: BUILDFIXED -- build static block decoding tables when needed
13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
14,15: 0 (reserved)
Library content (indicates missing functionality):
16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
deflate code when not needed)
17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
and decode gzip streams (to avoid linking crc code)
18-19: 0 (reserved)
Operation variations (changes in library functionality):
20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
21: FASTEST -- deflate algorithm with only one, lowest compression level
22,23: 0 (reserved)
The sprintf variant used by gzprintf (zero is best):
24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
26: 0 = returns value, 1 = void -- 1 means inferred string length returned
Remainder:
27-31: 0 (reserved)
*/
/* utility functions */
/*
The following utility functions are implemented on top of the
basic stream-oriented functions. To simplify the interface, some
default options are assumed (compression level and memory usage,
standard memory allocation functions). The source code of these
utility functions can easily be modified if you need special options.
*/
ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen));
/*
Compresses the source buffer into the destination buffer. sourceLen is
the byte length of the source buffer. Upon entry, destLen is the total
size of the destination buffer, which must be at least the value returned
by compressBound(sourceLen). Upon exit, destLen is the actual size of the
compressed buffer.
This function can be used to compress a whole file at once if the
input file is mmap'ed.
compress returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_BUF_ERROR if there was not enough room in the output
buffer.
*/
ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen,
int level));
/*
Compresses the source buffer into the destination buffer. The level
parameter has the same meaning as in deflateInit. sourceLen is the byte
length of the source buffer. Upon entry, destLen is the total size of the
destination buffer, which must be at least the value returned by
compressBound(sourceLen). Upon exit, destLen is the actual size of the
compressed buffer.
compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
Z_STREAM_ERROR if the level parameter is invalid.
*/
ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
/*
compressBound() returns an upper bound on the compressed size after
compress() or compress2() on sourceLen bytes. It would be used before
a compress() or compress2() call to allocate the destination buffer.
*/
ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen));
/*
Decompresses the source buffer into the destination buffer. sourceLen is
the byte length of the source buffer. Upon entry, destLen is the total
size of the destination buffer, which must be large enough to hold the
entire uncompressed data. (The size of the uncompressed data must have
been saved previously by the compressor and transmitted to the decompressor
by some mechanism outside the scope of this compression library.)
Upon exit, destLen is the actual size of the compressed buffer.
This function can be used to decompress a whole file at once if the
input file is mmap'ed.
uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_BUF_ERROR if there was not enough room in the output
buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete.
*/
typedef voidp gzFile;
ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
/*
Opens a gzip (.gz) file for reading or writing. The mode parameter
is as in fopen ("rb" or "wb") but can also include a compression level
("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for
Huffman only compression as in "wb1h", or 'R' for run-length encoding
as in "wb1R". (See the description of deflateInit2 for more information
about the strategy parameter.)
gzopen can be used to read a file which is not in gzip format; in this
case gzread will directly read from the file without decompression.
gzopen returns NULL if the file could not be opened or if there was
insufficient memory to allocate the (de)compression state; errno
can be checked to distinguish the two cases (if errno is zero, the
zlib error is Z_MEM_ERROR). */
ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode));
/*
gzdopen() associates a gzFile with the file descriptor fd. File
- descriptors are obtained from calls like open, dup, creat, pipe or
+ descriptors are obtained from calls like open, dup, create, pipe or
fileno (in the file has been previously opened with fopen).
The mode parameter is as in gzopen.
The next call of gzclose on the returned gzFile will also close the
file descriptor fd, just like fclose(fdopen(fd), mode) closes the file
descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode).
gzdopen returns NULL if there was insufficient memory to allocate
the (de)compression state.
*/
ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
/*
Dynamically update the compression level or strategy. See the description
of deflateInit2 for the meaning of these parameters.
gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
opened for writing.
*/
ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
/*
Reads the given number of uncompressed bytes from the compressed file.
If the input file was not in gzip format, gzread copies the given number
of bytes into the buffer.
gzread returns the number of uncompressed bytes actually read (0 for
end of file, -1 for error). */
ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
voidpc buf, unsigned len));
/*
Writes the given number of uncompressed bytes into the compressed file.
gzwrite returns the number of uncompressed bytes actually written
(0 in case of error).
*/
ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...));
/*
Converts, formats, and writes the args to the compressed file under
control of the format string, as in fprintf. gzprintf returns the number of
uncompressed bytes actually written (0 in case of error). The number of
uncompressed bytes written is limited to 4095. The caller should assure that
this limit is not exceeded. If it is exceeded, then gzprintf() will return
return an error (0) with nothing written. In this case, there may also be a
buffer overflow with unpredictable consequences, which is possible only if
zlib was compiled with the insecure functions sprintf() or vsprintf()
because the secure snprintf() or vsnprintf() functions were not available.
*/
ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
/*
Writes the given null-terminated string to the compressed file, excluding
the terminating null character.
gzputs returns the number of characters written, or -1 in case of error.
*/
ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len));
/*
Reads bytes from the compressed file until len-1 characters are read, or
a newline character is read and transferred to buf, or an end-of-file
condition is encountered. The string is then terminated with a null
character.
gzgets returns buf, or Z_NULL in case of error.
*/
ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c));
/*
Writes c, converted to an unsigned char, into the compressed file.
gzputc returns the value that was written, or -1 in case of error.
*/
ZEXTERN int ZEXPORT gzgetc OF((gzFile file));
/*
Reads one byte from the compressed file. gzgetc returns this byte
or -1 in case of end of file or error.
*/
ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file));
/*
Push one character back onto the stream to be read again later.
Only one character of push-back is allowed. gzungetc() returns the
character pushed, or -1 on failure. gzungetc() will fail if a
character has been pushed but not read yet, or if c is -1. The pushed
character will be discarded if the stream is repositioned with gzseek()
or gzrewind().
*/
ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
/*
Flushes all pending output into the compressed file. The parameter
flush is as in the deflate() function. The return value is the zlib
error number (see function gzerror below). gzflush returns Z_OK if
the flush parameter is Z_FINISH and all output could be flushed.
gzflush should be called only when strictly necessary because it can
degrade compression.
*/
ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file,
z_off_t offset, int whence));
/*
Sets the starting position for the next gzread or gzwrite on the
given compressed file. The offset represents a number of bytes in the
uncompressed data stream. The whence parameter is defined as in lseek(2);
the value SEEK_END is not supported.
If the file is opened for reading, this function is emulated but can be
extremely slow. If the file is opened for writing, only forward seeks are
supported; gzseek then compresses a sequence of zeroes up to the new
starting position.
gzseek returns the resulting offset location as measured in bytes from
the beginning of the uncompressed stream, or -1 in case of error, in
particular if the file is opened for writing and the new starting position
would be before the current position.
*/
ZEXTERN int ZEXPORT gzrewind OF((gzFile file));
/*
Rewinds the given file. This function is supported only for reading.
gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
*/
ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file));
/*
Returns the starting position for the next gzread or gzwrite on the
given compressed file. This position represents a number of bytes in the
uncompressed data stream.
gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
*/
ZEXTERN int ZEXPORT gzeof OF((gzFile file));
/*
Returns 1 when EOF has previously been detected reading the given
input stream, otherwise zero.
*/
ZEXTERN int ZEXPORT gzdirect OF((gzFile file));
/*
Returns 1 if file is being read directly without decompression, otherwise
zero.
*/
ZEXTERN int ZEXPORT gzclose OF((gzFile file));
/*
Flushes all pending output if necessary, closes the compressed file
and deallocates all the (de)compression state. The return value is the zlib
error number (see function gzerror below).
*/
ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum));
/*
Returns the error message for the last error which occurred on the
given compressed file. errnum is set to zlib error number. If an
error occurred in the file system and not in the compression library,
errnum is set to Z_ERRNO and the application may consult errno
to get the exact error code.
*/
ZEXTERN void ZEXPORT gzclearerr OF((gzFile file));
/*
Clears the error and end-of-file flags for file. This is analogous to the
clearerr() function in stdio. This is useful for continuing to read a gzip
file that is being written concurrently.
*/
/* checksum functions */
/*
These functions are not related to compression but are exported
anyway because they might be useful in applications using the
compression library.
*/
ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
/*
Update a running Adler-32 checksum with the bytes buf[0..len-1] and
return the updated checksum. If buf is NULL, this function returns
the required initial value for the checksum.
An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
much faster. Usage example:
uLong adler = adler32(0L, Z_NULL, 0);
while (read_buffer(buffer, length) != EOF) {
adler = adler32(adler, buffer, length);
}
if (adler != original_adler) error();
*/
ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
z_off_t len2));
/*
Combine two Adler-32 checksums into one. For two sequences of bytes, seq1
and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of
seq1 and seq2 concatenated, requiring only adler1, adler2, and len2.
*/
ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len));
/*
Update a running CRC-32 with the bytes buf[0..len-1] and return the
updated CRC-32. If buf is NULL, this function returns the required initial
value for the for the crc. Pre- and post-conditioning (one's complement) is
performed within this function so it shouldn't be done by the application.
Usage example:
uLong crc = crc32(0L, Z_NULL, 0);
while (read_buffer(buffer, length) != EOF) {
crc = crc32(crc, buffer, length);
}
if (crc != original_crc) error();
*/
ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
/*
Combine two CRC-32 check values into one. For two sequences of bytes,
seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32
check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
len2.
*/
/* various hacks, don't look :) */
/* deflateInit and inflateInit are macros to allow checking the zlib version
* and the compiler's view of z_stream:
*/
ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level,
const char *version, int stream_size));
ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm,
const char *version, int stream_size));
ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method,
int windowBits, int memLevel,
int strategy, const char *version,
int stream_size));
ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits,
const char *version, int stream_size));
ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
unsigned char FAR *window,
const char *version,
int stream_size));
#define deflateInit(strm, level) \
deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
#define inflateInit(strm) \
inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream))
#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
(strategy), ZLIB_VERSION, sizeof(z_stream))
#define inflateInit2(strm, windowBits) \
inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
#define inflateBackInit(strm, windowBits, window) \
inflateBackInit_((strm), (windowBits), (window), \
ZLIB_VERSION, sizeof(z_stream))
#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL)
struct internal_state {int dummy;}; /* hack for buggy compilers */
#endif
ZEXTERN const char * ZEXPORT zError OF((int));
ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z));
ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void));
#ifdef __cplusplus
}
#endif
#endif /* ZLIB_H */
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/inftrees.h b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/inftrees.h
index baa53a0b1a..f53665311c 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/inftrees.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/inftrees.h
@@ -1,62 +1,62 @@
/* inftrees.h -- header to use inftrees.c
* Copyright (C) 1995-2005, 2010 Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
/* WARNING: this file should *not* be used by applications. It is
part of the implementation of the compression library and is
subject to change. Applications should only use zlib.h.
*/
/* Structure for decoding tables. Each entry provides either the
information needed to do the operation requested by the code that
indexed that table entry, or it provides a pointer to another
table that indexes more bits of the code. op indicates whether
the entry is a pointer to another table, a literal, a length or
distance, an end-of-block, or an invalid code. For a table
pointer, the low four bits of op is the number of index bits of
that table. For a length or distance, the low four bits of op
is the number of extra bits to get after the code. bits is
the number of bits in this code or part of the code to drop off
of the bit buffer. val is the actual byte to output in the case
of a literal, the base length or distance, or the offset from
the current table to the next table. Each entry is four bytes. */
typedef struct {
unsigned char op; /* operation, extra bits, table bits */
unsigned char bits; /* bits in this part of the code */
unsigned short val; /* offset in table or code value */
} code;
/* op values as set by inflate_table():
00000000 - literal
0000tttt - table link, tttt != 0 is the number of table index bits
0001eeee - length or distance, eeee is the number of extra bits
01100000 - end of block
01000000 - invalid code
*/
/* Maximum size of the dynamic table. The maximum number of code structures is
1444, which is the sum of 852 for literal/length codes and 592 for distance
codes. These values were found by exhaustive searches using the program
- examples/enough.c found in the zlib distribtution. The arguments to that
+ examples/enough.c found in the zlib distribution. The arguments to that
program are the number of symbols, the initial root table size, and the
maximum bit length of a code. "enough 286 9 15" for literal/length codes
returns returns 852, and "enough 30 6 15" for distance codes returns 592.
The initial root table size (9 or 6) is found in the fifth argument of the
inflate_table() calls in inflate.c and infback.c. If the root table size is
changed, then these maximum sizes would be need to be recalculated and
updated. */
#define ENOUGH_LENS 852
#define ENOUGH_DISTS 592
#define ENOUGH (ENOUGH_LENS+ENOUGH_DISTS)
/* Type of code to build for inflate_table() */
typedef enum {
CODES,
LENS,
DISTS
} codetype;
int ZLIB_INTERNAL inflate_table OF((codetype type, unsigned short FAR *lens,
unsigned codes, code FAR * FAR *table,
unsigned FAR *bits, unsigned short FAR *work));
diff --git a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/zlib.h b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/zlib.h
index 3e0c7672ac..05df0d5c9f 100644
--- a/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/zlib.h
+++ b/core/utilities/mediaserver/upnpsdk/Neptune/ThirdParty/zlib-1.2.8/zlib.h
@@ -1,1768 +1,1768 @@
/* zlib.h -- interface of the 'zlib' general purpose compression library
version 1.2.8, April 28th, 2013
Copyright (C) 1995-2013 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu
The data format used by the zlib library is described by RFCs (Request for
Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950
(zlib format), rfc1951 (deflate format) and rfc1952 (gzip format).
*/
#ifndef ZLIB_H
#define ZLIB_H
#include "zconf.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ZLIB_VERSION "1.2.8"
#define ZLIB_VERNUM 0x1280
#define ZLIB_VER_MAJOR 1
#define ZLIB_VER_MINOR 2
#define ZLIB_VER_REVISION 8
#define ZLIB_VER_SUBREVISION 0
/*
The 'zlib' compression library provides in-memory compression and
decompression functions, including integrity checks of the uncompressed data.
This version of the library supports only one compression method (deflation)
but other algorithms will be added later and will have the same stream
interface.
Compression can be done in a single step if the buffers are large enough,
or can be done by repeated calls of the compression function. In the latter
case, the application must provide more input and/or consume the output
(providing more output space) before each call.
The compressed data format used by default by the in-memory functions is
the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped
around a deflate stream, which is itself documented in RFC 1951.
The library also supports reading and writing files in gzip (.gz) format
with an interface similar to that of stdio using the functions that start
with "gz". The gzip format is different from the zlib format. gzip is a
gzip wrapper, documented in RFC 1952, wrapped around a deflate stream.
This library can optionally read and write gzip streams in memory as well.
The zlib format was designed to be compact and fast for use in memory
and on communications channels. The gzip format was designed for single-
file compression on file systems, has a larger header than zlib to maintain
directory information, and uses a different, slower check method than zlib.
The library does not install any signal handler. The decoder checks
the consistency of the compressed data, so the library should never crash
even in case of corrupted input.
*/
typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size));
typedef void (*free_func) OF((voidpf opaque, voidpf address));
struct internal_state;
typedef struct z_stream_s {
z_const Bytef *next_in; /* next input byte */
uInt avail_in; /* number of bytes available at next_in */
uLong total_in; /* total number of input bytes read so far */
Bytef *next_out; /* next output byte should be put there */
uInt avail_out; /* remaining free space at next_out */
uLong total_out; /* total number of bytes output so far */
z_const char *msg; /* last error message, NULL if no error */
struct internal_state FAR *state; /* not visible by applications */
alloc_func zalloc; /* used to allocate the internal state */
free_func zfree; /* used to free the internal state */
voidpf opaque; /* private data object passed to zalloc and zfree */
int data_type; /* best guess about the data type: binary or text */
uLong adler; /* adler32 value of the uncompressed data */
uLong reserved; /* reserved for future use */
} z_stream;
typedef z_stream FAR *z_streamp;
/*
gzip header information passed to and from zlib routines. See RFC 1952
for more details on the meanings of these fields.
*/
typedef struct gz_header_s {
int text; /* true if compressed data believed to be text */
uLong time; /* modification time */
int xflags; /* extra flags (not used when writing a gzip file) */
int os; /* operating system */
Bytef *extra; /* pointer to extra field or Z_NULL if none */
uInt extra_len; /* extra field length (valid if extra != Z_NULL) */
uInt extra_max; /* space at extra (only when reading header) */
Bytef *name; /* pointer to zero-terminated file name or Z_NULL */
uInt name_max; /* space at name (only when reading header) */
Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */
uInt comm_max; /* space at comment (only when reading header) */
int hcrc; /* true if there was or will be a header crc */
int done; /* true when done reading gzip header (not used
when writing a gzip file) */
} gz_header;
typedef gz_header FAR *gz_headerp;
/*
The application must update next_in and avail_in when avail_in has dropped
to zero. It must update next_out and avail_out when avail_out has dropped
to zero. The application must initialize zalloc, zfree and opaque before
calling the init function. All other fields are set by the compression
library and must not be updated by the application.
The opaque value provided by the application will be passed as the first
parameter for calls of zalloc and zfree. This can be useful for custom
memory management. The compression library attaches no meaning to the
opaque value.
zalloc must return Z_NULL if there is not enough memory for the object.
If zlib is used in a multi-threaded application, zalloc and zfree must be
thread safe.
On 16-bit systems, the functions zalloc and zfree must be able to allocate
exactly 65536 bytes, but will not be required to allocate more than this if
the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, pointers
returned by zalloc for objects of exactly 65536 bytes *must* have their
offset normalized to zero. The default allocation function provided by this
library ensures this (see zutil.c). To reduce memory requirements and avoid
any allocation of 64K objects, at the expense of compression ratio, compile
the library with -DMAX_WBITS=14 (see zconf.h).
The fields total_in and total_out can be used for statistics or progress
reports. After compression, total_in holds the total size of the
uncompressed data and may be saved for use in the decompressor (particularly
if the decompressor wants to decompress everything in a single step).
*/
/* constants */
#define Z_NO_FLUSH 0
#define Z_PARTIAL_FLUSH 1
#define Z_SYNC_FLUSH 2
#define Z_FULL_FLUSH 3
#define Z_FINISH 4
#define Z_BLOCK 5
#define Z_TREES 6
/* Allowed flush values; see deflate() and inflate() below for details */
#define Z_OK 0
#define Z_STREAM_END 1
#define Z_NEED_DICT 2
#define Z_ERRNO (-1)
#define Z_STREAM_ERROR (-2)
#define Z_DATA_ERROR (-3)
#define Z_MEM_ERROR (-4)
#define Z_BUF_ERROR (-5)
#define Z_VERSION_ERROR (-6)
/* Return codes for the compression/decompression functions. Negative values
* are errors, positive values are used for special but normal events.
*/
#define Z_NO_COMPRESSION 0
#define Z_BEST_SPEED 1
#define Z_BEST_COMPRESSION 9
#define Z_DEFAULT_COMPRESSION (-1)
/* compression levels */
#define Z_FILTERED 1
#define Z_HUFFMAN_ONLY 2
#define Z_RLE 3
#define Z_FIXED 4
#define Z_DEFAULT_STRATEGY 0
/* compression strategy; see deflateInit2() below for details */
#define Z_BINARY 0
#define Z_TEXT 1
#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */
#define Z_UNKNOWN 2
/* Possible values of the data_type field (though see inflate()) */
#define Z_DEFLATED 8
/* The deflate compression method (the only one supported in this version) */
#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */
#define zlib_version zlibVersion()
/* for compatibility with versions < 1.0.2 */
/* basic functions */
ZEXTERN const char * ZEXPORT zlibVersion OF((void));
/* The application can compare zlibVersion and ZLIB_VERSION for consistency.
If the first character differs, the library code actually used is not
compatible with the zlib.h header file used by the application. This check
is automatically made by deflateInit and inflateInit.
*/
/*
ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
Initializes the internal stream state for compression. The fields
zalloc, zfree and opaque must be initialized before by the caller. If
zalloc and zfree are set to Z_NULL, deflateInit updates them to use default
allocation functions.
The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9:
1 gives best speed, 9 gives best compression, 0 gives no compression at all
(the input data is simply copied a block at a time). Z_DEFAULT_COMPRESSION
requests a default compromise between speed and compression (currently
equivalent to level 6).
deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_STREAM_ERROR if level is not a valid compression level, or
Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible
with the version assumed by the caller (ZLIB_VERSION). msg is set to null
if there is no error message. deflateInit does not perform any compression:
this will be done by deflate().
*/
ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
/*
deflate compresses as much data as possible, and stops when the input
buffer becomes empty or the output buffer becomes full. It may introduce
some output latency (reading input without producing any output) except when
forced to flush.
The detailed semantics are as follows. deflate performs one or both of the
following actions:
- Compress more input starting at next_in and update next_in and avail_in
accordingly. If not all input can be processed (because there is not
enough room in the output buffer), next_in and avail_in are updated and
processing will resume at this point for the next call of deflate().
- Provide more output starting at next_out and update next_out and avail_out
accordingly. This action is forced if the parameter flush is non zero.
Forcing flush frequently degrades the compression ratio, so this parameter
should be set only when necessary (in interactive applications). Some
output may be provided even if flush is not set.
Before the call of deflate(), the application should ensure that at least
one of the actions is possible, by providing more input and/or consuming more
output, and updating avail_in or avail_out accordingly; avail_out should
never be zero before the call. The application can consume the compressed
output when it wants, for example when the output buffer is full (avail_out
== 0), or after each call of deflate(). If deflate returns Z_OK and with
zero avail_out, it must be called again after making room in the output
buffer because there might be more output pending.
Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to
decide how much data to accumulate before producing output, in order to
maximize compression.
If the parameter flush is set to Z_SYNC_FLUSH, all pending output is
flushed to the output buffer and the output is aligned on a byte boundary, so
that the decompressor can get all input data available so far. (In
particular avail_in is zero after the call if enough output space has been
provided before the call.) Flushing may degrade compression for some
compression algorithms and so it should be used only when necessary. This
completes the current deflate block and follows it with an empty stored block
that is three bits plus filler bits to the next byte, followed by four bytes
(00 00 ff ff).
If flush is set to Z_PARTIAL_FLUSH, all pending output is flushed to the
output buffer, but the output is not aligned to a byte boundary. All of the
input data so far will be available to the decompressor, as for Z_SYNC_FLUSH.
This completes the current deflate block and follows it with an empty fixed
codes block that is 10 bits long. This assures that enough bytes are output
in order for the decompressor to finish the block before the empty fixed code
block.
If flush is set to Z_BLOCK, a deflate block is completed and emitted, as
for Z_SYNC_FLUSH, but the output is not aligned on a byte boundary, and up to
seven bits of the current block are held to be written as the next byte after
the next deflate block is completed. In this case, the decompressor may not
be provided enough bits at this point in order to complete decompression of
the data provided so far to the compressor. It may need to wait for the next
block to be emitted. This is for advanced applications that need to control
the emission of deflate blocks.
If flush is set to Z_FULL_FLUSH, all output is flushed as with
Z_SYNC_FLUSH, and the compression state is reset so that decompression can
restart from this point if previous compressed data has been damaged or if
random access is desired. Using Z_FULL_FLUSH too often can seriously degrade
compression.
If deflate returns with avail_out == 0, this function must be called again
with the same value of the flush parameter and more output space (updated
avail_out), until the flush is complete (deflate returns with non-zero
avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that
avail_out is greater than six to avoid repeated flush markers due to
avail_out == 0 on return.
If the parameter flush is set to Z_FINISH, pending input is processed,
pending output is flushed and deflate returns with Z_STREAM_END if there was
enough output space; if deflate returns with Z_OK, this function must be
called again with Z_FINISH and more output space (updated avail_out) but no
more input data, until it returns with Z_STREAM_END or an error. After
deflate has returned Z_STREAM_END, the only possible operations on the stream
are deflateReset or deflateEnd.
Z_FINISH can be used immediately after deflateInit if all the compression
is to be done in a single step. In this case, avail_out must be at least the
value returned by deflateBound (see below). Then deflate is guaranteed to
return Z_STREAM_END. If not enough output space is provided, deflate will
not return Z_STREAM_END, and it must be called again as described above.
deflate() sets strm->adler to the adler32 checksum of all input read
so far (that is, total_in bytes).
deflate() may update strm->data_type if it can make a good guess about
the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered
binary. This field is only for information purposes and does not affect the
compression algorithm in any manner.
deflate() returns Z_OK if some progress has been made (more input
processed or more output produced), Z_STREAM_END if all input has been
consumed and all output has been produced (only when flush is set to
Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example
if next_in or next_out was Z_NULL), Z_BUF_ERROR if no progress is possible
(for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not
fatal, and deflate() can be called again with more input and more output
space to continue compressing.
*/
ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
/*
All dynamically allocated data structures for this stream are freed.
This function discards any unprocessed input and does not flush any pending
output.
deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the
stream state was inconsistent, Z_DATA_ERROR if the stream was freed
prematurely (some input or output was discarded). In the error case, msg
may be set but then points to a static string (which must not be
deallocated).
*/
/*
ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm));
Initializes the internal stream state for decompression. The fields
next_in, avail_in, zalloc, zfree and opaque must be initialized before by
the caller. If next_in is not Z_NULL and avail_in is large enough (the
exact value depends on the compression method), inflateInit determines the
compression method from the zlib header and allocates all data structures
accordingly; otherwise the allocation will be deferred to the first call of
inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to
use default allocation functions.
inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
version assumed by the caller, or Z_STREAM_ERROR if the parameters are
invalid, such as a null pointer to the structure. msg is set to null if
there is no error message. inflateInit does not perform any decompression
apart from possibly reading the zlib header if present: actual decompression
will be done by inflate(). (So next_in and avail_in may be modified, but
next_out and avail_out are unused and unchanged.) The current implementation
of inflateInit() does not process any header information -- that is deferred
until inflate() is called.
*/
ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush));
/*
inflate decompresses as much data as possible, and stops when the input
buffer becomes empty or the output buffer becomes full. It may introduce
some output latency (reading input without producing any output) except when
forced to flush.
The detailed semantics are as follows. inflate performs one or both of the
following actions:
- Decompress more input starting at next_in and update next_in and avail_in
accordingly. If not all input can be processed (because there is not
enough room in the output buffer), next_in is updated and processing will
resume at this point for the next call of inflate().
- Provide more output starting at next_out and update next_out and avail_out
accordingly. inflate() provides as much output as possible, until there is
no more input data or no more space in the output buffer (see below about
the flush parameter).
Before the call of inflate(), the application should ensure that at least
one of the actions is possible, by providing more input and/or consuming more
output, and updating the next_* and avail_* values accordingly. The
application can consume the uncompressed output when it wants, for example
when the output buffer is full (avail_out == 0), or after each call of
inflate(). If inflate returns Z_OK and with zero avail_out, it must be
called again after making room in the output buffer because there might be
more output pending.
The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, Z_FINISH,
Z_BLOCK, or Z_TREES. Z_SYNC_FLUSH requests that inflate() flush as much
output as possible to the output buffer. Z_BLOCK requests that inflate()
stop if and when it gets to the next deflate block boundary. When decoding
the zlib or gzip format, this will cause inflate() to return immediately
after the header and before the first block. When doing a raw inflate,
inflate() will go ahead and process the first block, and will return when it
gets to the end of that block, or when it runs out of data.
The Z_BLOCK option assists in appending to or combining deflate streams.
Also to assist in this, on return inflate() will set strm->data_type to the
number of unused bits in the last byte taken from strm->next_in, plus 64 if
inflate() is currently decoding the last block in the deflate stream, plus
128 if inflate() returned immediately after decoding an end-of-block code or
decoding the complete header up to just before the first byte of the deflate
stream. The end-of-block will not be indicated until all of the uncompressed
data from that block has been written to strm->next_out. The number of
unused bits may in general be greater than seven, except when bit 7 of
data_type is set, in which case the number of unused bits will be less than
eight. data_type is set as noted here every time inflate() returns for all
flush options, and so can be used to determine the amount of currently
consumed input in bits.
The Z_TREES option behaves as Z_BLOCK does, but it also returns when the
end of each deflate block header is reached, before any actual data in that
block is decoded. This allows the caller to determine the length of the
deflate block header for later use in random access within a deflate block.
256 is added to the value of strm->data_type when inflate() returns
immediately after reaching the end of the deflate block header.
inflate() should normally be called until it returns Z_STREAM_END or an
error. However if all decompression is to be performed in a single step (a
single call of inflate), the parameter flush should be set to Z_FINISH. In
this case all pending input is processed and all pending output is flushed;
avail_out must be large enough to hold all of the uncompressed data for the
operation to complete. (The size of the uncompressed data may have been
saved by the compressor for this purpose.) The use of Z_FINISH is not
required to perform an inflation in one step. However it may be used to
inform inflate that a faster approach can be used for the single inflate()
call. Z_FINISH also informs inflate to not maintain a sliding window if the
stream completes, which reduces inflate's memory footprint. If the stream
does not complete, either because not all of the stream is provided or not
enough output space is provided, then a sliding window will be allocated and
inflate() can be called again to continue the operation as if Z_NO_FLUSH had
been used.
In this implementation, inflate() always flushes as much output as
possible to the output buffer, and always uses the faster approach on the
first call. So the effects of the flush parameter in this implementation are
on the return value of inflate() as noted below, when inflate() returns early
when Z_BLOCK or Z_TREES is used, and when inflate() avoids the allocation of
memory for a sliding window when Z_FINISH is used.
If a preset dictionary is needed after this call (see inflateSetDictionary
below), inflate sets strm->adler to the Adler-32 checksum of the dictionary
chosen by the compressor and returns Z_NEED_DICT; otherwise it sets
strm->adler to the Adler-32 checksum of all output produced so far (that is,
total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described
below. At the end of the stream, inflate() checks that its computed adler32
checksum is equal to that saved by the compressor and returns Z_STREAM_END
only if the checksum is correct.
inflate() can decompress and check either zlib-wrapped or gzip-wrapped
deflate data. The header type is detected automatically, if requested when
initializing with inflateInit2(). Any information contained in the gzip
header is not retained, so applications that need that information should
instead use raw inflate, see inflateInit2() below, or inflateBack() and
perform their own processing of the gzip header and trailer. When processing
gzip-wrapped deflate data, strm->adler32 is set to the CRC-32 of the output
- producted so far. The CRC-32 is checked against the gzip trailer.
+ produced so far. The CRC-32 is checked against the gzip trailer.
inflate() returns Z_OK if some progress has been made (more input processed
or more output produced), Z_STREAM_END if the end of the compressed data has
been reached and all uncompressed output has been produced, Z_NEED_DICT if a
preset dictionary is needed at this point, Z_DATA_ERROR if the input data was
corrupted (input stream not conforming to the zlib format or incorrect check
value), Z_STREAM_ERROR if the stream structure was inconsistent (for example
next_in or next_out was Z_NULL), Z_MEM_ERROR if there was not enough memory,
Z_BUF_ERROR if no progress is possible or if there was not enough room in the
output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and
inflate() can be called again with more input and more output space to
continue decompressing. If Z_DATA_ERROR is returned, the application may
then call inflateSync() to look for a good compression block if a partial
recovery of the data is desired.
*/
ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm));
/*
All dynamically allocated data structures for this stream are freed.
This function discards any unprocessed input and does not flush any pending
output.
inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state
was inconsistent. In the error case, msg may be set but then points to a
static string (which must not be deallocated).
*/
/* Advanced functions */
/*
The following functions are needed only in some special applications.
*/
/*
ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm,
int level,
int method,
int windowBits,
int memLevel,
int strategy));
This is another version of deflateInit with more compression options. The
fields next_in, zalloc, zfree and opaque must be initialized before by the
caller.
The method parameter is the compression method. It must be Z_DEFLATED in
this version of the library.
The windowBits parameter is the base two logarithm of the window size
(the size of the history buffer). It should be in the range 8..15 for this
version of the library. Larger values of this parameter result in better
compression at the expense of memory usage. The default value is 15 if
deflateInit is used instead.
windowBits can also be -8..-15 for raw deflate. In this case, -windowBits
determines the window size. deflate() will then generate raw deflate data
with no zlib header or trailer, and will not compute an adler32 check value.
windowBits can also be greater than 15 for optional gzip encoding. Add
16 to windowBits to write a simple gzip header and trailer around the
compressed data instead of a zlib wrapper. The gzip header will have no
file name, no extra data, no comment, no modification time (set to zero), no
header crc, and the operating system will be set to 255 (unknown). If a
gzip stream is being written, strm->adler is a crc32 instead of an adler32.
The memLevel parameter specifies how much memory should be allocated
for the internal compression state. memLevel=1 uses minimum memory but is
slow and reduces compression ratio; memLevel=9 uses maximum memory for
optimal speed. The default value is 8. See zconf.h for total memory usage
as a function of windowBits and memLevel.
The strategy parameter is used to tune the compression algorithm. Use the
value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a
filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no
string match), or Z_RLE to limit match distances to one (run-length
encoding). Filtered data consists mostly of small values with a somewhat
random distribution. In this case, the compression algorithm is tuned to
compress them better. The effect of Z_FILTERED is to force more Huffman
coding and less string matching; it is somewhat intermediate between
Z_DEFAULT_STRATEGY and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as
fast as Z_HUFFMAN_ONLY, but give better compression for PNG image data. The
strategy parameter only affects the compression ratio but not the
correctness of the compressed output even if it is not set appropriately.
Z_FIXED prevents the use of dynamic Huffman codes, allowing for a simpler
decoder for special applications.
deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_STREAM_ERROR if any parameter is invalid (such as an invalid
method), or Z_VERSION_ERROR if the zlib library version (zlib_version) is
incompatible with the version assumed by the caller (ZLIB_VERSION). msg is
set to null if there is no error message. deflateInit2 does not perform any
compression: this will be done by deflate().
*/
ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm,
const Bytef *dictionary,
uInt dictLength));
/*
Initializes the compression dictionary from the given byte sequence
without producing any compressed output. When using the zlib format, this
function must be called immediately after deflateInit, deflateInit2 or
deflateReset, and before any call of deflate. When doing raw deflate, this
function must be called either before any call of deflate, or immediately
after the completion of a deflate block, i.e. after all input has been
consumed and all output has been delivered when using any of the flush
options Z_BLOCK, Z_PARTIAL_FLUSH, Z_SYNC_FLUSH, or Z_FULL_FLUSH. The
compressor and decompressor must use exactly the same dictionary (see
inflateSetDictionary).
The dictionary should consist of strings (byte sequences) that are likely
to be encountered later in the data to be compressed, with the most commonly
used strings preferably put towards the end of the dictionary. Using a
dictionary is most useful when the data to be compressed is short and can be
predicted with good accuracy; the data can then be compressed better than
with the default empty dictionary.
Depending on the size of the compression data structures selected by
deflateInit or deflateInit2, a part of the dictionary may in effect be
discarded, for example if the dictionary is larger than the window size
provided in deflateInit or deflateInit2. Thus the strings most likely to be
useful should be put at the end of the dictionary, not at the front. In
addition, the current implementation of deflate will use at most the window
size minus 262 bytes of the provided dictionary.
Upon return of this function, strm->adler is set to the adler32 value
of the dictionary; the decompressor may later use this value to determine
which dictionary has been used by the compressor. (The adler32 value
applies to the whole dictionary even if only a subset of the dictionary is
actually used by the compressor.) If a raw deflate was requested, then the
adler32 value is not computed and strm->adler is not set.
deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a
parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
inconsistent (for example if deflate has already been called for this stream
or if not at a block boundary for raw deflate). deflateSetDictionary does
not perform any compression: this will be done by deflate().
*/
ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest,
z_streamp source));
/*
Sets the destination stream as a complete copy of the source stream.
This function can be useful when several compression strategies will be
tried, for example when there are several ways of pre-processing the input
data with a filter. The streams that will be discarded should then be freed
by calling deflateEnd. Note that deflateCopy duplicates the internal
compression state which can be quite large, so this strategy is slow and can
consume lots of memory.
deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
(such as zalloc being Z_NULL). msg is left unchanged in both source and
destination.
*/
ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm));
/*
This function is equivalent to deflateEnd followed by deflateInit,
but does not free and reallocate all the internal compression state. The
stream will keep the same compression level and any other attributes that
may have been set by deflateInit2.
deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being Z_NULL).
*/
ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm,
int level,
int strategy));
/*
Dynamically update the compression level and compression strategy. The
interpretation of level and strategy is as in deflateInit2. This can be
used to switch between compression and straight copy of the input data, or
to switch to a different kind of input data requiring a different strategy.
If the compression level is changed, the input available so far is
compressed with the old level (and may be flushed); the new level will take
effect only at the next call of deflate().
Before the call of deflateParams, the stream state must be set as for
a call of deflate(), since the currently available input may have to be
compressed and flushed. In particular, strm->avail_out must be non-zero.
deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source
stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR if
strm->avail_out was zero.
*/
ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm,
int good_length,
int max_lazy,
int nice_length,
int max_chain));
/*
Fine tune deflate's internal compression parameters. This should only be
used by someone who understands the algorithm used by zlib's deflate for
searching for the best matching string, and even then only by the most
fanatic optimizer trying to squeeze out the last compressed bit for their
specific input data. Read the deflate.c source code for the meaning of the
max_lazy, good_length, nice_length, and max_chain parameters.
deflateTune() can be called after deflateInit() or deflateInit2(), and
returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream.
*/
ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm,
uLong sourceLen));
/*
deflateBound() returns an upper bound on the compressed size after
deflation of sourceLen bytes. It must be called after deflateInit() or
deflateInit2(), and after deflateSetHeader(), if used. This would be used
to allocate an output buffer for deflation in a single pass, and so would be
called before deflate(). If that first deflate() call is provided the
sourceLen input bytes, an output buffer allocated to the size returned by
deflateBound(), and the flush value Z_FINISH, then deflate() is guaranteed
to return Z_STREAM_END. Note that it is possible for the compressed size to
be larger than the value returned by deflateBound() if flush options other
than Z_FINISH or Z_NO_FLUSH are used.
*/
ZEXTERN int ZEXPORT deflatePending OF((z_streamp strm,
unsigned *pending,
int *bits));
/*
deflatePending() returns the number of bytes and bits of output that have
been generated, but not yet provided in the available output. The bytes not
provided would be due to the available output space having being consumed.
The number of bits of output not provided are between 0 and 7, where they
await more bits to join them in order to fill out a full byte. If pending
or bits are Z_NULL, then those values are not set.
deflatePending returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm,
int bits,
int value));
/*
deflatePrime() inserts bits in the deflate output stream. The intent
is that this function is used to start off the deflate output with the bits
leftover from a previous deflate stream when appending to it. As such, this
function can only be used for raw deflate, and must be used before the first
deflate() call after a deflateInit2() or deflateReset(). bits must be less
than or equal to 16, and that many of the least significant bits of value
will be inserted in the output.
deflatePrime returns Z_OK if success, Z_BUF_ERROR if there was not enough
room in the internal buffer to insert the bits, or Z_STREAM_ERROR if the
source stream state was inconsistent.
*/
ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm,
gz_headerp head));
/*
deflateSetHeader() provides gzip header information for when a gzip
stream is requested by deflateInit2(). deflateSetHeader() may be called
after deflateInit2() or deflateReset() and before the first call of
deflate(). The text, time, os, extra field, name, and comment information
in the provided gz_header structure are written to the gzip header (xflag is
ignored -- the extra flags are set according to the compression level). The
caller must assure that, if not Z_NULL, name and comment are terminated with
a zero byte, and that if extra is not Z_NULL, that extra_len bytes are
available there. If hcrc is true, a gzip header crc is included. Note that
the current versions of the command-line version of gzip (up through version
1.3.x) do not support header crc's, and will report that it is a "multi-part
gzip file" and give up.
If deflateSetHeader is not used, the default gzip header has text false,
the time set to zero, and os set to 255, with no extra, name, or comment
fields. The gzip header is returned to the default state by deflateReset().
deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
/*
ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm,
int windowBits));
This is another version of inflateInit with an extra parameter. The
fields next_in, avail_in, zalloc, zfree and opaque must be initialized
before by the caller.
The windowBits parameter is the base two logarithm of the maximum window
size (the size of the history buffer). It should be in the range 8..15 for
this version of the library. The default value is 15 if inflateInit is used
instead. windowBits must be greater than or equal to the windowBits value
provided to deflateInit2() while compressing, or it must be equal to 15 if
deflateInit2() was not used. If a compressed stream with a larger window
size is given as input, inflate() will return with the error code
Z_DATA_ERROR instead of trying to allocate a larger window.
windowBits can also be zero to request that inflate use the window size in
the zlib header of the compressed stream.
windowBits can also be -8..-15 for raw inflate. In this case, -windowBits
determines the window size. inflate() will then process raw deflate data,
not looking for a zlib or gzip header, not generating a check value, and not
looking for any check values for comparison at the end of the stream. This
is for use with other formats that use the deflate compressed data format
such as zip. Those formats provide their own check values. If a custom
format is developed using the raw deflate format for compressed data, it is
recommended that a check value such as an adler32 or a crc32 be applied to
the uncompressed data as is done in the zlib, gzip, and zip formats. For
most applications, the zlib format should be used as is. Note that comments
above on the use in deflateInit2() applies to the magnitude of windowBits.
windowBits can also be greater than 15 for optional gzip decoding. Add
32 to windowBits to enable zlib and gzip decoding with automatic header
detection, or add 16 to decode only the gzip format (the zlib format will
return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is a
crc32 instead of an adler32.
inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_VERSION_ERROR if the zlib library version is incompatible with the
version assumed by the caller, or Z_STREAM_ERROR if the parameters are
invalid, such as a null pointer to the structure. msg is set to null if
there is no error message. inflateInit2 does not perform any decompression
apart from possibly reading the zlib header if present: actual decompression
will be done by inflate(). (So next_in and avail_in may be modified, but
next_out and avail_out are unused and unchanged.) The current implementation
of inflateInit2() does not process any header information -- that is
deferred until inflate() is called.
*/
ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm,
const Bytef *dictionary,
uInt dictLength));
/*
Initializes the decompression dictionary from the given uncompressed byte
sequence. This function must be called immediately after a call of inflate,
if that call returned Z_NEED_DICT. The dictionary chosen by the compressor
can be determined from the adler32 value returned by that call of inflate.
The compressor and decompressor must use exactly the same dictionary (see
deflateSetDictionary). For raw inflate, this function can be called at any
time to set the dictionary. If the provided dictionary is smaller than the
window and there is already data in the window, then the provided dictionary
will amend what's there. The application must insure that the dictionary
that was used for compression is provided.
inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a
parameter is invalid (e.g. dictionary being Z_NULL) or the stream state is
inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the
expected one (incorrect adler32 value). inflateSetDictionary does not
perform any decompression: this will be done by subsequent calls of
inflate().
*/
ZEXTERN int ZEXPORT inflateGetDictionary OF((z_streamp strm,
Bytef *dictionary,
uInt *dictLength));
/*
Returns the sliding dictionary being maintained by inflate. dictLength is
set to the number of bytes in the dictionary, and that many bytes are copied
to dictionary. dictionary must have enough space, where 32768 bytes is
always enough. If inflateGetDictionary() is called with dictionary equal to
Z_NULL, then only the dictionary length is returned, and nothing is copied.
- Similary, if dictLength is Z_NULL, then it is not set.
+ Similarily, if dictLength is Z_NULL, then it is not set.
inflateGetDictionary returns Z_OK on success, or Z_STREAM_ERROR if the
stream state is inconsistent.
*/
ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm));
/*
Skips invalid compressed data until a possible full flush point (see above
for the description of deflate with Z_FULL_FLUSH) can be found, or until all
available input is skipped. No output is provided.
inflateSync searches for a 00 00 FF FF pattern in the compressed data.
All full flush points have this pattern, but not all occurrences of this
pattern are full flush points.
inflateSync returns Z_OK if a possible full flush point has been found,
Z_BUF_ERROR if no more input was provided, Z_DATA_ERROR if no flush point
has been found, or Z_STREAM_ERROR if the stream structure was inconsistent.
In the success case, the application may save the current current value of
total_in which indicates where valid compressed data was found. In the
error case, the application may repeatedly call inflateSync, providing more
input each time, until success or end of the input data.
*/
ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest,
z_streamp source));
/*
Sets the destination stream as a complete copy of the source stream.
This function can be useful when randomly accessing a large stream. The
first pass through the stream can periodically record the inflate state,
allowing restarting inflate at those points when randomly accessing the
stream.
inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_STREAM_ERROR if the source stream state was inconsistent
(such as zalloc being Z_NULL). msg is left unchanged in both source and
destination.
*/
ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm));
/*
This function is equivalent to inflateEnd followed by inflateInit,
but does not free and reallocate all the internal decompression state. The
stream will keep attributes that may have been set by inflateInit2.
inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being Z_NULL).
*/
ZEXTERN int ZEXPORT inflateReset2 OF((z_streamp strm,
int windowBits));
/*
This function is the same as inflateReset, but it also permits changing
the wrap and window size requests. The windowBits parameter is interpreted
the same as it is for inflateInit2.
inflateReset2 returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent (such as zalloc or state being Z_NULL), or if
the windowBits parameter is invalid.
*/
ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm,
int bits,
int value));
/*
This function inserts bits in the inflate input stream. The intent is
that this function is used to start inflating at a bit position in the
middle of a byte. The provided bits will be used before any bytes are used
from next_in. This function should only be used with raw inflate, and
should be used before the first inflate() call after inflateInit2() or
inflateReset(). bits must be less than or equal to 16, and that many of the
least significant bits of value will be inserted in the input.
If bits is negative, then the input stream bit buffer is emptied. Then
inflatePrime() can be called again to put bits in the buffer. This is used
to clear out bits leftover after feeding inflate a block description prior
to feeding inflate codes.
inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
ZEXTERN long ZEXPORT inflateMark OF((z_streamp strm));
/*
This function returns two values, one in the lower 16 bits of the return
value, and the other in the remaining upper bits, obtained by shifting the
return value down 16 bits. If the upper value is -1 and the lower value is
zero, then inflate() is currently decoding information outside of a block.
If the upper value is -1 and the lower value is non-zero, then inflate is in
the middle of a stored block, with the lower value equaling the number of
bytes from the input remaining to copy. If the upper value is not -1, then
it is the number of bits back from the current bit position in the input of
the code (literal or length/distance pair) currently being processed. In
that case the lower value is the number of bytes already emitted for that
code.
A code is being processed if inflate is waiting for more input to complete
decoding of the code, or if it has completed decoding but is waiting for
more output space to write the literal or match data.
inflateMark() is used to mark locations in the input data for random
access, which may be at bit positions, and to note those cases where the
output of a code may span boundaries of random access blocks. The current
location in the input stream can be determined from avail_in and data_type
as noted in the description for the Z_BLOCK flush parameter for inflate.
inflateMark returns the value noted above or -1 << 16 if the provided
source stream state was inconsistent.
*/
ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm,
gz_headerp head));
/*
inflateGetHeader() requests that gzip header information be stored in the
provided gz_header structure. inflateGetHeader() may be called after
inflateInit2() or inflateReset(), and before the first call of inflate().
As inflate() processes the gzip stream, head->done is zero until the header
is completed, at which time head->done is set to one. If a zlib stream is
being decoded, then head->done is set to -1 to indicate that there will be
no gzip header information forthcoming. Note that Z_BLOCK or Z_TREES can be
used to force inflate() to return immediately after header processing is
complete and before any actual data is decompressed.
The text, time, xflags, and os fields are filled in with the gzip header
contents. hcrc is set to true if there is a header CRC. (The header CRC
was valid if done is set to one.) If extra is not Z_NULL, then extra_max
contains the maximum number of bytes to write to extra. Once done is true,
extra_len contains the actual extra field length, and extra contains the
extra field, or that field truncated if extra_max is less than extra_len.
If name is not Z_NULL, then up to name_max characters are written there,
terminated with a zero unless the length is greater than name_max. If
comment is not Z_NULL, then up to comm_max characters are written there,
terminated with a zero unless the length is greater than comm_max. When any
of extra, name, or comment are not Z_NULL and the respective field is not
present in the header, then that field is set to Z_NULL to signal its
absence. This allows the use of deflateSetHeader() with the returned
structure to duplicate the header. However if those fields are set to
allocated memory, then the application will need to save those pointers
elsewhere so that they can be eventually freed.
If inflateGetHeader is not used, then the header information is simply
discarded. The header is always checked for validity, including the header
CRC if present. inflateReset() will reset the process to discard the header
information. The application would need to call inflateGetHeader() again to
retrieve the header from the next gzip stream.
inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source
stream state was inconsistent.
*/
/*
ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits,
unsigned char FAR *window));
Initialize the internal stream state for decompression using inflateBack()
calls. The fields zalloc, zfree and opaque in strm must be initialized
before the call. If zalloc and zfree are Z_NULL, then the default library-
derived memory allocation routines are used. windowBits is the base two
logarithm of the window size, in the range 8..15. window is a caller
supplied buffer of that size. Except for special applications where it is
assured that deflate was used with small window sizes, windowBits must be 15
and a 32K byte window must be supplied to be able to decompress general
deflate streams.
See inflateBack() for the usage of these routines.
inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of
the parameters are invalid, Z_MEM_ERROR if the internal state could not be
allocated, or Z_VERSION_ERROR if the version of the library does not match
the version of the header file.
*/
typedef unsigned (*in_func) OF((void FAR *,
z_const unsigned char FAR * FAR *));
typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned));
ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm,
in_func in, void FAR *in_desc,
out_func out, void FAR *out_desc));
/*
inflateBack() does a raw inflate with a single call using a call-back
interface for input and output. This is potentially more efficient than
inflate() for file i/o applications, in that it avoids copying between the
output and the sliding window by simply making the window itself the output
buffer. inflate() can be faster on modern CPUs when used with large
buffers. inflateBack() trusts the application to not change the output
buffer passed by the output function, at least until inflateBack() returns.
inflateBackInit() must be called first to allocate the internal state
and to initialize the state with the user-provided window buffer.
inflateBack() may then be used multiple times to inflate a complete, raw
deflate stream with each call. inflateBackEnd() is then called to free the
allocated state.
A raw deflate stream is one with no zlib or gzip header or trailer.
This routine would normally be used in a utility that reads zip or gzip
files and writes out uncompressed files. The utility would decode the
header and process the trailer on its own, hence this routine expects only
the raw deflate stream to decompress. This is different from the normal
behavior of inflate(), which expects either a zlib or gzip header and
trailer around the deflate stream.
inflateBack() uses two subroutines supplied by the caller that are then
called by inflateBack() for input and output. inflateBack() calls those
routines until it reads a complete deflate stream and writes out all of the
uncompressed data, or until it encounters an error. The function's
parameters and return types are defined above in the in_func and out_func
typedefs. inflateBack() will call in(in_desc, &buf) which should return the
number of bytes of provided input, and a pointer to that input in buf. If
there is no input available, in() must return zero--buf is ignored in that
case--and inflateBack() will return a buffer error. inflateBack() will call
out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out()
should return zero on success, or non-zero on failure. If out() returns
non-zero, inflateBack() will return with an error. Neither in() nor out()
are permitted to change the contents of the window provided to
inflateBackInit(), which is also the buffer that out() uses to write from.
The length written by out() will be at most the window size. Any non-zero
amount of input may be provided by in().
For convenience, inflateBack() can be provided input on the first call by
setting strm->next_in and strm->avail_in. If that input is exhausted, then
in() will be called. Therefore strm->next_in must be initialized before
calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called
immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in
must also be initialized, and then if strm->avail_in is not zero, input will
initially be taken from strm->next_in[0 .. strm->avail_in - 1].
The in_desc and out_desc parameters of inflateBack() is passed as the
first parameter of in() and out() respectively when they are called. These
descriptors can be optionally used to pass any information that the caller-
supplied in() and out() functions need to do their job.
On return, inflateBack() will set strm->next_in and strm->avail_in to
pass back any unused input that was provided by the last in() call. The
return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR
if in() or out() returned an error, Z_DATA_ERROR if there was a format error
in the deflate stream (in which case strm->msg is set to indicate the nature
of the error), or Z_STREAM_ERROR if the stream was not properly initialized.
In the case of Z_BUF_ERROR, an input or output error can be distinguished
using strm->next_in which will be Z_NULL only if in() returned an error. If
strm->next_in is not Z_NULL, then the Z_BUF_ERROR was due to out() returning
non-zero. (in() will always be called before out(), so strm->next_in is
assured to be defined if out() returns non-zero.) Note that inflateBack()
cannot return Z_OK.
*/
ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm));
/*
All memory allocated by inflateBackInit() is freed.
inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream
state was inconsistent.
*/
ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void));
/* Return flags indicating compile-time options.
Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other:
1.0: size of uInt
3.2: size of uLong
5.4: size of voidpf (pointer)
7.6: size of z_off_t
Compiler, assembler, and debug options:
8: DEBUG
9: ASMV or ASMINF -- use ASM code
10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention
11: 0 (reserved)
One-time table building (smaller code, but not thread-safe if true):
12: BUILDFIXED -- build static block decoding tables when needed
13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed
14,15: 0 (reserved)
Library content (indicates missing functionality):
16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking
deflate code when not needed)
17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect
and decode gzip streams (to avoid linking crc code)
18-19: 0 (reserved)
Operation variations (changes in library functionality):
20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate
21: FASTEST -- deflate algorithm with only one, lowest compression level
22,23: 0 (reserved)
The sprintf variant used by gzprintf (zero is best):
24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format
25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure!
26: 0 = returns value, 1 = void -- 1 means inferred string length returned
Remainder:
27-31: 0 (reserved)
*/
#ifndef Z_SOLO
/* utility functions */
/*
The following utility functions are implemented on top of the basic
stream-oriented functions. To simplify the interface, some default options
are assumed (compression level and memory usage, standard memory allocation
functions). The source code of these utility functions can be modified if
you need special options.
*/
ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen));
/*
Compresses the source buffer into the destination buffer. sourceLen is
the byte length of the source buffer. Upon entry, destLen is the total size
of the destination buffer, which must be at least the value returned by
compressBound(sourceLen). Upon exit, destLen is the actual size of the
compressed buffer.
compress returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_BUF_ERROR if there was not enough room in the output
buffer.
*/
ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen,
int level));
/*
Compresses the source buffer into the destination buffer. The level
parameter has the same meaning as in deflateInit. sourceLen is the byte
length of the source buffer. Upon entry, destLen is the total size of the
destination buffer, which must be at least the value returned by
compressBound(sourceLen). Upon exit, destLen is the actual size of the
compressed buffer.
compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough
memory, Z_BUF_ERROR if there was not enough room in the output buffer,
Z_STREAM_ERROR if the level parameter is invalid.
*/
ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen));
/*
compressBound() returns an upper bound on the compressed size after
compress() or compress2() on sourceLen bytes. It would be used before a
compress() or compress2() call to allocate the destination buffer.
*/
ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen,
const Bytef *source, uLong sourceLen));
/*
Decompresses the source buffer into the destination buffer. sourceLen is
the byte length of the source buffer. Upon entry, destLen is the total size
of the destination buffer, which must be large enough to hold the entire
uncompressed data. (The size of the uncompressed data must have been saved
previously by the compressor and transmitted to the decompressor by some
mechanism outside the scope of this compression library.) Upon exit, destLen
is the actual size of the uncompressed buffer.
uncompress returns Z_OK if success, Z_MEM_ERROR if there was not
enough memory, Z_BUF_ERROR if there was not enough room in the output
buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. In
the case where there is not enough room, uncompress() will fill the output
buffer with the uncompressed data up to that point.
*/
/* gzip file access functions */
/*
This library supports reading and writing files in gzip (.gz) format with
an interface similar to that of stdio, using the functions that start with
"gz". The gzip format is different from the zlib format. gzip is a gzip
wrapper, documented in RFC 1952, wrapped around a deflate stream.
*/
typedef struct gzFile_s *gzFile; /* semi-opaque gzip file descriptor */
/*
ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
Opens a gzip (.gz) file for reading or writing. The mode parameter is as
in fopen ("rb" or "wb") but can also include a compression level ("wb9") or
a strategy: 'f' for filtered data as in "wb6f", 'h' for Huffman-only
compression as in "wb1h", 'R' for run-length encoding as in "wb1R", or 'F'
for fixed code compression as in "wb9F". (See the description of
deflateInit2 for more information about the strategy parameter.) 'T' will
request transparent writing or appending with no compression and not using
the gzip format.
"a" can be used instead of "w" to request that the gzip stream that will
be written be appended to the file. "+" will result in an error, since
reading and writing to the same gzip file is not supported. The addition of
"x" when writing will create the file exclusively, which fails if the file
already exists. On systems that support it, the addition of "e" when
reading or writing will set the flag to close the file on an execve() call.
These functions, as well as gzip, will read and decode a sequence of gzip
streams in a file. The append function of gzopen() can be used to create
such a file. (Also see gzflush() for another way to do this.) When
appending, gzopen does not test whether the file begins with a gzip stream,
nor does it look for the end of the gzip streams to begin appending. gzopen
will simply append a gzip stream to the existing file.
gzopen can be used to read a file which is not in gzip format; in this
case gzread will directly read from the file without decompression. When
reading, this will be detected automatically by looking for the magic two-
byte gzip header.
gzopen returns NULL if the file could not be opened, if there was
insufficient memory to allocate the gzFile state, or if an invalid mode was
specified (an 'r', 'w', or 'a' was not provided, or '+' was provided).
errno can be checked to determine if the reason gzopen failed was that the
file could not be opened.
*/
ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode));
/*
gzdopen associates a gzFile with the file descriptor fd. File descriptors
- are obtained from calls like open, dup, creat, pipe or fileno (if the file
+ are obtained from calls like open, dup, create, pipe or fileno (if the file
has been previously opened with fopen). The mode parameter is as in gzopen.
The next call of gzclose on the returned gzFile will also close the file
descriptor fd, just like fclose(fdopen(fd, mode)) closes the file descriptor
fd. If you want to keep fd open, use fd = dup(fd_keep); gz = gzdopen(fd,
mode);. The duplicated descriptor should be saved to avoid a leak, since
gzdopen does not close fd if it fails. If you are using fileno() to get the
file descriptor from a FILE *, then you will have to use dup() to avoid
double-close()ing the file descriptor. Both gzclose() and fclose() will
close the associated file descriptor, so they need to have different file
descriptors.
gzdopen returns NULL if there was insufficient memory to allocate the
gzFile state, if an invalid mode was specified (an 'r', 'w', or 'a' was not
provided, or '+' was provided), or if fd is -1. The file descriptor is not
used until the next gz* read, write, seek, or close operation, so gzdopen
will not detect if fd is invalid (unless fd is -1).
*/
ZEXTERN int ZEXPORT gzbuffer OF((gzFile file, unsigned size));
/*
Set the internal buffer size used by this library's functions. The
default buffer size is 8192 bytes. This function must be called after
gzopen() or gzdopen(), and before any other calls that read or write the
file. The buffer memory allocation is always deferred to the first read or
write. Two buffers are allocated, either both of the specified size when
writing, or one of the specified size and the other twice that size when
reading. A larger buffer size of, for example, 64K or 128K bytes will
noticeably increase the speed of decompression (reading).
The new buffer size also affects the maximum length for gzprintf().
gzbuffer() returns 0 on success, or -1 on failure, such as being called
too late.
*/
ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy));
/*
Dynamically update the compression level or strategy. See the description
of deflateInit2 for the meaning of these parameters.
gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not
opened for writing.
*/
ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
/*
Reads the given number of uncompressed bytes from the compressed file. If
the input file is not in gzip format, gzread copies the given number of
bytes into the buffer directly from the file.
After reaching the end of a gzip stream in the input, gzread will continue
to read, looking for another gzip stream. Any number of gzip streams may be
concatenated in the input file, and will all be decompressed by gzread().
If something other than a gzip stream is encountered after a gzip stream,
that remaining trailing garbage is ignored (and no error is returned).
gzread can be used to read a gzip file that is being concurrently written.
Upon reaching the end of the input, gzread will return with the available
data. If the error code returned by gzerror is Z_OK or Z_BUF_ERROR, then
gzclearerr can be used to clear the end of file indicator in order to permit
gzread to be tried again. Z_OK indicates that a gzip stream was completed
on the last gzread. Z_BUF_ERROR indicates that the input file ended in the
middle of a gzip stream. Note that gzread does not return -1 in the event
of an incomplete gzip stream. This error is deferred until gzclose(), which
will return Z_BUF_ERROR if the last gzread ended in the middle of a gzip
stream. Alternatively, gzerror can be used before gzclose to detect this
case.
gzread returns the number of uncompressed bytes actually read, less than
len for end of file, or -1 for error.
*/
ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
voidpc buf, unsigned len));
/*
Writes the given number of uncompressed bytes into the compressed file.
gzwrite returns the number of uncompressed bytes written or 0 in case of
error.
*/
ZEXTERN int ZEXPORTVA gzprintf Z_ARG((gzFile file, const char *format, ...));
/*
Converts, formats, and writes the arguments to the compressed file under
control of the format string, as in fprintf. gzprintf returns the number of
uncompressed bytes actually written, or 0 in case of error. The number of
uncompressed bytes written is limited to 8191, or one less than the buffer
size given to gzbuffer(). The caller should assure that this limit is not
exceeded. If it is exceeded, then gzprintf() will return an error (0) with
nothing written. In this case, there may also be a buffer overflow with
unpredictable consequences, which is possible only if zlib was compiled with
the insecure functions sprintf() or vsprintf() because the secure snprintf()
or vsnprintf() functions were not available. This can be determined using
zlibCompileFlags().
*/
ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s));
/*
Writes the given null-terminated string to the compressed file, excluding
the terminating null character.
gzputs returns the number of characters written, or -1 in case of error.
*/
ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len));
/*
Reads bytes from the compressed file until len-1 characters are read, or a
newline character is read and transferred to buf, or an end-of-file
condition is encountered. If any characters are read or if len == 1, the
string is terminated with a null character. If no characters are read due
to an end-of-file or len < 1, then the buffer is left untouched.
gzgets returns buf which is a null-terminated string, or it returns NULL
for end-of-file or in case of error. If there was an error, the contents at
buf are indeterminate.
*/
ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c));
/*
Writes c, converted to an unsigned char, into the compressed file. gzputc
returns the value that was written, or -1 in case of error.
*/
ZEXTERN int ZEXPORT gzgetc OF((gzFile file));
/*
Reads one byte from the compressed file. gzgetc returns this byte or -1
in case of end of file or error. This is implemented as a macro for speed.
As such, it does not do all of the checking the other functions do. I.e.
it does not check to see if file is NULL, nor whether the structure file
points to has been clobbered or not.
*/
ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file));
/*
Push one character back onto the stream to be read as the first character
on the next read. At least one character of push-back is allowed.
gzungetc() returns the character pushed, or -1 on failure. gzungetc() will
fail if c is -1, and may fail if a character has been pushed but not read
yet. If gzungetc is used immediately after gzopen or gzdopen, at least the
output buffer size of pushed characters is allowed. (See gzbuffer above.)
The pushed character will be discarded if the stream is repositioned with
gzseek() or gzrewind().
*/
ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush));
/*
Flushes all pending output into the compressed file. The parameter flush
is as in the deflate() function. The return value is the zlib error number
(see function gzerror below). gzflush is only permitted when writing.
If the flush parameter is Z_FINISH, the remaining data is written and the
gzip stream is completed in the output. If gzwrite() is called again, a new
gzip stream will be started in the output. gzread() is able to read such
- concatented gzip streams.
+ concatenated gzip streams.
gzflush should be called only when strictly necessary because it will
degrade compression if called too often.
*/
/*
ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file,
z_off_t offset, int whence));
Sets the starting position for the next gzread or gzwrite on the given
compressed file. The offset represents a number of bytes in the
uncompressed data stream. The whence parameter is defined as in lseek(2);
the value SEEK_END is not supported.
If the file is opened for reading, this function is emulated but can be
extremely slow. If the file is opened for writing, only forward seeks are
supported; gzseek then compresses a sequence of zeroes up to the new
starting position.
gzseek returns the resulting offset location as measured in bytes from
the beginning of the uncompressed stream, or -1 in case of error, in
particular if the file is opened for writing and the new starting position
would be before the current position.
*/
ZEXTERN int ZEXPORT gzrewind OF((gzFile file));
/*
Rewinds the given file. This function is supported only for reading.
gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET)
*/
/*
ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file));
Returns the starting position for the next gzread or gzwrite on the given
compressed file. This position represents a number of bytes in the
uncompressed data stream, and is zero when starting, even if appending or
reading a gzip stream from the middle of a file using gzdopen().
gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR)
*/
/*
ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile file));
Returns the current offset in the file being read or written. This offset
includes the count of bytes that precede the gzip stream, for example when
appending or when using gzdopen() for reading. When reading, the offset
does not include as yet unused buffered input. This information can be used
for a progress indicator. On error, gzoffset() returns -1.
*/
ZEXTERN int ZEXPORT gzeof OF((gzFile file));
/*
Returns true (1) if the end-of-file indicator has been set while reading,
false (0) otherwise. Note that the end-of-file indicator is set only if the
read tried to go past the end of the input, but came up short. Therefore,
just like feof(), gzeof() may return false even if there is no more data to
read, in the event that the last read request was for the exact number of
bytes remaining in the input file. This will happen if the input file size
is an exact multiple of the buffer size.
If gzeof() returns true, then the read functions will return no more data,
unless the end-of-file indicator is reset by gzclearerr() and the input file
has grown since the previous end of file was detected.
*/
ZEXTERN int ZEXPORT gzdirect OF((gzFile file));
/*
Returns true (1) if file is being copied directly while reading, or false
(0) if file is a gzip stream being decompressed.
If the input file is empty, gzdirect() will return true, since the input
does not contain a gzip stream.
If gzdirect() is used immediately after gzopen() or gzdopen() it will
cause buffers to be allocated to allow reading the file to determine if it
is a gzip file. Therefore if gzbuffer() is used, it should be called before
gzdirect().
When writing, gzdirect() returns true (1) if transparent writing was
requested ("wT" for the gzopen() mode), or false (0) otherwise. (Note:
gzdirect() is not needed when writing. Transparent writing must be
explicitly requested, so the application already knows the answer. When
linking statically, using gzdirect() will include all of the zlib code for
gzip file reading and decompression, which may not be desired.)
*/
ZEXTERN int ZEXPORT gzclose OF((gzFile file));
/*
Flushes all pending output if necessary, closes the compressed file and
deallocates the (de)compression state. Note that once file is closed, you
cannot call gzerror with file, since its structures have been deallocated.
gzclose must not be called more than once on the same file, just as free
must not be called more than once on the same allocation.
gzclose will return Z_STREAM_ERROR if file is not valid, Z_ERRNO on a
file operation error, Z_MEM_ERROR if out of memory, Z_BUF_ERROR if the
last read ended in the middle of a gzip stream, or Z_OK on success.
*/
ZEXTERN int ZEXPORT gzclose_r OF((gzFile file));
ZEXTERN int ZEXPORT gzclose_w OF((gzFile file));
/*
Same as gzclose(), but gzclose_r() is only for use when reading, and
gzclose_w() is only for use when writing or appending. The advantage to
using these instead of gzclose() is that they avoid linking in zlib
compression or decompression code that is not used when only reading or only
writing respectively. If gzclose() is used, then both compression and
decompression code will be included the application when linking to a static
zlib library.
*/
ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum));
/*
Returns the error message for the last error which occurred on the given
compressed file. errnum is set to zlib error number. If an error occurred
in the file system and not in the compression library, errnum is set to
Z_ERRNO and the application may consult errno to get the exact error code.
The application must not modify the returned string. Future calls to
this function may invalidate the previously returned string. If file is
closed, then the string previously returned by gzerror will no longer be
available.
gzerror() should be used to distinguish errors from end-of-file for those
functions above that do not distinguish those cases in their return values.
*/
ZEXTERN void ZEXPORT gzclearerr OF((gzFile file));
/*
Clears the error and end-of-file flags for file. This is analogous to the
clearerr() function in stdio. This is useful for continuing to read a gzip
file that is being written concurrently.
*/
#endif /* !Z_SOLO */
/* checksum functions */
/*
These functions are not related to compression but are exported
anyway because they might be useful in applications using the compression
library.
*/
ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len));
/*
Update a running Adler-32 checksum with the bytes buf[0..len-1] and
return the updated checksum. If buf is Z_NULL, this function returns the
required initial value for the checksum.
An Adler-32 checksum is almost as reliable as a CRC32 but can be computed
much faster.
Usage example:
uLong adler = adler32(0L, Z_NULL, 0);
while (read_buffer(buffer, length) != EOF) {
adler = adler32(adler, buffer, length);
}
if (adler != original_adler) error();
*/
/*
ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2,
z_off_t len2));
Combine two Adler-32 checksums into one. For two sequences of bytes, seq1
and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for
each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of
seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. Note
that the z_off_t type (like off_t) is a signed integer. If len2 is
negative, the result has no meaning or utility.
*/
ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len));
/*
Update a running CRC-32 with the bytes buf[0..len-1] and return the
updated CRC-32. If buf is Z_NULL, this function returns the required
initial value for the crc. Pre- and post-conditioning (one's complement) is
performed within this function so it shouldn't be done by the application.
Usage example:
uLong crc = crc32(0L, Z_NULL, 0);
while (read_buffer(buffer, length) != EOF) {
crc = crc32(crc, buffer, length);
}
if (crc != original_crc) error();
*/
/*
ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2));
Combine two CRC-32 check values into one. For two sequences of bytes,
seq1 and seq2 with lengths len1 and len2, CRC-32 check values were
calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32
check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and
len2.
*/
/* various hacks, don't look :) */
/* deflateInit and inflateInit are macros to allow checking the zlib version
* and the compiler's view of z_stream:
*/
ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level,
const char *version, int stream_size));
ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm,
const char *version, int stream_size));
ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method,
int windowBits, int memLevel,
int strategy, const char *version,
int stream_size));
ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits,
const char *version, int stream_size));
ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits,
unsigned char FAR *window,
const char *version,
int stream_size));
#define deflateInit(strm, level) \
deflateInit_((strm), (level), ZLIB_VERSION, (int)sizeof(z_stream))
#define inflateInit(strm) \
inflateInit_((strm), ZLIB_VERSION, (int)sizeof(z_stream))
#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\
(strategy), ZLIB_VERSION, (int)sizeof(z_stream))
#define inflateInit2(strm, windowBits) \
inflateInit2_((strm), (windowBits), ZLIB_VERSION, \
(int)sizeof(z_stream))
#define inflateBackInit(strm, windowBits, window) \
inflateBackInit_((strm), (windowBits), (window), \
ZLIB_VERSION, (int)sizeof(z_stream))
#ifndef Z_SOLO
/* gzgetc() macro and its supporting function and exposed data structure. Note
* that the real internal state is much larger than the exposed structure.
* This abbreviated structure exposes just enough for the gzgetc() macro. The
* user should not mess with these exposed elements, since their names or
* behavior could change in the future, perhaps even capriciously. They can
* only be used by the gzgetc() macro. You have been warned.
*/
struct gzFile_s {
unsigned have;
unsigned char *next;
z_off64_t pos;
};
ZEXTERN int ZEXPORT gzgetc_ OF((gzFile file)); /* backward compatibility */
#ifdef Z_PREFIX_SET
# undef z_gzgetc
# define z_gzgetc(g) \
((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g))
#else
# define gzgetc(g) \
((g)->have ? ((g)->have--, (g)->pos++, *((g)->next)++) : gzgetc(g))
#endif
/* provide 64-bit offset functions if _LARGEFILE64_SOURCE defined, and/or
* change the regular functions to 64 bits if _FILE_OFFSET_BITS is 64 (if
* both are true, the application gets the *64 functions, and the regular
* functions are changed to 64 bits) -- in case these are set on systems
* without large file support, _LFS64_LARGEFILE must also be true
*/
#ifdef Z_LARGE64
ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
ZEXTERN z_off64_t ZEXPORT gzseek64 OF((gzFile, z_off64_t, int));
ZEXTERN z_off64_t ZEXPORT gztell64 OF((gzFile));
ZEXTERN z_off64_t ZEXPORT gzoffset64 OF((gzFile));
ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off64_t));
ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off64_t));
#endif
#if !defined(ZLIB_INTERNAL) && defined(Z_WANT64)
# ifdef Z_PREFIX_SET
# define z_gzopen z_gzopen64
# define z_gzseek z_gzseek64
# define z_gztell z_gztell64
# define z_gzoffset z_gzoffset64
# define z_adler32_combine z_adler32_combine64
# define z_crc32_combine z_crc32_combine64
# else
# define gzopen gzopen64
# define gzseek gzseek64
# define gztell gztell64
# define gzoffset gzoffset64
# define adler32_combine adler32_combine64
# define crc32_combine crc32_combine64
# endif
# ifndef Z_LARGE64
ZEXTERN gzFile ZEXPORT gzopen64 OF((const char *, const char *));
ZEXTERN z_off_t ZEXPORT gzseek64 OF((gzFile, z_off_t, int));
ZEXTERN z_off_t ZEXPORT gztell64 OF((gzFile));
ZEXTERN z_off_t ZEXPORT gzoffset64 OF((gzFile));
ZEXTERN uLong ZEXPORT adler32_combine64 OF((uLong, uLong, z_off_t));
ZEXTERN uLong ZEXPORT crc32_combine64 OF((uLong, uLong, z_off_t));
# endif
#else
ZEXTERN gzFile ZEXPORT gzopen OF((const char *, const char *));
ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile, z_off_t, int));
ZEXTERN z_off_t ZEXPORT gztell OF((gzFile));
ZEXTERN z_off_t ZEXPORT gzoffset OF((gzFile));
ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
#endif
#else /* Z_SOLO */
ZEXTERN uLong ZEXPORT adler32_combine OF((uLong, uLong, z_off_t));
ZEXTERN uLong ZEXPORT crc32_combine OF((uLong, uLong, z_off_t));
#endif /* !Z_SOLO */
/* hack for buggy compilers */
#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL)
struct internal_state {int dummy;};
#endif
/* undocumented functions */
ZEXTERN const char * ZEXPORT zError OF((int));
ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp));
ZEXTERN const z_crc_t FAR * ZEXPORT get_crc_table OF((void));
ZEXTERN int ZEXPORT inflateUndermine OF((z_streamp, int));
ZEXTERN int ZEXPORT inflateResetKeep OF((z_streamp));
ZEXTERN int ZEXPORT deflateResetKeep OF((z_streamp));
#if defined(_WIN32) && !defined(Z_SOLO)
ZEXTERN gzFile ZEXPORT gzopen_w OF((const wchar_t *path,
const char *mode));
#endif
#if defined(STDC) || defined(Z_HAVE_STDARG_H)
# ifndef Z_SOLO
ZEXTERN int ZEXPORTVA gzvprintf Z_ARG((gzFile file,
const char *format,
va_list va));
# endif
#endif
#ifdef __cplusplus
}
#endif
#endif /* ZLIB_H */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPoint.cpp b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPoint.cpp
index c5cb61b0d9..4aeec3caa3 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPoint.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPoint.cpp
@@ -1,1872 +1,1872 @@
/*****************************************************************
|
| Platinum - Control Point
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "PltCtrlPoint.h"
#include "PltUPnP.h"
#include "PltDeviceData.h"
#include "PltUtilities.h"
#include "PltCtrlPointTask.h"
#include "PltSsdp.h"
#include "PltHttpServer.h"
#include "PltConstants.h"
NPT_SET_LOCAL_LOGGER("platinum.core.ctrlpoint")
/*----------------------------------------------------------------------
| PLT_CtrlPointListenerOnDeviceAddedIterator class
+---------------------------------------------------------------------*/
class PLT_CtrlPointListenerOnDeviceAddedIterator
{
public:
PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference& device) :
m_Device(device) {}
NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
return listener->OnDeviceAdded(m_Device);
}
private:
PLT_DeviceDataReference& m_Device;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointListenerOnDeviceRemovedIterator class
+---------------------------------------------------------------------*/
class PLT_CtrlPointListenerOnDeviceRemovedIterator
{
public:
PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference& device) :
m_Device(device) {}
NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
return listener->OnDeviceRemoved(m_Device);
}
private:
PLT_DeviceDataReference& m_Device;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointListenerOnActionResponseIterator class
+---------------------------------------------------------------------*/
class PLT_CtrlPointListenerOnActionResponseIterator
{
public:
PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res,
PLT_ActionReference& action,
void* userdata) :
m_Res(res), m_Action(action), m_Userdata(userdata) {}
NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
return listener->OnActionResponse(m_Res, m_Action, m_Userdata);
}
private:
NPT_Result m_Res;
PLT_ActionReference& m_Action;
void* m_Userdata;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointListenerOnEventNotifyIterator class
+---------------------------------------------------------------------*/
class PLT_CtrlPointListenerOnEventNotifyIterator
{
public:
PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service* service,
NPT_List<PLT_StateVariable*>* vars) :
m_Service(service), m_Vars(vars) {}
NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
return listener->OnEventNotify(m_Service, m_Vars);
}
private:
PLT_Service* m_Service;
NPT_List<PLT_StateVariable*>* m_Vars;
};
/*----------------------------------------------------------------------
| PLT_AddGetSCPDRequestIterator class
+---------------------------------------------------------------------*/
class PLT_AddGetSCPDRequestIterator
{
public:
PLT_AddGetSCPDRequestIterator(PLT_CtrlPointGetSCPDsTask& task,
PLT_DeviceDataReference& device) :
m_Task(task), m_Device(device) {}
NPT_Result operator()(PLT_Service*& service) const {
// look for the host and port of the device
NPT_String scpd_url = service->GetSCPDURL(true);
NPT_LOG_FINER_3("Queueing SCPD request for service \"%s\" of device \"%s\" @ %s",
(const char*)service->GetServiceID(),
(const char*)service->GetDevice()->GetFriendlyName(),
(const char*)scpd_url);
// verify url before queuing just in case
NPT_HttpUrl url(scpd_url);
if (!url.IsValid()) {
NPT_LOG_SEVERE_3("Invalid SCPD url \"%s\" for service \"%s\" of device \"%s\"!",
(const char*)scpd_url,
(const char*)service->GetServiceID(),
(const char*)service->GetDevice()->GetFriendlyName());
return NPT_ERROR_INVALID_SYNTAX;
}
// Create request and attach service to it
PLT_CtrlPointGetSCPDRequest* request =
new PLT_CtrlPointGetSCPDRequest((PLT_DeviceDataReference&)m_Device, scpd_url, "GET", NPT_HTTP_PROTOCOL_1_1);
return m_Task.AddSCPDRequest(request);
}
private:
PLT_CtrlPointGetSCPDsTask& m_Task;
PLT_DeviceDataReference m_Device;
};
/*----------------------------------------------------------------------
| PLT_EventSubscriberRemoverIterator class
+---------------------------------------------------------------------*/
// Note: The PLT_CtrlPoint::m_Lock must be acquired prior to using any
// function such as Apply on this iterator
class PLT_EventSubscriberRemoverIterator
{
public:
PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint* ctrl_point) :
m_CtrlPoint(ctrl_point) {}
~PLT_EventSubscriberRemoverIterator() {}
NPT_Result operator()(PLT_Service*& service) const {
PLT_EventSubscriberReference sub;
if (NPT_SUCCEEDED(NPT_ContainerFind(m_CtrlPoint->m_Subscribers,
PLT_EventSubscriberFinderByService(service), sub))) {
NPT_LOG_INFO_1("Removed subscriber \"%s\"", (const char*)sub->GetSID());
m_CtrlPoint->m_Subscribers.Remove(sub);
}
return NPT_SUCCESS;
}
private:
PLT_CtrlPoint* m_CtrlPoint;
};
/*----------------------------------------------------------------------
| PLT_ServiceReadyIterator class
+---------------------------------------------------------------------*/
class PLT_ServiceReadyIterator
{
public:
PLT_ServiceReadyIterator() {}
NPT_Result operator()(PLT_Service*& service) const {
return service->IsValid()?NPT_SUCCESS:NPT_FAILURE;
}
};
/*----------------------------------------------------------------------
| PLT_DeviceReadyIterator class
+---------------------------------------------------------------------*/
class PLT_DeviceReadyIterator
{
public:
PLT_DeviceReadyIterator() {}
NPT_Result operator()(PLT_DeviceDataReference& device) const {
NPT_Result res = device->m_Services.ApplyUntil(
PLT_ServiceReadyIterator(),
NPT_UntilResultNotEquals(NPT_SUCCESS));
if (NPT_FAILED(res)) return res;
res = device->m_EmbeddedDevices.ApplyUntil(
PLT_DeviceReadyIterator(),
NPT_UntilResultNotEquals(NPT_SUCCESS));
if (NPT_FAILED(res)) return res;
// a device must have at least one service or embedded device
// otherwise it's not ready
if (device->m_Services.GetItemCount() == 0 &&
device->m_EmbeddedDevices.GetItemCount() == 0) {
return NPT_FAILURE;
}
return NPT_SUCCESS;
}
};
/*----------------------------------------------------------------------
| PLT_CtrlPoint::PLT_CtrlPoint
+---------------------------------------------------------------------*/
PLT_CtrlPoint::PLT_CtrlPoint(const char* search_criteria /* = "upnp:rootdevice" */) :
m_EventHttpServer(NULL),
m_TaskManager(NULL),
m_SearchCriteria(search_criteria),
m_Started(false)
{
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::~PLT_CtrlPoint
+---------------------------------------------------------------------*/
PLT_CtrlPoint::~PLT_CtrlPoint()
{
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::IgnoreUUID
+---------------------------------------------------------------------*/
void
PLT_CtrlPoint::IgnoreUUID(const char* uuid)
{
if (!m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
m_UUIDsToIgnore.Add(uuid);
}
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::Start
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::Start(PLT_SsdpListenTask* task)
{
if (m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
m_TaskManager = new PLT_TaskManager();
m_EventHttpServer = new PLT_HttpServer();
m_EventHttpServer->AddRequestHandler(new PLT_HttpRequestHandler(this), "/", true, true);
m_EventHttpServer->Start();
// house keeping task
m_TaskManager->StartTask(new PLT_CtrlPointHouseKeepingTask(this));
// add ourselves as an listener to SSDP multicast advertisements
task->AddListener(this);
//
// use next line instead for DLNA testing, faster frequency for M-SEARCH
//return m_SearchCriteria.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_SearchCriteria, 1, 5000):NPT_SUCCESS;
//
m_Started = true;
return m_SearchCriteria.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_SearchCriteria):NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::GetPort
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::GetPort(NPT_UInt16& port)
{
if (!m_Started) return NPT_ERROR_INVALID_STATE;
port = m_EventHttpServer->GetPort();
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::Stop
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::Stop(PLT_SsdpListenTask* task)
{
if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
m_Started = false;
task->RemoveListener(this);
m_EventHttpServer->Stop();
m_TaskManager->Abort();
// force remove all devices
NPT_List<PLT_DeviceDataReference>::Iterator iter = m_RootDevices.GetFirstItem();
while (iter) {
NotifyDeviceRemoved(*iter);
++iter;
}
// we can safely clear everything without a lock
// as there are no more tasks pending
m_RootDevices.Clear();
m_Subscribers.Clear();
m_EventHttpServer = NULL;
m_TaskManager = NULL;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::AddListener
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::AddListener(PLT_CtrlPointListener* listener)
{
NPT_AutoLock lock(m_Lock);
if (!m_ListenerList.Contains(listener)) {
m_ListenerList.Add(listener);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::RemoveListener
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener* listener)
{
NPT_AutoLock lock(m_Lock);
m_ListenerList.Remove(listener);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::CreateSearchTask
+---------------------------------------------------------------------*/
PLT_SsdpSearchTask*
PLT_CtrlPoint::CreateSearchTask(const NPT_HttpUrl& url,
const char* target,
NPT_Cardinal mx,
NPT_TimeInterval frequency,
const NPT_IpAddress& address)
{
// make sure mx is at least 1
if (mx<1) mx=1;
// create socket
NPT_Reference<NPT_UdpMulticastSocket> socket(new NPT_UdpMulticastSocket());
socket->SetInterface(address);
socket->SetTimeToLive(PLT_Constants::GetInstance().GetSearchMulticastTimeToLive());
// bind to something > 1024 and different than 1900
int retries = 20;
do {
int random = NPT_System::GetRandomInteger();
int port = (unsigned short)(1024 + (random % 15000));
if (port == 1900) continue;
if (NPT_SUCCEEDED(socket->Bind(
NPT_SocketAddress(NPT_IpAddress::Any, port),
false)))
break;
} while (--retries);
if (retries == 0) {
NPT_LOG_SEVERE("Couldn't bind socket for Search Task");
return NULL;
}
// create request
NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1);
PLT_UPnPMessageHelper::SetMX(*request, mx);
PLT_UPnPMessageHelper::SetST(*request, target);
PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\"");
request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, *PLT_Constants::GetInstance().GetDefaultUserAgent());
// create task
PLT_SsdpSearchTask* task = new PLT_SsdpSearchTask(
socket.AsPointer(),
this,
request,
(frequency.ToMillis()>0 && frequency.ToMillis()<5000)?NPT_TimeInterval(5.):frequency); /* repeat no less than every 5 secs */
socket.Detach();
return task;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::Search
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::Search(const NPT_HttpUrl& url,
const char* target,
NPT_Cardinal mx /* = 5 */,
NPT_TimeInterval frequency /* = NPT_TimeInterval(50.) */,
NPT_TimeInterval initial_delay /* = NPT_TimeInterval(0.) */)
{
if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
NPT_List<NPT_NetworkInterface*> if_list;
NPT_List<NPT_NetworkInterface*>::Iterator net_if;
NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr;
NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list, true));
for (net_if = if_list.GetFirstItem();
net_if;
net_if++) {
// make sure the interface is at least broadcast or multicast
if (!((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST) &&
!((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST)) {
continue;
}
for (net_if_addr = (*net_if)->GetAddresses().GetFirstItem();
net_if_addr;
net_if_addr++) {
// create task
PLT_SsdpSearchTask* task = CreateSearchTask(url,
target,
mx,
frequency,
(*net_if_addr).GetPrimaryAddress());
m_TaskManager->StartTask(task, &initial_delay);
}
}
if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::Discover
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::Discover(const NPT_HttpUrl& url,
const char* target,
NPT_Cardinal mx, /* = 5 */
NPT_TimeInterval frequency /* = NPT_TimeInterval(50.) */,
NPT_TimeInterval initial_delay /* = NPT_TimeInterval(0.) */)
{
if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
// make sure mx is at least 1
if (mx<1) mx = 1;
// create socket
NPT_UdpSocket* socket = new NPT_UdpSocket();
// create request
NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1);
PLT_UPnPMessageHelper::SetMX(*request, mx);
PLT_UPnPMessageHelper::SetST(*request, target);
PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\"");
request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, *PLT_Constants::GetInstance().GetDefaultUserAgent());
// force HOST to be the regular multicast address:port
// Some servers do care (like WMC) otherwise they won't respond to us
request->GetHeaders().SetHeader(NPT_HTTP_HEADER_HOST, "239.255.255.250:1900");
// create task
PLT_ThreadTask* task = new PLT_SsdpSearchTask(
socket,
this,
request,
(frequency.ToMillis()>0 && frequency.ToMillis()<5000)?NPT_TimeInterval(5.):frequency); /* repeat no less than every 5 secs */
return m_TaskManager->StartTask(task, &initial_delay);
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::DoHouseKeeping
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::DoHouseKeeping()
{
NPT_List<PLT_DeviceDataReference> devices_to_remove;
// remove expired devices
{
NPT_AutoLock lock(m_Lock);
PLT_DeviceDataReference head, device;
while (NPT_SUCCEEDED(m_RootDevices.PopHead(device))) {
NPT_TimeStamp last_update = device->GetLeaseTimeLastUpdate();
NPT_TimeInterval lease_time = device->GetLeaseTime();
// check if device lease time has expired or if failed to renew subscribers
// TODO: UDA 1.1 says that root device and all embedded devices must have expired
// before we can assume they're all no longer unavailable (we may have missed the root device renew)
NPT_TimeStamp now;
NPT_System::GetCurrentTimeStamp(now);
if (now > last_update + NPT_TimeInterval((double)lease_time*2)) {
devices_to_remove.Add(device);
} else {
// add the device back to our list since it is still alive
m_RootDevices.Add(device);
// keep track of first device added back to list
// to know we checked all devices in initial list
if (head.IsNull()) head = device;
}
// have we exhausted initial list?
if (!head.IsNull() && head == *m_RootDevices.GetFirstItem())
break;
};
}
// remove old devices
{
NPT_AutoLock lock(m_Lock);
for (NPT_List<PLT_DeviceDataReference>::Iterator device =
devices_to_remove.GetFirstItem();
device;
device++) {
RemoveDevice(*device);
}
}
// renew subscribers of subscribed device services
NPT_List<PLT_ThreadTask*> tasks;
{
NPT_AutoLock lock(m_Lock);
NPT_List<PLT_EventSubscriberReference>::Iterator sub = m_Subscribers.GetFirstItem();
while (sub) {
NPT_TimeStamp now;
NPT_System::GetCurrentTimeStamp(now);
// time to renew if within 90 secs of expiration
if (now > (*sub)->GetExpirationTime() - NPT_TimeStamp(90.)) {
PLT_ThreadTask* task = RenewSubscriber(*sub);
if (task) tasks.Add(task);
}
sub++;
}
}
// Queue up all tasks now outside of lock, in case they
// block because the task manager has maxed out number of running tasks
// and to avoid a deadlock with tasks trying to acquire the lock in the response
NPT_List<PLT_ThreadTask*>::Iterator task = tasks.GetFirstItem();
while (task) {
PLT_ThreadTask* _task = *task++;
m_TaskManager->StartTask(_task);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::FindDevice
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::FindDevice(const char* uuid,
PLT_DeviceDataReference& device,
bool return_root /* = false */)
{
NPT_List<PLT_DeviceDataReference>::Iterator iter = m_RootDevices.GetFirstItem();
while (iter) {
// device uuid found immediately as root device
if ((*iter)->GetUUID().Compare(uuid) == 0) {
device = *iter;
return NPT_SUCCESS;
} else if (NPT_SUCCEEDED((*iter)->FindEmbeddedDevice(uuid, device))) {
// we found the uuid as an embedded device of this root
// return root if told, otherwise return found embedded device
if (return_root) device = (*iter);
return NPT_SUCCESS;
}
++iter;
}
return NPT_ERROR_NO_SUCH_ITEM;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::FindActionDesc
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::FindActionDesc(PLT_DeviceDataReference& device,
const char* service_type,
const char* action_name,
PLT_ActionDesc*& action_desc)
{
if (device.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
// look for the service
PLT_Service* service;
if (NPT_FAILED(device->FindServiceByType(service_type, service))) {
NPT_LOG_FINE_1("Service %s not found", (const char*)service_type);
return NPT_FAILURE;
}
action_desc = service->FindActionDesc(action_name);
if (action_desc == NULL) {
NPT_LOG_FINE_1("Action %s not found in service", action_name);
return NPT_FAILURE;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::CreateAction
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::CreateAction(PLT_DeviceDataReference& device,
const char* service_type,
const char* action_name,
PLT_ActionReference& action)
{
if (device.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
NPT_AutoLock lock(m_Lock);
PLT_ActionDesc* action_desc;
NPT_CHECK_SEVERE(FindActionDesc(device,
service_type,
action_name,
action_desc));
PLT_DeviceDataReference root_device;
NPT_CHECK_SEVERE(FindDevice(device->GetUUID(), root_device, true));
action = new PLT_Action(*action_desc, root_device);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::SetupResponse
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response)
{
NPT_COMPILER_UNUSED(context);
if (request.GetMethod().Compare("NOTIFY") == 0) {
return ProcessHttpNotify(request, context, response);
}
NPT_LOG_SEVERE("CtrlPoint received bad http request\r\n");
response.SetStatus(412, "Precondition Failed");
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::DecomposeLastChangeVar
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::DecomposeLastChangeVar(NPT_List<PLT_StateVariable*>& vars)
{
// parse LastChange var into smaller vars
PLT_StateVariable* lastChangeVar = NULL;
if (NPT_SUCCEEDED(NPT_ContainerFind(vars,
PLT_StateVariableNameFinder("LastChange"),
lastChangeVar))) {
vars.Remove(lastChangeVar);
PLT_Service* var_service = lastChangeVar->GetService();
NPT_String text = lastChangeVar->GetValue();
NPT_XmlNode* xml = NULL;
NPT_XmlParser parser;
if (NPT_FAILED(parser.Parse(text, xml)) || !xml || !xml->AsElementNode()) {
delete xml;
return NPT_ERROR_INVALID_FORMAT;
}
NPT_XmlElementNode* node = xml->AsElementNode();
if (!node->GetTag().Compare("Event", true)) {
// look for the instance with attribute id = 0
NPT_XmlElementNode* instance = NULL;
for (NPT_Cardinal i=0; i<node->GetChildren().GetItemCount(); i++) {
NPT_XmlElementNode* child;
if (NPT_FAILED(PLT_XmlHelper::GetChild(node, child, i)))
continue;
if (!child->GetTag().Compare("InstanceID", true)) {
// extract the "val" attribute value
NPT_String value;
if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(child, "val", value)) &&
!value.Compare("0")) {
instance = child;
break;
}
}
}
// did we find an instance with id = 0 ?
if (instance != NULL) {
// all the children of the Instance node are state variables
for (NPT_Cardinal j=0; j<instance->GetChildren().GetItemCount(); j++) {
NPT_XmlElementNode* var_node;
if (NPT_FAILED(PLT_XmlHelper::GetChild(instance, var_node, j)))
continue;
// look for the state variable in this service
const NPT_String* value = var_node->GetAttribute("val");
PLT_StateVariable* var = var_service->FindStateVariable(var_node->GetTag());
if (value != NULL && var != NULL) {
// get the value and set the state variable
// if it succeeded, add it to the list of vars we'll event
if (NPT_SUCCEEDED(var->SetValue(*value))) {
vars.Add(var);
NPT_LOG_FINE_2("LastChange var change for (%s): %s",
(const char*)var->GetName(),
(const char*)var->GetValue());
}
}
}
}
}
delete xml;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessEventNotification
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessEventNotification(PLT_EventSubscriberReference subscriber,
PLT_EventNotification* notification,
NPT_List<PLT_StateVariable*> &vars)
{
NPT_XmlElementNode* xml = NULL;
PLT_Service* service = subscriber->GetService();
PLT_DeviceData* device = service->GetDevice();
NPT_String uuid = device->GetUUID();
NPT_String service_id = service->GetServiceID();
// callback uri for this sub
NPT_String callback_uri = "/" + uuid + "/" + service_id;
if (notification->m_RequestUrl.GetPath().Compare(callback_uri, true)) {
NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
}
// if the sequence number is less than our current one, we got it out of order
// so we disregard it
if (subscriber->GetEventKey() && notification->m_EventKey < subscriber->GetEventKey()) {
NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
}
// parse body
if (NPT_FAILED(PLT_XmlHelper::Parse(notification->m_XmlBody, xml))) {
NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
}
// check envelope
if (xml->GetTag().Compare("propertyset", true)) {
NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
}
// check property set
// keep a vector of the state variables that changed
NPT_XmlElementNode* property;
PLT_StateVariable* var;
for (NPT_List<NPT_XmlNode*>::Iterator children = xml->GetChildren().GetFirstItem();
children;
children++) {
NPT_XmlElementNode* child = (*children)->AsElementNode();
if (!child) continue;
// check property
if (child->GetTag().Compare("property", true)) continue;
if (NPT_FAILED(PLT_XmlHelper::GetChild(child, property))) {
NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
}
var = service->FindStateVariable(property->GetTag());
if (var == NULL) continue;
if (NPT_FAILED(var->SetValue(property->GetText()?*property->GetText():""))) {
NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
}
vars.Add(var);
}
// update sequence
subscriber->SetEventKey(notification->m_EventKey);
// Look if a state variable LastChange was received and decompose it into
// independent state variable updates
DecomposeLastChangeVar(vars);
delete xml;
return NPT_SUCCESS;
failure:
NPT_LOG_SEVERE("CtrlPoint failed to process event notification");
delete xml;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::AddPendingEventNotification
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::AddPendingEventNotification(PLT_EventNotification *notification)
{
// Only keep a maximum of 20 pending notifications
while (m_PendingNotifications.GetItemCount() > 20) {
PLT_EventNotification *garbage = NULL;
m_PendingNotifications.PopHead(garbage);
delete garbage;
}
m_PendingNotifications.Add(notification);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessPendingEventNotifications
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessPendingEventNotifications()
{
NPT_Cardinal count = m_PendingNotifications.GetItemCount();
while (count--) {
NPT_List<PLT_StateVariable*> vars;
PLT_Service *service = NULL;
PLT_EventNotification *notification;
if (NPT_SUCCEEDED(m_PendingNotifications.PopHead(notification))) {
PLT_EventSubscriberReference sub;
// look for the subscriber with that sid
if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderBySID(notification->m_SID),
sub))) {
m_PendingNotifications.Add(notification);
continue;
}
// keep track of service for listeners later
service = sub->GetService();
// Reprocess notification
NPT_LOG_WARNING_1("Reprocessing delayed notification for subscriber", (const char*)notification->m_SID);
NPT_Result result = ProcessEventNotification(sub, notification, vars);
delete notification;
if (NPT_FAILED(result)) continue;
}
// notify listeners
if (service && vars.GetItemCount()) {
m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
}
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessHttpNotify
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessHttpNotify(const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response)
{
NPT_COMPILER_UNUSED(context);
NPT_AutoLock lock(m_Lock);
NPT_List<PLT_StateVariable*> vars;
PLT_Service* service = NULL;
PLT_EventSubscriberReference sub;
NPT_Result result;
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessHttpNotify:", request);
// Create notification from request
PLT_EventNotification* notification = PLT_EventNotification::Parse(request, context, response);
NPT_CHECK_POINTER_LABEL_WARNING(notification, bad_request);
// Give a last change to process pending notifications before throwing them out
// by AddPendingNotification
ProcessPendingEventNotifications();
// look for the subscriber with that sid
if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderBySID(notification->m_SID),
sub))) {
NPT_LOG_WARNING_1("Subscriber %s not found, delaying notification process.\n", (const char*)notification->m_SID);
AddPendingEventNotification(notification);
return NPT_SUCCESS;
}
// Process notification for subscriber
service = sub->GetService();
result = ProcessEventNotification(sub, notification, vars);
delete notification;
NPT_CHECK_LABEL_WARNING(result, bad_request);
// Notify listeners
if (vars.GetItemCount()) {
m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
}
return NPT_SUCCESS;
bad_request:
NPT_LOG_SEVERE("CtrlPoint received bad event notify request\r\n");
if (response.GetStatusCode() == 200) {
response.SetStatus(412, "Precondition Failed");
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessSsdpSearchResponse
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessSsdpSearchResponse(NPT_Result res,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response)
{
NPT_CHECK_SEVERE(res);
NPT_CHECK_POINTER_SEVERE(response);
NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
NPT_String protocol = response->GetProtocol();
NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSsdpSearchResponse from %s:%d",
(const char*)context.GetRemoteAddress().GetIpAddress().ToString() ,
context.GetRemoteAddress().GetPort());
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, prefix, response);
// any 2xx responses are ok
if (response->GetStatusCode()/100 == 2) {
const NPT_String* st = response->GetHeaders().GetHeaderValue("st");
const NPT_String* usn = response->GetHeaders().GetHeaderValue("usn");
const NPT_String* ext = response->GetHeaders().GetHeaderValue("ext");
NPT_CHECK_POINTER_SEVERE(st);
NPT_CHECK_POINTER_SEVERE(usn);
NPT_CHECK_POINTER_SEVERE(ext);
NPT_String uuid;
// if we get an advertisement other than uuid
// verify it's formatted properly
if (usn != st) {
NPT_List<NPT_String> components = usn->Split("::");
if (components.GetItemCount() != 2)
return NPT_FAILURE;
if (st->Compare(*components.GetItem(1), true))
return NPT_FAILURE;
uuid = components.GetItem(0)->SubString(5);
} else {
uuid = usn->SubString(5);
}
if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
NPT_LOG_FINE_1("CtrlPoint received a search response from ourselves (%s)\n", (const char*)uuid);
return NPT_SUCCESS;
}
return ProcessSsdpMessage(*response, context, uuid);
}
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::OnSsdpPacket
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::OnSsdpPacket(const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context)
{
return ProcessSsdpNotify(request, context);
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessSsdpNotify
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessSsdpNotify(const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context)
{
// get the address of who sent us some data back
NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
NPT_String method = request.GetMethod();
NPT_String uri = request.GetUrl().GetPath(true);
NPT_String protocol = request.GetProtocol();
if (method.Compare("NOTIFY") == 0) {
const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request);
const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request);
const NPT_String* usn = PLT_UPnPMessageHelper::GetUSN(request);
NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSsdpNotify from %s:%d (%s)",
context.GetRemoteAddress().GetIpAddress().ToString().GetChars(),
context.GetRemoteAddress().GetPort(),
usn?usn->GetChars():"unknown");
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, prefix, request);
if ((uri.Compare("*") != 0) || (protocol.Compare("HTTP/1.1") != 0))
return NPT_FAILURE;
NPT_CHECK_POINTER_SEVERE(nts);
NPT_CHECK_POINTER_SEVERE(nt);
NPT_CHECK_POINTER_SEVERE(usn);
NPT_String uuid;
// if we get an advertisement other than uuid
// verify it's formatted properly
if (*usn != *nt) {
NPT_List<NPT_String> components = usn->Split("::");
if (components.GetItemCount() != 2)
return NPT_FAILURE;
if (nt->Compare(*components.GetItem(1), true))
return NPT_FAILURE;
uuid = components.GetItem(0)->SubString(5);
} else {
uuid = usn->SubString(5);
}
if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
NPT_LOG_FINE_1("Received a NOTIFY request from ourselves (%s)\n", (const char*)uuid);
return NPT_SUCCESS;
}
// if it's a byebye, remove the device and return right away
if (nts->Compare("ssdp:byebye", true) == 0) {
NPT_LOG_INFO_1("Received a byebye NOTIFY request from %s\n", (const char*)uuid);
NPT_AutoLock lock(m_Lock);
// look for root device
PLT_DeviceDataReference root_device;
FindDevice(uuid, root_device, true);
if (!root_device.IsNull()) RemoveDevice(root_device);
return NPT_SUCCESS;
}
return ProcessSsdpMessage(request, context, uuid);
}
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::AddDevice
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::AddDevice(PLT_DeviceDataReference& data)
{
NPT_AutoLock lock(m_Lock);
return NotifyDeviceReady(data);
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::NotifyDeviceReady
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::NotifyDeviceReady(PLT_DeviceDataReference& data)
{
m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(data));
/* recursively add embedded devices */
NPT_Array<PLT_DeviceDataReference> embedded_devices =
data->GetEmbeddedDevices();
for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
NotifyDeviceReady(embedded_devices[i]);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::RemoveDevice
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference& data)
{
NPT_AutoLock lock(m_Lock);
NotifyDeviceRemoved(data);
CleanupDevice(data);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::NotifyDeviceRemoved
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::NotifyDeviceRemoved(PLT_DeviceDataReference& data)
{
m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceRemovedIterator(data));
/* recursively add embedded devices */
NPT_Array<PLT_DeviceDataReference> embedded_devices =
data->GetEmbeddedDevices();
for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
NotifyDeviceRemoved(embedded_devices[i]);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::CleanupDevice
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::CleanupDevice(PLT_DeviceDataReference& data)
{
if (data.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
NPT_LOG_INFO_1("Removing %s from device list\n", (const char*)data->GetUUID());
// Note: This must take the lock prior to being called
// we can't take the lock here because this function
// will be recursively called if device contains embedded devices
/* recursively remove embedded devices */
NPT_Array<PLT_DeviceDataReference> embedded_devices = data->GetEmbeddedDevices();
for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
CleanupDevice(embedded_devices[i]);
}
/* remove from list */
m_RootDevices.Remove(data);
/* unsubscribe from services */
data->m_Services.Apply(PLT_EventSubscriberRemoverIterator(this));
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessSsdpMessage
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessSsdpMessage(const NPT_HttpMessage& message,
const NPT_HttpRequestContext& context,
NPT_String& uuid)
{
NPT_COMPILER_UNUSED(context);
NPT_AutoLock lock(m_Lock);
// check if we should ignore our own UUID
if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) return NPT_SUCCESS;
const NPT_String* url = PLT_UPnPMessageHelper::GetLocation(message);
NPT_CHECK_POINTER_SEVERE(url);
// Fix for Connect360 which uses localhost in device description url
NPT_HttpUrl location(*url);
if (location.GetHost().ToLowercase() == "localhost" ||
location.GetHost().ToLowercase() == "127.0.0.1") {
location.SetHost(context.GetRemoteAddress().GetIpAddress().ToString());
}
// be nice and assume a default lease time if not found even though it's required
NPT_TimeInterval leasetime;
if (NPT_FAILED(PLT_UPnPMessageHelper::GetLeaseTime(message, leasetime))) {
leasetime = *PLT_Constants::GetInstance().GetDefaultSubscribeLease();
}
// check if device (or embedded device) is already known
PLT_DeviceDataReference data;
if (NPT_SUCCEEDED(FindDevice(uuid, data))) {
// // in case we missed the byebye and the device description has changed (ip or port)
// // reset base and assumes device is the same (same number of services and embedded devices)
// // FIXME: The right way is to remove the device and rescan it though but how do we know it changed?
// PLT_DeviceReadyIterator device_tester;
// if (NPT_SUCCEEDED(device_tester(data)) && data->GetDescriptionUrl().Compare(location.ToString(), true)) {
// NPT_LOG_INFO_2("Old device \"%s\" detected @ new location %s",
// (const char*)data->GetFriendlyName(),
// (const char*)location.ToString());
// data->SetURLBase(location);
// }
// renew expiration time
data->SetLeaseTime(leasetime);
NPT_LOG_FINE_1("Device \"%s\" expiration time renewed..",
(const char*)data->GetFriendlyName());
return NPT_SUCCESS;
}
// start inspection
return InspectDevice(location, uuid, leasetime);
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::InspectDevice
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::InspectDevice(const NPT_HttpUrl& location,
const char* uuid,
NPT_TimeInterval leasetime)
{
NPT_AutoLock lock(m_Lock);
// check if already inspecting device
NPT_String pending_uuid;
if (NPT_SUCCEEDED(NPT_ContainerFind(m_PendingInspections,
NPT_StringFinder(uuid),
pending_uuid))) {
return NPT_SUCCESS;
}
NPT_LOG_INFO_2("Inspecting device \"%s\" detected @ %s",
uuid,
(const char*)location.ToString());
if (!location.IsValid()) {
NPT_LOG_INFO_1("Invalid device description url: %s",
(const char*) location.ToString());
return NPT_FAILURE;
}
// remember that we're now inspecting the device
m_PendingInspections.Add(uuid);
// Start a task to retrieve the description
PLT_CtrlPointGetDescriptionTask* task = new PLT_CtrlPointGetDescriptionTask(
location,
this,
leasetime,
uuid);
// Add a delay to make sure that we received late NOTIFY bye-bye
NPT_TimeInterval delay(.5f);
m_TaskManager->StartTask(task, &delay);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::FetchDeviceSCPDs
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::FetchDeviceSCPDs(PLT_CtrlPointGetSCPDsTask* task,
PLT_DeviceDataReference& device,
NPT_Cardinal level)
{
if (level == 5 && device->m_EmbeddedDevices.GetItemCount()) {
NPT_LOG_FATAL("Too many embedded devices depth! ");
return NPT_FAILURE;
}
++level;
// fetch embedded devices services scpds first
for (NPT_Cardinal i = 0;
i<device->m_EmbeddedDevices.GetItemCount();
i++) {
NPT_CHECK_SEVERE(FetchDeviceSCPDs(task, device->m_EmbeddedDevices[i], level));
}
// Get SCPD of device services now and bail right away if one fails
return device->m_Services.ApplyUntil(
PLT_AddGetSCPDRequestIterator(*task, device),
NPT_UntilResultNotEquals(NPT_SUCCESS));
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessGetDescriptionResponse
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessGetDescriptionResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response,
NPT_TimeInterval leasetime,
NPT_String uuid)
{
NPT_COMPILER_UNUSED(request);
NPT_AutoLock lock(m_Lock);
PLT_CtrlPointGetSCPDsTask* task = NULL;
NPT_String desc;
PLT_DeviceDataReference root_device;
PLT_DeviceDataReference device;
// Add a delay, some devices need it (aka Rhapsody)
NPT_TimeInterval delay(0.1f);
NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessGetDescriptionResponse @ %s (result = %d, status = %d)",
(const char*)request.GetUrl().ToString(),
res,
response?response->GetStatusCode():0);
// Remove pending inspection
m_PendingInspections.Remove(uuid);
// verify response was ok
NPT_CHECK_LABEL_FATAL(res, bad_response);
NPT_CHECK_POINTER_LABEL_FATAL(response, bad_response);
// log response
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, prefix, response);
// get response body
res = PLT_HttpHelper::GetBody(*response, desc);
NPT_CHECK_SEVERE(res);
// create new root device
NPT_CHECK_SEVERE(PLT_DeviceData::SetDescription(root_device, leasetime, request.GetUrl(), desc, context));
// make sure root device was not previously queried
if (NPT_FAILED(FindDevice(root_device->GetUUID(), device))) {
m_RootDevices.Add(root_device);
NPT_LOG_INFO_3("Device \"%s\" is now known as \"%s\" (%s)",
(const char*)root_device->GetUUID(),
(const char*)root_device->GetFriendlyName(),
(const char*)root_device->GetDescriptionUrl(NULL));
// create one single task to fetch all scpds one after the other
task = new PLT_CtrlPointGetSCPDsTask(this, root_device);
NPT_CHECK_LABEL_SEVERE(res = FetchDeviceSCPDs(task, root_device, 0),
cleanup);
// if device has embedded devices, we want to delay fetching scpds
// just in case there's a chance all the initial NOTIFY bye-bye have
// not all been received yet which would cause to remove the devices
// as we're adding them
if (root_device->m_EmbeddedDevices.GetItemCount() > 0) {
delay = 1.f;
}
NPT_CHECK_LABEL_SEVERE(res = m_TaskManager->StartTask(task, &delay),
failure);
}
return NPT_SUCCESS;
bad_response:
NPT_LOG_SEVERE_2("Bad Description response @ %s: %s",
(const char*)request.GetUrl().ToString(),
(const char*)desc);
cleanup:
if (task) delete task;
failure:
return res;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessGetSCPDResponse
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessGetSCPDResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response,
PLT_DeviceDataReference& device)
{
NPT_COMPILER_UNUSED(context);
NPT_AutoLock lock(m_Lock);
PLT_DeviceReadyIterator device_tester;
NPT_String scpd;
PLT_DeviceDataReference root_device;
PLT_Service* service;
NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessGetSCPDResponse for a service of device \"%s\" @ %s (result = %d, status = %d)",
(const char*)device->GetFriendlyName(),
(const char*)request.GetUrl().ToString(),
res,
response?response->GetStatusCode():0);
// verify response was ok
NPT_CHECK_LABEL_FATAL(res, bad_response);
NPT_CHECK_POINTER_LABEL_FATAL(response, bad_response);
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, prefix, response);
// make sure root device hasn't disappeared
NPT_CHECK_LABEL_WARNING(FindDevice(device->GetUUID(), root_device, true),
bad_response);
res = device->FindServiceBySCPDURL(request.GetUrl().ToRequestString(), service);
NPT_CHECK_LABEL_SEVERE(res, bad_response);
// get response body
res = PLT_HttpHelper::GetBody(*response, scpd);
NPT_CHECK_LABEL_FATAL(res, bad_response);
// DIAL support
if (root_device->GetType().Compare("urn:dial-multiscreen-org:device:dial:1") == 0) {
AddDevice(root_device);
return NPT_SUCCESS;
}
// set the service scpd
res = service->SetSCPDXML(scpd);
NPT_CHECK_LABEL_SEVERE(res, bad_response);
// if root device is ready, notify listeners about it and embedded devices
if (NPT_SUCCEEDED(device_tester(root_device))) {
AddDevice(root_device);
}
return NPT_SUCCESS;
bad_response:
NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s",
(const char*)device->GetFriendlyName(),
(const char*)scpd);
if (!root_device.IsNull()) RemoveDevice(root_device);
return res;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::RenewSubscriber
+---------------------------------------------------------------------*/
PLT_ThreadTask*
PLT_CtrlPoint::RenewSubscriber(PLT_EventSubscriberReference subscriber)
{
NPT_AutoLock lock(m_Lock);
PLT_DeviceDataReference root_device;
if (NPT_FAILED(FindDevice(subscriber->GetService()->GetDevice()->GetUUID(),
root_device,
true))) {
return NULL;
}
NPT_LOG_FINE_3("Renewing subscriber \"%s\" for service \"%s\" of device \"%s\"",
(const char*)subscriber->GetSID(),
(const char*)subscriber->GetService()->GetServiceID(),
(const char*)subscriber->GetService()->GetDevice()->GetFriendlyName());
// create the request
NPT_HttpRequest* request = new NPT_HttpRequest(
subscriber->GetService()->GetEventSubURL(true),
"SUBSCRIBE",
NPT_HTTP_PROTOCOL_1_1);
PLT_UPnPMessageHelper::SetSID(*request, subscriber->GetSID());
PLT_UPnPMessageHelper::SetTimeOut(*request,
(NPT_Int32)PLT_Constants::GetInstance().GetDefaultSubscribeLease()->ToSeconds());
// Prepare the request
// create a task to post the request
return new PLT_CtrlPointSubscribeEventTask(
request,
this,
root_device,
subscriber->GetService());
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::Subscribe
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::Subscribe(PLT_Service* service,
bool cancel,
void* userdata)
{
NPT_AutoLock lock(m_Lock);
if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
NPT_HttpRequest* request = NULL;
// make sure service is subscribable
if (!service->IsSubscribable()) return NPT_FAILURE;
// event url
NPT_HttpUrl url(service->GetEventSubURL(true));
// look for the corresponding root device & sub
PLT_DeviceDataReference root_device;
PLT_EventSubscriberReference sub;
NPT_CHECK_WARNING(FindDevice(service->GetDevice()->GetUUID(),
root_device,
true));
// look for the subscriber with that service to decide if it's a renewal or not
NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderByService(service),
sub);
if (cancel == false) {
// renewal?
if (!sub.IsNull()) {
PLT_ThreadTask* task = RenewSubscriber(sub);
return m_TaskManager->StartTask(task);
}
NPT_LOG_INFO_2("Subscribing to service \"%s\" of device \"%s\"",
(const char*)service->GetServiceID(),
(const char*)service->GetDevice()->GetFriendlyName());
// prepare the callback url
NPT_String uuid = service->GetDevice()->GetUUID();
NPT_String service_id = service->GetServiceID();
NPT_String callback_uri = "/" + uuid + "/" + service_id;
// create the request
request = new NPT_HttpRequest(url, "SUBSCRIBE", NPT_HTTP_PROTOCOL_1_1);
// specify callback url using ip of interface used when
// retrieving device description
NPT_HttpUrl callbackUrl(
service->GetDevice()->m_LocalIfaceIp.ToString(),
m_EventHttpServer->GetPort(),
callback_uri);
// set the required headers for a new subscription
PLT_UPnPMessageHelper::SetNT(*request, "upnp:event");
PLT_UPnPMessageHelper::SetCallbacks(*request,
"<" + callbackUrl.ToString() + ">");
PLT_UPnPMessageHelper::SetTimeOut(*request,
(NPT_Int32)PLT_Constants::GetInstance().GetDefaultSubscribeLease()->ToSeconds());
} else {
NPT_LOG_INFO_3("Unsubscribing subscriber \"%s\" for service \"%s\" of device \"%s\"",
(const char*)(!sub.IsNull()?sub->GetSID().GetChars():"unknown"),
(const char*)service->GetServiceID(),
(const char*)service->GetDevice()->GetFriendlyName());
// cancellation
if (sub.IsNull()) return NPT_FAILURE;
// create the request
request = new NPT_HttpRequest(url, "UNSUBSCRIBE", NPT_HTTP_PROTOCOL_1_1);
PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID());
// remove from list now
m_Subscribers.Remove(sub, true);
}
// verify we have request to send just in case
NPT_CHECK_POINTER_FATAL(request);
// Prepare the request
// create a task to post the request
PLT_ThreadTask* task = new PLT_CtrlPointSubscribeEventTask(
request,
this,
root_device,
service,
userdata);
m_TaskManager->StartTask(task);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessSubscribeResponse
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessSubscribeResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response,
PLT_Service* service,
void* /* userdata */)
{
NPT_COMPILER_UNUSED(context);
NPT_AutoLock lock(m_Lock);
const NPT_String* sid = NULL;
NPT_Int32 seconds;
PLT_EventSubscriberReference sub;
bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE");
NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)",
(const char*)subscription?"S":"Uns",
(const char*)service->GetServiceID(),
res,
response?response->GetStatusCode():0);
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, prefix, response);
// if there's a failure or it's a response to a cancellation
// we get out (any 2xx status code ok)
if (NPT_FAILED(res) || response == NULL || response->GetStatusCode()/100 != 2) {
goto failure;
}
if (subscription) {
if (!(sid = PLT_UPnPMessageHelper::GetSID(*response)) ||
NPT_FAILED(PLT_UPnPMessageHelper::GetTimeOut(*response, seconds))) {
NPT_CHECK_LABEL_SEVERE(res = NPT_ERROR_INVALID_SYNTAX, failure);
}
// Look for subscriber
NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderBySID(*sid),
sub);
NPT_LOG_INFO_5("%s subscriber \"%s\" for service \"%s\" of device \"%s\" (timeout = %d)",
!sub.IsNull()?"Updating timeout for":"Creating new",
(const char*)*sid,
(const char*)service->GetServiceID(),
(const char*)service->GetDevice()->GetFriendlyName(),
seconds);
// create new subscriber if sid never seen before
// or update subscriber expiration otherwise
if (sub.IsNull()) {
sub = new PLT_EventSubscriber(m_TaskManager, service, *sid, seconds);
m_Subscribers.Add(sub);
} else {
sub->SetTimeout(seconds);
}
- // Process any pending notifcations for that subscriber we got a bit too early
+ // Process any pending notifications for that subscriber we got a bit too early
ProcessPendingEventNotifications();
return NPT_SUCCESS;
}
goto remove_sub;
failure:
NPT_LOG_SEVERE_4("%subscription failed of sub \"%s\" for service \"%s\" of device \"%s\"",
(const char*)subscription?"S":"Uns",
(const char*)(sid?*sid:"Unknown"),
(const char*)service->GetServiceID(),
(const char*)service->GetDevice()->GetFriendlyName());
res = NPT_FAILED(res)?res:NPT_FAILURE;
remove_sub:
// in case it was a renewal look for the subscriber with that service and remove it from the list
if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderByService(service),
sub))) {
m_Subscribers.Remove(sub);
}
return res;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::InvokeAction
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::InvokeAction(PLT_ActionReference& action,
void* userdata)
{
if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
PLT_Service* service = action->GetActionDesc().GetService();
// create the request
NPT_HttpUrl url(service->GetControlURL(true));
NPT_HttpRequest* request = new NPT_HttpRequest(url, "POST", NPT_HTTP_PROTOCOL_1_1);
// create a memory stream for our request body
NPT_MemoryStreamReference stream(new NPT_MemoryStream);
action->FormatSoapRequest(*stream);
// set the request body
NPT_HttpEntity* entity = NULL;
PLT_HttpHelper::SetBody(*request, (NPT_InputStreamReference)stream, &entity);
entity->SetContentType("text/xml; charset=\"utf-8\"");
NPT_String service_type = service->GetServiceType();
NPT_String action_name = action->GetActionDesc().GetName();
request->GetHeaders().SetHeader("SOAPAction", "\"" + service_type + "#" + action_name + "\"");
// create a task to post the request
PLT_CtrlPointInvokeActionTask* task = new PLT_CtrlPointInvokeActionTask(
request,
this,
action,
userdata);
// queue the request
m_TaskManager->StartTask(task);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ProcessActionResponse
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ProcessActionResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& /*context*/,
NPT_HttpResponse* response,
PLT_ActionReference& action,
void* userdata)
{
NPT_String service_type;
NPT_String str;
NPT_XmlElementNode* xml = NULL;
NPT_String name;
NPT_String soap_action_name;
NPT_XmlElementNode* soap_action_response;
NPT_XmlElementNode* soap_body;
NPT_XmlElementNode* fault;
const NPT_String* attr = NULL;
PLT_ActionDesc& action_desc = action->GetActionDesc();
// reset the error code and desc
action->SetError(0, "");
// check context validity
if (NPT_FAILED(res) || response == NULL) {
PLT_Service* service = action_desc.GetService();
NPT_LOG_WARNING_4("Failed to reach %s for %s.%s (%d)",
request.GetUrl().ToString().GetChars(),
service->GetDevice()->GetUUID().GetChars(),
service->GetServiceName().GetChars(),
res);
(void)service;
goto failure;
}
PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessActionResponse:", response);
NPT_LOG_FINER("Reading/Parsing Action Response Body...");
if (NPT_FAILED(PLT_HttpHelper::ParseBody(*response, xml))) {
goto failure;
}
NPT_LOG_FINER("Analyzing Action Response Body...");
// read envelope
if (xml->GetTag().Compare("Envelope", true))
goto failure;
// check namespace
if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/"))
goto failure;
// check encoding
attr = xml->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/");
if (!attr || attr->Compare("http://schemas.xmlsoap.org/soap/encoding/"))
goto failure;
// read action
soap_body = PLT_XmlHelper::GetChild(xml, "Body");
if (soap_body == NULL)
goto failure;
// check if an error occurred
fault = PLT_XmlHelper::GetChild(soap_body, "Fault");
if (fault != NULL) {
// we have an error
ParseFault(action, fault);
goto failure;
}
if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body, soap_action_response)))
goto failure;
// verify action name is identical to SOAPACTION header
if (soap_action_response->GetTag().Compare(action_desc.GetName() + "Response", true))
goto failure;
// verify namespace
if (!soap_action_response->GetNamespace() ||
soap_action_response->GetNamespace()->Compare(action_desc.GetService()->GetServiceType()))
goto failure;
// read all the arguments if any
for (NPT_List<NPT_XmlNode*>::Iterator args = soap_action_response->GetChildren().GetFirstItem();
args;
args++) {
NPT_XmlElementNode* child = (*args)->AsElementNode();
if (!child) continue;
action->SetArgumentValue(child->GetTag(), child->GetText()?*child->GetText():"");
if (NPT_FAILED(res)) goto failure;
}
// create a buffer for our response body and call the service
res = action->VerifyArguments(false);
if (NPT_FAILED(res)) goto failure;
goto cleanup;
failure:
// override res with failure if necessary
if (NPT_SUCCEEDED(res)) res = NPT_FAILURE;
// fallthrough
cleanup:
{
NPT_AutoLock lock(m_Lock);
m_ListenerList.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res, action, userdata));
}
delete xml;
(void)request;
return res;
}
/*----------------------------------------------------------------------
| PLT_CtrlPoint::ParseFault
+---------------------------------------------------------------------*/
NPT_Result
PLT_CtrlPoint::ParseFault(PLT_ActionReference& action,
NPT_XmlElementNode* fault)
{
NPT_XmlElementNode* detail = fault->GetChild("detail");
if (detail == NULL) return NPT_FAILURE;
NPT_XmlElementNode *upnp_error, *error_code, *error_desc;
upnp_error = detail->GetChild("upnp_error");
// WMP12 Hack
if (upnp_error == NULL) {
upnp_error = detail->GetChild("UPnPError", NPT_XML_ANY_NAMESPACE);
if (upnp_error == NULL) return NPT_FAILURE;
}
error_code = upnp_error->GetChild("errorCode", NPT_XML_ANY_NAMESPACE);
error_desc = upnp_error->GetChild("errorDescription", NPT_XML_ANY_NAMESPACE);
NPT_Int32 code = 501;
NPT_String desc;
if (error_code && error_code->GetText()) {
NPT_String value = *error_code->GetText();
value.ToInteger(code);
}
if (error_desc && error_desc->GetText()) {
desc = *error_desc->GetText();
}
action->SetError(code, desc);
return NPT_SUCCESS;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPointTask.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPointTask.h
index 15ac8870f3..54a13bfb61 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPointTask.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltCtrlPointTask.h
@@ -1,225 +1,225 @@
/*****************************************************************
|
| Platinum - Control Point Tasks
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP ControlPoint Tasks
*/
#ifndef _PLT_CONTROL_POINT_TASK_H_
#define _PLT_CONTROL_POINT_TASK_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
#include "PltHttpClientTask.h"
#include "PltDatagramStream.h"
#include "PltDeviceData.h"
#include "PltCtrlPoint.h"
/*----------------------------------------------------------------------
| forward declarations
+---------------------------------------------------------------------*/
class PLT_Action;
/*----------------------------------------------------------------------
| PLT_CtrlPointGetDescriptionTask class
+---------------------------------------------------------------------*/
/**
The PLT_CtrlPointGetDescriptionTask class fetches the description xml document
from a UPnP device
*/
class PLT_CtrlPointGetDescriptionTask : public PLT_HttpClientSocketTask
{
public:
PLT_CtrlPointGetDescriptionTask(const NPT_HttpUrl& url,
PLT_CtrlPoint* ctrl_point,
NPT_TimeInterval leasetime,
NPT_String uuid);
~PLT_CtrlPointGetDescriptionTask() override;
protected:
// PLT_HttpClientSocketTask methods
NPT_Result ProcessResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response) override;
protected:
PLT_CtrlPoint* m_CtrlPoint;
NPT_TimeInterval m_LeaseTime;
NPT_String m_UUID;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointGetSCPDRequest class
+---------------------------------------------------------------------*/
/**
The PLT_CtrlPointGetSCPDRequest class is used by a PLT_CtrlPointGetSCPDsTask task
to fetch a specific SCPD xml document for a given service of a given device.
*/
class PLT_CtrlPointGetSCPDRequest : public NPT_HttpRequest
{
public:
PLT_CtrlPointGetSCPDRequest(PLT_DeviceDataReference& device,
const char* url,
const char* method = "GET",
const char* protocol = NPT_HTTP_PROTOCOL_1_1) : // 1.1 for pipelining
NPT_HttpRequest(url, method, protocol), m_Device(device) {}
~PLT_CtrlPointGetSCPDRequest() override {}
// members
PLT_DeviceDataReference m_Device;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointGetSCPDsTask class
+---------------------------------------------------------------------*/
/**
The PLT_CtrlPointGetSCPDsTask class fetches the SCPD xml document of one or more
services for a given device.
*/
class PLT_CtrlPointGetSCPDsTask : public PLT_HttpClientSocketTask
{
public:
PLT_CtrlPointGetSCPDsTask(PLT_CtrlPoint* ctrl_point, PLT_DeviceDataReference& root_device);
~PLT_CtrlPointGetSCPDsTask() override {}
NPT_Result AddSCPDRequest(PLT_CtrlPointGetSCPDRequest* request) {
return PLT_HttpClientSocketTask::AddRequest((NPT_HttpRequest*)request);
}
// override to prevent calling this directly
NPT_Result AddRequest(NPT_HttpRequest*) override {
// only queuing PLT_CtrlPointGetSCPDRequest allowed
return NPT_ERROR_NOT_SUPPORTED;
}
protected:
// PLT_HttpClientSocketTask methods
NPT_Result ProcessResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response) override;
protected:
PLT_CtrlPoint* m_CtrlPoint;
PLT_DeviceDataReference m_RootDevice;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointInvokeActionTask class
+---------------------------------------------------------------------*/
/**
The PLT_CtrlPointInvokeActionTask class is used by a PLT_CtrlPoint to invoke
a specific action of a given service for a given device.
*/
class PLT_CtrlPointInvokeActionTask : public PLT_HttpClientSocketTask
{
public:
PLT_CtrlPointInvokeActionTask(NPT_HttpRequest* request,
PLT_CtrlPoint* ctrl_point,
PLT_ActionReference& action,
void* userdata);
~PLT_CtrlPointInvokeActionTask() override;
protected:
// PLT_HttpClientSocketTask methods
NPT_Result ProcessResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response) override;
protected:
PLT_CtrlPoint* m_CtrlPoint;
PLT_ActionReference m_Action;
void* m_Userdata;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointHouseKeepingTask class
+---------------------------------------------------------------------*/
/**
The PLT_CtrlPointHouseKeepingTask class is used by a PLT_CtrlPoint to keep
- track of expired devices and autmatically renew event subscribers.
+ track of expired devices and automatically renew event subscribers.
*/
class PLT_CtrlPointHouseKeepingTask : public PLT_ThreadTask
{
public:
PLT_CtrlPointHouseKeepingTask(PLT_CtrlPoint* ctrl_point,
NPT_TimeInterval timer = NPT_TimeInterval(5.));
protected:
~PLT_CtrlPointHouseKeepingTask() override {}
// PLT_ThreadTask methods
void DoRun() override;
protected:
PLT_CtrlPoint* m_CtrlPoint;
NPT_TimeInterval m_Timer;
};
/*----------------------------------------------------------------------
| PLT_CtrlPointSubscribeEventTask class
+---------------------------------------------------------------------*/
/**
The PLT_CtrlPointSubscribeEventTask class is used to subscribe, renew or cancel
a subscription for a given service of a given device.
*/
class PLT_CtrlPointSubscribeEventTask : public PLT_HttpClientSocketTask
{
public:
PLT_CtrlPointSubscribeEventTask(NPT_HttpRequest* request,
PLT_CtrlPoint* ctrl_point,
PLT_DeviceDataReference& device,
PLT_Service* service,
void* userdata = NULL);
~PLT_CtrlPointSubscribeEventTask() override;
protected:
// PLT_HttpClientSocketTask methods
NPT_Result ProcessResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response) override;
protected:
PLT_CtrlPoint* m_CtrlPoint;
PLT_Service* m_Service;
PLT_DeviceDataReference m_Device; // force to keep a reference to device owning m_Service
void* m_Userdata;
};
#endif /* _PLT_CONTROL_POINT_TASK_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltDeviceHost.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltDeviceHost.h
index 8cf6118664..16c398be9a 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltDeviceHost.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltDeviceHost.h
@@ -1,341 +1,341 @@
/*****************************************************************
|
| Platinum - Device Host
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP Device
*/
#ifndef _PLT_DEVICE_HOST_H_
#define _PLT_DEVICE_HOST_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
#include "PltDeviceData.h"
#include "PltSsdp.h"
#include "PltTaskManager.h"
#include "PltAction.h"
#include "PltHttp.h"
#include "PltHttpServer.h"
/*----------------------------------------------------------------------
| forward declarations
+---------------------------------------------------------------------*/
class PLT_SsdpDeviceAnnounceTask;
class PLT_SsdpListenTask;
/*----------------------------------------------------------------------
| PLT_DeviceHost class
+---------------------------------------------------------------------*/
/**
UPnP Device Host.
The PLT_DeviceHost class is a base class for implementing a UPnP Device. It handles
network announcements and responses to searches from ControlPoints. ControlPoint
action invocations are also received and delegated to derived classes. A
PLT_DeviceHost also takes care of eventing when services state variables change.
*/
class PLT_DeviceHost : public PLT_DeviceData,
public PLT_SsdpPacketListener,
public NPT_HttpRequestHandler
{
public:
/**
Creates a new instance of UPnP Device Host.
@param description_path Relative path for description url
@param uuid UPnP device unique id
@param device_type UPnP device type
@param friendly_name Name advertised for UPnP device
@param show_ip Flag to indicate if device IP should be appended to friendly name
@param port local port for the device host internal HTTP server, 0 for randomly
selected.
@param port_rebind Flag to indicate if device host should automatically try to look
for another port if failing to choose the one passed.
*/
PLT_DeviceHost(const char* description_path = "/",
const char* uuid = "",
const char* device_type = "",
const char* friendly_name = "",
bool show_ip = false,
NPT_UInt16 port = 0,
bool port_rebind = false);
~PLT_DeviceHost() override;
virtual void SetExtraBroadcast(bool broadcast) { m_ExtraBroascast = broadcast; }
/**
When a UPnP device comes up, the specifications require that a SSDP bye-bye
sequence is sent to force the removal of the device in case it wasn't sent
properly during the last shutdown.
@param bye_bye_first Boolean to indicate that SSDP bye-bye sequence should
be sent first or not.
*/
virtual void SetByeByeFirst(bool bye_bye_first) { m_ByeByeFirst = bye_bye_first; }
/**
Returns the port used by the internal HTTP server for all incoming requests.
@return port
*/
virtual NPT_UInt16 GetPort() { return m_Port; };
/**
Sets the lease time.
@param lease_time Lease Time
*/
NPT_Result SetLeaseTime(NPT_TimeInterval lease_time) { return PLT_DeviceData::SetLeaseTime(lease_time); }
protected:
/**
NPT_HttpRequestHandler method for setting up the response of an incoming
HTTP request.
@param request the request received
@param context the context of the request
@param response the response to set up
*/
NPT_Result SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response) override;
/**
Static method similar to Announce.
@param device the device to announce
@param request the SSDP pre formatted request
@param socket the network socket to use to send the request
@param type PLT_SsdpAnnounceType enum if the announce is a SSDP bye-bye, update or alive.
*/
static NPT_Result Announce(PLT_DeviceData* device,
NPT_HttpRequest& request,
NPT_UdpSocket& socket,
PLT_SsdpAnnounceType type);
/**
Called during SSDP announce. The HTTP request is already configured with
the right method and host.
@param request the SSDP pre formatted request
@param socket the network socket to use to send the request
@param type PLT_SsdpAnnounceType enum if the announce is a SSDP bye-bye, update or alive.
*/
NPT_Result Announce(NPT_HttpRequest& request,
NPT_UdpSocket& socket,
PLT_SsdpAnnounceType type) {
return Announce(this, request, socket, type);
}
/**
PLT_SsdpPacketListener method called when a M-SEARCH SSDP packet is received.
@param request SSDP packet
@param context the context of the request
*/
NPT_Result OnSsdpPacket(const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context) override;
/**
Static method similar to SendSsdpSearchResponse.
@param device the device to announce
@param response the SSDP pre formatted response
@param socket the network socket to use to send the request
@param st the original request search target
@param addr the remote address to send the response back to in case the socket
is not already connected.
*/
static NPT_Result SendSsdpSearchResponse(PLT_DeviceData* device,
NPT_HttpResponse& response,
NPT_UdpSocket& socket,
const char* st,
const NPT_SocketAddress* addr = NULL);
/**
Called by PLT_SsdpDeviceSearchResponseTask when responding to a M-SEARCH
SSDP request.
@param response the SSDP pre formatted response
@param socket the network socket to use to send the request
@param st the original request search target
@param addr the remote address to send the response back to in case the socket
is not already connected.
*/
virtual NPT_Result SendSsdpSearchResponse(NPT_HttpResponse& response,
NPT_UdpSocket& socket,
const char* st,
const NPT_SocketAddress* addr = NULL) {
return SendSsdpSearchResponse(this, response, socket, st, addr);
}
public:
/**
Add UPnP icon information to serve from file system.
@param icon the icon information including url path
@param fileroot the file system root path
@param urlroot the url root path of the icon url to match to fileroot
- Note: As an exemple, if the icon url path is "/images/icon1.jpg", the fileroot
+ Note: As an example, if the icon url path is "/images/icon1.jpg", the fileroot
is "/Users/joe/www" and the urlroot is "/", when a request is made for
"/images/icon1.jpg", the file is expected to be found at
"/Users/joe/www/images/icon1.jpg". If the urlroot were "/images", the file
would be expected to be found at "/Users/joe/www/icon1.jpg".
*/
virtual NPT_Result AddIcon(const PLT_DeviceIcon& icon,
const char* fileroot,
const char* urlroot = "/");
/**
Add UPnP icon information to serve using static image.
@param icon the icon information including url path
@param data the image data
@param size the image data size
@param copy boolean to indicate the data should be copied internally
*/
virtual NPT_Result AddIcon(const PLT_DeviceIcon& icon,
const void* data,
NPT_Size size,
bool copy = true);
protected:
/**
Required method for setting up UPnP services of device host
(and any embedded). Called when device starts.
*/
virtual NPT_Result SetupServices() = 0;
/**
Default implementation for registering device icon resources. Override to
use different ones. Called when device starts.
*/
virtual NPT_Result SetupIcons();
/**
Default implementation for setting up device host. This calls SetupServices
and SetupIcons when device starts.
*/
virtual NPT_Result SetupDevice();
/**
Called by PLT_TaskManager when the device is started.
@param task the SSDP listening task to attach to for receiving
SSDP M-SEARCH messages.
*/
virtual NPT_Result Start(PLT_SsdpListenTask* task);
/**
Called by PLT_TaskManager when the device is stoped.
@param task the SSDP listening task to detach from to stop receiving
SSDP M-SEARCH messages.
*/
virtual NPT_Result Stop(PLT_SsdpListenTask* task);
/**
This mehod is called when an action performed by a control point has been
received and needs to be answered.
@param action the action information to answer
@param context the context information including the HTTP request and
local and remote socket information (IP & port).
*/
virtual NPT_Result OnAction(PLT_ActionReference& action,
const PLT_HttpRequestContext& context);
/**
This method is called when a control point is requesting the device
description.
@param request the HTTP request
@param context the context information including local and remote socket information.
@param response the response to setup.
*/
virtual NPT_Result ProcessGetDescription(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response);
/**
This method is called when a control point is requesting a service SCPD.
@param service the service
@param request the HTTP request
@param context the context information including local and remote socket information.
@param response the response to setup.
*/
virtual NPT_Result ProcessGetSCPD(PLT_Service* service,
NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response);
/**
This method is called when a "GET" request for a resource other than the device
description, SCPD, or icons has been received.
@param request the HTTP request
@param context the context information including local and remote socket information.
@param response the response to setup.
*/
virtual NPT_Result ProcessHttpGetRequest(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response);
/**
This method is called when a "POST" request has been received. This is usually
an UPnP service action invocation. This will deserialize the request and call
the OnAction method.
@param request the HTTP request
@param context the context information including local and remote socket information.
@param response the response to setup.
*/
virtual NPT_Result ProcessHttpPostRequest(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response);
/**
This method is called when a request from a subscriber has been received. This is
- for any new subscritions, existing subscrition renewal or cancellation.
+ for any new subscriptions, existing subscription renewal or cancellation.
@param request the HTTP request
@param context the context information including local and remote socket information.
@param response the response to setup.
*/
virtual NPT_Result ProcessHttpSubscriberRequest(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response);
protected:
friend class PLT_UPnP;
friend class PLT_UPnP_DeviceStartIterator;
friend class PLT_UPnP_DeviceStopIterator;
friend class PLT_Service;
friend class NPT_Reference<PLT_DeviceHost>;
friend class PLT_SsdpDeviceSearchResponseInterfaceIterator;
friend class PLT_SsdpDeviceSearchResponseTask;
friend class PLT_SsdpAnnounceInterfaceIterator;
PLT_TaskManagerReference m_TaskManager;
PLT_HttpServerReference m_HttpServer;
bool m_ExtraBroascast;
NPT_UInt16 m_Port;
bool m_PortRebind;
bool m_ByeByeFirst;
bool m_Started;
};
typedef NPT_Reference<PLT_DeviceHost> PLT_DeviceHostReference;
#endif /* _PLT_DEVICE_HOST_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.cpp b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.cpp
index b86fb2379f..2f7ee19456 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.cpp
@@ -1,920 +1,920 @@
/*****************************************************************
|
| Platinum - Service
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "PltService.h"
#include "PltSsdp.h"
#include "PltUPnP.h"
#include "PltDeviceData.h"
#include "PltUtilities.h"
NPT_SET_LOCAL_LOGGER("platinum.core.service")
/*----------------------------------------------------------------------
| PLT_Service::PLT_Service
+---------------------------------------------------------------------*/
PLT_Service::PLT_Service(PLT_DeviceData* device,
const char* type,
const char* id,
const char* name,
const char* last_change_namespace /* = NULL */) :
m_Device(device),
m_ServiceType(type),
m_ServiceID(id),
m_ServiceName(name),
m_EventTask(NULL),
m_EventingPaused(false),
m_LastChangeNamespace(last_change_namespace)
{
if (name) InitURLs(name);
}
/*----------------------------------------------------------------------
| PLT_Service::~PLT_Service
+---------------------------------------------------------------------*/
PLT_Service::~PLT_Service()
{
Cleanup();
}
/*----------------------------------------------------------------------
| PLT_Service::Cleanup
+---------------------------------------------------------------------*/
void
PLT_Service::Cleanup()
{
m_ActionDescs.Apply(NPT_ObjectDeleter<PLT_ActionDesc>());
m_StateVars.Apply(NPT_ObjectDeleter<PLT_StateVariable>());
m_ActionDescs.Clear();
m_StateVars.Clear();
m_Subscribers.Clear();
}
/*----------------------------------------------------------------------
| PLT_Service::GetSCPDXML
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::GetSCPDXML(NPT_String& scpd)
{
NPT_Result res;
// it is required to have at least 1 state variable
if (m_StateVars.GetItemCount() == 0) return NPT_FAILURE;
NPT_XmlElementNode* spec = NULL;
NPT_XmlElementNode* actionList = NULL;
NPT_XmlElementNode* top = new NPT_XmlElementNode("scpd");
NPT_XmlElementNode* serviceStateTable = NULL;
NPT_CHECK_LABEL_SEVERE(res = top->SetNamespaceUri("", "urn:schemas-upnp-org:service-1-0"), cleanup);
// add spec version
spec = new NPT_XmlElementNode("specVersion");
NPT_CHECK_LABEL_SEVERE(res = top->AddChild(spec), cleanup);
NPT_CHECK_LABEL_SEVERE(res = PLT_XmlHelper::AddChildText(spec, "major", "1"), cleanup);
NPT_CHECK_LABEL_SEVERE(res = PLT_XmlHelper::AddChildText(spec, "minor", "0"), cleanup);
// add actions
actionList = new NPT_XmlElementNode("actionList");
NPT_CHECK_LABEL_SEVERE(res = top->AddChild(actionList), cleanup);
NPT_CHECK_LABEL_SEVERE(res = m_ActionDescs.ApplyUntil(
PLT_GetSCPDXMLIterator<PLT_ActionDesc>(actionList),
NPT_UntilResultNotEquals(NPT_SUCCESS)), cleanup);
// add service state table
serviceStateTable = new NPT_XmlElementNode("serviceStateTable");
NPT_CHECK_LABEL_SEVERE(res = top->AddChild(serviceStateTable), cleanup);
NPT_CHECK_LABEL_SEVERE(res = m_StateVars.ApplyUntil(
PLT_GetSCPDXMLIterator<PLT_StateVariable>(serviceStateTable),
NPT_UntilResultNotEquals(NPT_SUCCESS)), cleanup);
// serialize node
NPT_CHECK_LABEL_SEVERE(res = PLT_XmlHelper::Serialize(*top, scpd, true, 2), cleanup);
cleanup:
delete top;
return res;
}
/*----------------------------------------------------------------------
| PLT_Service::ToXML
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::GetDescription(NPT_XmlElementNode* parent, NPT_XmlElementNode** service_out /* = NULL */)
{
NPT_XmlElementNode* service = new NPT_XmlElementNode("service");
if (service_out) {
*service_out = service;
}
NPT_CHECK_SEVERE(parent->AddChild(service));
NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "serviceType", m_ServiceType));
NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "serviceId", m_ServiceID));
NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "SCPDURL", GetSCPDURL()));
NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "controlURL", GetControlURL()));
NPT_CHECK_SEVERE(PLT_XmlHelper::AddChildText(service, "eventSubURL", GetEventSubURL()));
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::InitURLs
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::InitURLs(const char* service_name)
{
m_SCPDURL = service_name;
m_SCPDURL += "/" + m_Device->GetUUID() + NPT_String("/scpd.xml");
m_ControlURL = service_name;
m_ControlURL += "/" + m_Device->GetUUID() + NPT_String("/control.xml");
m_EventSubURL = service_name;
m_EventSubURL += "/" + m_Device->GetUUID() + NPT_String("/event.xml");
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::SetSCPDXML
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::SetSCPDXML(const char* scpd)
{
if (scpd == NULL) return NPT_ERROR_INVALID_PARAMETERS;
Cleanup();
NPT_XmlParser parser;
NPT_XmlNode* tree = NULL;
NPT_Result res;
NPT_Array<NPT_XmlElementNode*> stateVariables;
NPT_Array<NPT_XmlElementNode*> actions;
NPT_XmlElementNode* root;
NPT_XmlElementNode* actionList;
NPT_XmlElementNode* stateTable;
res = parser.Parse(scpd, tree);
NPT_CHECK_LABEL_FATAL(res, failure);
// make sure root tag is right
root = tree->AsElementNode();
if (!root || NPT_String::Compare(root->GetTag(), "scpd") != 0) {
NPT_LOG_SEVERE("Invalid scpd root tag name");
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
// make sure we have required children presents
stateTable = PLT_XmlHelper::GetChild(root, "serviceStateTable");
if (!stateTable ||
stateTable->GetChildren().GetItemCount() == 0 ||
NPT_FAILED(PLT_XmlHelper::GetChildren(stateTable,
stateVariables,
"stateVariable"))) {
NPT_LOG_SEVERE("No state variables found");
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
for (int k = 0 ; k < (int)stateVariables.GetItemCount(); k++) {
NPT_String name, type, send;
PLT_XmlHelper::GetChildText(stateVariables[k], "name", name);
PLT_XmlHelper::GetChildText(stateVariables[k], "dataType", type);
PLT_XmlHelper::GetAttribute(stateVariables[k], "sendEvents", send);
if (name.GetLength() == 0 || type.GetLength() == 0) {
NPT_LOG_SEVERE_1("Invalid state variable name or type at position %d", k+1);
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
PLT_StateVariable* variable = new PLT_StateVariable(this);
m_StateVars.Add(variable);
variable->m_Name = name;
variable->m_DataType = type;
variable->m_IsSendingEvents = IsTrue(send);
PLT_XmlHelper::GetChildText(stateVariables[k], "defaultValue", variable->m_DefaultValue);
NPT_XmlElementNode* allowedValueList = PLT_XmlHelper::GetChild(stateVariables[k], "allowedValueList");
if (allowedValueList) {
NPT_Array<NPT_XmlElementNode*> allowedValues;
PLT_XmlHelper::GetChildren(allowedValueList, allowedValues, "allowedValue");
for (int l = 0 ; l < (int)allowedValues.GetItemCount(); l++) {
const NPT_String* text = allowedValues[l]->GetText();
if (text) {
variable->m_AllowedValues.Add(new NPT_String(*text));
}
}
} else {
NPT_XmlElementNode* allowedValueRange = PLT_XmlHelper::GetChild(stateVariables[k], "allowedValueRange");
if (allowedValueRange) {
NPT_String min, max, step;
PLT_XmlHelper::GetChildText(allowedValueRange, "minimum", min);
PLT_XmlHelper::GetChildText(allowedValueRange, "maximum", max);
PLT_XmlHelper::GetChildText(allowedValueRange, "step", step);
// these are required but try to be nice
// in case bad devices provide nothing
if (min.GetLength() == 0) {
if (variable->m_DataType == "ui1" ||
variable->m_DataType == "ui2" ||
variable->m_DataType == "ui4") {
min = NPT_String::FromInteger(0);
} else if (variable->m_DataType == "i1") {
min = NPT_String::FromInteger(-128);
} else if (variable->m_DataType == "i2") {
min = NPT_String::FromInteger(-32768);
} else if (variable->m_DataType == "i4" ||
variable->m_DataType == "int") {
min = NPT_String::FromInteger(-2147483647 - 1);
} else {
NPT_LOG_SEVERE_1("Invalid variable data type %s", variable->m_DataType.GetChars());
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
}
if (max.GetLength() == 0) {
if (variable->m_DataType == "ui1") {
max = NPT_String::FromInteger(0xff);
} else if (variable->m_DataType == "ui2") {
max = NPT_String::FromInteger(0xffff);
} else if (variable->m_DataType == "ui4") {
max = NPT_String::FromInteger(0xffffffff);
} else if (variable->m_DataType == "i1") {
max = NPT_String::FromInteger(0x7f);
} else if (variable->m_DataType == "i2") {
max = NPT_String::FromInteger(0x7fff);
} else if (variable->m_DataType == "i4" ||
variable->m_DataType == "int") {
max = NPT_String::FromInteger(0x7fffffff);
} else {
NPT_LOG_SEVERE_1("Invalid variable data type %s", variable->m_DataType.GetChars());
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
}
variable->m_AllowedValueRange = new NPT_AllowedValueRange;
NPT_ParseInteger32(min, variable->m_AllowedValueRange->min_value);
NPT_ParseInteger32(max, variable->m_AllowedValueRange->max_value);
variable->m_AllowedValueRange->step = -1;
if (step.GetLength() != 0) {
NPT_ParseInteger(step, variable->m_AllowedValueRange->step);
}
}
}
}
// actions
actionList = PLT_XmlHelper::GetChild(root, "actionList");
if (actionList) {
res = PLT_XmlHelper::GetChildren(actionList, actions, "action");
NPT_CHECK_LABEL_SEVERE(res, failure);
for (int i = 0 ; i < (int)actions.GetItemCount(); i++) {
NPT_String action_name;
PLT_XmlHelper::GetChildText(actions[i], "name", action_name);
if (action_name.GetLength() == 0) {
NPT_LOG_SEVERE_1("Invalid action name for action number %d", i+1);
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
PLT_ActionDesc* action_desc = new PLT_ActionDesc(action_name, this);
m_ActionDescs.Add(action_desc);
// action arguments
NPT_XmlElementNode* argumentList = PLT_XmlHelper::GetChild(actions[i], "argumentList");
if (argumentList == NULL || !argumentList->GetChildren().GetItemCount())
continue; // no arguments is ok I guess
NPT_Array<NPT_XmlElementNode*> arguments;
NPT_CHECK_LABEL_SEVERE(PLT_XmlHelper::GetChildren(argumentList, arguments, "argument"), failure);
bool ret_value_found = false;
for (int j = 0 ; j < (int)arguments.GetItemCount(); j++) {
NPT_String name, direction, relatedStateVar;
PLT_XmlHelper::GetChildText(arguments[j], "name", name);
PLT_XmlHelper::GetChildText(arguments[j], "direction", direction);
PLT_XmlHelper::GetChildText(arguments[j], "relatedStateVariable", relatedStateVar);
if (name.GetLength() == 0 || direction.GetLength() == 0 || relatedStateVar.GetLength() == 0) {
NPT_LOG_SEVERE_1("Invalid argument for action %s", (const char*)action_name);
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
// make sure the related state variable exists
PLT_StateVariable* variable = FindStateVariable(relatedStateVar);
if (variable == NULL) {
NPT_LOG_SEVERE_1("State variable not found %s", (const char*)relatedStateVar);
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
}
bool ret_value = false;
NPT_XmlElementNode* ret_val_node = PLT_XmlHelper::GetChild(arguments[j], "retVal");
if (ret_val_node) {
// verify this is the only retVal we've had
if (ret_value_found) {
NPT_LOG_SEVERE("Return argument already found");
NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure);
} else {
ret_value = true;
ret_value_found = true;
}
}
action_desc->GetArgumentDescs().Add(new PLT_ArgumentDesc(name, j, direction, variable, ret_value));
}
}
}
// delete the tree
delete tree;
return NPT_SUCCESS;
failure:
NPT_LOG_FATAL_1("Failed to parse scpd: %s", scpd);
if (tree) delete tree;
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_Service::GetSCPDURL
+---------------------------------------------------------------------*/
NPT_String
PLT_Service::GetSCPDURL(bool absolute /* = false */)
{
NPT_HttpUrl url = GetDevice()->NormalizeURL(m_SCPDURL);
return absolute?url.ToString():url.ToRequestString();
}
/*----------------------------------------------------------------------
| PLT_Service::GetControlURL
+---------------------------------------------------------------------*/
NPT_String
PLT_Service::GetControlURL(bool absolute /* = false */)
{
NPT_HttpUrl url = GetDevice()->NormalizeURL(m_ControlURL);
return absolute?url.ToString():url.ToRequestString();
}
/*----------------------------------------------------------------------
| PLT_Service::GetEventSubURL
+---------------------------------------------------------------------*/
NPT_String
PLT_Service::GetEventSubURL(bool absolute /* = false */)
{
NPT_HttpUrl url = GetDevice()->NormalizeURL(m_EventSubURL);
return absolute?url.ToString():url.ToRequestString();
}
/*----------------------------------------------------------------------
| PLT_Service::ForceVersion
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::ForceVersion(NPT_Cardinal version)
{
if (version < 1) return NPT_FAILURE;
m_ServiceType = m_ServiceType.SubString(0, m_ServiceType.GetLength()-1);
m_ServiceType += NPT_String::FromIntegerU(version);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::FindActionDesc
+---------------------------------------------------------------------*/
PLT_ActionDesc*
PLT_Service::FindActionDesc(const char* name)
{
PLT_ActionDesc* action = NULL;
NPT_ContainerFind(m_ActionDescs, PLT_ActionDescNameFinder(name), action);
return action;
}
/*----------------------------------------------------------------------
| PLT_Service::FindStateVariable
+---------------------------------------------------------------------*/
PLT_StateVariable*
PLT_Service::FindStateVariable(const char* name)
{
PLT_StateVariable* stateVariable = NULL;
NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
return stateVariable;
}
/*----------------------------------------------------------------------
| PLT_Service::GetStateVariableValue
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::GetStateVariableValue(const char* name, NPT_String& value)
{
PLT_StateVariable* stateVariable = FindStateVariable(name);
NPT_CHECK_POINTER_FATAL(stateVariable);
value = stateVariable->GetValue();
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::IsSubscribable
+---------------------------------------------------------------------*/
bool
PLT_Service::IsSubscribable()
{
NPT_List<PLT_StateVariable*>::Iterator var = m_StateVars.GetFirstItem();
while (var) {
if ((*var)->IsSendingEvents()) return true;
++var;
}
return false;
}
/*----------------------------------------------------------------------
| PLT_Service::SetStateVariable
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::SetStateVariable(const char* name, const char* value, const bool clearonsend /*=false*/)
{
PLT_StateVariable* stateVariable = NULL;
NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
if (stateVariable == NULL)
return NPT_FAILURE;
return stateVariable->SetValue(value, clearonsend);
}
/*----------------------------------------------------------------------
| PLT_Service::SetStateVariableRate
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::SetStateVariableRate(const char* name, NPT_TimeInterval rate)
{
PLT_StateVariable* stateVariable = NULL;
NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
if (stateVariable == NULL)
return NPT_FAILURE;
return stateVariable->SetRate(rate);
}
/*----------------------------------------------------------------------
| PLT_Service::SetStateVariableExtraAttribute
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::SetStateVariableExtraAttribute(const char* name,
const char* key,
const char* value)
{
PLT_StateVariable* stateVariable = NULL;
NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
if (stateVariable == NULL)
return NPT_FAILURE;
return stateVariable->SetExtraAttribute(key, value);
}
/*----------------------------------------------------------------------
| PLT_Service::IncStateVariable
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::IncStateVariable(const char* name)
{
PLT_StateVariable* stateVariable = NULL;
NPT_ContainerFind(m_StateVars, PLT_StateVariableNameFinder(name), stateVariable);
if (stateVariable == NULL)
return NPT_FAILURE;
NPT_String value = stateVariable->GetValue();
NPT_Int32 num;
if (value.GetLength() == 0 || NPT_FAILED(value.ToInteger(num))) {
return NPT_FAILURE;
}
// convert value to int
return stateVariable->SetValue(NPT_String::FromInteger(num+1));
}
/*----------------------------------------------------------------------
| PLT_Service::ProcessNewSubscription
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::ProcessNewSubscription(PLT_TaskManagerReference task_manager,
const NPT_SocketAddress& addr,
const NPT_String& callback_urls,
int timeout,
NPT_HttpResponse& response)
{
NPT_LOG_FINE_2("New subscription for %s (timeout = %d)", m_EventSubURL.GetChars(), timeout);
// // first look if we don't have a subscriber with same callbackURL
// PLT_EventSubscriber* subscriber = NULL;
// if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers, PLT_EventSubscriberFinderByCallbackURL(strCallbackURL),
// subscriber))) {
// // update local interface and timeout
// subscriber->m_local_if.SetIpAddress((unsigned long) addr.GetIpAddress());
// subscriber->m_ExpirationTime = NPT_Time(NULL) + timeout;
//
// PLT_UPnPMessageHelper::SetSID("uuid:" + subscriber->m_sid);
// PLT_UPnPMessageHelper::SetTimeOut(timeout);
// return NPT_SUCCESS;
// }
//
// reject if we have too many subscribers already
if (m_Subscribers.GetItemCount() > 30) {
response.SetStatus(500, "Internal Server Error");
return NPT_FAILURE;
}
//TODO: prevent hacking by making sure callbackurl is not ourselves?
// generate a unique subscriber ID
NPT_String sid;
PLT_UPnPMessageHelper::GenerateGUID(sid);
sid = "uuid:" + sid;
PLT_EventSubscriberReference subscriber(new PLT_EventSubscriber(task_manager, this, sid, timeout));
// parse the callback URLs
bool reachable = false;
if (callback_urls[0] == '<') {
char* szURLs = (char*)(const char*)callback_urls;
char* brackL = szURLs;
char* brackR = szURLs;
while (++brackR < szURLs + callback_urls.GetLength()) {
if (*brackR == '>') {
NPT_String strCallbackURL = NPT_String(brackL+1, (NPT_Size)(brackR-brackL-1));
NPT_HttpUrl url(strCallbackURL);
if (url.IsValid()) {
subscriber->AddCallbackURL(strCallbackURL);
reachable = true;
}
brackL = ++brackR;
}
}
}
if (reachable == false) {
NPT_CHECK_LABEL_FATAL(NPT_FAILURE, cleanup);
}
// keep track of which interface we receive the request, we will use this one
// when notifying
subscriber->SetLocalIf(addr);
PLT_UPnPMessageHelper::SetSID(response, subscriber->GetSID());
PLT_UPnPMessageHelper::SetTimeOut(response, timeout);
{
NPT_AutoLock lock(m_Lock);
// new subscriber should get all vars in the LastChange var
UpdateLastChange(m_StateVars);
// send all state vars to sub immediately
NPT_Result res = subscriber->Notify(m_StateVars);
// reset LastChange var to what was really just changed
UpdateLastChange(m_StateVarsChanged);
// make sure the event worked before spawning our recurrent task
NPT_CHECK_LABEL_FATAL(res, cleanup);
// schedule a recurring event notification task if not running already
if (!m_EventTask) {
PLT_ServiceEventTask *task = new PLT_ServiceEventTask(this);
NPT_CHECK_SEVERE(task_manager->StartTask(task));
m_EventTask = task;
}
m_Subscribers.Add(subscriber);
}
return NPT_SUCCESS;
cleanup:
response.SetStatus(412, "Precondition Failed");
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_Service::ProcessRenewSubscription
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::ProcessRenewSubscription(const NPT_SocketAddress& addr,
const NPT_String& sid,
int timeout_secs,
NPT_HttpResponse& response)
{
NPT_AutoLock lock(m_Lock);
NPT_LOG_FINE_2("Renewing subscription for %s (sub=%s)",
m_EventSubURL.GetChars(),
sid.GetChars());
// first look if we don't have a subscriber with same callbackURL
PLT_EventSubscriberReference subscriber;
if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderBySID(sid),
subscriber))) {
NPT_TimeStamp now, expiration;
NPT_System::GetCurrentTimeStamp(now);
expiration = subscriber->GetExpirationTime();
// renew subscriber if it has not expired
if (expiration > now ) {
// update local interface and timeout
subscriber->SetLocalIf(addr);
subscriber->SetTimeout(timeout_secs);
PLT_UPnPMessageHelper::SetSID(response, subscriber->GetSID());
PLT_UPnPMessageHelper::SetTimeOut(response, timeout_secs);
return NPT_SUCCESS;
} else {
NPT_LOG_FINE_1("Subscriber \"%s\" didn't renew in time", (const char*)subscriber->GetSID());
m_Subscribers.Remove(subscriber);
}
}
NPT_LOG_WARNING_1("Failed to renew subscription for %s!", sid.GetChars());
// didn't find a valid Subscriber in our list
response.SetStatus(412, "Precondition Failed");
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_Service::ProcessCancelSubscription
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::ProcessCancelSubscription(const NPT_SocketAddress& /* addr */,
const NPT_String& sid,
NPT_HttpResponse& response)
{
NPT_AutoLock lock(m_Lock);
// first look if we don't have a subscriber with same callbackURL
PLT_EventSubscriberReference sub;
if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers,
PLT_EventSubscriberFinderBySID(sid),
sub))) {
NPT_LOG_FINE_2("Cancelling subscription for %s (sub=%s)",
m_EventSubURL.GetChars(),
sid.GetChars());
// remove sub
m_Subscribers.Remove(sub);
return NPT_SUCCESS;
}
NPT_LOG_WARNING_1("Cancelling subscription for unknown subscriber %s!", sid.GetChars());
// didn't find a valid Subscriber in our list
response.SetStatus(412, "Precondition Failed");
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_Service::AddChanged
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::AddChanged(PLT_StateVariable* var)
{
NPT_AutoLock lock(m_Lock);
// no event task means no subscribers yet, so don't bother
// Note: this will take care also when setting default state
// variables values during init and avoid being published
if (!m_EventTask) return NPT_SUCCESS;
if (var->IsSendingEvents()) {
if (!m_StateVarsToPublish.Contains(var)) m_StateVarsToPublish.Add(var);
} else if (var->IsSendingEvents(true)) {
if (!m_StateVarsChanged.Contains(var)) m_StateVarsChanged.Add(var);
UpdateLastChange(m_StateVarsChanged);
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::UpdateLastChange
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::UpdateLastChange(NPT_List<PLT_StateVariable*>& vars)
{
PLT_StateVariable* var = FindStateVariable("LastChange");
if (var == NULL) return NPT_FAILURE;
NPT_ASSERT(m_LastChangeNamespace.GetLength() > 0);
if (vars.GetItemCount() == 0) {
// no vars to update, remove LastChange from vars to publish
m_StateVarsToPublish.Remove(var);
return NPT_SUCCESS;
}
NPT_Reference<NPT_XmlElementNode> top(new NPT_XmlElementNode("Event"));
NPT_CHECK_SEVERE(top->SetNamespaceUri("", m_LastChangeNamespace));
NPT_XmlElementNode* instance = new NPT_XmlElementNode("InstanceID");
NPT_CHECK_SEVERE(top->AddChild(instance));
NPT_CHECK_SEVERE(instance->SetAttribute("val", "0"));
// build list of changes
NPT_CHECK_SEVERE(vars.ApplyUntil(
PLT_LastChangeXMLIterator(instance),
NPT_UntilResultNotEquals(NPT_SUCCESS)));
// serialize node
NPT_String value;
NPT_CHECK_SEVERE(PLT_XmlHelper::Serialize(*top, value, false));
// set the state change directly instead of calling SetValue
// to avoid recursive lock, instead add LastChange var directly
var->m_Value = value;
// add to list of vars scheduled to be published next time if not already there
if (!m_StateVarsToPublish.Contains(var)) m_StateVarsToPublish.Add(var);
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::PauseEventing
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::PauseEventing(bool pause /* = TRUE */)
{
NPT_AutoLock lock(m_Lock);
m_EventingPaused = pause;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Service::NotifyChanged
+---------------------------------------------------------------------*/
NPT_Result
PLT_Service::NotifyChanged()
{
NPT_AutoLock lock(m_Lock);
// no eventing for now
if (m_EventingPaused) return NPT_SUCCESS;
// pick the vars that are ready to be published
// based on their moderation rate and last publication
NPT_List<PLT_StateVariable*> vars_ready;
NPT_List<PLT_StateVariable*>::Iterator iter = m_StateVarsToPublish.GetFirstItem();
while (iter) {
PLT_StateVariable* var = *iter;
if (var->IsReadyToPublish()) {
vars_ready.Add(var);
m_StateVarsToPublish.Erase(iter++);
// clear last changed list if we're about to send LastChange var
if (!var->GetName().Compare("LastChange")) m_StateVarsChanged.Clear();
continue;
}
++iter;
}
// if nothing to publish then bail out now
// we'll clean up expired subscribers when we have something to publish
if (vars_ready.GetItemCount() == 0) return NPT_SUCCESS;
// send vars that are ready to go and remove old subscribers
NPT_List<PLT_EventSubscriberReference>::Iterator sub_iter = m_Subscribers.GetFirstItem();
while (sub_iter) {
PLT_EventSubscriberReference sub = *sub_iter;
NPT_TimeStamp now, expiration;
NPT_System::GetCurrentTimeStamp(now);
expiration = sub->GetExpirationTime();
// forget sub if it didn't renew subscription in time or if notification failed
if (expiration == NPT_TimeStamp() || now < expiration + NPT_TimeStamp(30.f)) {
// TODO: Notification is asynchronous, so we won't know if it failed until
// the subscriber m_SubscriberTask is done
NPT_Result res = vars_ready.GetItemCount()?sub->Notify(vars_ready):NPT_SUCCESS;
if (NPT_SUCCEEDED(res)) {
++sub_iter;
continue;
}
}
m_Subscribers.Erase(sub_iter++);
}
- // some state variables must be cleared immediatly after sending
+ // some state variables must be cleared immediately after sending
iter = vars_ready.GetFirstItem();
while (iter) {
PLT_StateVariable* var = *iter;
var->OnSendCompleted();
++iter;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_ServiceSCPDURLFinder::operator()
+---------------------------------------------------------------------*/
bool
PLT_ServiceSCPDURLFinder::operator()(PLT_Service* const & service) const
{
return m_URL.Compare(service->GetSCPDURL(m_URL.StartsWith("http://")?true:false), true)?false:true;
}
/*----------------------------------------------------------------------
| PLT_ServiceControlURLFinder::operator()
+---------------------------------------------------------------------*/
bool
PLT_ServiceControlURLFinder::operator()(PLT_Service* const & service) const
{
return m_URL.Compare(service->GetControlURL(m_URL.StartsWith("http://")?true:false), true)?false:true;
}
/*----------------------------------------------------------------------
| PLT_ServiceEventSubURLFinder::operator()
+---------------------------------------------------------------------*/
bool
PLT_ServiceEventSubURLFinder::operator()(PLT_Service* const & service) const
{
return m_URL.Compare(service->GetEventSubURL(m_URL.StartsWith("http://")?true:false), true)?false:true;
}
/*----------------------------------------------------------------------
| PLT_ServiceIDFinder::operator()
+---------------------------------------------------------------------*/
bool
PLT_ServiceIDFinder::operator()(PLT_Service* const & service) const
{
return m_Id.Compare(service->GetServiceID(), true) ? false : true;
}
/*----------------------------------------------------------------------
| PLT_ServiceTypeFinder::operator()
+---------------------------------------------------------------------*/
bool
PLT_ServiceTypeFinder::operator()(PLT_Service* const & service) const
{
// DLNA: match any version if last char is '*'
if (m_Type.EndsWith("*")) {
return m_Type.CompareN(service->GetServiceType(), m_Type.GetLength()-1, true) ? false : true;
}
return m_Type.Compare(service->GetServiceType(), true) ? false : true;
}
/*----------------------------------------------------------------------
| PLT_ServiceNameFinder::operator()
+---------------------------------------------------------------------*/
bool
PLT_ServiceNameFinder::operator()(PLT_Service* const & service) const
{
return m_Name.Compare(service->GetServiceName(), true) ? false : true;
}
/*----------------------------------------------------------------------
| PLT_GetLastChangeXMLIterator::operator()
+---------------------------------------------------------------------*/
NPT_Result
PLT_LastChangeXMLIterator::operator()(PLT_StateVariable* const &var) const
{
// only add vars that are indirectly evented
if (!var->IsSendingEvents(true)) return NPT_SUCCESS;
NPT_XmlElementNode* variable = new NPT_XmlElementNode((const char*)var->GetName());
NPT_CHECK_SEVERE(m_Node->AddChild(variable));
NPT_CHECK_SEVERE(var->Serialize(*variable));
return NPT_SUCCESS;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.h
index ebca19542c..3eb28b6e56 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltService.h
@@ -1,522 +1,522 @@
/*****************************************************************
|
| Platinum - Service
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP Service
*/
#ifndef _PLT_SERVICE_H_
#define _PLT_SERVICE_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
#include "PltEvent.h"
#include "PltArgument.h"
#include "PltStateVariable.h"
#include "PltAction.h"
/*----------------------------------------------------------------------
| forward declarations
+---------------------------------------------------------------------*/
class PLT_DeviceData;
/*----------------------------------------------------------------------
| PLT_Service class
+---------------------------------------------------------------------*/
/**
UPnP Service.
The PLT_Service class holds information about a UPnP service of a given device.
It maintains a list of actions and state variables. A PLT_DeviceData instance can own
one or more PLT_Service instances. When a PLT_Service is advertised as part of a
a UPnP Device (PLT_DeviceHost), it also maintains a list of subscribers to nofify when
state variables change.
*/
class PLT_Service
{
public:
// methods
/**
Create an instance of a UPnP Service either hosted or discovered.
@param device Pointer to the PLT_DeviceData the service is associated to
@param type String representing the UPnP service type
@param id String representing the UPnP service id
@param name A String to create unique service SCPD, control and eventing urls
@param last_change_namespace A String for the LastChange state variable namespace if any
*/
PLT_Service(PLT_DeviceData* device,
const char* type,
const char* id,
const char* name,
const char* last_change_namespace = NULL);
virtual ~PLT_Service();
// methods
/**
When service is hosted by a PLT_DeviceHost, this setups the SCPD, control and event urls.
@param service_name the service name used to format unique urls
*/
NPT_Result InitURLs(const char* service_name);
/**
Verify the service has been properly initialized or is a valid discovered service.
@return true if valid.
*/
bool IsValid() { return (m_ActionDescs.GetItemCount() > 0); }
/**
When a PLT_DeviceHost needs to change more than one state variables at a time
but would rather send only one event with all state variable changes, this can be
used to pause and resume the automatic eventing.
@param pause Flag to indicate if eventing should be paused or resumed
*/
NPT_Result PauseEventing(bool pause = true);
// class methods
static bool IsTrue(const NPT_String& value) {
if (value.Compare("1", true) &&
value.Compare("true", true) &&
value.Compare("yes", true)) {
return false;
}
return true;
}
// accessor methods
/**
Set the SCPD url for control points to be able to fetch the SCPD xml document.
@param url relative path of SCPD url
*/
NPT_Result SetSCPDURL(const char* url) { m_SCPDURL = url; return NPT_SUCCESS; }
/*
Set the Service Control url for control points to be able to invoke actions.
@param url relative path of control url
*/
NPT_Result SetControlURL(const char* url) { m_ControlURL = url; return NPT_SUCCESS; };
/**
Set the Service Event subscription url for control points to be able to subscribe
to events.
@param url relative path of even url
*/
NPT_Result SetEventSubURL(const char* url) { m_EventSubURL = url; return NPT_SUCCESS; };
/**
Return the SCPD url associated with this service.
@param absolute flag to indicate if absolute url including ip and port should
be returned
@return SCPD url
*/
NPT_String GetSCPDURL(bool absolute = false);
/**
Return the Control url associated with this service.
@param absolute flag to indicate if absolute url including ip and port should
be returned
@return Control url
*/
NPT_String GetControlURL(bool absolute = false);
/**
Return the Event subscription url associated with this service.
@param absolute flag to indicate if absolute url including ip and port should
be returned
@return Event url
*/
NPT_String GetEventSubURL(bool absolute = false);
/**
Return the service id.
@return service id
*/
const NPT_String& GetServiceID() const { return m_ServiceID; }
/**
Return the service type.
@return service type
*/
const NPT_String& GetServiceType() const { return m_ServiceType; }
/**
Return the service friendly name.
@return service name
*/
const NPT_String& GetServiceName() const { return m_ServiceName; }
/**
Return the PLT_DeviceData* the service is associated with.
@return PLT_DeviceData pointer
*/
PLT_DeviceData* GetDevice() { return m_Device; }
/**
When a control point discover a new service with a higher version number
than it can work with, a lower version can be set to force backward
compatibility.
@param version Integer specifying the version to use
*/
NPT_Result ForceVersion(NPT_Cardinal version);
/**
Return the service SCPD xml document.
@param xml String to receive document
*/
NPT_Result GetSCPDXML(NPT_String& xml);
/**
Set the service SCPD xml document.
@param xml String SCPD xml document
*/
NPT_Result SetSCPDXML(const char* xml);
/**
Populate the UPnP Device description document with service information.
@param parent XML Element where to insert the service XML Element
@param service Pointer to service XML Element node newly created so it can be
extended with additional non standard information.
*/
NPT_Result GetDescription(NPT_XmlElementNode* parent, NPT_XmlElementNode** service = NULL);
/**
Set a new value for a given state variable. The service keeps track of which
state variables have changed and events are being triggered by a PLT_ServiceEventTask
when necessary.
@param name state variable name
@param value new State Variable value.
- @param clearonsend whether the State Variable should clear immediatly in ::OnSendingCompleted
+ @param clearonsend whether the State Variable should clear immediately in ::OnSendingCompleted
*/
NPT_Result SetStateVariable(const char* name, const char* value, const bool clearonsend = false);
/**
Certain state variables notifications must not be sent faster than a certain
rate according to the UPnP specs. This sets the rate for a given state variable.
@param name state variable name
@param rate a time interval specifying the minimum interval allowed between
notifications.
*/
NPT_Result SetStateVariableRate(const char* name, NPT_TimeInterval rate);
/**
Certain state variables require extra xml attributes when serialized.
@param name state variable name
@param key the attribute name
@param value the attribute value
*/
NPT_Result SetStateVariableExtraAttribute(const char* name, const char* key, const char* value);
/**
Helper function to increment a state variable representing a number.
@param name state variable name
*/
NPT_Result IncStateVariable(const char* name);
/**
Return the PLT_StateVariable pointer given a state variable name.
@param name state variable name
@return PLT_StateVariable pointer
*/
PLT_StateVariable* FindStateVariable(const char* name);
/**
Return the state variable value given a state variable name.
@param name state variable name
@param value state variable value output
*/
NPT_Result GetStateVariableValue(const char* name, NPT_String& value);
/**
Return whether a service is capable of sending events.
@return true if sending events
*/
bool IsSubscribable();
/**
Return the list of state variables.
@return list of state variable pointers.
*/
const NPT_List<PLT_StateVariable*>& GetStateVariables() const { return m_StateVars; }
/**
Return the PLT_ActionDesc given an action name
@param name action name
@return PLT_ActioDesc pointer
*/
PLT_ActionDesc* FindActionDesc(const char* name);
/**
Return an array of actions descriptions PLT_ActionDesc.
@return array of PLT_ActionDesc pointers.
*/
const NPT_Array<PLT_ActionDesc*>& GetActionDescs() const { return m_ActionDescs; }
private:
/**
A task to send events.
The PLT_ServiceEventTask is started when receiving a first subscription. It
monitors if some state variables have changed and sends events to all
subscribers if so.
*/
class PLT_ServiceEventTask : public PLT_ThreadTask {
public:
PLT_ServiceEventTask(PLT_Service* service) : m_Service(service) {}
void DoRun() override {
while (!IsAborting(100)) m_Service->NotifyChanged();
}
private:
PLT_Service* m_Service;
};
// methods
void Cleanup();
/**
Called by a PLT_StateVariable to keep track of what events need to be
sent by the PLT_ServiceEventTask task.
@param var PLT_StateVariable pointer
*/
NPT_Result AddChanged(PLT_StateVariable* var);
/**
Certain UPnP services combine state variable changes into one single
state variable called "LastChange". This function updates the LastChange
state variable by looking through the list passed for state variables that
are not individually evented.
*/
NPT_Result UpdateLastChange(NPT_List<PLT_StateVariable*>& vars);
/**
Send state variable change events to all subscribers.
*/
NPT_Result NotifyChanged();
// Events
/**
Called by PLT_DeviceHost when it receives a request for a new subscription.
*/
NPT_Result ProcessNewSubscription(
PLT_TaskManagerReference task_manager,
const NPT_SocketAddress& addr,
const NPT_String& callback_urls,
int timeout,
NPT_HttpResponse& response);
/**
Called by PLT_DeviceHost when it receives a request renewing an existing
subscription.
*/
NPT_Result ProcessRenewSubscription(
const NPT_SocketAddress& addr,
const NPT_String& sid,
int timeout,
NPT_HttpResponse& response);
/**
Called by PLT_DeviceHost when it receives a request to cancel an existing
subscription.
*/
NPT_Result ProcessCancelSubscription(
const NPT_SocketAddress& addr,
const NPT_String& sid,
NPT_HttpResponse& response);
protected:
// friends that need to call private functions
friend class PLT_StateVariable; // AddChanged
friend class PLT_DeviceHost; // ProcessXXSubscription
//members
PLT_DeviceData* m_Device;
NPT_String m_ServiceType;
NPT_String m_ServiceID;
NPT_String m_ServiceName;
NPT_String m_SCPDURL;
NPT_String m_ControlURL;
NPT_String m_EventSubURL;
PLT_ServiceEventTask* m_EventTask;
NPT_Array<PLT_ActionDesc*> m_ActionDescs;
NPT_List<PLT_StateVariable*> m_StateVars;
NPT_Mutex m_Lock;
NPT_List<PLT_StateVariable*> m_StateVarsChanged;
NPT_List<PLT_StateVariable*> m_StateVarsToPublish;
NPT_List<PLT_EventSubscriberReference> m_Subscribers;
bool m_EventingPaused;
NPT_String m_LastChangeNamespace;
};
/*----------------------------------------------------------------------
| PLT_ServiceSCPDURLFinder
+---------------------------------------------------------------------*/
/**
The PLT_ServiceSCPDURLFinder class returns an instance of a PLT_Service given a
service SCPD url.
*/
class PLT_ServiceSCPDURLFinder
{
public:
// methods
PLT_ServiceSCPDURLFinder(const char* url) : m_URL(url) {}
virtual ~PLT_ServiceSCPDURLFinder() {}
bool operator()(PLT_Service* const & service) const;
private:
// members
NPT_String m_URL;
};
/*----------------------------------------------------------------------
| PLT_ServiceControlURLFinder
+---------------------------------------------------------------------*/
/**
The PLT_ServiceControlURLFinder class returns an instance of a PLT_Service
given a service control url.
*/
class PLT_ServiceControlURLFinder
{
public:
// methods
PLT_ServiceControlURLFinder(const char* url) : m_URL(url) {}
virtual ~PLT_ServiceControlURLFinder() {}
bool operator()(PLT_Service* const & service) const;
private:
// members
NPT_String m_URL;
};
/*----------------------------------------------------------------------
| PLT_ServiceEventSubURLFinder
+---------------------------------------------------------------------*/
/**
The PLT_ServiceEventSubURLFinder class returns an instance of a PLT_Service
given a service event subscription url.
*/
class PLT_ServiceEventSubURLFinder
{
public:
// methods
PLT_ServiceEventSubURLFinder(const char* url) : m_URL(url) {}
virtual ~PLT_ServiceEventSubURLFinder() {}
bool operator()(PLT_Service* const & service) const;
private:
// members
NPT_String m_URL;
};
/*----------------------------------------------------------------------
| PLT_ServiceIDFinder
+---------------------------------------------------------------------*/
/**
The PLT_ServiceIDFinder class returns an instance of a PLT_Service given a
service id.
*/
class PLT_ServiceIDFinder
{
public:
// methods
PLT_ServiceIDFinder(const char* id) : m_Id(id) {}
virtual ~PLT_ServiceIDFinder() {}
bool operator()(PLT_Service* const & service) const;
private:
// members
NPT_String m_Id;
};
/*----------------------------------------------------------------------
| PLT_ServiceTypeFinder
+---------------------------------------------------------------------*/
/**
The PLT_ServiceTypeFinder class returns an instance of a PLT_Service given a
service type.
*/
class PLT_ServiceTypeFinder
{
public:
// methods
PLT_ServiceTypeFinder(const char* type) : m_Type(type) {}
virtual ~PLT_ServiceTypeFinder() {}
bool operator()(PLT_Service* const & service) const;
private:
// members
NPT_String m_Type;
};
/*----------------------------------------------------------------------
| PLT_ServiceNameFinder
+---------------------------------------------------------------------*/
/**
The PLT_ServiceNameFinder class returns an instance of a PLT_Service given a
service name.
*/
class PLT_ServiceNameFinder
{
public:
// methods
PLT_ServiceNameFinder(const char* name) : m_Name(name) {}
virtual ~PLT_ServiceNameFinder() {}
bool operator()(PLT_Service* const & service) const;
private:
// members
NPT_String m_Name;
};
/*----------------------------------------------------------------------
| PLT_LastChangeXMLIterator
+---------------------------------------------------------------------*/
/**
The PLT_LastChangeXMLIterator class is used to serialize the LastChange variable
changes into xml given a list of state variables.
*/
class PLT_LastChangeXMLIterator
{
public:
// methods
PLT_LastChangeXMLIterator(NPT_XmlElementNode* node) : m_Node(node) {}
virtual ~PLT_LastChangeXMLIterator() {}
NPT_Result operator()(PLT_StateVariable* const & var) const;
private:
NPT_XmlElementNode* m_Node;
};
#endif /* _PLT_SERVICE_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltSsdp.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltSsdp.h
index 0bbbd75952..444153bfb4 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltSsdp.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltSsdp.h
@@ -1,389 +1,389 @@
/*****************************************************************
|
| Platinum - SSDP
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP SSDP
*/
#ifndef _PLT_SSDP_H_
#define _PLT_SSDP_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
#include "PltThreadTask.h"
#include "PltHttpServerTask.h"
/*----------------------------------------------------------------------
| forward declarations
+---------------------------------------------------------------------*/
class PLT_DeviceHost;
/*----------------------------------------------------------------------
| PLT_SsdpAnnounceType
+---------------------------------------------------------------------*/
typedef enum {
PLT_ANNOUNCETYPE_BYEBYE,
PLT_ANNOUNCETYPE_ALIVE,
PLT_ANNOUNCETYPE_UPDATE
} PLT_SsdpAnnounceType;
/*----------------------------------------------------------------------
| PLT_SsdpPacketListener class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpPacketListener class is an interface for handling SSDP packets
(M-SEARCH and NOTIFY).
*/
class PLT_SsdpPacketListener
{
public:
virtual ~PLT_SsdpPacketListener() {}
virtual NPT_Result OnSsdpPacket(const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context) = 0;
};
/*----------------------------------------------------------------------
| PLT_SsdpSearchResponseListener class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpSearchResponseListener class is an interface for handling SSDP M-SEARCH
responses.
*/
class PLT_SsdpSearchResponseListener
{
public:
virtual ~PLT_SsdpSearchResponseListener() {}
virtual NPT_Result ProcessSsdpSearchResponse(NPT_Result res,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response) = 0;
};
/*----------------------------------------------------------------------
| PLT_SsdpSender class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpSender class provides a mechanism to format and send SSDP packets.
*/
class PLT_SsdpSender
{
public:
static NPT_Result SendSsdp(NPT_HttpRequest& request,
const char* usn,
const char* nt,
NPT_UdpSocket& socket,
bool notify,
const NPT_SocketAddress* addr = NULL);
static NPT_Result SendSsdp(NPT_HttpResponse& response,
const char* usn,
const char* nt,
NPT_UdpSocket& socket,
bool notify,
const NPT_SocketAddress* addr = NULL);
private:
static NPT_Result FormatPacket(NPT_HttpMessage& message,
const char* usn,
const char* nt,
NPT_UdpSocket& socket,
bool notify);
};
/*----------------------------------------------------------------------
| PLT_SsdpDeviceSearchResponseInterfaceIterator class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpDeviceSearchResponseInterfaceIterator class looks for the best network
interface to use then sends a SSDP M-SEARCH response.
*/
class PLT_SsdpDeviceSearchResponseInterfaceIterator
{
public:
PLT_SsdpDeviceSearchResponseInterfaceIterator(PLT_DeviceHost* device,
NPT_SocketAddress remote_addr,
const char* st) :
m_Device(device), m_RemoteAddr(remote_addr), m_ST(st) {}
virtual ~PLT_SsdpDeviceSearchResponseInterfaceIterator() {}
NPT_Result operator()(NPT_NetworkInterface*& if_addr) const;
private:
PLT_DeviceHost* m_Device;
NPT_SocketAddress m_RemoteAddr;
NPT_String m_ST;
};
/*----------------------------------------------------------------------
| PLT_SsdpDeviceSearchResponseTask class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpDeviceSearchResponseTask class is used by a PLT_DeviceHost to respond
to SSDP M-SEARCH requests from UPnP ControlPoints.
*/
class PLT_SsdpDeviceSearchResponseTask : public PLT_ThreadTask
{
public:
PLT_SsdpDeviceSearchResponseTask(PLT_DeviceHost* device,
NPT_SocketAddress remote_addr,
const char* st) :
m_Device(device), m_RemoteAddr(remote_addr), m_ST(st) {}
protected:
~PLT_SsdpDeviceSearchResponseTask() override {}
// PLT_ThreadTask methods
void DoRun() override;
protected:
PLT_DeviceHost* m_Device;
NPT_SocketAddress m_RemoteAddr;
NPT_String m_ST;
};
/*----------------------------------------------------------------------
| PLT_SsdpAnnounceInterfaceIterator class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpAnnounceInterfaceIterator class is used to send SSDP announcements
- given a list of network interaces.
+ given a list of network interfaces.
*/
class PLT_SsdpAnnounceInterfaceIterator
{
public:
PLT_SsdpAnnounceInterfaceIterator(PLT_DeviceHost* device, PLT_SsdpAnnounceType type, bool broadcast = false) :
m_Device(device), m_Type(type), m_Broadcast(broadcast) {}
NPT_Result operator()(NPT_NetworkInterface*& if_addr) const;
private:
PLT_DeviceHost* m_Device;
PLT_SsdpAnnounceType m_Type;
bool m_Broadcast;
};
/*----------------------------------------------------------------------
| PLT_SsdpInitMulticastIterator class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpInitMulticastIterator class is used to join a multicast group
given a list of IP addresses.
*/
class PLT_SsdpInitMulticastIterator
{
public:
PLT_SsdpInitMulticastIterator(NPT_UdpMulticastSocket* socket) :
m_Socket(socket) {}
NPT_Result operator()(NPT_IpAddress& if_addr) const {
NPT_IpAddress addr;
addr.ResolveName("239.255.255.250");
// OSX bug, since we're reusing the socket, we need to leave group first
// before joining it
m_Socket->LeaveGroup(addr, if_addr);
return m_Socket->JoinGroup(addr, if_addr);
}
private:
NPT_UdpMulticastSocket* m_Socket;
};
/*----------------------------------------------------------------------
| PLT_SsdpDeviceAnnounceTask class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpDeviceAnnounceTask class is a task to send UPnP Device SSDP announcements
(alive or byebye). It can be setup to automatically repeat after an interval.
*/
class PLT_SsdpDeviceAnnounceTask : public PLT_ThreadTask
{
public:
PLT_SsdpDeviceAnnounceTask(PLT_DeviceHost* device,
NPT_TimeInterval repeat,
bool is_byebye_first = false,
bool extra_broadcast = false) :
m_Device(device),
m_Repeat(repeat),
m_IsByeByeFirst(is_byebye_first),
m_ExtraBroadcast(extra_broadcast) {}
protected:
~PLT_SsdpDeviceAnnounceTask() override {}
// PLT_ThreadTask methods
void DoRun() override;
protected:
PLT_DeviceHost* m_Device;
NPT_TimeInterval m_Repeat;
bool m_IsByeByeFirst;
bool m_ExtraBroadcast;
};
/*----------------------------------------------------------------------
| PLT_NetworkInterfaceAddressSearchIterator class
+---------------------------------------------------------------------*/
/**
The PLT_NetworkInterfaceAddressSearchIterator class returns the network interface
given an IP address.
*/
class PLT_NetworkInterfaceAddressSearchIterator
{
public:
PLT_NetworkInterfaceAddressSearchIterator(NPT_String ip) : m_Ip(ip) {}
virtual ~PLT_NetworkInterfaceAddressSearchIterator() {}
NPT_Result operator()(NPT_NetworkInterface*& addr) const {
NPT_List<NPT_NetworkInterfaceAddress>::Iterator niaddr = addr->GetAddresses().GetFirstItem();
if (!niaddr) return NPT_FAILURE;
return (m_Ip.Compare((*niaddr).GetPrimaryAddress().ToString(), true) == 0) ? NPT_SUCCESS : NPT_FAILURE;
}
private:
NPT_String m_Ip;
};
/*----------------------------------------------------------------------
| PLT_SsdpPacketListenerIterator class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpPacketListenerIterator class iterates through a list of
PLT_SsdpPacketListener instances to notify of a new SSDP incoming packet.
*/
class PLT_SsdpPacketListenerIterator
{
public:
PLT_SsdpPacketListenerIterator(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context) :
m_Request(request), m_Context(context) {}
NPT_Result operator()(PLT_SsdpPacketListener*& listener) const {
return listener->OnSsdpPacket(m_Request, m_Context);
}
private:
NPT_HttpRequest& m_Request;
const NPT_HttpRequestContext& m_Context;
};
/*----------------------------------------------------------------------
| PLT_SsdpListenTask class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpListenTask class is used to listen for incoming SSDP packets and
keep track of a list of PLT_SsdpPacketListener listeners to notify when a new
SSDP packet has arrived.
*/
class PLT_SsdpListenTask : public PLT_HttpServerSocketTask
{
public:
PLT_SsdpListenTask(NPT_Socket* socket) :
PLT_HttpServerSocketTask(socket, true) {
// Change read time out for UDP because iPhone 3.0 seems to hang
// after reading everything from the socket even though
// more stuff arrived
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
m_Socket->SetReadTimeout(10000);
#endif
}
NPT_Result AddListener(PLT_SsdpPacketListener* listener) {
NPT_AutoLock lock(m_Mutex);
m_Listeners.Add(listener);
return NPT_SUCCESS;
}
NPT_Result RemoveListener(PLT_SsdpPacketListener* listener) {
NPT_AutoLock lock(m_Mutex);
m_Listeners.Remove(listener);
return NPT_SUCCESS;
}
// PLT_Task methods
void DoAbort() override;
protected:
~PLT_SsdpListenTask() override {}
// PLT_HttpServerSocketTask methods
NPT_Result GetInputStream(NPT_InputStreamReference& stream) override;
NPT_Result GetInfo(NPT_SocketInfo& info) override;
NPT_Result SetupResponse(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response) override;
protected:
PLT_InputDatagramStreamReference m_Datagram;
NPT_List<PLT_SsdpPacketListener*> m_Listeners;
NPT_Mutex m_Mutex;
};
/*----------------------------------------------------------------------
| PLT_SsdpSearchTask class
+---------------------------------------------------------------------*/
/**
The PLT_SsdpSearchTask class is a task used by a PLT_CtrlPoint to issue a SSDP
- M-SEARCH request. It can be set to repeat at a certain frequencey.
+ M-SEARCH request. It can be set to repeat at a certain frequency.
*/
class PLT_SsdpSearchTask : public PLT_ThreadTask
{
public:
PLT_SsdpSearchTask(NPT_UdpSocket* socket,
PLT_SsdpSearchResponseListener* listener,
NPT_HttpRequest* request,
NPT_TimeInterval frequency = NPT_TimeInterval(0.)); // pass 0 for one time
protected:
~PLT_SsdpSearchTask() override;
// PLT_ThreadTask methods
void DoAbort() override;
void DoRun() override;
virtual NPT_Result ProcessResponse(NPT_Result res,
const NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse* response);
private:
PLT_SsdpSearchResponseListener* m_Listener;
NPT_HttpRequest* m_Request;
NPT_TimeInterval m_Frequency;
bool m_Repeat;
NPT_UdpSocket* m_Socket;
};
#endif /* _PLT_SSDP_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltStateVariable.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltStateVariable.h
index 465e95cfeb..d54969e928 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltStateVariable.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltStateVariable.h
@@ -1,231 +1,231 @@
/*****************************************************************
|
| Platinum - Service State Variable
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP State Variable
*/
#ifndef _PLT_STATE_VARIABLE_H_
#define _PLT_STATE_VARIABLE_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
/*----------------------------------------------------------------------
| forward declarations
+---------------------------------------------------------------------*/
class PLT_Argument;
class PLT_Service;
/*----------------------------------------------------------------------
| NPT_AllowedValueRange struct
+---------------------------------------------------------------------*/
/**
The NPT_AllowedValueRange struct holds the min, max and step value allowed of
a UPnP Service state variable.
*/
typedef struct {
NPT_Int32 min_value;
NPT_Int32 max_value;
NPT_Int32 step;
} NPT_AllowedValueRange;
/*----------------------------------------------------------------------
| PLT_StateVariable class
+---------------------------------------------------------------------*/
/**
The PLT_StateVariable class maintains the state of a UPnP Service state variable.
It is used by a PLT_DeviceHost instance to notify subscribers of a change or by a
subscriber (PLT_CtrlPoint) when a service state variable change notification
has been received.
*/
class PLT_StateVariable
{
public:
PLT_StateVariable(PLT_Service* service);
~PLT_StateVariable();
/**
Populate the SCPD xml document with state variable information.
@param node XML Element where to insert the state variable XML Element
*/
NPT_Result GetSCPDXML(NPT_XmlElementNode* node);
/**
Return the PLT_Service that this state variable is associated with.
@return PLT_Service pointer.
*/
PLT_Service* GetService();
/**
Return whether the state variable is eventable directly or indirectly. A state
variable sends events indirectly when part of the "LastChange" state variable.
@param indirectly Boolean to test if the state variable is sending events indirectly
@return Whether the state variable sends events according to the input flag specified.
*/
bool IsSendingEvents(bool indirectly = false);
/**
Force the state variable to send events directly.
*/
void DisableIndirectEventing();
/**
Certain state variables notifications must not be sent faster than a certain
rate according to the UPnP specs. This sets the rate for a given state variable.
@param rate time interval to respect between notifications.
*/
NPT_Result SetRate(NPT_TimeInterval rate);
/**
Set the state variable value. The value is first validated to make sure
it is an allowed value. Once the value is validated, it is marked for eventing by
calling the PLT_Service AddChanged function.
@param value new state variable value. Can be a comma separated list of values.
- @param clearonsend whether the statevariable should be cleared immediatly after sending
+ @param clearonsend whether the statevariable should be cleared immediately after sending
*/
NPT_Result SetValue(const char* value, const bool clearonsend = false);
/**
Validate the new value of the state variable.
@param value new state variable value. Can be a comma separated list of values.
*/
NPT_Result ValidateValue(const char* value);
/**
Certain state variables require extra xml attributes when serialized.
@param name the attribute name
@param value the attribute value
*/
NPT_Result SetExtraAttribute(const char* name, const char* value);
/**
Return the state variable name.
@return state variable name.
*/
const NPT_String& GetName() const { return m_Name; }
/**
Return the current state variable value.
@return state variable current value.
*/
const NPT_String& GetValue() const { return m_Value; }
/**
Return the state variable data type.
@return state variable data type.
*/
const NPT_String& GetDataType() const { return m_DataType; }
/**
Return the state variable allowed value range if any.
@return state variable value range pointer or null if none.
*/
const NPT_AllowedValueRange* GetAllowedValueRange() const { return m_AllowedValueRange; }
/**
Helper function to return a state variable given a list of state variables
and a state variable name.
@param vars list of state variables
@param name state variable name to look for
@return PLT_StateVariable pointer.
*/
static PLT_StateVariable* Find(NPT_List<PLT_StateVariable*>& vars,
const char* name);
protected:
/**
Return whether the state variable value changed and subscribers need to
be notified.
*/
bool IsReadyToPublish();
/**
* If this statevariable should clear after sending to all subscribers, clears the value without
* eventing the change
*/
void OnSendCompleted();
/**
Serialize the state variable into xml.
*/
NPT_Result Serialize(NPT_XmlElementNode& node);
protected:
friend class PLT_Service;
friend class PLT_LastChangeXMLIterator;
//members
PLT_Service* m_Service;
NPT_AllowedValueRange* m_AllowedValueRange;
NPT_String m_Name;
NPT_String m_DataType;
NPT_String m_DefaultValue;
bool m_IsSendingEvents;
bool m_IsSendingEventsIndirectly;
bool m_ShouldClearOnSend;
NPT_TimeInterval m_Rate;
NPT_TimeStamp m_LastEvent;
NPT_Array<NPT_String*> m_AllowedValues;
NPT_String m_Value;
NPT_Map<NPT_String,NPT_String> m_ExtraAttributes;
};
/*----------------------------------------------------------------------
| PLT_StateVariableNameFinder
+---------------------------------------------------------------------*/
/**
The PLT_StateVariableNameFinder class returns the PLT_StateVariable instance
given a state variable name.
*/
class PLT_StateVariableNameFinder
{
public:
// methods
PLT_StateVariableNameFinder(const char* name) : m_Name(name) {}
virtual ~PLT_StateVariableNameFinder() {}
bool operator()(const PLT_StateVariable* const & state_variable) const {
return state_variable->GetName().Compare(m_Name, true) ? false : true;
}
private:
// members
NPT_String m_Name;
};
#endif /* _PLT_STATE_VARIABLE_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltUtilities.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltUtilities.h
index bdc1340173..d0f9657a41 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltUtilities.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Core/PltUtilities.h
@@ -1,765 +1,765 @@
/*****************************************************************
|
| Platinum - Utilities
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
#ifndef _PLT_UTILITIES_H_
#define _PLT_UTILITIES_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
/*----------------------------------------------------------------------
| PLT_XmlAttributeFinder
+---------------------------------------------------------------------*/
/**
The PLT_XmlAttributeFinder class is used to determine if an attribute
exists given an xml element node, an attribute name and namespace.
*/
class PLT_XmlAttributeFinder
{
public:
// if 'namespc' is NULL, we're looking for ANY namespace
// if 'namespc' is '\0', we're looking for NO namespace
// if 'namespc' is non-empty, look for that SPECIFIC namespace
PLT_XmlAttributeFinder(const NPT_XmlElementNode& element,
const char* name,
const char* namespc) :
m_Element(element), m_Name(name), m_Namespace(namespc) {}
bool operator()(const NPT_XmlAttribute* const & attribute) const {
if (attribute->GetName() == m_Name) {
if (m_Namespace) {
const NPT_String& prefix = attribute->GetPrefix();
if (m_Namespace[0] == '\0') {
// match if the attribute has NO namespace
return prefix.IsEmpty();
} else {
// match if the attribute has the SPECIFIC namespace
// we're looking for
const NPT_String* namespc = m_Element.GetNamespaceUri(prefix);
return namespc && *namespc == m_Namespace;
}
} else {
// ANY namespace will match
return true;
}
} else {
return false;
}
}
private:
const NPT_XmlElementNode& m_Element;
const char* m_Name;
const char* m_Namespace;
};
/*----------------------------------------------------------------------
| PLT_XmlHelper
+---------------------------------------------------------------------*/
/**
The PLT_XmlHelper class is a set of utility functions for manipulating
xml documents and DOM trees.
*/
class PLT_XmlHelper
{
public:
// static methods
static NPT_Result Parse(const NPT_String& xml, NPT_XmlElementNode*& tree) {
// reset tree
tree = NULL;
// parse body
NPT_XmlParser parser;
NPT_XmlNode* node;
NPT_Result result = parser.Parse(xml, node);
if (NPT_FAILED(result)) {
//NPT_LOG_FINEST_1("Failed to parse %s", xml.IsEmpty()?"(empty string)":xml.GetChars());
NPT_CHECK(result);
}
tree = node->AsElementNode();
if (!tree) {
delete node;
return NPT_FAILURE;
}
return NPT_SUCCESS;
}
static NPT_Result GetChildText(NPT_XmlElementNode* node,
const char* tag,
NPT_String& value,
const char* namespc = "",
NPT_Cardinal max_size = 1024) {
value = "";
if (!node) return NPT_FAILURE;
// special case "" means we look for the same namespace as the parent
if (namespc && namespc[0] == '\0') {
namespc = node->GetNamespace()?node->GetNamespace()->GetChars():NPT_XML_NO_NAMESPACE;
}
NPT_XmlElementNode* child = node->GetChild(tag, namespc);
if (!child) return NPT_FAILURE;
const NPT_String* text = child->GetText();
// DLNA 7.3.17
value = text?text->SubString(0, max_size):"";
return NPT_SUCCESS;
}
static NPT_Result RemoveAttribute(NPT_XmlElementNode* node,
const char* name,
const char* namespc = "") {
if (!node) return NPT_FAILURE;
// special case "" means we look for the same namespace as the parent
if (namespc && namespc[0] == '\0') {
namespc = node->GetNamespace()?node->GetNamespace()->GetChars():NPT_XML_NO_NAMESPACE;
}
NPT_List<NPT_XmlAttribute*>::Iterator attribute;
attribute = node->GetAttributes().Find(PLT_XmlAttributeFinder(*node, name, namespc));
if (!attribute) return NPT_FAILURE;
delete *attribute;
NPT_CHECK(node->GetAttributes().Erase(attribute));
return NPT_SUCCESS;
}
static NPT_Result GetAttribute(NPT_XmlElementNode* node,
const char* name,
NPT_XmlAttribute*& attr,
const char* namespc = "") {
attr = NULL;
if (!node) return NPT_FAILURE;
// special case "" means we look for the same namespace as the parent
if (namespc && namespc[0] == '\0') {
namespc = node->GetNamespace()?node->GetNamespace()->GetChars():NPT_XML_NO_NAMESPACE;
}
NPT_List<NPT_XmlAttribute*>::Iterator attribute;
attribute = node->GetAttributes().Find(PLT_XmlAttributeFinder(*node, name, namespc));
if (!attribute) {
//NPT_Debug("Failed to find attribute [%s]:%s", namespc, name);
return NPT_FAILURE;
}
attr = (*attribute);
return NPT_SUCCESS;
}
static NPT_Result GetAttribute(NPT_XmlElementNode* node,
const char* name,
NPT_String& value,
const char* namespc = "",
NPT_Cardinal max_size = 1024) {
value = "";
NPT_XmlAttribute* attribute = NULL;
NPT_Result result = GetAttribute(node, name, attribute, namespc);
if (NPT_FAILED(result)) return result;
if (!attribute) return NPT_FAILURE;
// DLNA 7.3.17 truncate to 1024 bytes
value = attribute->GetValue().SubString(0, max_size);
return NPT_SUCCESS;
}
static NPT_Result SetAttribute(NPT_XmlElementNode* node,
const char* name,
const char* value,
const char* namespc = "") {
NPT_XmlAttribute* attribute = NULL;
NPT_CHECK(GetAttribute(node, name, attribute, namespc));
if (!attribute) return NPT_FAILURE;
attribute->SetValue(value);
return NPT_SUCCESS;
}
static NPT_Result AddChildText(NPT_XmlElementNode* node,
const char* tag,
const char* text,
const char* prefix = NULL) {
if (!node) return NPT_FAILURE;
NPT_XmlElementNode* child = new NPT_XmlElementNode(prefix, tag);
child->AddText(text);
return node->AddChild(child);
}
static bool IsMatch(const NPT_XmlNode* const & node, const char* tag, const char* namespc_mapped) {
// if m_Namespace is NULL, we're looking for ANY namespace
// if m_Namespace is '\0', we're looking for NO namespace
// if m_Namespace is non-empty, look for that SPECIFIC namespace
const NPT_XmlElementNode* element = node->AsElementNode();
// is tag the same (case sensitive)?
if (element && element->GetTag() == tag) {
if (namespc_mapped) {
// look for a SPECIFIC namespace or NO namespace
const NPT_String* namespc = element->GetNamespace();
if (namespc) {
// the element has a namespace, match if it is equal to
// what we're looking for
return *namespc == namespc_mapped;
} else {
// the element does not have a namespace, match if we're
// looking for NO namespace
return namespc_mapped[0] == '\0';
}
} else {
// ANY namespace will match
return true;
}
}
return false;
}
static NPT_Result GetChildren(NPT_XmlElementNode* node,
NPT_Array<NPT_XmlElementNode*>& children,
const char* tag,
const char* namespc = "") {
if (!node) return NPT_FAILURE;
// special case "" means we look for the same namespace as the parent
if (namespc && namespc[0] == '\0') {
namespc = node->GetNamespace()?node->GetNamespace()->GetChars():NPT_XML_NO_NAMESPACE;
}
const char* namespc_mapped = (namespc==NULL)?"":(namespc[0]=='*' && namespc[1]=='\0')?NULL:namespc;
// get all children first
NPT_List<NPT_XmlNode*>& allchildren = node->GetChildren();
// iterate through children and add only elements with matching tag
NPT_List<NPT_XmlNode*>::Iterator child = allchildren.GetFirstItem();
while (child) {
if (IsMatch(*child, tag, namespc_mapped)) {
children.Add((*child)->AsElementNode());
}
++child;
}
return NPT_SUCCESS;
}
static NPT_XmlElementNode* GetChild(NPT_XmlElementNode* node,
const char* tag,
const char* namespc = "") {
if (!node) return NULL;
// special case "" means we look for the same namespace as the parent
if (namespc && namespc[0] == '\0') {
namespc = node->GetNamespace()?node->GetNamespace()->GetChars():NPT_XML_NO_NAMESPACE;
}
return node->GetChild(tag, namespc);
}
static NPT_Result GetChild(NPT_XmlElementNode* parent,
NPT_XmlElementNode*& child,
NPT_Ordinal n = 0) {
if (!parent) return NPT_FAILURE;
// reset child
child = NULL;
// get all children first
NPT_List<NPT_XmlNode*>::Iterator children = parent->GetChildren().GetFirstItem();
while (children) {
if ((*children)->AsElementNode() && n-- == 0) {
child = (*children)->AsElementNode();
return NPT_SUCCESS;
}
children++;
}
return NPT_FAILURE;
}
static NPT_Result Serialize(NPT_XmlNode& node, NPT_String& xml, bool add_header = true, NPT_Int8 indentation = 0) {
NPT_XmlWriter writer(indentation);
NPT_StringOutputStreamReference stream(new NPT_StringOutputStream(&xml));
NPT_CHECK(writer.Serialize(node, *stream, add_header));
return NPT_SUCCESS;
}
static NPT_String Serialize(NPT_XmlNode& node, bool add_header = true, NPT_Int8 indentation = 0) {
NPT_XmlWriter writer(indentation);
NPT_String xml;
NPT_StringOutputStreamReference stream(new NPT_StringOutputStream(&xml));
if (NPT_FAILED(writer.Serialize(node, *stream, add_header))) {
NPT_Debug("Failed to serialize xml node");
return "";
}
return xml;
}
private:
// members
};
/*----------------------------------------------------------------------
| NPT_StringFinder
+---------------------------------------------------------------------*/
/**
The NPT_StringFinder class is used to determine if a string is found
as part of a list of strings.
*/
class NPT_StringFinder
{
public:
// methods
explicit NPT_StringFinder(NPT_String& value, bool ignore_case = false) :
m_Value(value.GetChars()), m_IgnoreCase(ignore_case) {}
explicit NPT_StringFinder(const char* value, bool ignore_case = false) :
m_Value(value), m_IgnoreCase(ignore_case) {}
virtual ~NPT_StringFinder() {}
bool operator()(const NPT_String* const & value) const {
return value->Compare(m_Value, m_IgnoreCase) ? false : true;
}
bool operator()(const NPT_String& value) const {
return value.Compare(m_Value, m_IgnoreCase) ? false : true;
}
private:
// members
const char* m_Value;
bool m_IgnoreCase;
};
/*----------------------------------------------------------------------
| NPT_IpAddressFinder
+---------------------------------------------------------------------*/
/**
The NPT_IpAddressFinder class is used to determine if a IP Address is found
as part of a list of IP Addresses.
*/
class NPT_IpAddressFinder
{
public:
// methods
NPT_IpAddressFinder(NPT_IpAddress ip) : m_Value(ip) {}
virtual ~NPT_IpAddressFinder() {}
bool operator()(const NPT_IpAddress* const & value) const {
return *value == m_Value;
}
bool operator()(const NPT_IpAddress& value) const {
return value == m_Value;
}
private:
// members
NPT_IpAddress m_Value;
};
/*----------------------------------------------------------------------
| PLT_UPnPMessageHelper class
+---------------------------------------------------------------------*/
/**
The PLT_UPnPMessageHelper class is a set of utility functions for manipulating
specific UPnP HTTP headers.
*/
class PLT_UPnPMessageHelper
{
public:
// methods
static const NPT_String* GetST(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("ST");
}
static NPT_Result SetST(NPT_HttpMessage& message,
const char* st) {
return message.GetHeaders().SetHeader("ST", st);
}
static const NPT_String* GetNT(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("NT");
}
static NPT_Result SetNT(NPT_HttpMessage& message,
const char* nt) {
return message.GetHeaders().SetHeader("NT", nt);
}
static const NPT_String* GetNTS(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("NTS");
}
static NPT_Result SetNTS(NPT_HttpMessage& message,
const char* nts) {
return message.GetHeaders().SetHeader("NTS", nts);
}
static const NPT_String* GetMAN(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("MAN");
}
static NPT_Result SetMAN(NPT_HttpMessage& message,
const char* man) {
return message.GetHeaders().SetHeader("MAN", man);
}
static const NPT_String* GetLocation(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("Location");
}
static NPT_Result SetLocation(NPT_HttpMessage& message,
const char* location) {
return message.GetHeaders().SetHeader("Location", location);
}
static const NPT_String* GetServer(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER);
}
static NPT_Result SetServer(NPT_HttpMessage& message,
const char* server,
bool replace = true) {
return message.GetHeaders().SetHeader(
NPT_HTTP_HEADER_SERVER,
server,
replace);
}
static const NPT_String* GetUSN(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("USN");
}
static NPT_Result SetUSN(NPT_HttpMessage& message,
const char* usn) {
return message.GetHeaders().SetHeader("USN", usn);
}
static const NPT_String* GetCallbacks(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("CALLBACK");
}
static NPT_Result SetCallbacks(NPT_HttpMessage& message, const char* callbacks) {
return message.GetHeaders().SetHeader("CALLBACK", callbacks);
}
static const NPT_String* GetSID(const NPT_HttpMessage& message) {
return message.GetHeaders().GetHeaderValue("SID");
}
static NPT_Result SetSID(NPT_HttpMessage& message,
const char* sid) {
return message.GetHeaders().SetHeader("SID", sid);
}
static NPT_Result GetLeaseTime(const NPT_HttpMessage& message, NPT_TimeInterval& lease) {
const NPT_String* cc =
message.GetHeaders().GetHeaderValue("Cache-Control");
NPT_CHECK_POINTER(cc);
return ExtractLeaseTime(*cc, lease);
}
static NPT_Result SetLeaseTime(NPT_HttpMessage& message, const NPT_TimeInterval& lease) {
return message.GetHeaders().SetHeader("Cache-Control",
"max-age="+NPT_String::FromInteger(lease.ToSeconds()));
}
static NPT_Result GetBootId(const NPT_HttpMessage& message, NPT_UInt32& bootId) {
bootId = 0;
const NPT_String* bid = message.GetHeaders().GetHeaderValue("BOOTID.UPNP.ORG");
NPT_CHECK_POINTER(bid);
return NPT_ParseInteger32(*bid, bootId, false);
}
static NPT_Result SetBootId(NPT_HttpMessage& message, const NPT_UInt32& bootId) {
return message.GetHeaders().SetHeader("BOOTID.UPNP.ORG",
NPT_String::FromInteger(bootId));
}
static NPT_Result GetNextBootId(const NPT_HttpMessage& message, NPT_UInt32& nextBootId) {
nextBootId = 0;
const NPT_String* nbid = message.GetHeaders().GetHeaderValue("NEXTBOOTID.UPNP.ORG");
NPT_CHECK_POINTER(nbid);
return NPT_ParseInteger32(*nbid, nextBootId, false);
}
static NPT_Result SetNextBootId(NPT_HttpMessage& message, const NPT_UInt32& nextBootId) {
return message.GetHeaders().SetHeader("NEXTBOOTID.UPNP.ORG",
NPT_String::FromInteger(nextBootId));
}
static NPT_Result GetConfigId(const NPT_HttpMessage& message, NPT_UInt32& configId) {
configId = 0;
const NPT_String* cid = message.GetHeaders().GetHeaderValue("CONFIGID.UPNP.ORG");
NPT_CHECK_POINTER(cid);
return NPT_ParseInteger32(*cid, configId, false);
}
static NPT_Result SetConfigId(NPT_HttpMessage& message, const NPT_UInt32& configId) {
return message.GetHeaders().SetHeader("CONFIGID.UPNP.ORG", NPT_String::FromInteger(configId));
}
static NPT_Result GetTimeOut(const NPT_HttpMessage& message, NPT_Int32& seconds) {
seconds = 0;
const NPT_String* timeout =
message.GetHeaders().GetHeaderValue("TIMEOUT");
NPT_CHECK_POINTER(timeout);
return ExtractTimeOut(*timeout, seconds);
}
static NPT_Result SetTimeOut(NPT_HttpMessage& message, const NPT_Int32 seconds) {
if (seconds >= 0) {
return message.GetHeaders().SetHeader("TIMEOUT", "Second-"+NPT_String::FromInteger(seconds));
} else {
return message.GetHeaders().SetHeader("TIMEOUT", "Second-infinite");
}
}
static NPT_Result SetDate(NPT_HttpMessage& message) {
NPT_TimeStamp now;
NPT_System::GetCurrentTimeStamp(now);
NPT_DateTime date(now);
return message.GetHeaders().SetHeader("Date", date.ToString(NPT_DateTime::FORMAT_RFC_1123));
}
static NPT_Result GetIfModifiedSince(const NPT_HttpMessage& message, NPT_DateTime& date) {
const NPT_String* value = message.GetHeaders().GetHeaderValue("If-Modified-Since");
if (!value) return NPT_FAILURE;
// Try RFC 1123, RFC 1036, then ANSI
if (NPT_SUCCEEDED(date.FromString(*value, NPT_DateTime::FORMAT_RFC_1123)))
return NPT_SUCCESS;
if (NPT_SUCCEEDED(date.FromString(*value, NPT_DateTime::FORMAT_RFC_1036)))
return NPT_SUCCESS;
return date.FromString(*value, NPT_DateTime::FORMAT_ANSI);
}
static NPT_Result SetIfModifiedSince(NPT_HttpMessage& message, const NPT_DateTime& date) {
return message.GetHeaders().SetHeader("If-Modified-Since",
date.ToString(NPT_DateTime::FORMAT_RFC_1123));
}
static NPT_Result GetMX(const NPT_HttpMessage& message, NPT_UInt32& value) {
value = 0;
const NPT_String* mx =
message.GetHeaders().GetHeaderValue("MX");
NPT_CHECK_POINTER(mx);
return NPT_ParseInteger32(*mx, value, false); // no relax to be UPnP compliant
}
static NPT_Result SetMX(NPT_HttpMessage& message, const NPT_UInt32 mx) {
return message.GetHeaders().SetHeader("MX",
NPT_String::FromInteger(mx));
}
static NPT_Result GetSeq(const NPT_HttpMessage& message, NPT_UInt32& value) {
value = 0;
const NPT_String* seq =
message.GetHeaders().GetHeaderValue("SEQ");
NPT_CHECK_POINTER(seq);
return NPT_ParseInteger32(*seq, value);
}
static NPT_Result SetSeq(NPT_HttpMessage& message, const NPT_UInt32 seq) {
return message.GetHeaders().SetHeader("SEQ",
NPT_String::FromInteger(seq));
}
static const char* GenerateUUID(int count, NPT_String& uuid) {
uuid = "";
for (int i=0;i<(count<100?count:100);i++) {
int random = NPT_System::GetRandomInteger();
uuid += (char)((random % 25) + 66);
}
return uuid;
}
static const char* GenerateSerialNumber(NPT_String& sn, int count = 40) {
sn = "{";
for (int i=0;i<count;i++) {
char nibble = (char)(NPT_System::GetRandomInteger() % 16);
sn += (nibble < 10) ? ('0' + nibble) : ('a' + (nibble-10));
}
sn += "}";
return sn;
}
static const char* GenerateGUID(NPT_String& guid) {
guid = "";
for (int i=0;i<32;i++) {
char nibble = (char)(NPT_System::GetRandomInteger() % 16);
guid += (nibble < 10) ? ('0' + nibble) : ('a' + (nibble-10));
if (i == 7 || i == 11 || i == 15 || i == 19) {
guid += '-';
}
}
return guid;
}
static NPT_Result ExtractLeaseTime(const NPT_String& cache_control, NPT_TimeInterval& lease) {
NPT_Int32 value;
if (cache_control.StartsWith("max-age=", true) &&
NPT_SUCCEEDED(NPT_ParseInteger32(cache_control.GetChars()+8, value))) {
lease.SetSeconds(value);
return NPT_SUCCESS;
}
return NPT_FAILURE;
}
static NPT_Result ExtractTimeOut(const char* timeout, NPT_Int32& len) {
NPT_String temp = timeout;
if (temp.CompareN("Second-", 7, true)) {
return NPT_ERROR_INVALID_FORMAT;
}
if (temp.Compare("Second-infinite", true) == 0) {
len = NPT_TIMEOUT_INFINITE;
return NPT_SUCCESS;
}
return temp.SubString(7).ToInteger(len);
}
static NPT_Result GetIPAddresses(NPT_List<NPT_IpAddress>& ips, bool with_localhost = false) {
NPT_List<NPT_NetworkInterface*> if_list;
NPT_CHECK(GetNetworkInterfaces(if_list, with_localhost));
NPT_List<NPT_NetworkInterface*>::Iterator iface = if_list.GetFirstItem();
while (iface) {
NPT_IpAddress ip = (*(*iface)->GetAddresses().GetFirstItem()).GetPrimaryAddress();
if (ip.ToString().Compare("0.0.0.0") &&
(with_localhost || ip.ToString().Compare("127.0.0.1"))) {
ips.Add(ip);
}
++iface;
}
if (with_localhost && !ips.Find(NPT_IpAddressFinder(NPT_IpAddress(127, 0, 0, 1)))) {
NPT_IpAddress localhost;
localhost.Parse("127.0.0.1");
ips.Add(localhost);
}
if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
return NPT_SUCCESS;
}
static NPT_Result GetNetworkInterfaces(NPT_List<NPT_NetworkInterface*>& if_list,
bool with_localhost = false) {
NPT_CHECK(_GetNetworkInterfaces(if_list, with_localhost, false));
// if no valid interfaces or if requested, add localhost interface
if (if_list.GetItemCount() == 0) {
NPT_CHECK(_GetNetworkInterfaces(if_list, true, true));
}
return NPT_SUCCESS;
}
static NPT_Result GetMACAddresses(NPT_List<NPT_String>& addresses) {
NPT_List<NPT_NetworkInterface*> if_list;
NPT_CHECK(GetNetworkInterfaces(if_list));
NPT_List<NPT_NetworkInterface*>::Iterator iface = if_list.GetFirstItem();
while (iface) {
NPT_String ip = (*(*iface)->GetAddresses().GetFirstItem()).GetPrimaryAddress().ToString();
if (ip.Compare("0.0.0.0") && ip.Compare("127.0.0.1")) {
addresses.Add((*iface)->GetMacAddress().ToString());
}
++iface;
}
if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
return NPT_SUCCESS;
}
static bool IsLocalNetworkAddress(const NPT_IpAddress& address) {
if (address.ToString() == "127.0.0.1") return true;
NPT_List<NPT_NetworkInterface*> if_list;
NPT_NetworkInterface::GetNetworkInterfaces(if_list);
NPT_List<NPT_NetworkInterface*>::Iterator iface = if_list.GetFirstItem();
while (iface) {
if((*iface)->IsAddressInNetwork(address)) return true;
++iface;
}
if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
return false;
}
private:
static NPT_Result _GetNetworkInterfaces(NPT_List<NPT_NetworkInterface*>& if_list,
bool include_localhost = false,
bool only_localhost = false) {
NPT_List<NPT_NetworkInterface*> _if_list;
NPT_CHECK(NPT_NetworkInterface::GetNetworkInterfaces(_if_list));
NPT_NetworkInterface* iface;
while (NPT_SUCCEEDED(_if_list.PopHead(iface))) {
// only interested in non PTP & multicast capable interfaces
if ((iface->GetAddresses().GetItemCount() == 0) ||
!(iface->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST) ||
(iface->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_POINT_TO_POINT)) {
delete iface;
continue;
}
NPT_String ip = iface->GetAddresses().GetFirstItem()->GetPrimaryAddress().ToString();
if (iface->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_LOOPBACK) {
if (include_localhost || only_localhost) {
if_list.Add(iface);
continue;
}
} else if (ip.Compare("0.0.0.0") && !only_localhost) {
if_list.Add(iface);
continue;
}
delete iface;
}
- // cleanup any remaining items in list if we breaked early
+ // cleanup any remaining items in list if we broke early
_if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
return NPT_SUCCESS;
}
};
#endif // _PLT_UTILITIES_H_
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaBrowser.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaBrowser.h
index 2484dcc2b7..1f5983daa7 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaBrowser.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaBrowser.h
@@ -1,184 +1,184 @@
/*****************************************************************
|
| Platinum - AV Media Browser (Media Server Control Point)
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP AV Media Controller implementation.
*/
#ifndef _PLT_MEDIA_BROWSER_H_
#define _PLT_MEDIA_BROWSER_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "PltCtrlPoint.h"
#include "PltMediaItem.h"
/*----------------------------------------------------------------------
| PLT_BrowseInfo
+---------------------------------------------------------------------*/
/**
The PLT_BrowseInfo struct is used to marshall Browse or Search action
response results across different threads of execution.
*/
typedef struct {
NPT_String object_id;
PLT_MediaObjectListReference items;
NPT_UInt32 si;
NPT_UInt32 nr;
NPT_UInt32 tm;
NPT_UInt32 uid;
} PLT_BrowseInfo;
/*----------------------------------------------------------------------
| PLT_MediaBrowserDelegate
+---------------------------------------------------------------------*/
/**
The PLT_MediaBrowserDelegate class is an interface for receiving PLT_MediaBrowser
events or action responses.
*/
class PLT_MediaBrowserDelegate
{
public:
virtual ~PLT_MediaBrowserDelegate() {}
virtual bool OnMSAdded(PLT_DeviceDataReference& /* device */) { return true; }
virtual void OnMSRemoved(PLT_DeviceDataReference& /* device */) {}
virtual void OnMSStateVariablesChanged(
PLT_Service* /*service*/,
NPT_List<PLT_StateVariable*>* /*vars*/) {}
// ContentDirectory
virtual void OnBrowseResult(
NPT_Result /*res*/,
PLT_DeviceDataReference& /*device*/,
PLT_BrowseInfo* /*info*/,
void* /*userdata*/) {}
virtual void OnSearchResult(
NPT_Result /*res*/,
PLT_DeviceDataReference& /*device*/,
PLT_BrowseInfo* /*info*/,
void* /*userdata*/) {}
virtual void OnGetSearchCapabilitiesResult(
NPT_Result /*res*/,
PLT_DeviceDataReference& /*device*/,
NPT_String /*searchCapabilities*/,
void* /*userdata*/) {}
virtual void OnGetSortCapabilitiesResult(
NPT_Result /*res*/,
PLT_DeviceDataReference& /*device*/,
NPT_String /*sortCapabilities*/,
void* /*userdata*/) {}
};
/*----------------------------------------------------------------------
| PLT_MediaBrowser
+---------------------------------------------------------------------*/
/**
The PLT_MediaBrowser class implements a UPnP AV Media Server control point.
*/
class PLT_MediaBrowser : public PLT_CtrlPointListener
{
public:
PLT_MediaBrowser(PLT_CtrlPointReference& ctrl_point,
PLT_MediaBrowserDelegate* delegate = NULL);
~PLT_MediaBrowser() override;
// ContentDirectory service
virtual NPT_Result Browse(PLT_DeviceDataReference& device,
const char* object_id,
NPT_UInt32 start_index,
NPT_UInt32 count = 30, // DLNA recommendations
bool browse_metadata = false,
- const char* filter = "dc:date,upnp:genre,res,res@duration,res@size,upnp:albumArtURI,upnp:originalTrackNumber,upnp:album,upnp:artist,upnp:author", // explicitely specify res otherwise WMP won't return a URL!
+ const char* filter = "dc:date,upnp:genre,res,res@duration,res@size,upnp:albumArtURI,upnp:originalTrackNumber,upnp:album,upnp:artist,upnp:author", // explicitly specify res otherwise WMP won't return a URL!
const char* sort_criteria = "",
void* userdata = NULL);
virtual NPT_Result Search(PLT_DeviceDataReference& device,
const char* container_id,
const char* search_criteria,
NPT_UInt32 start_index,
NPT_UInt32 count = 30, // DLNA recommendations
- const char* filter = "dc:date,upnp:genre,res,res@duration,res@size,upnp:albumArtURI,upnp:originalTrackNumber,upnp:album,upnp:artist,upnp:author", // explicitely specify res otherwise WMP won't return a URL!
+ const char* filter = "dc:date,upnp:genre,res,res@duration,res@size,upnp:albumArtURI,upnp:originalTrackNumber,upnp:album,upnp:artist,upnp:author", // explicitly specify res otherwise WMP won't return a URL!
void* userdata = NULL);
virtual NPT_Result GetSearchCapabilities(PLT_DeviceDataReference& device,
void* userdata = NULL);
virtual NPT_Result GetSortCapabilities(PLT_DeviceDataReference& device,
void* userdata = NULL);
// methods
virtual const NPT_Lock<PLT_DeviceDataReferenceList>& GetMediaServers() { return m_MediaServers; }
virtual NPT_Result FindServer(const char* uuid, PLT_DeviceDataReference& device);
virtual void SetDelegate(PLT_MediaBrowserDelegate* delegate) { m_Delegate = delegate; }
protected:
// PLT_CtrlPointListener methods
NPT_Result OnDeviceAdded(PLT_DeviceDataReference& device) override;
NPT_Result OnDeviceRemoved(PLT_DeviceDataReference& device) override;
NPT_Result OnActionResponse(NPT_Result res, PLT_ActionReference& action, void* userdata) override;
NPT_Result OnEventNotify(PLT_Service* service, NPT_List<PLT_StateVariable*>* vars) override;
// ContentDirectory service responses
virtual NPT_Result OnBrowseResponse(NPT_Result res,
PLT_DeviceDataReference& device,
PLT_ActionReference& action,
void* userdata);
virtual NPT_Result OnSearchResponse(NPT_Result res,
PLT_DeviceDataReference& device,
PLT_ActionReference& action,
void* userdata);
virtual NPT_Result OnGetSearchCapabilitiesResponse(NPT_Result res,
PLT_DeviceDataReference& device,
PLT_ActionReference& action,
void* userdata);
virtual NPT_Result OnGetSortCapabilitiesResponse(NPT_Result res,
PLT_DeviceDataReference& device,
PLT_ActionReference& action,
void* userdata);
protected:
PLT_CtrlPointReference m_CtrlPoint;
PLT_MediaBrowserDelegate* m_Delegate;
NPT_Lock<PLT_DeviceDataReferenceList> m_MediaServers;
};
#endif /* _PLT_MEDIA_BROWSER_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.cpp b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.cpp
index fac16902f7..e59dd7af1b 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.cpp
@@ -1,1128 +1,1128 @@
/*****************************************************************
|
| Platinum - AV Media Item
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "PltMediaItem.h"
#include "PltMediaServer.h"
#include "PltDidl.h"
#include "PltUtilities.h"
#include "PltService.h"
#include "PltMimeType.h"
NPT_SET_LOCAL_LOGGER("platinum.media.server.item")
/*----------------------------------------------------------------------
| globals
+---------------------------------------------------------------------*/
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(PLT_MediaObject)
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(PLT_MediaItem)
NPT_DEFINE_DYNAMIC_CAST_ANCHOR(PLT_MediaContainer)
/*----------------------------------------------------------------------
| PLT_PersonRoles::AddPerson
+---------------------------------------------------------------------*/
NPT_Result
PLT_PersonRoles::Add(const NPT_String& name, const NPT_String& role /* = "" */)
{
PLT_PersonRole person;
person.name = name;
person.role = role;
return NPT_List<PLT_PersonRole>::Add(person);
}
/*----------------------------------------------------------------------
| PLT_PersonRoles::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_PersonRoles::ToDidl(NPT_String& didl, const NPT_String& tag)
{
NPT_String tmp;
for (NPT_List<PLT_PersonRole>::Iterator it =
NPT_List<PLT_PersonRole>::GetFirstItem(); it; it++) {
// if there's an empty artist, allow it only if there's nothing else
if (it->name.IsEmpty() && m_ItemCount>1 && !tmp.IsEmpty()) continue;
tmp += "<upnp:" + tag;
if (!it->role.IsEmpty()) {
tmp += " role=\"";
PLT_Didl::AppendXmlEscape(tmp, it->role);
tmp += "\"";
}
tmp += ">";
PLT_Didl::AppendXmlEscape(tmp, it->name);
tmp += "</upnp:" + tag + ">";
}
didl += tmp;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_PersonRoles::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_PersonRoles::FromDidl(const NPT_Array<NPT_XmlElementNode*>& nodes)
{
for (NPT_Cardinal i=0; i<nodes.GetItemCount(); i++) {
PLT_PersonRole person;
const NPT_String* name = nodes[i]->GetText();
const NPT_String* role = nodes[i]->GetAttribute("role");
// DLNA 7.3.17
if (name) person.name = name->SubString(0, 1024);
if (role) person.role = role->SubString(0, 1024);
NPT_CHECK(NPT_List<PLT_PersonRole>::Add(person));
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Artworks::Add
+---------------------------------------------------------------------*/
NPT_Result
PLT_Artworks::Add(const NPT_String& type, const NPT_String& url)
{
PLT_Artwork artwork;
artwork.type = type;
artwork.url = url;
return NPT_List<PLT_Artwork>::Add(artwork);
}
/*----------------------------------------------------------------------
| PLT_Artworks::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_Artworks::ToDidl(NPT_String& didl, const NPT_String& tag)
{
NPT_String tmp;
for (NPT_List<PLT_Artwork>::Iterator it =
NPT_List<PLT_Artwork>::GetFirstItem(); it; it++) {
if (it->type.IsEmpty()) continue;
tmp += "<xbmc:" + tag;
if (!it->type.IsEmpty()) {
tmp += " type=\"";
PLT_Didl::AppendXmlEscape(tmp, it->type);
tmp += "\"";
}
tmp += ">";
PLT_Didl::AppendXmlEscape(tmp, it->url);
tmp += "</xbmc:" + tag + ">";
}
didl += tmp;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_Artworks::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_Artworks::FromDidl(const NPT_Array<NPT_XmlElementNode*>& nodes)
{
for (NPT_Cardinal i=0; i<nodes.GetItemCount(); i++) {
PLT_Artwork artwork;
const NPT_String* url = nodes[i]->GetText();
const NPT_String* type = nodes[i]->GetAttribute("type");
if (type) artwork.type = *type;
if (url) artwork.url = *url;
NPT_CHECK(NPT_List<PLT_Artwork>::Add(artwork));
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaItemResource::PLT_MediaItemResource
+---------------------------------------------------------------------*/
PLT_MediaItemResource::PLT_MediaItemResource()
{
m_Uri = "";
m_ProtocolInfo = PLT_ProtocolInfo();
m_Duration = (NPT_UInt32)-1;
m_Size = (NPT_LargeSize)-1;
m_Protection = "";
m_Bitrate = (NPT_UInt32)-1;
m_BitsPerSample = (NPT_UInt32)-1;
m_SampleFrequency = (NPT_UInt32)-1;
m_NbAudioChannels = (NPT_UInt32)-1;
m_Resolution = "";
m_ColorDepth = (NPT_UInt32)-1;
}
/*----------------------------------------------------------------------
| PLT_MediaObject::GetUPnPClass
+---------------------------------------------------------------------*/
const char*
PLT_MediaObject::GetUPnPClass(const char* filename,
const PLT_HttpRequestContext* context /* = NULL */)
{
NPT_COMPILER_UNUSED(context);
const char* ret = NULL;
NPT_String mime_type = PLT_MimeType::GetMimeType(filename, context);
if (mime_type.StartsWith("audio")) {
ret = "object.item.audioItem.musicTrack";
} else if (mime_type.StartsWith("video")) {
ret = "object.item.videoItem"; //Note: 360 wants "object.item.videoItem" and not "object.item.videoItem.Movie"
} else if (mime_type.StartsWith("image")) {
ret = "object.item.imageItem.photo";
} else {
ret = "object.item";
}
return ret;
}
/*----------------------------------------------------------------------
| PLT_MediaObject::Reset
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaObject::Reset()
{
m_ObjectClass.type = "";
m_ObjectClass.friendly_name = "";
m_ObjectID = "";
m_ParentID = "";
m_Title = "";
m_Creator = "";
m_Date = "";
m_Restricted = true;
m_People.actors.Clear();
m_People.artists.Clear();
m_People.authors.Clear();
m_People.directors.Clear();
m_People.publisher.Clear();
m_Affiliation.album = "";
m_Affiliation.genres.Clear();
m_Affiliation.playlist = "";
m_Description.description = "";
m_Description.long_description = "";
m_Description.icon_uri = "";
m_ExtraInfo.album_arts.Clear();
m_ExtraInfo.artist_discography_uri = "";
m_MiscInfo.original_track_number = 0;
m_MiscInfo.last_position = 0;
m_MiscInfo.last_time = "";
m_MiscInfo.play_count = -1;
m_MiscInfo.dvdregioncode = 0;
m_MiscInfo.toc = "";
m_MiscInfo.user_annotation = "";
m_Recorded.program_title = "";
m_Recorded.series_title = "";
m_Recorded.episode_number = 0;
m_Recorded.episode_count = 0;
m_Recorded.episode_season = 0;
m_Resources.Clear();
m_XbmcInfo.last_playerstate = "";
m_XbmcInfo.date_added = "";
m_XbmcInfo.rating = 0.0f;
m_XbmcInfo.votes = 0;
m_XbmcInfo.artwork.Clear();
m_XbmcInfo.unique_identifier = "";
m_XbmcInfo.countries.Clear();
m_XbmcInfo.user_rating = 0;
m_Didl = "";
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaObject::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaObject::ToDidl(const NPT_String& filter, NPT_String& didl)
{
return ToDidl(PLT_Didl::ConvertFilterToMask(filter), didl);
}
/*----------------------------------------------------------------------
| PLT_MediaObject::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaObject::ToDidl(NPT_UInt64 mask, NPT_String& didl)
{
// title is required
didl += "<dc:title>";
PLT_Didl::AppendXmlEscape(didl, m_Title);
didl += "</dc:title>";
// creator
if (mask & PLT_FILTER_MASK_CREATOR) {
didl += "<dc:creator>";
if (m_Creator.IsEmpty()) m_Creator = "Unknown";
PLT_Didl::AppendXmlEscape(didl, m_Creator);
didl += "</dc:creator>";
}
// date
if ((mask & PLT_FILTER_MASK_DATE) && !m_Date.IsEmpty()) {
didl += "<dc:date>";
PLT_Didl::AppendXmlEscape(didl, m_Date);
didl += "</dc:date>";
}
// artist
if (mask & PLT_FILTER_MASK_ARTIST) {
// force an empty artist just in case (not DLNA Compliant though)
//if (m_People.artists.GetItemCount() == 0) m_People.artists.Add("");
m_People.artists.ToDidl(didl, "artist");
}
// actor
if (mask & PLT_FILTER_MASK_ACTOR) {
m_People.actors.ToDidl(didl, "actor");
}
// actor
if (mask & PLT_FILTER_MASK_AUTHOR) {
m_People.authors.ToDidl(didl, "author");
}
// director
if (mask & PLT_FILTER_MASK_DIRECTOR) {
m_People.directors.ToDidl(didl, "director");
}
// publisher
if (mask & PLT_FILTER_MASK_PUBLISHER) {
// Add unknown publisher
if (m_People.publisher.GetItemCount() == 0)
m_People.publisher.Add("Unknown");
for (NPT_List<NPT_String>::Iterator it =
m_People.publisher.GetFirstItem(); it; ++it) {
didl += "<dc:publisher>";
PLT_Didl::AppendXmlEscape(didl, (*it));
didl += "</dc:publisher>";
}
}
// album
if ((mask & PLT_FILTER_MASK_ALBUM) && !m_Affiliation.album.IsEmpty()) {
didl += "<upnp:album>";
PLT_Didl::AppendXmlEscape(didl, m_Affiliation.album);
didl += "</upnp:album>";
}
// genre
if (mask & PLT_FILTER_MASK_GENRE) {
// Add unknown genre
if (m_Affiliation.genres.GetItemCount() == 0)
m_Affiliation.genres.Add("Unknown");
for (NPT_List<NPT_String>::Iterator it =
m_Affiliation.genres.GetFirstItem(); it; ++it) {
didl += "<upnp:genre>";
PLT_Didl::AppendXmlEscape(didl, (*it));
didl += "</upnp:genre>";
}
}
// album art URI
if ((mask & PLT_FILTER_MASK_ALBUMARTURI) && m_ExtraInfo.album_arts.GetItemCount()) {
for (NPT_List<PLT_AlbumArtInfo>::Iterator iter = m_ExtraInfo.album_arts.GetFirstItem();
iter;
iter++) {
didl += "<upnp:albumArtURI";
if (!(*iter).dlna_profile.IsEmpty()) {
didl += " dlna:profileID=\"";
PLT_Didl::AppendXmlEscape(didl, (*iter).dlna_profile);
didl += "\"";
}
didl += ">";
PLT_Didl::AppendXmlEscape(didl, (*iter).uri);
didl += "</upnp:albumArtURI>";
}
}
// description
if ((mask & PLT_FILTER_MASK_DESCRIPTION) && !m_Description.description.IsEmpty()) {
didl += "<dc:description>";
PLT_Didl::AppendXmlEscape(didl, m_Description.description);
didl += "</dc:description>";
}
// long description
if ((mask & PLT_FILTER_MASK_LONGDESCRIPTION) && !m_Description.long_description.IsEmpty()) {
didl += "<upnp:longDescription>";
PLT_Didl::AppendXmlEscape(didl, m_Description.long_description);
didl += "</upnp:longDescription>";
}
// icon
if ((mask & PLT_FILTER_MASK_ICON) && !m_Description.icon_uri.IsEmpty()) {
didl += "<upnp:icon>";
PLT_Didl::AppendXmlEscape(didl, m_Description.icon_uri);
didl += "</upnp:icon>";
}
// rating
if ((mask & PLT_FILTER_MASK_RATING) && !m_Description.rating.IsEmpty()) {
didl += "<upnp:rating>";
PLT_Didl::AppendXmlEscape(didl, m_Description.rating);
didl += "</upnp:rating>";
}
// original track number
if ((mask & PLT_FILTER_MASK_ORIGINALTRACK) && m_MiscInfo.original_track_number > 0) {
didl += "<upnp:originalTrackNumber>";
didl += NPT_String::FromInteger(m_MiscInfo.original_track_number);
didl += "</upnp:originalTrackNumber>";
}
// last playback position
if (mask & PLT_FILTER_MASK_LASTPOSITION && m_MiscInfo.last_position > 0) {
didl += "<upnp:lastPlaybackPosition>";
didl += NPT_String::FromInteger(m_MiscInfo.last_position);
didl += "</upnp:lastPlaybackPosition>";
}
// last playback datetime
if (mask & PLT_FILTER_MASK_LASTPLAYBACK && !m_MiscInfo.last_time.IsEmpty()) {
didl += "<upnp:lastPlaybackTime>";
PLT_Didl::AppendXmlEscape(didl, m_MiscInfo.last_time);
didl += "</upnp:lastPlaybackTime>";
}
// playcount
if (mask & PLT_FILTER_MASK_PLAYCOUNT && m_MiscInfo.play_count > -1) {
didl += "<upnp:playbackCount>";
didl += NPT_String::FromInteger(m_MiscInfo.play_count);
didl += "</upnp:playbackCount>";
}
// program title
if (mask & PLT_FILTER_MASK_PROGRAMTITLE && !m_Recorded.program_title.IsEmpty()) {
didl += "<upnp:programTitle>";
PLT_Didl::AppendXmlEscape(didl, m_Recorded.program_title);
didl += "</upnp:programTitle>";
}
// series title
if ((mask & PLT_FILTER_MASK_SERIESTITLE) && !m_Recorded.series_title.IsEmpty()) {
didl += "<upnp:seriesTitle>";
PLT_Didl::AppendXmlEscape(didl, m_Recorded.series_title);
didl += "</upnp:seriesTitle>";
}
// episode number
if ((mask & PLT_FILTER_MASK_EPISODE) && m_Recorded.episode_number > 0) {
didl += "<upnp:episodeNumber>";
didl += NPT_String::FromInteger(m_Recorded.episode_number);
didl += "</upnp:episodeNumber>";
}
// episode count
if ((mask & PLT_FILTER_MASK_EPISODE_COUNT) && m_Recorded.episode_count > 0) {
didl += "<upnp:episodeCount>";
didl += NPT_String::FromInteger(m_Recorded.episode_count);
didl += "</upnp:episodeCount>";
}
// episode count
if ((mask & PLT_FILTER_MASK_EPISODE_SEASON)) {
didl += "<upnp:episodeSeason>";
didl += NPT_String::FromInteger(m_Recorded.episode_season);
didl += "</upnp:episodeSeason>";
}
if ((mask & PLT_FILTER_MASK_TOC) && !m_MiscInfo.toc.IsEmpty()) {
didl += "<upnp:toc>";
PLT_Didl::AppendXmlEscape(didl, m_MiscInfo.toc);
didl += "</upnp:toc>";
}
// resource
if (mask & PLT_FILTER_MASK_RES) {
for (NPT_Cardinal i=0; i<m_Resources.GetItemCount(); i++) {
didl += "<res";
if ((mask & PLT_FILTER_MASK_RES_DURATION) && m_Resources[i].m_Duration != (NPT_UInt32)-1) {
didl += " duration=\"";
didl += PLT_Didl::FormatTimeStamp(m_Resources[i].m_Duration);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_SIZE) && m_Resources[i].m_Size != (NPT_LargeSize)-1) {
didl += " size=\"";
didl += NPT_String::FromIntegerU(m_Resources[i].m_Size);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_PROTECTION) && !m_Resources[i].m_Protection.IsEmpty()) {
didl += " protection=\"";
PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_Protection);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_RESOLUTION) && !m_Resources[i].m_Resolution.IsEmpty()) {
didl += " resolution=\"";
PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_Resolution);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_BITRATE) && m_Resources[i].m_Bitrate != (NPT_Size)-1) {
didl += " bitrate=\"";
didl += NPT_String::FromIntegerU(m_Resources[i].m_Bitrate);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_BITSPERSAMPLE) && m_Resources[i].m_BitsPerSample != (NPT_Size)-1) {
didl += " bitsPerSample=\"";
didl += NPT_String::FromIntegerU(m_Resources[i].m_BitsPerSample);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_SAMPLEFREQUENCY) && m_Resources[i].m_SampleFrequency != (NPT_Size)-1) {
didl += " sampleFrequency=\"";
didl += NPT_String::FromIntegerU(m_Resources[i].m_SampleFrequency);
didl += "\"";
}
if ((mask & PLT_FILTER_MASK_RES_NRAUDIOCHANNELS) && m_Resources[i].m_NbAudioChannels != (NPT_Size)-1) {
didl += " nrAudioChannels=\"";
didl += NPT_String::FromIntegerU(m_Resources[i].m_NbAudioChannels);
didl += "\"";
}
didl += " protocolInfo=\"";
PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_ProtocolInfo.ToString());
didl += "\"";
/* adding custom data */
NPT_List<NPT_Map<NPT_String, NPT_String>::Entry*>::Iterator entry = m_Resources[i].m_CustomData.GetEntries().GetFirstItem();
while (entry)
{
didl += " ";
PLT_Didl::AppendXmlEscape(didl, (*entry)->GetKey());
didl += "=\"";
PLT_Didl::AppendXmlEscape(didl, (*entry)->GetValue());
didl += "\"";
entry++;
}
didl += ">";
PLT_Didl::AppendXmlEscape(didl, m_Resources[i].m_Uri);
didl += "</res>";
}
}
//sec resources related
for (NPT_Cardinal i = 0; i < m_SecResources.GetItemCount(); i++) {
didl += "<sec:";
PLT_Didl::AppendXmlEscape(didl, m_SecResources[i].name);
NPT_List<NPT_Map<NPT_String, NPT_String>::Entry*>::Iterator entry = m_SecResources[i].attributes.GetEntries().GetFirstItem();
while (entry)
{
didl += " sec:";
PLT_Didl::AppendXmlEscape(didl, (*entry)->GetKey());
didl += "=\"";
PLT_Didl::AppendXmlEscape(didl, (*entry)->GetValue());
didl += "\"";
entry++;
}
didl += ">";
PLT_Didl::AppendXmlEscape(didl, m_SecResources[i].value);
didl += "</sec:";
PLT_Didl::AppendXmlEscape(didl, m_SecResources[i].name);
didl += ">";
}
// xbmc dateadded
if ((mask & PLT_FILTER_MASK_XBMC_DATEADDED) && !m_XbmcInfo.date_added.IsEmpty()) {
didl += "<xbmc:dateadded>";
PLT_Didl::AppendXmlEscape(didl, m_XbmcInfo.date_added);
didl += "</xbmc:dateadded>";
}
// xbmc rating
if (mask & PLT_FILTER_MASK_XBMC_RATING) {
didl += "<xbmc:rating>";
didl += NPT_String::Format("%.1f", m_XbmcInfo.rating);
didl += "</xbmc:rating>";
}
// xbmc votes
if (mask & PLT_FILTER_MASK_XBMC_VOTES && m_XbmcInfo.votes != 0) {
didl += "<xbmc:votes>";
didl += NPT_String::Format("%i", m_XbmcInfo.votes);
didl += "</xbmc:votes>";
}
// xbmc artwork
if (mask & PLT_FILTER_MASK_XBMC_ARTWORK) {
m_XbmcInfo.artwork.ToDidl(didl, "artwork");
}
// xbmc unique identifier
if (mask & PLT_FILTER_MASK_XBMC_UNIQUE_IDENTIFIER && !m_XbmcInfo.unique_identifier.IsEmpty()) {
didl += "<xbmc:uniqueidentifier>";
PLT_Didl::AppendXmlEscape(didl, m_XbmcInfo.unique_identifier);
didl += "</xbmc:uniqueidentifier>";
}
// country
if (mask & PLT_FILTER_MASK_XBMC_COUNTRY) {
for (NPT_List<NPT_String>::Iterator it =
m_XbmcInfo.countries.GetFirstItem(); it; ++it) {
didl += "<xbmc:country>";
PLT_Didl::AppendXmlEscape(didl, (*it));
didl += "</xbmc:country>";
}
}
// user rating
if (mask & PLT_FILTER_MASK_XBMC_USERRATING) {
didl += "<xbmc:userrating>";
didl += NPT_String::FromInteger(m_XbmcInfo.user_rating);
didl += "</xbmc:userrating>";
}
// xbmc last playback state
if (mask & PLT_FILTER_MASK_XBMC_LASTPLAYERSTATE && !m_XbmcInfo.last_playerstate.IsEmpty()) {
didl += "<xbmc:lastPlayerState>";
PLT_Didl::AppendXmlEscape(didl, m_XbmcInfo.last_playerstate);
didl += "</xbmc:lastPlayerState>";
}
// class is required
didl += "<upnp:class";
if (!m_ObjectClass.friendly_name.IsEmpty()) {
didl += " name=\"" + m_ObjectClass.friendly_name+"\"";
}
didl += ">";
PLT_Didl::AppendXmlEscape(didl, m_ObjectClass.type);
didl += "</upnp:class>";
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaObject::FromDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaObject::FromDidl(NPT_XmlElementNode* entry)
{
NPT_String str, xml;
NPT_Array<NPT_XmlElementNode*> children;
NPT_Result res;
// check if item is restricted (is default true?)
if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "restricted", str, "", 5))) {
m_Restricted = PLT_Service::IsTrue(str);
}
// read non-required elements
PLT_XmlHelper::GetChildText(entry, "creator", m_Creator, didl_namespace_dc, 256);
PLT_XmlHelper::GetChildText(entry, "date", m_Date, didl_namespace_dc, 256);
// parse date and make sure it's valid
NPT_String parsed_date;
for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) {
NPT_DateTime date;
if (NPT_SUCCEEDED(date.FromString(m_Date, (NPT_DateTime::Format)format))) {
parsed_date = date.ToString((NPT_DateTime::Format)format);
break;
}
}
m_Date = parsed_date;
res = PLT_XmlHelper::GetAttribute(entry, "id", m_ObjectID);
NPT_CHECK_SEVERE(res);
res = PLT_XmlHelper::GetAttribute(entry, "parentID", m_ParentID);
NPT_CHECK_SEVERE(res);
PLT_XmlHelper::GetAttribute(entry, "refID", m_ReferenceID);
res = PLT_XmlHelper::GetChildText(entry, "title", m_Title, didl_namespace_dc);
NPT_CHECK_SEVERE(res);
res = PLT_XmlHelper::GetChildText(entry, "class", m_ObjectClass.type, didl_namespace_upnp);
NPT_CHECK_SEVERE(res);
// DLNA 7.3.17.3 max bytes for dc:title and upnp:class is 256 bytes
m_Title = m_Title.SubString(0, 256);
m_ObjectClass.type = m_ObjectClass.type.SubString(0, 256);
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "artist", didl_namespace_upnp);
m_People.artists.FromDidl(children);
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "author", didl_namespace_upnp);
m_People.authors.FromDidl(children);
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "actor", didl_namespace_upnp);
m_People.actors.FromDidl(children);
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "director", didl_namespace_upnp);
m_People.directors.FromDidl(children);
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "publisher", didl_namespace_dc);
for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
if (children[i]->GetText()) {
m_People.publisher.Add(*children[i]->GetText());
}
}
PLT_XmlHelper::GetChildText(entry, "album", m_Affiliation.album, didl_namespace_upnp, 256);
PLT_XmlHelper::GetChildText(entry, "programTitle", m_Recorded.program_title, didl_namespace_upnp);
PLT_XmlHelper::GetChildText(entry, "seriesTitle", m_Recorded.series_title, didl_namespace_upnp);
PLT_XmlHelper::GetChildText(entry, "episodeNumber", str, didl_namespace_upnp);
NPT_UInt32 value;
if (NPT_FAILED(str.ToInteger(value))) value = 0;
m_Recorded.episode_number = value;
PLT_XmlHelper::GetChildText(entry, "episodeCount", str, didl_namespace_upnp);
if (NPT_FAILED(str.ToInteger(value))) value = 0;
m_Recorded.episode_count = value;
PLT_XmlHelper::GetChildText(entry, "episodeSeason", str, didl_namespace_upnp);
if (NPT_FAILED(str.ToInteger(value))) value = -1;
m_Recorded.episode_season = value;
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "genre", didl_namespace_upnp);
for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
if (children[i]->GetText()) {
m_Affiliation.genres.Add(children[i]->GetText()->SubString(0, 256));
}
}
PLT_XmlHelper::GetChildText(entry, "description", m_Description.description, didl_namespace_dc);
PLT_XmlHelper::GetChildText(entry, "longDescription", m_Description.long_description, didl_namespace_upnp);
PLT_XmlHelper::GetChildText(entry, "icon", m_Description.icon_uri, didl_namespace_upnp);
PLT_XmlHelper::GetChildText(entry, "rating", m_Description.rating, didl_namespace_upnp);
PLT_XmlHelper::GetChildText(entry, "toc", m_MiscInfo.toc, didl_namespace_upnp);
// album arts
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "albumArtURI", didl_namespace_upnp);
for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
if (children[i]->GetText()) {
PLT_AlbumArtInfo info;
info.uri = children[i]->GetText()->SubString(0, 1024);
PLT_XmlHelper::GetAttribute(children[i], "profileID", info.dlna_profile, didl_namespace_dlna);
m_ExtraInfo.album_arts.Add(info);
}
}
PLT_XmlHelper::GetChildText(entry, "originalTrackNumber", str, didl_namespace_upnp);
if (NPT_FAILED(str.ToInteger(value))) value = 0;
m_MiscInfo.original_track_number = value;
PLT_XmlHelper::GetChildText(entry, "lastPlaybackPosition", str, didl_namespace_upnp);
if (NPT_FAILED(PLT_Didl::ParseTimeStamp(str, value))) {
// fall back to raw integer parsing
if (NPT_FAILED(str.ToInteger(value))) value = 0;
}
m_MiscInfo.last_position = value;
PLT_XmlHelper::GetChildText(entry, "lastPlaybackTime", m_MiscInfo.last_time, didl_namespace_upnp, 256);
NPT_String parsed_last_time;
for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) {
NPT_DateTime date;
if (NPT_SUCCEEDED(date.FromString(m_MiscInfo.last_time, (NPT_DateTime::Format)format))) {
parsed_last_time = date.ToString((NPT_DateTime::Format)format);
break;
}
}
m_MiscInfo.last_time = parsed_last_time;
PLT_XmlHelper::GetChildText(entry, "playbackCount", str, didl_namespace_upnp);
if (NPT_FAILED(str.ToInteger(value))) value = -1;
m_MiscInfo.play_count = value;
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "res");
for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
PLT_MediaItemResource resource;
// extract url
if (children[i]->GetText() == NULL) {
NPT_LOG_WARNING_1("No resource text found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i]));
} else {
resource.m_Uri = children[i]->GetText()->SubString(0, 1024);
// basic uri validation, ignoring scheme (could be rtsp)
NPT_HttpUrl url(resource.m_Uri, true);
if (!url.IsValid()) {
NPT_LOG_WARNING_1("Invalid resource uri: %s", (const char*)resource.m_Uri);
continue;
}
}
// extract protocol info
NPT_String protocol_info;
res = PLT_XmlHelper::GetAttribute(children[i], "protocolInfo", protocol_info, "", 256);
if (NPT_FAILED(res)) {
NPT_LOG_WARNING_1("No protocol info found in: %s", (const char*)PLT_XmlHelper::Serialize(*children[i]));
} else {
resource.m_ProtocolInfo = PLT_ProtocolInfo(protocol_info);
if (!resource.m_ProtocolInfo.IsValid()) {
NPT_LOG_WARNING_1("Invalid resource protocol info: %s", (const char*)protocol_info);
}
}
// extract known attributes
PLT_XmlHelper::GetAttribute(children[i], "protection", resource.m_Protection, "", 256);
PLT_XmlHelper::GetAttribute(children[i], "resolution", resource.m_Resolution, "", 256);
if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "size", str, "", 256))) {
if (NPT_FAILED(str.ToInteger64(resource.m_Size))) resource.m_Size = (NPT_Size)-1;
}
if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(children[i], "duration", str, "", 256))) {
if (NPT_FAILED(PLT_Didl::ParseTimeStamp(str, resource.m_Duration))) {
// if error while converting, ignore and set to -1 to indicate we don't know the duration
resource.m_Duration = (NPT_UInt32)-1;
PLT_XmlHelper::RemoveAttribute(children[i], "duration");
} else {
// DLNA: reformat duration in case it was not compliant
str = PLT_Didl::FormatTimeStamp(resource.m_Duration);
PLT_XmlHelper::SetAttribute(children[i], "duration", str);
}
}
m_Resources.Add(resource);
}
PLT_XmlHelper::GetChildText(entry, "lastPlayerState", m_XbmcInfo.last_playerstate, didl_namespace_xbmc, 2048);
PLT_XmlHelper::GetChildText(entry, "dateadded", m_XbmcInfo.date_added, didl_namespace_xbmc, 256);
// parse date and make sure it's valid
for (int format=0; format<=NPT_DateTime::FORMAT_RFC_1036; format++) {
NPT_DateTime date;
if (NPT_SUCCEEDED(date.FromString(m_XbmcInfo.date_added, (NPT_DateTime::Format)format))) {
parsed_date = date.ToString((NPT_DateTime::Format)format);
break;
}
}
m_XbmcInfo.date_added = parsed_date;
PLT_XmlHelper::GetChildText(entry, "rating", str, didl_namespace_xbmc);
NPT_Float floatValue;
if (NPT_FAILED(str.ToFloat(floatValue))) floatValue = 0.0;
m_XbmcInfo.rating = floatValue;
PLT_XmlHelper::GetChildText(entry, "votes", str, didl_namespace_xbmc, 256);
NPT_Int32 intValue;
if (NPT_FAILED(str.ToInteger(intValue))) intValue = 0;
m_XbmcInfo.votes = intValue;
children.Clear();
PLT_XmlHelper::GetChildren(entry, children, "artwork", didl_namespace_xbmc);
m_XbmcInfo.artwork.FromDidl(children);
PLT_XmlHelper::GetChildText(entry, "uniqueidentifier", m_XbmcInfo.unique_identifier, didl_namespace_xbmc, 256);
// re serialize the entry didl as a we might need to pass it to a renderer
// we may have modified the tree to "fix" issues, so as not to break a renderer
// (don't write xml prefix as this didl could be part of a larger document)
//res = PLT_XmlHelper::Serialize(*entry, xml, false);
m_Didl = "";
res = ToDidl(PLT_FILTER_MASK_ALL, m_Didl);
NPT_CHECK_SEVERE(res);
m_Didl = didl_header + m_Didl + didl_footer;
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaObjectList::PLT_MediaObjectList
+---------------------------------------------------------------------*/
PLT_MediaObjectList::PLT_MediaObjectList()
{
}
/*----------------------------------------------------------------------
| PLT_MediaObjectList::~PLT_MediaObjectList
+---------------------------------------------------------------------*/
PLT_MediaObjectList::~PLT_MediaObjectList()
{
Apply(NPT_ObjectDeleter<PLT_MediaObject>());
}
/*----------------------------------------------------------------------
| PLT_MediaItem::PLT_MediaItem
+---------------------------------------------------------------------*/
PLT_MediaItem::PLT_MediaItem()
{
Reset();
}
/*----------------------------------------------------------------------
| PLT_MediaItem::~PLT_MediaItem
+---------------------------------------------------------------------*/
PLT_MediaItem::~PLT_MediaItem()
{
}
/*----------------------------------------------------------------------
| PLT_MediaItem::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaItem::ToDidl(const NPT_String& filter, NPT_String& didl)
{
return PLT_MediaObject::ToDidl(filter, didl);
}
/*----------------------------------------------------------------------
| PLT_MediaItem::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaItem::ToDidl(NPT_UInt64 mask, NPT_String& didl)
{
didl += "<item id=\"";
PLT_Didl::AppendXmlEscape(didl, m_ObjectID);
didl += "\" parentID=\"";
PLT_Didl::AppendXmlEscape(didl, m_ParentID);
if ((mask & PLT_FILTER_MASK_REFID) && !m_ReferenceID.IsEmpty()) {
didl += "\" refID=\"";
PLT_Didl::AppendXmlEscape(didl, m_ReferenceID);
}
didl += "\" restricted=\"";
didl += m_Restricted?"1\"":"0\"";
didl += ">";
NPT_CHECK_SEVERE(PLT_MediaObject::ToDidl(mask, didl));
/* close tag */
didl += "</item>";
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaItem::FromDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaItem::FromDidl(NPT_XmlElementNode* entry)
{
/* reset first */
Reset();
if (entry->GetTag().Compare("item", true) != 0) {
NPT_CHECK_SEVERE(NPT_ERROR_INTERNAL);
}
NPT_Result result = PLT_MediaObject::FromDidl(entry);
return result;
}
/*----------------------------------------------------------------------
| PLT_MediaContainer::PLT_MediaContainer
+---------------------------------------------------------------------*/
PLT_MediaContainer::PLT_MediaContainer()
{
Reset();
}
/*----------------------------------------------------------------------
| PLT_MediaContainer::~PLT_MediaContainer
+---------------------------------------------------------------------*/
PLT_MediaContainer::~PLT_MediaContainer(void)
{
}
/*----------------------------------------------------------------------
| PLT_MediaContainer::Reset
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaContainer::Reset()
{
m_SearchClasses.Clear();
m_Searchable = false;
m_ChildrenCount = -1;
m_ContainerUpdateID = 0;
return PLT_MediaObject::Reset();
}
/*----------------------------------------------------------------------
| PLT_MediaContainer::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaContainer::ToDidl(const NPT_String& filter, NPT_String& didl)
{
return PLT_MediaObject::ToDidl(filter, didl);
}
/*----------------------------------------------------------------------
| PLT_MediaContainer::ToDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaContainer::ToDidl(NPT_UInt64 mask, NPT_String& didl)
{
// container id property
didl += "<container id=\"";
PLT_Didl::AppendXmlEscape(didl, m_ObjectID);
// parent id property
didl += "\" parentID=\"";
PLT_Didl::AppendXmlEscape(didl, m_ParentID);
// ref id
if ((mask & PLT_FILTER_MASK_REFID) && !m_ReferenceID.IsEmpty()) {
didl += "\" refID=\"";
PLT_Didl::AppendXmlEscape(didl, m_ReferenceID);
}
// restricted property
didl += "\" restricted=\"";
didl += m_Restricted?"1\"":"0\"";
// searchable property
if (mask & PLT_FILTER_MASK_SEARCHABLE) {
didl += " searchable=\"";
didl += m_Searchable?"1\"":"0\"";
}
// childcount property
if ((mask & PLT_FILTER_MASK_CHILDCOUNT) && m_ChildrenCount != -1) {
didl += " childCount=\"";
didl += NPT_String::FromInteger(m_ChildrenCount);
didl += "\"";
}
didl += ">";
if ((mask & PLT_FILTER_MASK_SEARCHCLASS) && m_SearchClasses.GetItemCount()) {
NPT_List<PLT_SearchClass>::Iterator search_class = m_SearchClasses.GetFirstItem();
while (search_class) {
didl += "<upnp:searchClass includeDerived=\"";
didl += (*search_class).include_derived?"1\"":"0\"";
- // frienly name is any
+ // friendly name is any
if (!(*search_class).friendly_name.IsEmpty()) {
didl += " name=\"" + (*search_class).friendly_name + "\"";
}
didl += ">";
didl += (*search_class).type;
didl += "</upnp:searchClass>";
++search_class;
}
}
NPT_CHECK_SEVERE(PLT_MediaObject::ToDidl(mask, didl));
/* close tag */
didl += "</container>";
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaContainer::FromDidl
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaContainer::FromDidl(NPT_XmlElementNode* entry)
{
NPT_String str;
/* reset first */
Reset();
// check entry type
if (entry->GetTag().Compare("Container", true) != 0)
return NPT_ERROR_INTERNAL;
// check if item is searchable (is default true?)
if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "searchable", str, "", 5))) {
m_Searchable = PLT_Service::IsTrue(str);
}
// look for childCount
if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(entry, "childCount", str, "", 256))) {
NPT_UInt32 count;
NPT_CHECK_SEVERE(str.ToInteger(count));
m_ChildrenCount = count;
}
// upnp:searchClass child elements
NPT_Array<NPT_XmlElementNode*> children;
PLT_XmlHelper::GetChildren(entry, children, "upnp:searchClass");
for (NPT_Cardinal i=0; i<children.GetItemCount(); i++) {
PLT_SearchClass search_class;
// extract url
if (children[i]->GetText() == NULL) {
NPT_LOG_WARNING_1("No searchClass text found in: %s",
(const char*)PLT_XmlHelper::Serialize(*children[i]));
continue;
}
// DLNA 7.3.17.4
search_class.type = children[i]->GetText()->SubString(0, 256);
// extract optional attribute name
PLT_XmlHelper::GetAttribute(children[i], "name", search_class.friendly_name);
// includeDerived property
if (NPT_FAILED(PLT_XmlHelper::GetAttribute(children[i], "includeDerived", str))) {
NPT_LOG_WARNING_1("No required attribute searchClass@includeDerived found in: %s",
(const char*)PLT_XmlHelper::Serialize(*children[i]));
continue;
}
search_class.include_derived = PLT_Service::IsTrue(str);
m_SearchClasses.Add(search_class);
}
return PLT_MediaObject::FromDidl(entry);
}
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.h
index 3a13967f5f..ec618e026d 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaItem.h
@@ -1,348 +1,348 @@
/*****************************************************************
|
| Platinum - AV Media Item
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
- UPnP AV Media Object reprensentation.
+ UPnP AV Media Object representation.
*/
#ifndef _PLT_MEDIA_ITEM_H_
#define _PLT_MEDIA_ITEM_H_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
#include "PltHttp.h"
#include "PltProtocolInfo.h"
/*----------------------------------------------------------------------
| typedefs
+---------------------------------------------------------------------*/
/**
The PLT_ObjectClass struct is used to assign a type to a PLT_MediaObject.
*/
typedef struct {
NPT_String type;
NPT_String friendly_name;
} PLT_ObjectClass;
typedef struct {
NPT_String type;
NPT_String friendly_name;
bool include_derived;
} PLT_SearchClass;
typedef struct {
NPT_String name;
NPT_String role;
} PLT_PersonRole;
class PLT_PersonRoles : public NPT_List<PLT_PersonRole>
{
public:
NPT_Result Add(const NPT_String& name, const NPT_String& role = "");
NPT_Result ToDidl(NPT_String& didl, const NPT_String& tag);
NPT_Result FromDidl(const NPT_Array<NPT_XmlElementNode*>& nodes);
};
typedef struct {
NPT_String allowed_use; // (CSV)
NPT_String validity_start;
NPT_String validity_end;
NPT_String remaining_time;
NPT_String usage_info;
NPT_String rights_info_uri;
NPT_String content_info_uri;
} PLT_Constraint;
typedef struct {
PLT_PersonRoles artists;
PLT_PersonRoles actors;
PLT_PersonRoles authors;
NPT_String producer; //TODO: can be multiple
PLT_PersonRoles directors;
NPT_List<NPT_String> publisher;
NPT_String contributor; // should match m_Creator (dc:creator) //TODO: can be multiple
} PLT_PeopleInfo;
typedef struct {
NPT_List<NPT_String> genres;
NPT_String album; //TODO: can be multiple
NPT_String playlist; // dc:title of the playlist item the content belongs too //TODO: can be multiple
} PLT_AffiliationInfo;
typedef struct {
NPT_String description;
NPT_String long_description;
NPT_String icon_uri;
NPT_String region;
NPT_String rating;
NPT_String rights; //TODO: can be multiple
NPT_String date;
NPT_String language;
} PLT_Description;
typedef struct {
NPT_String uri;
NPT_String dlna_profile;
} PLT_AlbumArtInfo;
typedef struct {
NPT_List<PLT_AlbumArtInfo> album_arts;
NPT_String artist_discography_uri;
NPT_String lyrics_uri;
NPT_List<NPT_String> relations; // dc:relation
} PLT_ExtraInfo;
typedef struct {
NPT_UInt32 dvdregioncode;
NPT_UInt32 original_track_number;
NPT_String toc;
NPT_String user_annotation; //TODO: can be multiple
NPT_UInt32 last_position;
NPT_String last_time;
NPT_Int32 play_count;
} PLT_MiscInfo;
typedef struct {
NPT_UInt64 total;
NPT_UInt64 used;
NPT_UInt64 free;
NPT_UInt64 max_partition;
NPT_UInt64 medium;
} PLT_StorageInfo;
typedef struct {
NPT_String program_title;
NPT_String series_title;
NPT_UInt32 episode_number;
NPT_UInt32 episode_count;
NPT_UInt32 episode_season;
} PLT_RecordedInfo;
typedef struct {
NPT_String type;
NPT_String url;
} PLT_Artwork;
class PLT_Artworks : public NPT_List<PLT_Artwork>
{
public:
NPT_Result Add(const NPT_String& type, const NPT_String& url);
NPT_Result ToDidl(NPT_String& didl, const NPT_String& tag);
NPT_Result FromDidl(const NPT_Array<NPT_XmlElementNode*>& nodes);
};
typedef struct {
NPT_String last_playerstate;
NPT_String date_added;
NPT_Float rating;
NPT_Int32 votes;
PLT_Artworks artwork;
NPT_String unique_identifier;
NPT_List<NPT_String> countries;
NPT_Int32 user_rating;
} PLT_XbmcInfo;
typedef struct {
NPT_String name;
NPT_Map<NPT_String, NPT_String> attributes;
NPT_String value;
} PLT_SecResource;
/*----------------------------------------------------------------------
| PLT_MediaItemResource
+---------------------------------------------------------------------*/
class PLT_MediaItemResource
{
public:
PLT_MediaItemResource();
~PLT_MediaItemResource() {}
NPT_String m_Uri;
PLT_ProtocolInfo m_ProtocolInfo;
NPT_UInt32 m_Duration; /* seconds */
NPT_LargeSize m_Size;
NPT_String m_Protection;
NPT_UInt32 m_Bitrate; /* bytes/seconds */
NPT_UInt32 m_BitsPerSample;
NPT_UInt32 m_SampleFrequency;
NPT_UInt32 m_NbAudioChannels;
NPT_String m_Resolution;
NPT_UInt32 m_ColorDepth;
/* to add custom data to resource, that are not standard one, or are only
proper for some type of devices (UPnP)*/
NPT_Map<NPT_String, NPT_String> m_CustomData;
};
/*----------------------------------------------------------------------
| PLT_MediaObject
+---------------------------------------------------------------------*/
/**
The PLT_MediaObject class is any data entity that can be returned by a
ContentDirectory Service from a browsing or searching action. This is the
base class from which PLT_MediaItem and PLT_MediaContainer derive.
*/
class PLT_MediaObject
{
protected:
NPT_IMPLEMENT_DYNAMIC_CAST(PLT_MediaObject)
PLT_MediaObject() : m_Restricted(true) {}
public:
virtual ~PLT_MediaObject() {}
bool IsContainer() { return m_ObjectClass.type.StartsWith("object.container"); }
static const char* GetUPnPClass(const char* filename,
const PLT_HttpRequestContext* context = NULL);
virtual NPT_Result Reset();
virtual NPT_Result ToDidl(const NPT_String& filter, NPT_String& didl);
virtual NPT_Result ToDidl(NPT_UInt64 mask, NPT_String& didl);
virtual NPT_Result FromDidl(NPT_XmlElementNode* entry);
public:
/* common properties */
PLT_ObjectClass m_ObjectClass;
NPT_String m_ObjectID;
NPT_String m_ParentID;
NPT_String m_ReferenceID;
/* metadata */
NPT_String m_Title;
NPT_String m_Creator;
NPT_String m_Date;
PLT_PeopleInfo m_People;
PLT_AffiliationInfo m_Affiliation;
PLT_Description m_Description;
PLT_RecordedInfo m_Recorded;
/* properties */
bool m_Restricted;
/* extras */
PLT_ExtraInfo m_ExtraInfo;
/* miscellaneous info */
PLT_MiscInfo m_MiscInfo;
/* resources related */
NPT_Array<PLT_MediaItemResource> m_Resources;
/* sec resources related */
NPT_Array<PLT_SecResource> m_SecResources;
/* XBMC specific */
PLT_XbmcInfo m_XbmcInfo;
/* original DIDL for Control Points to pass to a renderer when invoking SetAVTransportURI */
NPT_String m_Didl;
};
/*----------------------------------------------------------------------
| PLT_MediaItem
+---------------------------------------------------------------------*/
/**
The PLT_MediaItem class represents a first-level class derived directly from
PLT_MediaObject. It most often represents a single piece of AV data.
*/
class PLT_MediaItem : public PLT_MediaObject
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST_D(PLT_MediaItem, PLT_MediaObject)
PLT_MediaItem();
~PLT_MediaItem() override;
// PLT_MediaObject methods
NPT_Result ToDidl(const NPT_String& filter, NPT_String& didl) override;
NPT_Result ToDidl(NPT_UInt64 mask, NPT_String& didl) override;
NPT_Result FromDidl(NPT_XmlElementNode* entry) override;
};
/*----------------------------------------------------------------------
| PLT_MediaContainer
+---------------------------------------------------------------------*/
/**
The PLT_MediaContainer class represents a first-level class derived directly
from PLT_MediaObject. A PLT_MediaContainer represents a collection of
PLT_MediaObject instances.
*/
class PLT_MediaContainer : public PLT_MediaObject
{
public:
NPT_IMPLEMENT_DYNAMIC_CAST_D(PLT_MediaContainer, PLT_MediaObject)
PLT_MediaContainer();
~PLT_MediaContainer() override;
// PLT_MediaObject methods
NPT_Result Reset() override;
NPT_Result ToDidl(const NPT_String& filter, NPT_String& didl) override;
NPT_Result ToDidl(NPT_UInt64 mask, NPT_String& didl) override;
NPT_Result FromDidl(NPT_XmlElementNode* entry) override;
public:
NPT_List<PLT_SearchClass> m_SearchClasses;
/* properties */
bool m_Searchable;
/* container info related */
NPT_Int32 m_ChildrenCount;
NPT_UInt32 m_ContainerUpdateID;
};
/*----------------------------------------------------------------------
| PLT_MediaObjectList
+---------------------------------------------------------------------*/
/**
The PLT_MediaObjectList class is a list of PLT_MediaObject instances.
*/
class PLT_MediaObjectList : public NPT_List<PLT_MediaObject*>
{
public:
PLT_MediaObjectList();
protected:
virtual ~PLT_MediaObjectList(void);
friend class NPT_Reference<PLT_MediaObjectList>;
};
typedef NPT_Reference<PLT_MediaObjectList> PLT_MediaObjectListReference;
typedef NPT_Reference<PLT_MediaObject> PLT_MediaObjectReference;
#endif /* _PLT_MEDIA_ITEM_H_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaServer.cpp b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaServer.cpp
index 0a43dab603..41ccc95a2b 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaServer.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltMediaServer.cpp
@@ -1,711 +1,711 @@
/*****************************************************************
|
| Platinum - AV Media Server Device
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "PltUPnP.h"
#include "PltMediaServer.h"
#include "PltMediaItem.h"
#include "PltService.h"
#include "PltTaskManager.h"
#include "PltHttpServer.h"
#include "PltDidl.h"
NPT_SET_LOCAL_LOGGER("platinum.media.server")
/*----------------------------------------------------------------------
| forward references
+---------------------------------------------------------------------*/
extern NPT_UInt8 MS_ConnectionManagerSCPD[];
extern NPT_UInt8 MS_ContentDirectorywSearchSCPD[];
const char* BrowseFlagsStr[] = {
"BrowseMetadata",
"BrowseDirectChildren"
};
/*----------------------------------------------------------------------
| PLT_MediaServer::PLT_MediaServer
+---------------------------------------------------------------------*/
PLT_MediaServer::PLT_MediaServer(const char* friendly_name,
bool show_ip /* = false */,
const char* uuid /* = NULL */,
NPT_UInt16 port /* = 0 */,
bool port_rebind /* = false */) :
PLT_DeviceHost("/DeviceDescription.xml",
uuid,
"urn:schemas-upnp-org:device:MediaServer:1",
friendly_name,
show_ip,
port,
port_rebind),
m_Delegate(NULL)
{
m_ModelDescription = "Plutinosoft AV Media Server Device";
m_ModelName = "AV Media Server Device";
m_ModelURL = "http://www.plutinosoft.com/platinum";
m_DlnaDoc = "DMS-1.50";
}
/*----------------------------------------------------------------------
| PLT_MediaServer::~PLT_MediaServer
+---------------------------------------------------------------------*/
PLT_MediaServer::~PLT_MediaServer()
{
}
/*----------------------------------------------------------------------
| PLT_MediaServer::SetupServices
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::SetupServices()
{
NPT_Reference<PLT_Service> service;
{
service = new PLT_Service(
this,
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:upnp-org:serviceId:ContentDirectory",
"ContentDirectory");
NPT_CHECK_FATAL(service->SetSCPDXML((const char*) MS_ContentDirectorywSearchSCPD));
NPT_CHECK_FATAL(AddService(service.AsPointer()));
service->SetStateVariable("ContainerUpdateIDs", "");
service->SetStateVariableRate("ContainerUpdateIDs", NPT_TimeInterval(2.));
service->SetStateVariable("SystemUpdateID", "0");
service->SetStateVariableRate("SystemUpdateID", NPT_TimeInterval(2.));
service->SetStateVariable("SearchCapability", "@id,@refID,dc:title,upnp:class,upnp:genre,upnp:artist,upnp:author,upnp:author@role,upnp:album,dc:creator,res@size,res@duration,res@protocolInfo,res@protection,dc:publisher,dc:language,upnp:originalTrackNumber,dc:date,upnp:producer,upnp:rating,upnp:actor,upnp:director,upnp:toc,dc:description,microsoft:userRatingInStars,microsoft:userEffectiveRatingInStars,microsoft:userRating,microsoft:userEffectiveRating,microsoft:serviceProvider,microsoft:artistAlbumArtist,microsoft:artistPerformer,microsoft:artistConductor,microsoft:authorComposer,microsoft:authorOriginalLyricist,microsoft:authorWriter,upnp:userAnnotation,upnp:channelName,upnp:longDescription,upnp:programTitle");
service->SetStateVariable("SortCapability", "dc:title,upnp:genre,upnp:album,dc:creator,res@size,res@duration,res@bitrate,dc:publisher,dc:language,upnp:originalTrackNumber,dc:date,upnp:producer,upnp:rating,upnp:actor,upnp:director,upnp:toc,dc:description,microsoft:year,microsoft:userRatingInStars,microsoft:userEffectiveRatingInStars,microsoft:userRating,microsoft:userEffectiveRating,microsoft:serviceProvider,microsoft:artistAlbumArtist,microsoft:artistPerformer,microsoft:artistConductor,microsoft:authorComposer,microsoft:authorOriginalLyricist,microsoft:authorWriter,microsoft:sourceUrl,upnp:userAnnotation,upnp:channelName,upnp:longDescription,upnp:programTitle");
service.Detach();
service = NULL;
}
{
service = new PLT_Service(
this,
"urn:schemas-upnp-org:service:ConnectionManager:1",
"urn:upnp-org:serviceId:ConnectionManager",
"ConnectionManager");
NPT_CHECK_FATAL(service->SetSCPDXML((const char*) MS_ConnectionManagerSCPD));
NPT_CHECK_FATAL(AddService(service.AsPointer()));
service->SetStateVariable("CurrentConnectionIDs", "0");
service->SetStateVariable("SinkProtocolInfo", "");
service->SetStateVariable("SourceProtocolInfo", "http-get:*:*:*");
service.Detach();
service = NULL;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::UpdateSystemUpdateID
+---------------------------------------------------------------------*/
void
PLT_MediaServer::UpdateSystemUpdateID(NPT_UInt32 update)
{
NPT_COMPILER_UNUSED(update);
}
/*----------------------------------------------------------------------
| PLT_MediaServer::UpdateContainerUpdateID
+---------------------------------------------------------------------*/
void PLT_MediaServer::UpdateContainerUpdateID(const char* id, NPT_UInt32 update)
{
NPT_COMPILER_UNUSED(id);
NPT_COMPILER_UNUSED(update);
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnAction
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnAction(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
/* parse the action name */
NPT_String name = action->GetActionDesc().GetName();
// ContentDirectory
if (name.Compare("Browse", true) == 0) {
return OnBrowse(action, context);
}
if (name.Compare("Search", true) == 0) {
return OnSearch(action, context);
}
if (name.Compare("UpdateObject", true) == 0) {
return OnUpdate(action, context);
}
if (name.Compare("GetSystemUpdateID", true) == 0) {
return OnGetSystemUpdateID(action, context);
}
if (name.Compare("GetSortCapabilities", true) == 0) {
return OnGetSortCapabilities(action, context);
}
if (name.Compare("GetSearchCapabilities", true) == 0) {
return OnGetSearchCapabilities(action, context);
}
// ConnectionMananger
if (name.Compare("GetCurrentConnectionIDs", true) == 0) {
return OnGetCurrentConnectionIDs(action, context);
}
if (name.Compare("GetProtocolInfo", true) == 0) {
return OnGetProtocolInfo(action, context);
}
if (name.Compare("GetCurrentConnectionInfo", true) == 0) {
return OnGetCurrentConnectionInfo(action, context);
}
action->SetError(401,"No Such Action.");
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_FileMediaServer::ProcessHttpGetRequest
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::ProcessHttpGetRequest(NPT_HttpRequest& request,
const NPT_HttpRequestContext& context,
NPT_HttpResponse& response)
{
/* Try to handle file request */
if (m_Delegate) return m_Delegate->ProcessFileRequest(request, context, response);
return NPT_ERROR_NO_SUCH_ITEM;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnGetCurrentConnectionIDs
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnGetCurrentConnectionIDs(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
return action->SetArgumentsOutFromStateVariable();
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnGetProtocolInfo
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnGetProtocolInfo(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
return action->SetArgumentsOutFromStateVariable();
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnGetCurrentConnectionInfo
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnGetCurrentConnectionInfo(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
if (NPT_FAILED(action->VerifyArgumentValue("ConnectionID", "0"))) {
action->SetError(706,"No Such Connection.");
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("RcsID", "-1"))){
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("AVTransportID", "-1"))) {
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("ProtocolInfo", "http-get:*:*:*"))) {
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("PeerConnectionManager", "/"))) {
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("PeerConnectionID", "-1"))) {
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("Direction", "Output"))) {
return NPT_FAILURE;
}
if (NPT_FAILED(action->SetArgumentValue("Status", "Unknown"))) {
return NPT_FAILURE;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnGetSortCapabilities
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnGetSortCapabilities(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
return action->SetArgumentsOutFromStateVariable();
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnGetSearchCapabilities
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnGetSearchCapabilities(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
return action->SetArgumentsOutFromStateVariable();
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnGetSystemUpdateID
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnGetSystemUpdateID(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
return action->SetArgumentsOutFromStateVariable();
}
/*----------------------------------------------------------------------
| PLT_MediaServer::ParseBrowseFlag
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::ParseBrowseFlag(const char* str, BrowseFlags& flag)
{
if (NPT_String::Compare(str, BrowseFlagsStr[0], true) == 0) {
flag = BROWSEMETADATA;
return NPT_SUCCESS;
}
if (NPT_String::Compare(str, BrowseFlagsStr[1], true) == 0) {
flag = BROWSEDIRECTCHILDREN;
return NPT_SUCCESS;
}
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::ParseSort
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::ParseSort(const NPT_String& sort, NPT_List<NPT_String>& list)
{
// reset output params first
list.Clear();
// easy out
if (sort.GetLength() == 0 || sort == "*") return NPT_SUCCESS;
list = sort.Split(",");
// verify each property has a namespace
NPT_List<NPT_String>::Iterator property = list.GetFirstItem();
while (property) {
NPT_List<NPT_String> parsed_property = (*property).Split(":");
if (parsed_property.GetItemCount() != 2)
parsed_property = (*property).Split("@");
if (parsed_property.GetItemCount() != 2 ||
(!(*property).StartsWith("-") && !(*property).StartsWith("+"))) {
NPT_LOG_WARNING_1("Invalid SortCriteria property %s", (*property).GetChars());
return NPT_FAILURE;
}
property++;
}
return NPT_SUCCESS;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::ParseTagList
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::ParseTagList(const NPT_String& updates, NPT_Map<NPT_String,NPT_String>& tags)
{
// reset output params first
tags.Clear();
NPT_List<NPT_String> split = updates.Split(",");
NPT_XmlNode* node = NULL;
NPT_XmlElementNode* didl_partial = NULL;
NPT_XmlParser parser;
// as these are single name value pairs, separated by commas we wrap in a tag
// to create a valid tree
NPT_String xml("<TagValueList>");
for (NPT_List<NPT_String>::Iterator entry = split.GetFirstItem(); entry; entry++) {
NPT_String& element = (*entry);
if (element.IsEmpty())
xml.Append("<empty>empty</empty>");
else
xml.Append(element);
}
xml.Append("</TagValueList>");
NPT_LOG_FINE("Parsing TagList...");
NPT_CHECK_LABEL_SEVERE(parser.Parse(xml, node), cleanup);
if (!node || !node->AsElementNode()) {
NPT_LOG_SEVERE("Invalid node type");
goto cleanup;
}
didl_partial = node->AsElementNode();
if (didl_partial->GetTag().Compare("TagValueList", true)) {
NPT_LOG_SEVERE("Invalid node tag");
goto cleanup;
}
for (NPT_List<NPT_XmlNode*>::Iterator children = didl_partial->GetChildren().GetFirstItem(); children; children++) {
NPT_XmlElementNode* child = (*children)->AsElementNode();
if (!child) continue;
const NPT_String *txt = child->GetText();
tags[child->GetTag()] = txt ? *txt : "";
}
return NPT_SUCCESS;
cleanup:
if (node) delete node;
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnBrowse
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnBrowse(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_Result res;
NPT_String object_id;
NPT_String browse_flag_val;
NPT_String filter;
NPT_String start;
NPT_String count;
NPT_String sort;
NPT_List<NPT_String> sort_list;
if (NPT_FAILED(action->GetArgumentValue("ObjectId", object_id)) ||
NPT_FAILED(action->GetArgumentValue("BrowseFlag", browse_flag_val)) ||
NPT_FAILED(action->GetArgumentValue("Filter", filter)) ||
NPT_FAILED(action->GetArgumentValue("StartingIndex", start)) ||
NPT_FAILED(action->GetArgumentValue("RequestedCount", count)) ||
NPT_FAILED(action->GetArgumentValue("SortCriteria", sort))) {
NPT_LOG_WARNING("Missing arguments");
action->SetError(402, "Invalid args");
return NPT_SUCCESS;
}
/* extract flag */
BrowseFlags flag;
if (NPT_FAILED(ParseBrowseFlag(browse_flag_val, flag))) {
/* error */
NPT_LOG_WARNING_1("BrowseFlag value not allowed (%s)", (const char*)browse_flag_val);
action->SetError(402, "Invalid args");
return NPT_SUCCESS;
}
/* convert index and counts to int */
NPT_UInt32 starting_index, requested_count;
if (NPT_FAILED(start.ToInteger(starting_index)) ||
NPT_FAILED(count.ToInteger(requested_count)) ||
PLT_Didl::ConvertFilterToMask(filter) == 0) {
NPT_LOG_WARNING_3("Invalid arguments (%s, %s, %s)",
start.GetChars(), count.GetChars(), filter.GetChars());
action->SetError(402, "Invalid args");
return NPT_FAILURE;
}
/* parse sort criteria for validation */
if (NPT_FAILED(ParseSort(sort, sort_list))) {
NPT_LOG_WARNING_1("Unsupported or invalid sort criteria error (%s)",
sort.GetChars());
action->SetError(709, "Unsupported or invalid sort criteria error");
return NPT_FAILURE;
}
NPT_LOG_FINE_6("Processing %s from %s with id=\"%s\", filter=\"%s\", start=%d, count=%d",
(const char*)browse_flag_val,
(const char*)context.GetRemoteAddress().GetIpAddress().ToString(),
(const char*)object_id,
(const char*)filter,
starting_index,
requested_count);
/* Invoke the browse function */
if (flag == BROWSEMETADATA) {
res = OnBrowseMetadata(
action,
object_id,
filter,
starting_index,
requested_count,
sort,
context);
} else {
res = OnBrowseDirectChildren(
action,
object_id,
filter,
starting_index,
requested_count,
sort,
context);
}
if (NPT_FAILED(res) && (action->GetErrorCode() == 0)) {
action->SetError(800, "Internal error");
}
return res;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnSearch
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnSearch(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
NPT_COMPILER_UNUSED(context);
NPT_Result res;
NPT_String container_id;
NPT_String search;
NPT_String filter;
NPT_String start;
NPT_String count;
NPT_String sort;
NPT_List<NPT_String> sort_list;
if (NPT_FAILED(action->GetArgumentValue("ContainerId", container_id)) ||
NPT_FAILED(action->GetArgumentValue("SearchCriteria", search)) ||
NPT_FAILED(action->GetArgumentValue("Filter", filter)) ||
NPT_FAILED(action->GetArgumentValue("StartingIndex", start)) ||
NPT_FAILED(action->GetArgumentValue("RequestedCount", count)) ||
NPT_FAILED(action->GetArgumentValue("SortCriteria", sort))) {
NPT_LOG_WARNING("Missing arguments");
action->SetError(402, "Invalid args");
return NPT_SUCCESS;
}
/* convert index and counts to int */
NPT_UInt32 starting_index, requested_count;
if (NPT_FAILED(start.ToInteger(starting_index)) ||
NPT_FAILED(count.ToInteger(requested_count))) {
NPT_LOG_WARNING_2("Invalid arguments (%s, %s)",
start.GetChars(), count.GetChars());
action->SetError(402, "Invalid args");
return NPT_FAILURE;
}
/* parse sort criteria */
if (NPT_FAILED(ParseSort(sort, sort_list))) {
NPT_LOG_WARNING_1("Unsupported or invalid sort criteria error (%s)",
sort.GetChars());
action->SetError(709, "Unsupported or invalid sort criteria error");
return NPT_FAILURE;
}
NPT_LOG_INFO_5("Processing Search from %s with id=\"%s\", search=\"%s\", start=%d, count=%d",
(const char*)context.GetRemoteAddress().GetIpAddress().ToString(),
(const char*)container_id,
(const char*)search,
starting_index,
requested_count);
if (search.IsEmpty() || search == "*") {
res = OnBrowseDirectChildren(
action,
container_id,
filter,
starting_index,
requested_count,
sort,
context);
} else {
res = OnSearchContainer(
action,
container_id,
search,
filter,
starting_index,
requested_count,
sort,
context);
}
if (NPT_FAILED(res) && (action->GetErrorCode() == 0)) {
action->SetError(800, "Internal error");
}
return res;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnUpdate
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnUpdate(PLT_ActionReference& action,
const PLT_HttpRequestContext& context)
{
if (!m_Delegate)
return NPT_ERROR_NOT_IMPLEMENTED;
int err;
const char* msg = NULL;
NPT_String object_id, current_xml, new_xml;
NPT_Map<NPT_String,NPT_String> curr_values;
NPT_Map<NPT_String,NPT_String> new_values;
NPT_CHECK_LABEL(action->GetArgumentValue("ObjectID", object_id), args);
NPT_CHECK_LABEL(object_id.IsEmpty(),args);
NPT_CHECK_LABEL(action->GetArgumentValue("CurrentTagValue", current_xml), args);
NPT_CHECK_LABEL(action->GetArgumentValue("NewTagValue", new_xml), args);
if (NPT_FAILED(ParseTagList(current_xml, curr_values))) {
err = 702;
msg = "Invalid currentTagvalue";
goto failure;
}
if (NPT_FAILED(ParseTagList(new_xml, new_values))) {
err = 703;
msg = "Invalid newTagValue";
goto failure;
}
if (curr_values.GetEntryCount() != new_values.GetEntryCount()) {
err = 706;
- msg = "Paramater mismatch";
+ msg = "Parameters mismatch";
goto failure;
}
return m_Delegate->OnUpdateObject(action, object_id, curr_values, new_values, context);
args:
err = 402;
msg = "Invalid args";
failure:
NPT_LOG_WARNING(msg);
action->SetError(err, msg);
return NPT_FAILURE;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnBrowseMetadata
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnBrowseMetadata(PLT_ActionReference& action,
const char* object_id,
const char* filter,
NPT_UInt32 starting_index,
NPT_UInt32 requested_count,
const char* sort_criteria,
const PLT_HttpRequestContext& context)
{
if (m_Delegate) {
return m_Delegate->OnBrowseMetadata(action,
object_id,
filter,
starting_index,
requested_count,
sort_criteria,
context);
}
return NPT_ERROR_NOT_IMPLEMENTED;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnBrowseDirectChildren
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnBrowseDirectChildren(PLT_ActionReference& action,
const char* object_id,
const char* filter,
NPT_UInt32 starting_index,
NPT_UInt32 requested_count,
const char* sort_criteria,
const PLT_HttpRequestContext& context)
{
if (m_Delegate) {
return m_Delegate->OnBrowseDirectChildren(action,
object_id,
filter,
starting_index,
requested_count,
sort_criteria,
context);
}
return NPT_ERROR_NOT_IMPLEMENTED;
}
/*----------------------------------------------------------------------
| PLT_MediaServer::OnSearchContainer
+---------------------------------------------------------------------*/
NPT_Result
PLT_MediaServer::OnSearchContainer(PLT_ActionReference& action,
const char* object_id,
const char* search_criteria,
const char* filter,
NPT_UInt32 starting_index,
NPT_UInt32 requested_count,
const char* sort_criteria,
const PLT_HttpRequestContext& context)
{
if (m_Delegate) {
return m_Delegate->OnSearchContainer(action,
object_id,
search_criteria,
filter,
starting_index,
requested_count,
sort_criteria,
context);
}
return NPT_ERROR_NOT_IMPLEMENTED;
}
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h
index 6da21cb18a..1da6b91895 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Devices/MediaServer/PltSyncMediaBrowser.h
@@ -1,218 +1,218 @@
/*****************************************************************
|
| Platinum - Synchronous Media Browser
|
| Copyright (c) 2004-2010, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
| licensing@plutinosoft.com
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/** @file
UPnP AV Media Controller synchronous implementation.
*/
#ifndef _PLT_SYNC_MEDIA_BROWSER_
#define _PLT_SYNC_MEDIA_BROWSER_
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include "Neptune.h"
#include "PltCtrlPoint.h"
#include "PltMediaBrowser.h"
#include "PltMediaCache.h"
/*----------------------------------------------------------------------
| types
+---------------------------------------------------------------------*/
typedef NPT_Map<NPT_String, PLT_DeviceDataReference> PLT_DeviceMap;
typedef NPT_Map<NPT_String, PLT_DeviceDataReference>::Entry PLT_DeviceMapEntry;
typedef struct PLT_BrowseData {
NPT_SharedVariable shared_var;
NPT_Result res;
PLT_BrowseInfo info;
} PLT_BrowseData;
typedef NPT_Reference<PLT_BrowseData> PLT_BrowseDataReference;
typedef struct PLT_CapabilitiesData {
NPT_SharedVariable shared_var;
NPT_Result res;
NPT_String capabilities;
} PLT_CapabilitiesData;
typedef NPT_Reference<PLT_CapabilitiesData> PLT_CapabilitiesDataReference;
-// explicitely specify res otherwise WMP won't return a URL!
+// explicitly specify res otherwise WMP won't return a URL!
#define PLT_DEFAULT_FILTER "dc:date,dc:description,upnp:longDescription,upnp:genre,res,res@duration,res@size,upnp:albumArtURI,upnp:rating,upnp:lastPlaybackPosition,upnp:lastPlaybackTime,upnp:playbackCount,upnp:originalTrackNumber,upnp:episodeNumber,upnp:programTitle,upnp:seriesTitle,upnp:album,upnp:artist,upnp:author,upnp:director,dc:publisher,searchable,childCount,dc:title,dc:creator,upnp:actor,res@resolution,upnp:episodeCount,upnp:episodeSeason,xbmc:lastPlayerState,xbmc:dateadded,xbmc:rating,xbmc:votes,xbmc:artwork,xbmc:uniqueidentifier,xbmc:country,xbmc:userrating"
/*----------------------------------------------------------------------
| PLT_MediaContainerListener
+---------------------------------------------------------------------*/
class PLT_MediaContainerChangesListener
{
public:
virtual ~PLT_MediaContainerChangesListener() {}
virtual void OnContainerChanged(PLT_DeviceDataReference& device,
const char* item_id,
const char* update_id) = 0;
};
/*----------------------------------------------------------------------
| PLT_SyncMediaBrowser
+---------------------------------------------------------------------*/
class PLT_SyncMediaBrowser : public PLT_MediaBrowser,
public PLT_MediaBrowserDelegate
{
public:
PLT_SyncMediaBrowser(PLT_CtrlPointReference& ctrlPoint,
bool use_cache = false,
PLT_MediaContainerChangesListener* listener = NULL);
~PLT_SyncMediaBrowser() override;
// PLT_MediaBrowser methods
NPT_Result OnDeviceAdded(PLT_DeviceDataReference& device) override;
NPT_Result OnDeviceRemoved(PLT_DeviceDataReference& device) override;
// PLT_MediaBrowserDelegate methods
void OnMSStateVariablesChanged(PLT_Service* service,
NPT_List<PLT_StateVariable*>* vars) override;
void OnBrowseResult(NPT_Result res,
PLT_DeviceDataReference& device,
PLT_BrowseInfo* info,
void* userdata) override;
void OnSearchResult(NPT_Result res,
PLT_DeviceDataReference& device,
PLT_BrowseInfo* info,
void* userdata) override;
void OnGetSearchCapabilitiesResult(NPT_Result res,
PLT_DeviceDataReference& device,
NPT_String searchCapabilities,
void* userdata) override;
void OnGetSortCapabilitiesResult(NPT_Result res,
PLT_DeviceDataReference& device,
NPT_String sortCapabilities,
void* userdata) override;
// methods
void SetContainerListener(PLT_MediaContainerChangesListener* listener) {
m_ContainerListener = listener;
}
NPT_Result BrowseSync(PLT_DeviceDataReference& device,
const char* id,
PLT_MediaObjectListReference& list,
bool metadata = false,
NPT_Int32 start = 0,
NPT_Cardinal max_results = 0); // 0 means all
NPT_Result SearchSync(PLT_DeviceDataReference& device,
const char* container_id,
const char* search_criteria,
PLT_MediaObjectListReference& list,
NPT_Int32 start = 0,
NPT_Cardinal max_results = 0); // 0 means all
NPT_Result GetSearchCapabilitiesSync(PLT_DeviceDataReference& device,
NPT_String& searchCapabilities);
NPT_Result GetSortCapabilitiesSync(PLT_DeviceDataReference& device,
NPT_String& sortCapabilities);
const NPT_Lock<PLT_DeviceMap>& GetMediaServersMap() const { return m_MediaServers; }
bool IsCached(const char* uuid, const char* object_id);
protected:
NPT_Result BrowseSync(PLT_BrowseDataReference& browse_data,
PLT_DeviceDataReference& device,
const char* object_id,
NPT_Int32 index,
NPT_Int32 count,
bool browse_metadata = false,
const char* filter = PLT_DEFAULT_FILTER,
const char* sort = "");
NPT_Result SearchSync(PLT_BrowseDataReference& browse_data,
PLT_DeviceDataReference& device,
const char* container_id,
const char* search_criteria,
NPT_Int32 index,
NPT_Int32 count,
- const char* filter = PLT_DEFAULT_FILTER); // explicitely specify res otherwise WMP won't return a URL!
+ const char* filter = PLT_DEFAULT_FILTER); // explicitly specify res otherwise WMP won't return a URL!
private:
NPT_Result Find(const char* ip, PLT_DeviceDataReference& device);
NPT_Result WaitForResponse(NPT_SharedVariable& shared_var);
private:
NPT_Lock<PLT_DeviceMap> m_MediaServers;
PLT_MediaContainerChangesListener* m_ContainerListener;
bool m_UseCache;
PLT_MediaCache<PLT_MediaObjectListReference,NPT_String> m_Cache;
};
/*----------------------------------------------------------------------
| PLT_DeviceMapFinderByIp
+---------------------------------------------------------------------*/
class PLT_DeviceMapFinderByIp
{
public:
// methods
PLT_DeviceMapFinderByIp(const char* ip) : m_IP(ip) {}
bool operator()(const PLT_DeviceMapEntry* const& entry) const {
const PLT_DeviceDataReference& device = entry->GetValue();
return (device->GetURLBase().GetHost() == m_IP);
}
private:
// members
NPT_String m_IP;
};
/*----------------------------------------------------------------------
| PLT_DeviceFinderByUUID
+---------------------------------------------------------------------*/
class PLT_DeviceMapFinderByUUID
{
public:
// methods
PLT_DeviceMapFinderByUUID(const char* uuid) : m_UUID(uuid) {}
bool operator()(const PLT_DeviceMapEntry* const& entry) const {
PLT_DeviceDataReference device = entry->GetValue();
return device->GetUUID() == m_UUID;
}
private:
// members
NPT_String m_UUID;
};
#endif /* _PLT_SYNC_MEDIA_BROWSER_ */
diff --git a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Tools/TextToHeader/TextToHeader.cpp b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Tools/TextToHeader/TextToHeader.cpp
index 2e1aad7e89..15697f8441 100644
--- a/core/utilities/mediaserver/upnpsdk/Platinum/Source/Tools/TextToHeader/TextToHeader.cpp
+++ b/core/utilities/mediaserver/upnpsdk/Platinum/Source/Tools/TextToHeader/TextToHeader.cpp
@@ -1,267 +1,267 @@
/*****************************************************************
|
| Platinum - Tool text to .h
|
| Copyright (c) 2004-2008, Plutinosoft, LLC.
| All rights reserved.
| http://www.plutinosoft.com
|
| This program is free software; you can redistribute it and/or
| modify it under the terms of the GNU General Public License
| as published by the Free Software Foundation; either version 2
| of the License, or (at your option) any later version.
|
| OEMs, ISVs, VARs and other distributors that combine and
| distribute commercially licensed software with Platinum software
| and do not wish to distribute the source code for the commercially
| licensed software under version 2, or (at your option) any later
| version, of the GNU General Public License (the "GPL") must enter
| into a commercial license agreement with Plutinosoft, LLC.
|
| This program is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License
| along with this program; see the file LICENSE.txt. If not, write to
| the Free Software Foundation, Inc.,
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| http://www.gnu.org/licenses/gpl-2.0.html
|
****************************************************************/
/*----------------------------------------------------------------------
| includes
+---------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
/*----------------------------------------------------------------------
| globals
+---------------------------------------------------------------------*/
static struct {
const char* in_filename;
const char* variable_name;
const char* header_name;
const char* out_filename;
} Options;
/*----------------------------------------------------------------------
| PrintUsageAndExit
+---------------------------------------------------------------------*/
static void
PrintUsageAndExit(char** args)
{
- fprintf(stderr, "usage: %s [-v <variable> -h <header name>] <intput> <output>\n", args[0]);
+ fprintf(stderr, "usage: %s [-v <variable> -h <header name>] <input> <output>\n", args[0]);
fprintf(stderr, "-v : optional variable name\n");
fprintf(stderr, "-h : optional header name\n");
fprintf(stderr, "<input> : input scpd filename\n");
fprintf(stderr, "<output> : output filename\n");
exit(1);
}
/*----------------------------------------------------------------------
| ParseCommandLine
+---------------------------------------------------------------------*/
static void
ParseCommandLine(char** args)
{
const char* arg;
char** tmp = args+1;
/* default values */
Options.in_filename = NULL;
Options.variable_name = NULL;
Options.out_filename = NULL;
while ((arg = *tmp++)) {
if (!strcmp(arg, "-v")) {
Options.variable_name = *tmp++;
} else if (!strcmp(arg, "-h")) {
Options.header_name = *tmp++;
} else if (Options.in_filename == NULL) {
Options.in_filename = arg;
} else if (Options.out_filename == NULL) {
Options.out_filename = arg;
} else {
fprintf(stderr, "ERROR: too many arguments\n");
PrintUsageAndExit(args);
}
}
/* check args */
if (Options.in_filename == NULL) {
fprintf(stderr, "ERROR: input filename missing\n");
PrintUsageAndExit(args);
}
if (Options.out_filename == NULL) {
fprintf(stderr, "ERROR: output filename missing\n");
PrintUsageAndExit(args);
}
}
/*----------------------------------------------------------------------
| PrintHex
+---------------------------------------------------------------------*/
/*static void
PrintHex(unsigned char* h, unsigned int size)
{
unsigned int i;
for (i=0; i<size; i++) {
printf("%c%c",
h[i]>>4 >= 10 ?
'A' + (h[i]>>4)-10 :
'0' + (h[i]>>4),
(h[i]&0xF) >= 10 ?
'A' + (h[i]&0xF)-10 :
'0' + (h[i]&0xF));
}
}*/
/*----------------------------------------------------------------------
| PrintHexForHeader
+---------------------------------------------------------------------*/
static void
PrintHexForHeader(FILE* out, unsigned char h)
{
fprintf(out, "0x%c%c",
h>>4 >= 10 ?
'A' + (h>>4)-10 :
'0' + (h>>4),
(h&0xF) >= 10 ?
'A' + (h&0xF)-10 :
'0' + (h&0xF));
}
/*----------------------------------------------------------------------
| main
+---------------------------------------------------------------------*/
int
main(int /*argc*/, char** argv)
{
FILE* in;
FILE* out;
unsigned char* data_block = NULL;
unsigned long data_block_size;
unsigned long k;
unsigned char col;
/* parse command line */
ParseCommandLine(argv);
/* open input */
in = fopen(Options.in_filename, "rb");
if (in == NULL) {
fprintf(stderr, "ERROR: cannot open input file (%s): %s\n",
Options.in_filename, strerror(errno));
}
/* read data in one chunk */
{
struct stat info;
if (stat(Options.in_filename, &info)) {
fprintf(stderr, "ERROR: cannot get input file size\n");
return 1;
}
data_block_size = info.st_size;
data_block = (unsigned char*)new unsigned char[data_block_size+1];
if (data_block == NULL) {
fprintf(stderr, "ERROR: out of memory\n");
return 1;
}
if (fread(data_block, data_block_size, 1, in) != 1) {
fprintf(stderr, "ERROR: cannot read input file\n");
return 1;
}
data_block[data_block_size++] = 0;
}
/* open output */
out = fopen(Options.out_filename, "w+");
if (out == NULL) {
fprintf(stderr, "ERROR: cannot open out output file (%s): %s\n",
Options.out_filename, strerror(errno));
}
fprintf(out,
"/*****************************************************************\n"
"|\n"
"| Platinum - %s SCPD\n"
"|\n"
"| Copyright (c) 2004-2008, Plutinosoft, LLC.\n"
"| All rights reserved.\n"
"| http://www.plutinosoft.com\n"
"|\n"
"| This program is free software; you can redistribute it and/or\n"
"| modify it under the terms of the GNU General Public License\n"
"| as published by the Free Software Foundation; either version 2\n"
"| of the License, or (at your option) any later version.\n"
"|\n"
"| OEMs, ISVs, VARs and other distributors that combine and \n"
"| distribute commercially licensed software with Platinum software\n"
"| and do not wish to distribute the source code for the commercially\n"
"| licensed software under version 2, or (at your option) any later\n"
"| version, of the GNU General Public License (the \"GPL\") must enter\n"
"| into a commercial license agreement with Plutinosoft, LLC.\n"
"| \n"
"| This program is distributed in the hope that it will be useful,\n"
"| but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"| GNU General Public License for more details.\n"
"|\n"
"| You should have received a copy of the GNU General Public License\n"
"| along with this program; see the file LICENSE.txt. If not, write to\n"
"| the Free Software Foundation, Inc., \n"
"| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
"| http://www.gnu.org/licenses/gpl-2.0.html\n"
"|\n"
"****************************************************************/\n",
Options.header_name?Options.header_name:"");
fprintf(out, "\n"
"/*----------------------------------------------------------------------\n"
"| includes\n"
"+---------------------------------------------------------------------*/\n");
fprintf(out, "#include \"NptTypes.h\"\n");
fprintf(out, "\n"
"/*----------------------------------------------------------------------\n"
"| globals\n"
"+---------------------------------------------------------------------*/\n");
fprintf(out, "NPT_UInt8 %s[] =\n",
Options.variable_name?Options.variable_name:"kData");
fprintf(out, "{\n ");
col = 0;
/* rewind the input file */
fseek(in, 0, SEEK_SET);
for (k = 0; k < data_block_size; k++) {
//PrintHex(&data_block[k], 1);
PrintHexForHeader(out, data_block[k]);
if (k < data_block_size - 1) fprintf(out, ", ");
/* wrap around 20 columns */
if (++col > 19) {
col = 0;
fprintf(out, "\n ");
}
}
/* print footer */
fprintf(out, "\n};\n\n");
/* close file */
fclose(out);
/* close file */
fclose(in);
if (data_block) {
delete[] data_block;
}
return 0;
}
diff --git a/core/utilities/metadataedit/iptc/iptccredits.cpp b/core/utilities/metadataedit/iptc/iptccredits.cpp
index a7f9962eea..627571cb67 100644
--- a/core/utilities/metadataedit/iptc/iptccredits.cpp
+++ b/core/utilities/metadataedit/iptc/iptccredits.cpp
@@ -1,309 +1,309 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2006-10-12
* Description : IPTC credits settings page.
*
* Copyright (C) 2006-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "iptccredits.h"
// Qt includes
#include <QCheckBox>
#include <QLabel>
#include <QPushButton>
#include <QValidator>
#include <QGridLayout>
#include <QApplication>
#include <QStyle>
#include <QLineEdit>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "multistringsedit.h"
#include "dmetadata.h"
namespace Digikam
{
class Q_DECL_HIDDEN IPTCCredits::Private
{
public:
explicit Private()
{
copyrightCheck = 0;
creditCheck = 0;
sourceCheck = 0;
copyrightEdit = 0;
creditEdit = 0;
sourceEdit = 0;
bylineEdit = 0;
bylineTitleEdit = 0;
contactEdit = 0;
}
QCheckBox* copyrightCheck;
QCheckBox* creditCheck;
QCheckBox* sourceCheck;
QLineEdit* copyrightEdit;
QLineEdit* creditEdit;
QLineEdit* sourceEdit;
MultiStringsEdit* bylineEdit;
MultiStringsEdit* bylineTitleEdit;
MultiStringsEdit* contactEdit;
};
IPTCCredits::IPTCCredits(QWidget* const parent)
: QWidget(parent),
d(new Private)
{
QGridLayout* const grid = new QGridLayout(this);
// IPTC only accept printable Ascii char.
QRegExp asciiRx(QLatin1String("[\x20-\x7F]+$"));
QValidator* const asciiValidator = new QRegExpValidator(asciiRx, this);
// --------------------------------------------------------
d->copyrightCheck = new QCheckBox(i18n("Copyright:"), this);
d->copyrightEdit = new QLineEdit(this);
d->copyrightEdit->setClearButtonEnabled(true);
d->copyrightEdit->setValidator(asciiValidator);
d->copyrightEdit->setMaxLength(128);
d->copyrightEdit->setWhatsThis(i18n("Set here the necessary copyright notice. This field is limited "
"to 128 ASCII characters."));
// --------------------------------------------------------
d->bylineEdit = new MultiStringsEdit(this, i18n("Byline:"),
i18n("Set here the name of content creator."),
true, 32);
// --------------------------------------------------------
d->bylineTitleEdit = new MultiStringsEdit(this, i18n("Byline Title:"),
i18n("Set here the title of content creator."),
true, 32);
// --------------------------------------------------------
d->creditCheck = new QCheckBox(i18n("Credit:"), this);
d->creditEdit = new QLineEdit(this);
d->creditEdit->setClearButtonEnabled(true);
d->creditEdit->setValidator(asciiValidator);
d->creditEdit->setMaxLength(32);
d->creditEdit->setWhatsThis(i18n("Set here the content provider. "
"This field is limited to 32 ASCII characters."));
// --------------------------------------------------------
d->sourceCheck = new QCheckBox(i18nc("original owner of content", "Source:"), this);
d->sourceEdit = new QLineEdit(this);
d->sourceEdit->setClearButtonEnabled(true);
d->sourceEdit->setValidator(asciiValidator);
d->sourceEdit->setMaxLength(32);
d->sourceEdit->setWhatsThis(i18n("Set here the original owner of content. "
"This field is limited to 32 ASCII characters."));
// --------------------------------------------------------
d->contactEdit = new MultiStringsEdit(this, i18n("Contact:"),
- i18n("Set here the person or organisation to contact."),
+ i18n("Set here the person or organization to contact."),
true, 128);
// --------------------------------------------------------
QLabel* const note = new QLabel(i18n("<b>Note: "
"<b><a href='http://en.wikipedia.org/wiki/IPTC_Information_Interchange_Model'>IPTC</a></b> "
"text tags only support the printable "
"<b><a href='http://en.wikipedia.org/wiki/Ascii'>ASCII</a></b> "
"characters and limit string sizes. "
"Use contextual help for details.</b>"), this);
note->setOpenExternalLinks(true);
note->setWordWrap(true);
note->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
// --------------------------------------------------------
grid->addWidget(d->bylineEdit, 0, 0, 1, 3);
grid->addWidget(d->bylineTitleEdit, 1, 0, 1, 3);
grid->addWidget(d->contactEdit, 2, 0, 1, 3);
grid->addWidget(d->creditCheck, 3, 0, 1, 1);
grid->addWidget(d->creditEdit, 3, 1, 1, 2);
grid->addWidget(d->sourceCheck, 4, 0, 1, 1);
grid->addWidget(d->sourceEdit, 4, 1, 1, 2);
grid->addWidget(d->copyrightCheck, 5, 0, 1, 1);
grid->addWidget(d->copyrightEdit, 5, 1, 1, 2);
grid->addWidget(note, 6, 0, 1, 3);
grid->setColumnStretch(2, 10);
grid->setRowStretch(7, 10);
grid->setContentsMargins(QMargins());
grid->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing));
// --------------------------------------------------------
connect(d->copyrightCheck, SIGNAL(toggled(bool)),
d->copyrightEdit, SLOT(setEnabled(bool)));
connect(d->creditCheck, SIGNAL(toggled(bool)),
d->creditEdit, SLOT(setEnabled(bool)));
connect(d->sourceCheck, SIGNAL(toggled(bool)),
d->sourceEdit, SLOT(setEnabled(bool)));
// --------------------------------------------------------
connect(d->copyrightCheck, SIGNAL(toggled(bool)),
this, SIGNAL(signalModified()));
connect(d->bylineEdit, SIGNAL(signalModified()),
this, SIGNAL(signalModified()));
connect(d->bylineTitleEdit, SIGNAL(signalModified()),
this, SIGNAL(signalModified()));
connect(d->creditCheck, SIGNAL(toggled(bool)),
this, SIGNAL(signalModified()));
connect(d->sourceCheck, SIGNAL(toggled(bool)),
this, SIGNAL(signalModified()));
connect(d->contactEdit, SIGNAL(signalModified()),
this, SIGNAL(signalModified()));
// --------------------------------------------------------
connect(d->copyrightEdit, SIGNAL(textChanged(QString)),
this, SIGNAL(signalModified()));
connect(d->creditEdit, SIGNAL(textChanged(QString)),
this, SIGNAL(signalModified()));
connect(d->sourceEdit, SIGNAL(textChanged(QString)),
this, SIGNAL(signalModified()));
}
IPTCCredits::~IPTCCredits()
{
delete d;
}
void IPTCCredits::readMetadata(QByteArray& iptcData)
{
blockSignals(true);
DMetadata meta;
meta.setIptc(iptcData);
QString data;
QStringList list;
d->copyrightEdit->clear();
d->copyrightCheck->setChecked(false);
data = meta.getIptcTagString("Iptc.Application2.Copyright", false);
if (!data.isNull())
{
d->copyrightEdit->setText(data);
d->copyrightCheck->setChecked(true);
}
d->copyrightEdit->setEnabled(d->copyrightCheck->isChecked());
list = meta.getIptcTagsStringList("Iptc.Application2.Byline", false);
d->bylineEdit->setValues(list);
list = meta.getIptcTagsStringList("Iptc.Application2.BylineTitle", false);
d->bylineTitleEdit->setValues(list);
d->creditEdit->clear();
d->creditCheck->setChecked(false);
data = meta.getIptcTagString("Iptc.Application2.Credit", false);
if (!data.isNull())
{
d->creditEdit->setText(data);
d->creditCheck->setChecked(true);
}
d->creditEdit->setEnabled(d->creditCheck->isChecked());
d->sourceEdit->clear();
d->sourceCheck->setChecked(false);
data = meta.getIptcTagString("Iptc.Application2.Source", false);
if (!data.isNull())
{
d->sourceEdit->setText(data);
d->sourceCheck->setChecked(true);
}
d->sourceEdit->setEnabled(d->sourceCheck->isChecked());
list = meta.getIptcTagsStringList("Iptc.Application2.Contact", false);
d->contactEdit->setValues(list);
blockSignals(false);
}
void IPTCCredits::applyMetadata(QByteArray& iptcData)
{
QStringList oldList, newList;
DMetadata meta;
meta.setIptc(iptcData);
if (d->copyrightCheck->isChecked())
meta.setIptcTagString("Iptc.Application2.Copyright", d->copyrightEdit->text());
else
meta.removeIptcTag("Iptc.Application2.Copyright");
if (d->bylineEdit->getValues(oldList, newList))
meta.setIptcTagsStringList("Iptc.Application2.Byline", 32, oldList, newList);
else
meta.removeIptcTag("Iptc.Application2.Byline");
if (d->bylineTitleEdit->getValues(oldList, newList))
meta.setIptcTagsStringList("Iptc.Application2.BylineTitle", 32, oldList, newList);
else
meta.removeIptcTag("Iptc.Application2.BylineTitle");
if (d->creditCheck->isChecked())
meta.setIptcTagString("Iptc.Application2.Credit", d->creditEdit->text());
else
meta.removeIptcTag("Iptc.Application2.Credit");
if (d->sourceCheck->isChecked())
meta.setIptcTagString("Iptc.Application2.Source", d->sourceEdit->text());
else
meta.removeIptcTag("Iptc.Application2.Source");
if (d->contactEdit->getValues(oldList, newList))
meta.setIptcTagsStringList("Iptc.Application2.Contact", 128, oldList, newList);
else
meta.removeIptcTag("Iptc.Application2.Contact");
iptcData = meta.getIptc();
}
} // namespace Digikam
diff --git a/core/utilities/searchwindow/searchmodificationhelper.h b/core/utilities/searchwindow/searchmodificationhelper.h
index 00180aea06..5ba452533d 100644
--- a/core/utilities/searchwindow/searchmodificationhelper.h
+++ b/core/utilities/searchwindow/searchmodificationhelper.h
@@ -1,213 +1,213 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2000-12-05
* Description : helper class used to modify search albums in views
*
* Copyright (C) 2009-2010 by Johannes Wienke <languitar at semipol dot de>
* Copyright (C) 2014-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_SEARCH_MODIFICATION_HELPER_H
#define DIGIKAM_SEARCH_MODIFICATION_HELPER_H
// Qt includes
#include <QObject>
-// Local inclues
+// Local includes
#include "album.h"
namespace Digikam
{
class SketchWidget;
class ImageInfo;
/**
* Range of a contiguous dates selection <start date, end date>.
*/
typedef QPair<QDateTime, QDateTime> DateRange;
/**
* List of dates range selected.
*/
typedef QList<DateRange> DateRangeList;
/**
* Utility class providing methods to modify search albums (SAlbum) in a way
* useful to implement views.
*
* @author jwienke
*/
class SearchModificationHelper: public QObject
{
Q_OBJECT
public:
/**
* Constructor.
*
* @param parent parent for qt parent child mechanism
* @param dialogParent paret widget for dialogs displayed by this object
*/
SearchModificationHelper(QObject* const parent, QWidget* const dialogParent);
/**
* Destructor.
*/
virtual ~SearchModificationHelper();
/**
* @see slotCreateFuzzySearchFromSketch()
* @return the newly created album
*/
SAlbum* createFuzzySearchFromSketch(const QString& name,
SketchWidget* sketchWidget,
unsigned int numberOfResults,
QList<int>& targetAlbums,
bool overwriteIfExisting = false);
/**
* @see slotCreateFuzzySearchFromDropped()
* @return the newly created album
*/
SAlbum* createFuzzySearchFromDropped(const QString& name,
const QString& filePath,
float threshold,
float maxThreshold,
QList<int>& targetAlbums,
bool overwriteIfExisting = false);
/**
* @see slotCreateFuzzySearchFromImage()
* @return the newly created album
*/
SAlbum* createFuzzySearchFromImage(const QString& name,
const ImageInfo& image,
float threshold,
float maxThreshold,
QList<int>& targetAlbums,
bool overwriteIfExisting = false);
public Q_SLOTS:
/**
* Deletes the given search after prompting the user.
*
* @param searchAlbum search to delete
*/
void slotSearchDelete(SAlbum* searchAlbum);
/**
* Renames the given search via a dialog.
*
* @param searchAlbum search to rename
*/
void slotSearchRename(SAlbum* searchAlbum);
/**
* Creates a new timeline search.
*
* @param desiredName desired name for the search. If this name already
* exists and overwriteIfExisting is false, then the user
* will be prompted for a new name
* @param dateRanges date ranges to contain in this timeline search. If this
* is empty, no search will be created.
* @param overwriteIfExisting if true, an existing search with the desired
* name will be overwritten without prompting the
* user for a new name
*/
SAlbum* slotCreateTimeLineSearch(const QString& desiredName,
const DateRangeList& dateRanges,
bool overwriteIfExisting = false);
/**
* Creates a new fuzzy search based on a sketch created by the user and
* selects it in the AlbumManager after creation.
*
* @param name name of the new sketch search
* @param sketchWidget the widget containing the sketch of the user
* @param numberOfResults max number of results to display
* @param targetAlbums The image must be in one of these albums
* @param overwriteIfExisting if true, an existing search with the desired
* name will be overwritten without prompting the
* user for a new name
*/
void slotCreateFuzzySearchFromSketch(const QString& name,
SketchWidget* sketchWidget,
unsigned int numberOfResults,
QList<int>& targetAlbums,
bool overwriteIfExisting = false);
/**
* Creates a new fuzzy search for finding similar photos based on one photo
* and selects it in the album manager after creation.
*
* @param name of the new search
* @param image image to base this search on
* @param threshold threshold for image search, 0 <= threshold <= 1
* @param maxThreshold the maximum threshold of similarity.
* @param targetAlbums The image must be in one of these albums
* @param overwriteIfExisting if true, an existing search with the desired
* name will be overwritten without prompting the
* user for a new name
*/
void slotCreateFuzzySearchFromImage(const QString& name,
const ImageInfo& image,
float threshold,
float maxThreshold,
QList<int>& targetAlbums,
bool overwriteIfExisting = false);
/**
* Creates a new fuzzy search for finding similar photos based on the file
* path of a photo
* and selects it in the album manager after creation.
*
* @param name of the new search
* @param filePath path of the image to base this search on
* @param threshold minimum threshold for image search
* @param maxThreshold maximum threshold for image search
* @param targetAlbums The image must be in one of these albums
* @param overwriteIfExisting if true, an existing search with the desired
* name will be overwritten without prompting the
* user for a new name
*/
void slotCreateFuzzySearchFromDropped(const QString& name,
const QString& filePath,
float threshold,
float maxThreshold,
QList<int>& targetAlbums,
bool overwriteIfExisting);
private:
bool checkAlbum(const QString& name) const;
bool checkName(QString& name);
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_SEARCH_MODIFICATION_HELPER_H
diff --git a/core/utilities/setup/metadata/namespaceeditdlg.cpp b/core/utilities/setup/metadata/namespaceeditdlg.cpp
index 2f33a22e41..6aa8a75e7f 100644
--- a/core/utilities/setup/metadata/namespaceeditdlg.cpp
+++ b/core/utilities/setup/metadata/namespaceeditdlg.cpp
@@ -1,688 +1,688 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2015-07-03
* Description : dialog to edit and create digiKam xmp namespaces
*
* Copyright (C) 2015 by Veaceslav Munteanu <veaceslav dot munteanu90 at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#include "namespaceeditdlg.h"
// Qt includes
#include <QGridLayout>
#include <QLabel>
#include <QDialogButtonBox>
#include <QDialog>
#include <QPushButton>
#include <QApplication>
#include <QStyle>
#include <QPointer>
#include <QLineEdit>
#include <QCheckBox>
#include <QStandardPaths>
#include <QSpinBox>
#include <QDebug>
#include <QComboBox>
#include <QGroupBox>
// KDE includes
#include <klocalizedstring.h>
// Local includes
#include "dxmlguiwindow.h"
#include "digikam_debug.h"
namespace Digikam
{
class Q_DECL_HIDDEN NamespaceEditDlg::Private
{
public:
explicit Private()
{
buttons = 0;
create = 0;
topLabel = 0;
logo = 0;
gridLayout = 0;
page = 0;
// NamespaceEntry variables
subspaceCombo = 0;
specialOptsCombo = 0;
altSpecialOptsCombo = 0;
namespaceName = 0;
alternativeName = 0;
nameSpaceSeparator = 0;
isPath = 0;
ratingMappings = 0;
zeroStars = 0;
oneStar = 0;
twoStars = 0;
threeStars = 0;
fourStars = 0;
fiveStars = 0;
// Labels
tagTipLabel = 0;
ratingTipLabel = 0;
commentTipLabel = 0;
subspaceLabel = 0;
titleLabel = 0;
specialOptsLabel = 0;
alternativeNameLabel = 0;
altspecialOptsLabel = 0;
isTagLabel = 0;
separatorLabel = 0;
tipLabel2 = 0;
nsType = NamespaceEntry::TAGS;
}
QDialogButtonBox* buttons;
bool create;
QLabel* topLabel;
QLabel* logo;
QGridLayout* gridLayout;
QWidget* page;
// NamespaceEntry variables
QComboBox* subspaceCombo;
QComboBox* specialOptsCombo;
QComboBox* altSpecialOptsCombo;
QLineEdit* namespaceName;
QLineEdit* alternativeName;
QLineEdit* nameSpaceSeparator;
QCheckBox* isPath;
QGroupBox* ratingMappings;
QSpinBox* zeroStars;
QSpinBox* oneStar;
QSpinBox* twoStars;
QSpinBox* threeStars;
QSpinBox* fourStars;
QSpinBox* fiveStars;
// Labels
QLabel* tagTipLabel;
QLabel* ratingTipLabel;
QLabel* commentTipLabel;
QLabel* subspaceLabel;
QLabel* titleLabel;
QLabel* specialOptsLabel;
QLabel* alternativeNameLabel;
QLabel* altspecialOptsLabel;
QLabel* isTagLabel;
QLabel* separatorLabel;
QLabel* tipLabel2;
NamespaceEntry::NamespaceType nsType;
};
NamespaceEditDlg::NamespaceEditDlg(bool create,
NamespaceEntry& entry,
QWidget* const parent)
: QDialog(parent),
d(new Private())
{
setModal(true);
d->buttons = new QDialogButtonBox(QDialogButtonBox::Help |
QDialogButtonBox::Ok |
QDialogButtonBox::Cancel, this);
d->buttons->button(QDialogButtonBox::Ok)->setDefault(true);
if (create)
{
setWindowTitle(i18n("New Xmp Namespace"));
}
else
{
setWindowTitle(i18n("Edit Xmp Namespace"));
}
d->create = create;
d->nsType = entry.nsType;
setupTagGui(entry);
// --------------------------------------------------------
connect(d->buttons->button(QDialogButtonBox::Ok), SIGNAL(clicked()),
this, SLOT(accept()));
connect(d->buttons->button(QDialogButtonBox::Cancel), SIGNAL(clicked()),
this, SLOT(reject()));
connect(d->buttons->button(QDialogButtonBox::Help), SIGNAL(clicked()),
this, SLOT(slotHelp()));
// --------------------------------------------------------
if (!d->create)
{
populateFields(entry);
}
setType(entry.nsType);
if (entry.isDefault)
{
makeReadOnly();
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Entry type" << entry.nsType
<< "subspace" << entry.subspace
<< entry.isDefault;
adjustSize();
}
NamespaceEditDlg::~NamespaceEditDlg()
{
delete d;
}
bool NamespaceEditDlg::create(QWidget* const parent, NamespaceEntry& entry)
{
QPointer<NamespaceEditDlg> dlg = new NamespaceEditDlg(true,entry,parent);
qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName;
bool valRet = dlg->exec();
if (valRet == QDialog::Accepted)
{
dlg->saveData(entry);
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Name after save: " << entry.namespaceName;
delete dlg;
return valRet;
}
bool NamespaceEditDlg::edit(QWidget* const parent, NamespaceEntry& entry)
{
QPointer<NamespaceEditDlg> dlg = new NamespaceEditDlg(false, entry, parent);
qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName;
bool valRet = dlg->exec();
if (valRet == QDialog::Accepted && !entry.isDefault)
{
dlg->saveData(entry);
}
qCDebug(DIGIKAM_GENERAL_LOG) << "Name before save: " << entry.namespaceName;
delete dlg;
return valRet;
}
void NamespaceEditDlg::setupTagGui(NamespaceEntry& entry)
{
d->page = new QWidget(this);
d->gridLayout = new QGridLayout(d->page);
d->logo = new QLabel(d->page);
d->logo->setPixmap(QIcon::fromTheme(QLatin1String("digikam")).pixmap(QSize(48,48)));
d->topLabel = new QLabel(d->page);
d->topLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
d->topLabel->setWordWrap(false);
d->topLabel->setText(i18n("Add metadata namespace"));
d->subspaceCombo = new QComboBox(this);
d->subspaceLabel = new QLabel(d->page);
d->subspaceLabel->setText(i18n("Metadata Subspace"));
d->subspaceCombo->addItem(QLatin1String("EXIF"), (int)NamespaceEntry::EXIF);
d->subspaceCombo->addItem(QLatin1String("IPTC"), (int)NamespaceEntry::IPTC);
d->subspaceCombo->addItem(QLatin1String("XMP"), (int)NamespaceEntry::XMP);
d->subspaceCombo->setCurrentIndex((int)entry.subspace);
- qCDebug(DIGIKAM_GENERAL_LOG) << "Enrty subspace" << (int)entry.subspace;
+ qCDebug(DIGIKAM_GENERAL_LOG) << "Entry subspace" << (int)entry.subspace;
// -------------------Tag Elements---------------------------------
d->titleLabel = new QLabel(d->page);
d->titleLabel->setText(i18n("Name:"));
d->namespaceName = new QLineEdit(this);
//----------------- Tip Labels --------------------------------------
d->tagTipLabel = new QLabel(d->page);
d->tagTipLabel->setTextFormat(Qt::RichText);
d->tagTipLabel->setWordWrap(true);
d->tagTipLabel->setText(i18n("<p>To create new namespaces, you need to specify parameters:</p>"
"<p><ul><li>Namespace name with dots.<br/>"
"Ex.: <i>\"Xmp.digiKam.TagsList\"</i></li>"
"<li>Separator parameter, used by tag paths <br/>"
"Ex.: \"City/Paris\" or \"City|Paris\"</li>"
"<li>Specify if only keyword or the whole path must be written.</li></ul></p>"
));
d->ratingTipLabel = new QLabel(d->page);
d->ratingTipLabel->setTextFormat(Qt::RichText);
d->ratingTipLabel->setWordWrap(true);
d->ratingTipLabel->setText(i18n("<p>To create new rating namespaces, you need to specify parameters:</p>"
"<p><ul><li>Namespace name with dots.<br/>"
"Ex.: <i>\"Xmp.xmp.Rating\"</i></li>"
"<li>Rating mappings, if namespace need other values than 0-5 <br/>"
"Ex.: Microsoft uses 0 1 25 50 75 99</li>"
"<li>Select the correct namespace option from list.</li></ul></p>"
));
d->commentTipLabel = new QLabel(d->page);
d->commentTipLabel->setTextFormat(Qt::RichText);
d->commentTipLabel->setWordWrap(true);
d->commentTipLabel->setText(i18n("<p>To create new comment namespaces, you need to specify parameters:</p>"
"<p><ul><li>Namespace name with dots.<br/>"
"Ex.: <i>\"Xmp.xmp.Rating\"</i></li>"
"<li>Select the correct namespace option from list.</li></ul></p>"
));
// -------------------------------------------------------
d->specialOptsLabel = new QLabel(d->page);
d->specialOptsLabel->setText(i18n("Special Options"));
d->specialOptsCombo = new QComboBox(d->page);
d->specialOptsCombo->addItem(QLatin1String("NO_OPTS"), (int)NamespaceEntry::NO_OPTS);
if (entry.nsType == NamespaceEntry::COMMENT)
{
d->specialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANG"), NamespaceEntry::COMMENT_ALTLANG);
d->specialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANGLIST"), NamespaceEntry::COMMENT_ATLLANGLIST);
d->specialOptsCombo->addItem(QLatin1String("COMMENT_XMP"), NamespaceEntry::COMMENT_XMP);
d->specialOptsCombo->addItem(QLatin1String("COMMENT_JPEG"), NamespaceEntry::COMMENT_JPEG);
}
if (entry.nsType == NamespaceEntry::TAGS)
{
d->specialOptsCombo->addItem(QLatin1String("TAG_XMPBAG"), NamespaceEntry::TAG_XMPBAG);
d->specialOptsCombo->addItem(QLatin1String("TAG_XMPSEQ"), NamespaceEntry::TAG_XMPSEQ);
d->specialOptsCombo->addItem(QLatin1String("TAG_ACDSEE"), NamespaceEntry::TAG_ACDSEE);
}
d->alternativeNameLabel = new QLabel(d->page);
d->alternativeNameLabel->setText(i18n("Alternative name"));
d->alternativeName = new QLineEdit(d->page);
d->altspecialOptsLabel = new QLabel(d->page);
d->altspecialOptsLabel->setText(i18n("Alternative special options"));
d->altSpecialOptsCombo = new QComboBox(d->page);
d->altSpecialOptsCombo->addItem(QLatin1String("NO_OPTS"), (int)NamespaceEntry::NO_OPTS);
if (entry.nsType == NamespaceEntry::COMMENT)
{
d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANG"), NamespaceEntry::COMMENT_ALTLANG);
d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_ALTLANGLIST"), NamespaceEntry::COMMENT_ATLLANGLIST);
d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_XMP"), NamespaceEntry::COMMENT_XMP);
d->altSpecialOptsCombo->addItem(QLatin1String("COMMENT_JPEG"), NamespaceEntry::COMMENT_JPEG);
}
if (entry.nsType == NamespaceEntry::TAGS)
{
d->altSpecialOptsCombo->addItem(QLatin1String("TAG_XMPBAG"), NamespaceEntry::TAG_XMPBAG);
d->altSpecialOptsCombo->addItem(QLatin1String("TAG_XMPSEQ"), NamespaceEntry::TAG_XMPSEQ);
d->altSpecialOptsCombo->addItem(QLatin1String("TAG_ACDSEE"), NamespaceEntry::TAG_ACDSEE);
}
// --------------------------------------------------------
d->separatorLabel = new QLabel(d->page);
d->separatorLabel->setText(i18n("Separator:"));
d->nameSpaceSeparator = new QLineEdit(this);
// --------------------------------------------------------
d->isTagLabel = new QLabel(d->page);
d->isTagLabel->setText(i18n("Set Tags Path:"));
d->isPath = new QCheckBox(this);
d->tipLabel2 = new QLabel(d->page);
d->tipLabel2->setTextFormat(Qt::RichText);
d->tipLabel2->setWordWrap(true);
QPalette sample_palette;
sample_palette.setColor(QPalette::Window, QColor(255, 51, 51, 150));
sample_palette.setColor(QPalette::WindowText, Qt::black);
d->tipLabel2->setAutoFillBackground(true);
d->tipLabel2->setPalette(sample_palette);
d->tipLabel2->hide();
// ----------------------Rating Elements----------------------------------
d->ratingMappings = new QGroupBox(this);
d->ratingMappings->setFlat(true);
QGridLayout* const ratingMappingsLayout = new QGridLayout(d->ratingMappings);
QLabel* const ratingLabel = new QLabel(d->page);
ratingLabel->setText(i18n("Rating Mapping:"));
d->zeroStars = new QSpinBox(this);
d->zeroStars->setValue(0);
d->oneStar = new QSpinBox(this);
d->oneStar->setValue(1);
d->twoStars = new QSpinBox(this);
d->twoStars->setValue(2);
d->threeStars = new QSpinBox(this);
d->threeStars->setValue(3);
d->fourStars = new QSpinBox(this);
d->fourStars->setValue(4);
d->fiveStars = new QSpinBox(this);
d->fiveStars->setValue(5);
const int spacing = QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing);
const int cmargin = QApplication::style()->pixelMetric(QStyle::PM_DefaultChildMargin);
ratingMappingsLayout->addWidget(ratingLabel, 0, 0, 1, 2);
ratingMappingsLayout->addWidget(d->zeroStars, 1, 0, 1, 1);
ratingMappingsLayout->addWidget(d->oneStar, 1, 1, 1, 1);
ratingMappingsLayout->addWidget(d->twoStars, 1, 2, 1, 1);
ratingMappingsLayout->addWidget(d->threeStars, 1, 3, 1, 1);
ratingMappingsLayout->addWidget(d->fourStars, 1, 4, 1, 1);
ratingMappingsLayout->addWidget(d->fiveStars, 1, 5, 1, 1);
d->gridLayout->addWidget(d->logo, 0, 0, 1, 2);
d->gridLayout->addWidget(d->topLabel, 0, 1, 1, 4);
d->gridLayout->addWidget(d->tagTipLabel, 1, 0, 1, 6);
d->gridLayout->addWidget(d->ratingTipLabel, 2, 0, 1, 6);
d->gridLayout->addWidget(d->commentTipLabel, 3, 0, 1, 6);
d->gridLayout->addWidget(d->subspaceLabel, 5, 0, 1, 2);
d->gridLayout->addWidget(d->subspaceCombo, 5, 2, 1, 4);
d->gridLayout->addWidget(d->titleLabel, 6, 0, 1, 2);
d->gridLayout->addWidget(d->namespaceName, 6, 2, 1, 4);
d->gridLayout->addWidget(d->specialOptsLabel, 7, 0, 1, 2);
d->gridLayout->addWidget(d->specialOptsCombo, 7, 2, 1, 4);
d->gridLayout->addWidget(d->alternativeNameLabel, 8, 0, 1, 2);
d->gridLayout->addWidget(d->alternativeName, 8, 2, 1, 4);
d->gridLayout->addWidget(d->altspecialOptsLabel, 9, 0, 1, 3);
d->gridLayout->addWidget(d->altSpecialOptsCombo, 9, 3, 1, 3);
d->gridLayout->addWidget(d->separatorLabel, 10, 0, 1, 2);
d->gridLayout->addWidget(d->nameSpaceSeparator, 10, 2, 1, 4);
d->gridLayout->addWidget(d->isTagLabel, 11, 0, 1, 2);
d->gridLayout->addWidget(d->isPath, 11, 2, 1, 3);
d->gridLayout->addWidget(d->ratingMappings, 14, 0, 2, 6);
d->gridLayout->addWidget(d->tipLabel2, 15, 0, 1, 6);
d->gridLayout->setContentsMargins(cmargin, cmargin, cmargin, cmargin);
d->gridLayout->setSpacing(spacing);
QVBoxLayout* const vbx = new QVBoxLayout(this);
vbx->addWidget(d->page);
vbx->addWidget(d->buttons);
}
void NamespaceEditDlg::populateFields(NamespaceEntry& entry)
{
d->namespaceName->setText(entry.namespaceName);
d->nameSpaceSeparator->setText(entry.separator);
if (entry.tagPaths == NamespaceEntry::TAGPATH)
{
d->isPath->setChecked(true);
}
else
{
d->isPath->setChecked(false);
}
d->specialOptsCombo->setCurrentIndex((int)entry.specialOpts);
d->alternativeName->setText(entry.alternativeName);
d->altSpecialOptsCombo->setCurrentIndex((int)entry.secondNameOpts);
if (entry.convertRatio.size() == 6)
{
d->zeroStars->setValue(entry.convertRatio.at(0));
d->oneStar->setValue(entry.convertRatio.at(1));
d->twoStars->setValue(entry.convertRatio.at(2));
d->threeStars->setValue(entry.convertRatio.at(3));
d->fourStars->setValue(entry.convertRatio.at(4));
d->fiveStars->setValue(entry.convertRatio.at(5));
}
}
void NamespaceEditDlg::setType(NamespaceEntry::NamespaceType type)
{
switch(type)
{
case NamespaceEntry::TAGS:
qCDebug(DIGIKAM_GENERAL_LOG) << "Setting up tags";
d->ratingTipLabel->hide();
d->commentTipLabel->hide();
d->ratingMappings->hide();
// disable IPTC and EXIV for tags
d->subspaceCombo->setItemData(0, 0, Qt::UserRole -1);
d->subspaceCombo->setItemData(1, 0, Qt::UserRole -1);
break;
case NamespaceEntry::RATING:
d->tagTipLabel->hide();
d->commentTipLabel->hide();
d->isPath->hide();
d->isTagLabel->hide();
d->separatorLabel->hide();
d->nameSpaceSeparator->hide();
break;
case NamespaceEntry::COMMENT:
d->tagTipLabel->hide();
d->ratingTipLabel->hide();
d->isPath->hide();
d->isTagLabel->hide();
d->separatorLabel->hide();
d->nameSpaceSeparator->hide();
d->ratingMappings->hide();
break;
default:
break;
}
}
void NamespaceEditDlg::makeReadOnly()
{
QString txt = i18n("This is a default namespace. Default namespaces can only be disabled");
d->tipLabel2->setText(txt);
d->tipLabel2->show();
d->subspaceCombo->setDisabled(true);
d->specialOptsCombo->setDisabled(true);
d->altSpecialOptsCombo->setDisabled(true);
d->namespaceName->setDisabled(true);
d->alternativeName->setDisabled(true);
d->nameSpaceSeparator->setDisabled(true);
d->isPath->setDisabled(true);
d->ratingMappings->setDisabled(true);
d->zeroStars->setDisabled(true);
d->oneStar->setDisabled(true);
d->twoStars->setDisabled(true);
d->threeStars->setDisabled(true);
d->fourStars->setDisabled(true);
d->fiveStars->setDisabled(true);
}
bool NamespaceEditDlg::validifyCheck(QString& errMsg)
{
// bool result = true; NOT USED
if (d->namespaceName->text().isEmpty())
{
errMsg = i18n("The namespace name is required");
return false;
}
switch (d->subspaceCombo->currentData().toInt())
{
case NamespaceEntry::EXIF:
if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Exif"))
{
errMsg = i18n("EXIF namespace name must start with \"Exif\".");
return false;
}
if (!d->alternativeName->text().isEmpty() &&
d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Exif"))
{
errMsg = i18n("EXIF alternative namespace name must start with \"Exif\".");
return false;
}
break;
case NamespaceEntry::IPTC:
if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc"))
{
errMsg = i18n("IPTC namespace name must start with \"Iptc\".");
return false;
}
if(!d->alternativeName->text().isEmpty() &&
d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Iptc"))
{
errMsg = i18n("IPTC alternative namespace name must start with \"Iptc\".");
return false;
}
break;
case NamespaceEntry::XMP:
if (d->namespaceName->text().split(QLatin1Char('.')).first() != QLatin1String("Xmp"))
{
errMsg = i18n("XMP namespace name must start with \"Xmp\".");
return false;
}
if (!d->alternativeName->text().isEmpty() &&
d->alternativeName->text().split(QLatin1Char('.')).first() != QLatin1String("Xmp"))
{
errMsg = i18n("XMP alternative namespace name must start with \"Xmp\".");
return false;
}
break;
default:
break;
}
switch (d->nsType)
{
case NamespaceEntry::TAGS:
if (d->nameSpaceSeparator->text().isEmpty())
{
errMsg = i18n("Tag Path separator is required");
return false;
}
if (d->nameSpaceSeparator->text().size() > 1)
{
errMsg = i18n("Only one character is now supported as tag path separator");
return false;
}
break;
case NamespaceEntry::RATING:
break;
case NamespaceEntry::COMMENT:
break;
default:
break;
}
return true;
}
void NamespaceEditDlg::saveData(NamespaceEntry& entry)
{
entry.namespaceName = d->namespaceName->text();
entry.separator = d->nameSpaceSeparator->text();
if (d->isPath->isChecked())
{
entry.tagPaths = NamespaceEntry::TAGPATH;
}
else
{
entry.tagPaths = NamespaceEntry::TAG;
}
entry.alternativeName = d->alternativeName->text();
entry.specialOpts = (NamespaceEntry::SpecialOptions)d->specialOptsCombo->currentData().toInt();
entry.secondNameOpts = (NamespaceEntry::SpecialOptions)d->altSpecialOptsCombo->currentData().toInt();
entry.subspace = (NamespaceEntry::NsSubspace)d->subspaceCombo->currentData().toInt();
entry.convertRatio.clear();
entry.convertRatio.append(d->zeroStars->value());
entry.convertRatio.append(d->oneStar->value());
entry.convertRatio.append(d->twoStars->value());
entry.convertRatio.append(d->threeStars->value());
entry.convertRatio.append(d->fourStars->value());
entry.convertRatio.append(d->fiveStars->value());
}
void NamespaceEditDlg::accept()
{
QString errMsg;
if (validifyCheck(errMsg))
{
QDialog::accept();
}
else
{
d->tipLabel2->setText(errMsg);
d->tipLabel2->show();
}
}
void NamespaceEditDlg::slotHelp()
{
DXmlGuiWindow::openHandbook();
}
} // namespace Digikam
diff --git a/core/utilities/timeadjust/clockphotodialog.h b/core/utilities/timeadjust/clockphotodialog.h
index ec911adf9b..1469d77e8a 100644
--- a/core/utilities/timeadjust/clockphotodialog.h
+++ b/core/utilities/timeadjust/clockphotodialog.h
@@ -1,73 +1,73 @@
/* ============================================================
*
* This file is a part of digiKam project
* http://www.digikam.org
*
* Date : 2009-05-31
* Description : Figure out camera clock delta from a clock picture.
*
* Copyright (C) 2009 by Pieter Edelman <p dot edelman at gmx dot net>
* Copyright (C) 2011-2018 by Gilles Caulier <caulier dot gilles at gmail dot com>
*
* This program is free software; you can redistribute it
* and/or modify it under the terms of the GNU General
* Public License as published by the Free Software Foundation;
* either version 2, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* ============================================================ */
#ifndef DIGIKAM_CLOCK_PHOTO_DIALOG_H
#define DIGIKAM_CLOCK_PHOTO_DIALOG_H
// Qt includes
#include <QUrl>
#include <QDialog>
namespace Digikam
{
class DeltaTime;
class ClockPhotoDialog : public QDialog
{
Q_OBJECT
public:
explicit ClockPhotoDialog(QWidget* const parent, const QUrl& defaultUrl);
~ClockPhotoDialog();
/** Try to load the photo specified by the QUrl, and set the datetime widget
- * to the photo time. Return true on succes, or false if either the photo
+ * to the photo time. Return true on success, or false if either the photo
* can't be read or the datetime information can't be read.
*/
bool setImage(const QUrl&);
DeltaTime deltaValues() const;
private Q_SLOTS:
void slotLoadPhoto();
void slotOk();
void slotCancel();
private:
void loadSettings();
void saveSettings();
private:
class Private;
Private* const d;
};
} // namespace Digikam
#endif // DIGIKAM_CLOCK_PHOTO_DIALOG_H