Add Brightness/Contrast menu option

This commit is contained in:
David Capello 2017-10-06 16:19:30 -03:00
parent f15430ce88
commit cca9ff702a
9 changed files with 560 additions and 5 deletions

View File

@ -641,12 +641,14 @@
<separator /> <separator />
<item command="ReplaceColor" text="R&amp;eplace Color..." /> <item command="ReplaceColor" text="R&amp;eplace Color..." />
<item command="InvertColor" text="&amp;Invert..." /> <item command="InvertColor" text="&amp;Invert..." />
<item command="HueSaturation" text="Ad&amp;just Hue/Saturation..." /> <menu text="Adjustments">
<item command="BrightnessContrast" text="&amp;Brightness/Contrast..." />
<item command="HueSaturation" text="&amp;Hue/Saturation..." />
<item command="ColorCurve" text="&amp;Color Curve..." />
</menu>
<menu text="F&amp;X" id="fx_popup"> <menu text="F&amp;X" id="fx_popup">
<item command="ConvolutionMatrix" text="Convolution &amp;Matrix" /> <item command="ConvolutionMatrix" text="Convolution &amp;Matrix..." />
<item command="ColorCurve" text="&amp;Color Curve" /> <item command="Despeckle" text="&amp;Despeckle (Median Filter)..." />
<separator />
<item command="Despeckle" text="&amp;Despeckle (median filter)" />
</menu> </menu>
<item command="PasteText" text="Insert Text" /> <item command="PasteText" text="Insert Text" />
<!--menu text="Scripts"> <!--menu text="Scripts">

View File

@ -350,6 +350,7 @@ add_library(app-lib
commands/cmd_zoom.cpp commands/cmd_zoom.cpp
commands/command.cpp commands/command.cpp
commands/commands.cpp commands/commands.cpp
commands/filters/cmd_brightness_contrast.cpp
commands/filters/cmd_color_curve.cpp commands/filters/cmd_color_curve.cpp
commands/filters/cmd_convolution_matrix.cpp commands/filters/cmd_convolution_matrix.cpp
commands/filters/cmd_despeckle.cpp commands/filters/cmd_despeckle.cpp
@ -494,6 +495,7 @@ add_library(app-lib
ui/skin/skin_slider_property.cpp ui/skin/skin_slider_property.cpp
ui/skin/skin_theme.cpp ui/skin/skin_theme.cpp
ui/slice_window.cpp ui/slice_window.cpp
ui/slider2.cpp
ui/status_bar.cpp ui/status_bar.cpp
ui/tabs.cpp ui/tabs.cpp
ui/timeline/ani_controls.cpp ui/timeline/ani_controls.cpp

View File

@ -9,6 +9,7 @@ FOR_EACH_COMMAND(AddColor)
FOR_EACH_COMMAND(AdvancedMode) FOR_EACH_COMMAND(AdvancedMode)
FOR_EACH_COMMAND(AutocropSprite) FOR_EACH_COMMAND(AutocropSprite)
FOR_EACH_COMMAND(BackgroundFromLayer) FOR_EACH_COMMAND(BackgroundFromLayer)
FOR_EACH_COMMAND(BrightnessContrast)
FOR_EACH_COMMAND(Cancel) FOR_EACH_COMMAND(Cancel)
FOR_EACH_COMMAND(CanvasSize) FOR_EACH_COMMAND(CanvasSize)
FOR_EACH_COMMAND(CelProperties) FOR_EACH_COMMAND(CelProperties)

View File

@ -0,0 +1,109 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/color.h"
#include "app/commands/command.h"
#include "app/commands/filters/filter_manager_impl.h"
#include "app/commands/filters/filter_window.h"
#include "app/context.h"
#include "app/ini_file.h"
#include "app/modules/gui.h"
#include "app/ui/color_button.h"
#include "app/ui/color_sliders.h"
#include "app/ui/slider2.h"
#include "base/bind.h"
#include "doc/image.h"
#include "doc/mask.h"
#include "doc/sprite.h"
#include "filters/brightness_contrast_filter.h"
#include "ui/button.h"
#include "ui/label.h"
#include "ui/widget.h"
#include "ui/window.h"
namespace app {
static const char* ConfigSection = "BrightnessContrast";
class BrightnessContrastWindow : public FilterWindow {
public:
BrightnessContrastWindow(BrightnessContrastFilter& filter,
FilterManagerImpl& filterMgr)
: FilterWindow("Brightness/Contrast", ConfigSection, &filterMgr,
WithChannelsSelector,
WithoutTiledCheckBox)
, m_brightness(-100, 100, 0)
, m_contrast(-100, 100, 0)
, m_filter(filter)
{
getContainer()->addChild(new ui::Label("Brightness:"));
getContainer()->addChild(&m_brightness);
getContainer()->addChild(new ui::Label("Contrast:"));
getContainer()->addChild(&m_contrast);
m_brightness.Change.connect(base::Bind<void>(&BrightnessContrastWindow::onChange, this));
m_contrast.Change.connect(base::Bind<void>(&BrightnessContrastWindow::onChange, this));
}
private:
void onChange() {
m_filter.setBrightness(m_brightness.getValue() / 100.0);
m_filter.setContrast(m_contrast.getValue() / 100.0);
restartPreview();
}
Slider2 m_brightness;
Slider2 m_contrast;
BrightnessContrastFilter& m_filter;
};
class BrightnessContrastCommand : public Command {
public:
BrightnessContrastCommand();
Command* clone() const override { return new BrightnessContrastCommand(*this); }
protected:
bool onEnabled(Context* context) override;
void onExecute(Context* context) override;
};
BrightnessContrastCommand::BrightnessContrastCommand()
: Command("BrightnessContrast",
"Brightness Contrast",
CmdRecordableFlag)
{
}
bool BrightnessContrastCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasActiveSprite);
}
void BrightnessContrastCommand::onExecute(Context* context)
{
BrightnessContrastFilter filter;
FilterManagerImpl filterMgr(context, &filter);
filterMgr.setTarget(TARGET_RED_CHANNEL |
TARGET_GREEN_CHANNEL |
TARGET_BLUE_CHANNEL |
TARGET_GRAY_CHANNEL |
TARGET_ALPHA_CHANNEL);
BrightnessContrastWindow window(filter, filterMgr);
window.doModal();
}
Command* CommandFactory::createBrightnessContrastCommand()
{
return new BrightnessContrastCommand;
}
} // namespace app

