- Implemented Floyd Steinberg dithering algorithm.

- Remembers "orientation" state (rotated, mirrored or inverted) in file attribute of image.
- Update recent documents menu every time the menu is opened.
- Bug fix and optimization in Filter.cpp.
- Moved image manipulation code from ShowImageView.cpp to Filter.cpp. So it can take advantage of multiple CPUs.
- Experimental: Write/remove thumbnail to/from file icons.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@5480 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Michael Pfeiffer
2003-11-25 17:03:14 +00:00
parent e482b42659
commit a8fc713847
9 changed files with 875 additions and 284 deletions
+202 -149
View File
@@ -48,6 +48,7 @@
#include <Path.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Screen.h>
#include "ShowImageApp.h"
@@ -62,10 +63,25 @@
#define max(a,b) ((a)>(b)?(a):(b))
#endif
#define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
#define BORDER_WIDTH 16
#define BORDER_HEIGHT 16
#define PEN_SIZE 1.0f
const rgb_color kborderColor = { 0, 0, 0, 255 };
const rgb_color kBorderColor = { 0, 0, 0, 255 };
enum ShowImageView::image_orientation
ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations][kNumberOfOrientations] =
{
// rotate 90°
{k90, k180, k270, k0, k270V, k0V, k90V, k0H},
// rotate -90°
{k270, k0, k90, k180, k90V, k0H, k270V, k0V},
// mirror vertical
{k0H, k270V, k0V, k90V, k180, k270, k0, k90},
// mirror horizontal
{k0V, k90V, k0H, k270V, k0, k90, k180, k270}
};
// use patterns to simulate marching ants for selection
void
@@ -157,6 +173,7 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
settings = my_app->Settings();
InitPatterns();
fDither = false;
fBitmap = NULL;
fSelBitmap = NULL;
fDocumentIndex = 1;
@@ -177,6 +194,7 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
fScaler = NULL;
if (settings->Lock()) {
fDither = settings->GetBool("Dither", fDither);
fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds);
fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds);
fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay);
@@ -185,7 +203,7 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
}
SetViewColor(B_TRANSPARENT_COLOR);
SetHighColor(kborderColor);
SetHighColor(kBorderColor);
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
SetPenSize(PEN_SIZE);
}
@@ -236,7 +254,6 @@ void
ShowImageView::AddToRecentDocuments()
{
be_roster->AddToRecentDocuments(&fCurrentRef, APP_SIG);
be_app_messenger.SendMessage(MSG_UPDATE_RECENT_DOCUMENTS);
}
void
@@ -302,6 +319,45 @@ ShowImageView::SetImage(const entry_ref *pref)
return;
fCurrentRef = ref;
// restore orientation
int32 orientation;
fImageOrientation = k0;
fInverted = false;
if (file.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation)) == sizeof(orientation)) {
if (orientation & 256) {
DoImageOperation(ImageProcessor::ImageProcessor::kInvert, true);
}
orientation &= 255;
switch (orientation) {
case k0:
break;
case k90:
DoImageOperation(ImageProcessor::kRotateClockwise, true);
break;
case k180:
DoImageOperation(ImageProcessor::kRotateClockwise, true);
DoImageOperation(ImageProcessor::kRotateClockwise, true);
break;
case k270:
DoImageOperation(ImageProcessor::kRotateAntiClockwise, true);
break;
case k0V:
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true);
break;
case k90V:
DoImageOperation(ImageProcessor::kRotateClockwise, true);
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true);
break;
case k0H:
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorVertical, true);
break;
case k270V:
DoImageOperation(ImageProcessor::kRotateAntiClockwise, true);
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true);
break;
}
}
// get the number of documents (pages) if it has been supplied
int32 documentCount = 0;
if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
@@ -321,6 +377,16 @@ ShowImageView::SetImage(const entry_ref *pref)
Notify(info.name);
}
void
ShowImageView::SetDither(bool dither)
{
if (fDither != dither) {
SettingsSetBool("Dither", dither);
fDither = dither;
Invalidate();
}
}
void
ShowImageView::SetShowCaption(bool show)
{
@@ -449,13 +515,13 @@ ShowImageView::AlignBitmap()
rect.right = width-1;
rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
// center vertically
rect.OffsetBy(0, (height - rect.Height()) / 2);
rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2));
} else {
s = height / (rect.Height()+1.0);
rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
rect.bottom = height-1;
// center horizontally
rect.OffsetBy((width - rect.Width()) / 2, 0);
rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0);
}
} else {
// zoom image
@@ -611,10 +677,10 @@ ShowImageView::UpdateCaption()
Scaler*
ShowImageView::GetScaler(BRect rect)
{
if (fScaler == NULL || !fScaler->Matches(rect)) {
if (fScaler == NULL || !fScaler->Matches(rect, fDither)) {
DeleteScaler();
BMessenger msgr(this, Window());
fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE);
fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE, fDither);
fScaler->Start();
}
return fScaler;
@@ -623,12 +689,14 @@ ShowImageView::GetScaler(BRect rect)
void
ShowImageView::DrawImage(BRect rect)
{
if (fScaleBilinear) {
if (fScaleBilinear || fDither) {
Scaler* scaler = GetScaler(rect);
if (scaler != NULL && scaler->GetBitmap() != NULL && !scaler->IsRunning()) {
if (scaler != NULL && !scaler->IsRunning()) {
BBitmap* bitmap = scaler->GetBitmap();
DrawBitmap(bitmap, BPoint(rect.left, rect.top));
return;
if (bitmap) {
DrawBitmap(bitmap, BPoint(rect.left, rect.top));
return;
}
}
}
DrawBitmap(fBitmap, fBitmap->Bounds(), rect);
@@ -1215,6 +1283,9 @@ ShowImageView::KeyDown (const char * bytes, int32 numBytes)
BMessenger msgr(Window());
msgr.SendMessage(MSG_SLIDE_SHOW);
}
case 'T':
case 't':
SetIcon(*bytes == 't');
break;
}
}
@@ -1594,7 +1665,7 @@ ShowImageView::FirstFile()
void
ShowImageView::SetZoom(float zoom)
{
if (fScaleBilinear && fZoom != zoom) {
if ((fScaleBilinear || fDither) && fZoom != zoom) {
DeleteScaler();
}
fZoom = zoom;
@@ -1620,12 +1691,21 @@ void
ShowImageView::SetSlideShowDelay(float seconds)
{
ShowImageSettings* settings;
fSlideShowDelay = (int)(seconds * 10.0);
settings = my_app->Settings();
if (settings->Lock()) {
settings->SetInt32("SlideShowDelay", fSlideShowDelay);
settings->Unlock();
}
int32 delay = (int)(seconds * 10.0);
if (fSlideShowDelay != delay) {
// update counter
fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown);
if (fSlideShowCountDown <= 0) {
// show next image on next Pulse()
fSlideShowCountDown = 1;
}
fSlideShowDelay = delay;
settings = my_app->Settings();
if (settings->Lock()) {
settings->SetInt32("SlideShowDelay", fSlideShowDelay);
settings->Unlock();
}
}
}
void
@@ -1640,152 +1720,56 @@ ShowImageView::StopSlideShow()
fSlideShow = false;
}
int32
ShowImageView::BytesPerPixel(color_space cs) const
{
switch (cs) {
case B_RGB32: // fall through
case B_RGB32_BIG: // fall through
case B_RGBA32: // fall through
case B_RGBA32_BIG: return 4;
case B_RGB24_BIG: // fall through
case B_RGB24: return 3;
case B_RGB16: // fall through
case B_RGB16_BIG: // fall through
case B_RGB15: // fall through
case B_RGB15_BIG: // fall through
case B_RGBA15: // fall through
case B_RGBA15_BIG: return 2;
case B_GRAY8: // fall through
case B_CMAP8: return 1;
case B_GRAY1: return 0;
default: return -1;
}
}
void
ShowImageView::CopyPixel(uchar* dest, int32 destX, int32 destY, int32 destBPR, uchar* src, int32 x, int32 y, int32 bpr, int32 bpp)
ShowImageView::DoImageOperation(ImageProcessor::operation op, bool quiet)
{
dest += destBPR * destY + destX * bpp;
src += bpr * y + x * bpp;
memcpy(dest, src, bpp);
}
// FIXME: In color space with alpha channel the alpha value should not be inverted!
// This could be a problem when image is saved and opened in another application.
// Note: For B_CMAP8 InvertPixel inverts the color index not the color value!
void
ShowImageView::InvertPixel(int32 x, int32 y, uchar* dest, int32 destBPR, uchar* src, int32 bpr, int32 bpp)
{
dest += destBPR * y + x * bpp;
src += bpr * y + x * bpp;
for (; bpp > 0; bpp --, dest ++, src ++) {
*dest = ~*src;
}
}
// DoImageOperation supports only color spaces with bytes per pixel >= 1
// See above for limitations about kInvert
void
ShowImageView::DoImageOperation(image_operation op)
{
color_space cs;
int32 bpp;
BBitmap* bm;
int32 width, height;
uchar* src;
uchar* dest;
int32 bpr, destBPR;
int32 x, y, destX, destY;
BRect rect;
BMessenger msgr;
ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
imageProcessor.Start(false);
BBitmap* bm = imageProcessor.DetachBitmap();
if (fBitmap == NULL) return;
cs = fBitmap->ColorSpace();
bpp = BytesPerPixel(cs);
if (bpp < 1) return;
width = fBitmap->Bounds().IntegerWidth();
height = fBitmap->Bounds().IntegerHeight();
if (op == kRotateClockwise || op == kRotateAntiClockwise) {
rect.Set(0, 0, height, width);
// update orientation state
if (op != ImageProcessor::kInvert) {
// Note: If one of these fails, check its definition in class ImageProcessor.
ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations);
ASSERT(ImageProcessor::kRotateAntiClockwise < ImageProcessor::kNumberOfAffineTransformations);
ASSERT(ImageProcessor::kMirrorVertical < ImageProcessor::kNumberOfAffineTransformations);
ASSERT(ImageProcessor::kMirrorHorizontal < ImageProcessor::kNumberOfAffineTransformations);
fImageOrientation = fTransformation[op][fImageOrientation];
} else {
rect.Set(0, 0, width, height);
fInverted = !fInverted;
}
bm = new BBitmap(rect, cs);
if (bm == NULL) return;
src = (uchar*)fBitmap->Bits();
dest = (uchar*)bm->Bits();
bpr = fBitmap->BytesPerRow();
destBPR = bm->BytesPerRow();
switch (op) {
case kRotateClockwise:
for (y = 0; y <= height; y ++) {
for (x = 0; x <= width; x ++) {
destX = height - y;
destY = x;
CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
}
}
break;
case kRotateAntiClockwise:
for (y = 0; y <= height; y ++) {
for (x = 0; x <= width; x ++) {
destX = y;
destY = width - x;
CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
}
}
break;
case kMirrorHorizontal:
for (y = 0; y <= height; y ++) {
for (x = 0; x <= width; x ++) {
destX = x;
destY = height - y;
CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
}
}
break;
case kMirrorVertical:
for (y = 0; y <= height; y ++) {
for (x = 0; x <= width; x ++) {
destX = width - x;
destY = y;
CopyPixel(dest, destX, destY, destBPR, src, x, y, bpr, bpp);
}
}
break;
case kInvert:
for (y = 0; y <= height; y ++) {
for (x = 0; x <= width; x ++) {
InvertPixel(x, y, dest, destBPR, src, bpr, bpp);
}
}
break;
if (!quiet) {
// write orientation state
BNode node(&fCurrentRef);
int32 orientation = fImageOrientation;
if (fInverted) orientation += 256;
if (orientation != k0) {
node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation));
} else {
node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
}
}
// set new bitmap
DeleteBitmap();
fBitmap = bm;
// remove selection
SetHasSelection(false);
Notify(NULL);
if (!quiet) {
// remove selection
SetHasSelection(false);
Notify(NULL);
}
}
void
ShowImageView::Rotate(int degree)
{
if (degree == 90) {
DoImageOperation(kRotateClockwise);
DoImageOperation(ImageProcessor::kRotateClockwise);
} else if (degree == 270) {
DoImageOperation(kRotateAntiClockwise);
DoImageOperation(ImageProcessor::kRotateAntiClockwise);
}
}
@@ -1793,14 +1777,83 @@ void
ShowImageView::Mirror(bool vertical)
{
if (vertical) {
DoImageOperation(kMirrorVertical);
DoImageOperation(ImageProcessor::kMirrorVertical);
} else {
DoImageOperation(kMirrorHorizontal);
DoImageOperation(ImageProcessor::kMirrorHorizontal);
}
}
void
ShowImageView::Invert()
{
DoImageOperation(kInvert);
DoImageOperation(ImageProcessor::kInvert);
}
void
ShowImageView::SetIcon(bool clear, icon_size which)
{
int32 size;
switch (which) {
case B_MINI_ICON: size = 16;
break;
case B_LARGE_ICON: size = 32;
break;
default:
return;
}
BRect rect(fBitmap->Bounds());
float s;
s = size / (rect.Width()+1.0);
if (s * (rect.Height()+1.0) <= size) {
rect.right = size-1;
rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
// center vertically
rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
} else {
s = size / (rect.Height()+1.0);
rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
rect.bottom = size-1;
// center horizontally
rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
}
// scale bitmap to thumbnail size
BMessenger msgr;
Scaler scaler(fBitmap, rect, msgr, 0, true);
BBitmap* thumbnail = scaler.GetBitmap();
scaler.Start(false);
ASSERT(thumbnail->ColorSpace() == B_CMAP8);
// create icon from thumbnail
BBitmap icon(BRect(0, 0, size-1, size-1), B_CMAP8);
memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
BScreen screen;
const uchar* src = (uchar*)thumbnail->Bits();
uchar* dest = (uchar*)icon.Bits();
const int32 srcBPR = thumbnail->BytesPerRow();
const int32 destBPR = icon.BytesPerRow();
const int32 dx = (int32)rect.left;
const int32 dy = (int32)rect.top;
for (int32 y = 0; y <= rect.IntegerHeight(); y ++) {
for (int32 x = 0; x <= rect.IntegerWidth(); x ++) {
const uchar* s = src + y * srcBPR + x;
uchar* d = dest + (y+dy) * destBPR + (x+dx);
*d = *s;
}
}
// set icon
BNode node(&fCurrentRef);
BNodeInfo info(&node);
info.SetIcon(clear ? NULL : &icon, which);
}
void
ShowImageView::SetIcon(bool clear)
{
SetIcon(clear, B_MINI_ICON);
SetIcon(clear, B_LARGE_ICON);
}