127
src/app/ui/slider2.cpp Normal file
View File

@ -0,0 +1,127 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/slider2.h"
#include "app/ui/skin/skin_property.h"
#include "base/bind.h"
#include "ui/manager.h"
#include "ui/message.h"
namespace app {
// TODO merge this code with ColorEntry in color_sliders.cpp
Slider2::Slider2Entry::Slider2Entry(ui::Slider* slider)
: ui::Entry(4, "0")
, m_slider(slider)
, m_recentFocus(false)
{
}
bool Slider2::Slider2Entry::onProcessMessage(ui::Message* msg)
{
switch (msg->type()) {
case ui::kFocusEnterMessage:
m_recentFocus = true;
break;
case ui::kKeyDownMessage:
if (ui::Entry::onProcessMessage(msg))
return true;
if (hasFocus()) {
int scancode = static_cast<ui::KeyMessage*>(msg)->scancode();
switch (scancode) {
// Enter just remove the focus
case ui::kKeyEnter:
case ui::kKeyEnterPad:
releaseFocus();
return true;
case ui::kKeyDown:
case ui::kKeyUp: {
int value = textInt();
if (scancode == ui::kKeyDown)
--value;
else
++value;
setTextf("%d", MID(minValue(), value, maxValue()));
selectAllText();
onChange();
return true;
}
}
// Process focus movement key here because if our
// CustomizedGuiManager catches this kKeyDownMessage it
// will process it as a shortcut to switch the Timeline.
//
// Note: The default ui::Manager handles focus movement
// shortcuts only for foreground windows.
// TODO maybe that should change
if (hasFocus() &&
manager()->processFocusMovementMessage(msg))
return true;
}
return false;
}
bool result = Entry::onProcessMessage(msg);
if (msg->type() == ui::kMouseDownMessage && m_recentFocus) {
m_recentFocus = false;
selectAllText();
}
return result;
}
Slider2::Slider2(int min, int max, int value)
: m_slider(min, max, value)
, m_entry(&m_slider)
{
m_slider.setExpansive(true);
m_slider.setSizeHint(gfx::Size(128, 0));
skin::get_skin_property(&m_entry)->setLook(skin::MiniLook);
m_slider.Change.connect(base::Bind<void>(&Slider2::onSliderChange, this));
m_entry.Change.connect(base::Bind<void>(&Slider2::onEntryChange, this));
addChild(&m_slider);
addChild(&m_entry);
}
void Slider2::onChange()
{
Change();
}
void Slider2::onSliderChange()
{
m_entry.setTextf("%d", m_slider.getValue());
if (m_entry.hasFocus())
m_entry.selectAllText();
onChange();
}
void Slider2::onEntryChange()
{
int v = m_entry.textInt();
v = MID(m_slider.getMinValue(), v, m_slider.getMaxValue());
m_slider.setValue(v);
onChange();
}
} // namespace app

59
src/app/ui/slider2.h Normal file
View File

@ -0,0 +1,59 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef APP_UI_SLIDER2_H_INCLUDED
#define APP_UI_SLIDER2_H_INCLUDED
#pragma once
#include "ui/box.h"
#include "ui/entry.h"
#include "ui/slider.h"
namespace app {
// TODO merge this with IntEntry?
// It looks like there are 3 different modes to input values:
//
// 1. An entry field (Entry)
// 2. An entry field with a popup slider (IntEntry)
// 3. A slider + entry field (Slider2, ColorSliders+ColorEntry)
//
// We might merge all these modes in just on responsive widget, but
// I'm not sure.
class Slider2 : public ui::HBox {
public:
Slider2(int min, int max, int value);
int getValue() const { return m_slider.getValue(); }
// Signals
obs::signal<void()> Change;
private:
virtual void onChange();
void onSliderChange();
void onEntryChange();
class Slider2Entry : public ui::Entry {
public:
Slider2Entry(ui::Slider* slider);
private:
int minValue() const { return m_slider->getMinValue(); }
int maxValue() const { return m_slider->getMaxValue(); }
bool onProcessMessage(ui::Message* msg) override;
ui::Slider* m_slider;
// TODO remove this calling setFocus() in
// Widget::onProcessMessage() instead of
// Manager::handleWindowZOrder()
bool m_recentFocus;
};
ui::Slider m_slider;
Slider2Entry m_entry;
};
} // namespace app
#endif

View File

@ -2,6 +2,7 @@
# Copyright (C) 2001-2017 David Capello # Copyright (C) 2001-2017 David Capello
add_library(filters-lib add_library(filters-lib
brightness_contrast_filter.cpp
color_curve.cpp color_curve.cpp
color_curve_filter.cpp color_curve_filter.cpp
convolution_matrix.cpp convolution_matrix.cpp

View File

@ -0,0 +1,208 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "filters/brightness_contrast_filter.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/rgbmap.h"
#include "filters/filter_indexed_data.h"
#include "filters/filter_manager.h"
#include "gfx/hsl.h"
#include "gfx/rgb.h"
#include <cmath>
namespace filters {
using namespace doc;
const char* BrightnessContrastFilter::getName()
{
return "Brightness Contrast";
}
BrightnessContrastFilter::BrightnessContrastFilter()
: m_brightness(0.0)
, m_contrast(0.0)
, m_cmap(256)
{
updateMap();
}
void BrightnessContrastFilter::setBrightness(double brightness)
{
m_brightness = brightness;
updateMap();
}
void BrightnessContrastFilter::setContrast(double contrast)
{
m_contrast = contrast;
updateMap();
}
void BrightnessContrastFilter::applyToRgba(FilterManager* filterMgr)
{
FilterIndexedData* fid = filterMgr->getIndexedData();
if (filterMgr->isFirstRow()) {
m_picks = fid->getPalettePicks();
m_usePalette = (m_picks.picks() > 0);
if (m_usePalette)
applyToPalette(filterMgr);
}
const Palette* pal = fid->getPalette();
const uint32_t* src_address = (uint32_t*)filterMgr->getSourceAddress();
uint32_t* dst_address = (uint32_t*)filterMgr->getDestinationAddress();
const int w = filterMgr->getWidth();
const Target target = filterMgr->getTarget();
for (int x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
++src_address;
++dst_address;
continue;
}
color_t c = *(src_address++);
if (m_usePalette) {
int i =
pal->findExactMatch(rgba_getr(c),
rgba_getg(c),
rgba_getb(c),
rgba_geta(c), -1);
if (i >= 0)
c = fid->getNewPalette()->getEntry(i);
}
else {
applyFilterToRgb(target, c);
}
*(dst_address++) = c;
}
}
void BrightnessContrastFilter::applyToGrayscale(FilterManager* filterMgr)
{
const uint16_t* src_address = (uint16_t*)filterMgr->getSourceAddress();
uint16_t* dst_address = (uint16_t*)filterMgr->getDestinationAddress();
const int w = filterMgr->getWidth();
const Target target = filterMgr->getTarget();
for (int x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
++src_address;
++dst_address;
continue;
}
color_t c = *(src_address++);
int k = graya_getv(c);
int a = graya_geta(c);
if (target & TARGET_GRAY_CHANNEL) k = m_cmap[k];
*(dst_address++) = graya(k, a);
}
}
void BrightnessContrastFilter::applyToIndexed(FilterManager* filterMgr)
{
FilterIndexedData* fid = filterMgr->getIndexedData();
// Apply filter to color palette if there is no selection
if (!filterMgr->isMaskActive()) {
if (!filterMgr->isFirstRow())
return;
m_picks = fid->getPalettePicks();
if (m_picks.picks() == 0)
m_picks.all();
applyToPalette(filterMgr);
return;
}
// Apply filter to color region
const Target target = filterMgr->getTarget();
const Palette* pal = fid->getPalette();
const RgbMap* rgbmap = fid->getRgbMap();
const uint8_t* src_address = (uint8_t*)filterMgr->getSourceAddress();
uint8_t* dst_address = (uint8_t*)filterMgr->getDestinationAddress();
const int w = filterMgr->getWidth();
for (int x=0; x<w; x++) {
if (filterMgr->skipPixel()) {
++src_address;
++dst_address;
continue;
}
color_t c = pal->getEntry(*(src_address++));
applyFilterToRgb(target, c);
*(dst_address++) = rgbmap->mapColor(rgba_getr(c),
rgba_getg(c),
rgba_getb(c),
rgba_geta(c));
}
}
void BrightnessContrastFilter::applyToPalette(FilterManager* filterMgr)
{
const Target target = filterMgr->getTarget();
FilterIndexedData* fid = filterMgr->getIndexedData();
const Palette* pal = fid->getPalette();
Palette* newPal = fid->getNewPalette();
int i = 0;
for (bool state : m_picks) {
if (!state) {
++i;
continue;
}
color_t c = pal->getEntry(i);
applyFilterToRgb(target, c);
newPal->setEntry(i, c);
++i;
}
}
void BrightnessContrastFilter::applyFilterToRgb(
const Target target, doc::color_t& c)
{
int r = rgba_getr(c);
int g = rgba_getg(c);
int b = rgba_getb(c);
int a = rgba_geta(c);
if (target & TARGET_RED_CHANNEL ) r = m_cmap[r];
if (target & TARGET_GREEN_CHANNEL) g = m_cmap[g];
if (target & TARGET_BLUE_CHANNEL ) b = m_cmap[b];
c = rgba(r, g, b, a);
}
void BrightnessContrastFilter::updateMap()
{
int max = int(m_cmap.size());
for (int u=0; u<max; ++u) {
double x = double(u) / double(max-1);
double y = (m_contrast+1.0) * (x - 0.5) + 0.5;
y = y*(1.0+m_brightness);
y = MID(0.0, y, 1.0);
m_cmap[u] = int(255.5 * y);
}
}
} // namespace filters

View File

@ -0,0 +1,46 @@
// Aseprite
// Copyright (C) 2017 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifndef FILTERS_BRIGHTNESS_CONTRAST_FILTER_H_INCLUDED
#define FILTERS_BRIGHTNESS_CONTRAST_FILTER_H_INCLUDED
#pragma once
#include "doc/color.h"
#include "doc/palette_picks.h"
#include "filters/filter.h"
#include "filters/target.h"
#include <vector>
namespace filters {
class BrightnessContrastFilter : public Filter {
public:
BrightnessContrastFilter();
void setBrightness(double brightness);
void setContrast(double contrast);
// Filter implementation
const char* getName();
void applyToRgba(FilterManager* filterMgr);
void applyToGrayscale(FilterManager* filterMgr);
void applyToIndexed(FilterManager* filterMgr);
private:
void applyToPalette(FilterManager* filterMgr);
void applyFilterToRgb(const Target target, doc::color_t& color);
void updateMap();
double m_brightness, m_contrast;
doc::PalettePicks m_picks;
bool m_usePalette;
std::vector<int> m_cmap;
};
} // namespace filters
#endif