- 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

View File

@ -27,20 +27,30 @@
/*****************************************************************************/
#include <scheduler.h>
#include <Debug.h>
#include <Screen.h>
#include "Filter.h"
extern "C" int _kget_cpu_state_(int cpu);
// Implementation of FilterThread
FilterThread::FilterThread(Filter* filter, int i)
: fWorkerThread(-1)
, fFilter(filter)
FilterThread::FilterThread(Filter* filter, int32 i, int32 n, bool runInCurrentThread)
: fFilter(filter)
, fI(i)
, fN(n)
{
fWorkerThread = spawn_thread(worker_thread, "filter", suggest_thread_priority(B_STATUS_RENDERING), this);
if (fWorkerThread >= 0) {
resume_thread(fWorkerThread);
if (runInCurrentThread) {
Run();
} else {
delete this;
}
thread_id tid;
tid = spawn_thread(worker_thread, "filter", suggest_thread_priority(B_STATUS_RENDERING), this);
if (tid >= 0) {
resume_thread(tid);
} else {
delete this;
}
}
}
FilterThread::~FilterThread()
@ -58,7 +68,17 @@ FilterThread::worker_thread(void* data)
status_t
FilterThread::Run()
{
fFilter->Run(fI, fFilter->CPUCount());
if (fI == 0) {
// create destination image in first thread
fFilter->GetBitmap();
// and start other filter threads
for (int32 i = fI + 1; i < fN; i ++) {
new FilterThread(fFilter, i, fN);
}
}
if (fFilter->GetBitmap()) {
fFilter->Run(fI, fN);
}
delete this;
return B_OK;
}
@ -68,15 +88,29 @@ Filter::Filter(BBitmap* image, BMessenger listener, uint32 what)
: fListener(listener)
, fWhat(what)
, fStarted(false)
, fN(0)
, fNumberOfThreads(0)
, fIsRunning(false)
, fSrcImage(image)
, fDestImage(NULL)
{
// get the number of active CPUs
int count;
system_info info;
get_system_info(&info);
fCPUCount = info.cpu_count;
count = info.cpu_count;
fCPUCount = 0;
for (int i = 0; i < count; i ++) {
if (_kget_cpu_state_(i)) {
fCPUCount ++;
}
}
if (fCPUCount == 0) {
fCPUCount = 1;
}
fWaitForThreads = create_sem(0, "wait_for_threads");
#if TIME_FILTER
fStopWatch = NULL;
#endif
@ -97,37 +131,53 @@ Filter::GetBitmap()
return fDestImage;
}
void
Filter::Start()
BBitmap*
Filter::DetachBitmap()
{
GetBitmap();
if (fStarted || fSrcImage == NULL || fDestImage == NULL) return;
BBitmap* image = fDestImage;
fDestImage = NULL;
return image;
}
void
Filter::Start(bool async)
{
if (fStarted || fSrcImage == NULL) return;
#if TIME_FILTER
fStopWatch = new BStopWatch("Filter Time");
#endif
fNumberOfThreads = fCPUCount;
fN = NumberOfThreads();
fNumberOfThreads = fN;
fIsRunning = true;
fStarted = true;
// start first filter thread
new FilterThread(this, 0, fN, !async);
// start filter threads
for (int i = 0; i < fCPUCount; i ++) {
new FilterThread(this, i);
if (!async) {
Wait();
}
}
void
Filter::Wait()
{
if (fStarted) {
// wait for threads to exit
while (acquire_sem_etc(fWaitForThreads, fN, 0, 0) == B_INTERRUPTED);
// ready to start again
fStarted = false;
}
}
void
Filter::Stop()
{
if (fStarted) {
// tell FilterThreads to stop calculations
fIsRunning = false;
// wait for threads to exit
while (acquire_sem_etc(fWaitForThreads, fCPUCount, 0, 0) == B_INTERRUPTED);
// ready to start again
fStarted = false;
}
// tell FilterThreads to stop calculations
fIsRunning = false;
Wait();
}
void
@ -137,6 +187,7 @@ Filter::Done()
#if TIME_FILTER
delete fStopWatch; fStopWatch = NULL;
#endif
Completed();
if (fIsRunning) {
fListener.SendMessage(fWhat);
}
@ -151,6 +202,25 @@ Filter::IsRunning() const
return fIsRunning;
}
void
Filter::Completed()
{
}
int32
Filter::NumberOfThreads()
{
const int32 units = GetNumberOfUnits();
int32 n;
n = units / 32; // at least 32 units per CPU
if (n > CPUCount()) {
n = CPUCount();
} else if (n <= 0) {
n = 1; // at least one thread!
}
return n;
}
BBitmap*
Filter::GetSrcImage()
{
@ -164,14 +234,19 @@ Filter::GetDestImage()
}
// Implementation of (bilinear) Scaler
Scaler::Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what)
Scaler::Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what, bool dither)
: Filter(image, listener, what)
, fScaledImage(NULL)
, fRect(rect)
, fDither(dither)
{
}
Scaler::~Scaler()
{
if (GetDestImage() != fScaledImage) {
delete fScaledImage;
}
}
BBitmap*
@ -179,14 +254,22 @@ Scaler::CreateDestImage(BBitmap* srcImage)
{
if (srcImage == NULL || srcImage->ColorSpace() != B_RGB32 && srcImage->ColorSpace() !=B_RGBA32) return NULL;
BRect dest(0, 0, fRect.IntegerWidth(), fRect.IntegerHeight());
BBitmap* destImage = new BBitmap(dest, srcImage->ColorSpace());
BBitmap* destImage = new BBitmap(dest, fDither ? B_CMAP8 : srcImage->ColorSpace());
if (fDither) {
BRect dest(0, 0, fRect.IntegerWidth(), fRect.IntegerHeight());
fScaledImage = new BBitmap(dest, srcImage->ColorSpace());
} else {
fScaledImage = destImage;
}
return destImage;
}
bool
Scaler::Matches(BRect rect) const {
Scaler::Matches(BRect rect, bool dither) const
{
return fRect.IntegerWidth() == rect.IntegerWidth() &&
fRect.IntegerHeight() == rect.IntegerHeight();
fRect.IntegerHeight() == rect.IntegerHeight() &&
fDither == dither;
}
@ -216,7 +299,7 @@ Scaler::ScaleBilinear(intType fromRow, int32 toRow)
const int32 kBPP = 4;
src = GetSrcImage();
dest = GetDestImage();
dest = fScaledImage;
srcW = src->Bounds().IntegerWidth();
srcH = src->Bounds().IntegerHeight();
@ -244,7 +327,11 @@ Scaler::ScaleBilinear(intType fromRow, int32 toRow)
intType srcRow;
float alpha0, alpha1;
row = (float)y * (float)srcH / (float)destH;
if (destH == 0) {
row = 0;
} else {
row = (float)y * (float)srcH / (float)destH;
}
srcRow = (intType)row;
alpha1 = row - srcRow;
alpha0 = 1.0 - alpha1;
@ -313,7 +400,7 @@ Scaler::ScaleBilinear(intType fromRow, int32 toRow)
// Scale bilinear using fixed point calculations
// Is already more than two times faster than floating point version
// on AMD Athlon 1 GHz and Intel Pentium III 866 MHz.
// on AMD Athlon 1 GHz and Dual Intel Pentium III 866 MHz.
typedef struct {
int32 srcColumn;
@ -340,7 +427,7 @@ Scaler::ScaleBilinearFP(intType fromRow, int32 toRow)
const int32 kBPP = 4;
src = GetSrcImage();
dest = GetDestImage();
dest = fScaledImage;
srcW = src->Bounds().IntegerWidth();
srcH = src->Bounds().IntegerHeight();
@ -373,7 +460,11 @@ Scaler::ScaleBilinearFP(intType fromRow, int32 toRow)
intType srcRow;
fixed_point alpha0, alpha1;
row = to_fixed_point(y) * (long_fixed_point)fpSrcH / fpDestH;
if (fpDestH == 0) {
row = 0;
} else {
row = to_fixed_point(y) * (long_fixed_point)fpSrcH / fpDestH;
}
srcRow = from_fixed_point(row);
alpha1 = tail_value(row); // weight for row y+1
alpha0 = kFPOne - alpha1; // weight for row y
@ -442,7 +533,7 @@ Scaler::ScaleBilinearFP(intType fromRow, int32 toRow)
}
void
Scaler::RowValues(float* sum, const uchar* src, intType srcW, intType fromX, intType toX, const float a0X, const float a1X, const float deltaX, const int32 kBPP)
Scaler::RowValues(float* sum, const uchar* src, intType srcW, intType fromX, intType toX, const float a0X, const float a1X, const int32 kBPP)
{
sum[0] = a0X * src[0];
sum[1] = a0X * src[1];
@ -461,10 +552,6 @@ Scaler::RowValues(float* sum, const uchar* src, intType srcW, intType fromX, int
sum[1] += a1X * src[1];
sum[2] += a1X * src[2];
}
sum[0] /= deltaX;
sum[1] /= deltaX;
sum[2] /= deltaX;
}
typedef struct {
@ -492,7 +579,7 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
DownScaleColumnData* columnData;
src = GetSrcImage();
dest = GetDestImage();
dest = fScaledImage;
srcW = src->Bounds().IntegerWidth();
srcH = src->Bounds().IntegerHeight();
@ -508,6 +595,7 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
const float deltaX = (srcW + 1.0) / (destW + 1.0);
const float deltaY = (srcH + 1.0) / (destH + 1.0);
const float deltaXY = deltaX * deltaY;
columnData = new DownScaleColumnData[destW+1];
DownScaleColumnData* cd = columnData;
@ -548,7 +636,7 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
float totalSum[3];
float sum[3];
RowValues(sum, srcData, srcW, fromX, toX, a0X, a1X, deltaX, kBPP);
RowValues(sum, srcData, srcW, fromX, toX, a0X, a1X, kBPP);
totalSum[0] = a0Y * sum[0];
totalSum[1] = a0Y * sum[1];
totalSum[2] = a0Y * sum[2];
@ -556,36 +644,217 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
srcData += srcBPR;
for (int32 r = fromY+1; r < toY; r ++, srcData += srcBPR) {
RowValues(sum, srcData, srcW, fromX, toX, a0X, a1X, deltaX, kBPP);
RowValues(sum, srcData, srcW, fromX, toX, a0X, a1X, kBPP);
totalSum[0] += sum[0];
totalSum[1] += sum[1];
totalSum[2] += sum[2];
}
if (toY <= srcH) {
RowValues(sum, srcData, srcW, fromX, toX, a0X, a1X, deltaX, kBPP);
RowValues(sum, srcData, srcW, fromX, toX, a0X, a1X, kBPP);
totalSum[0] += a1Y * sum[0];
totalSum[1] += a1Y * sum[1];
totalSum[2] += a1Y * sum[2];
}
destData[0] = static_cast<uchar>(totalSum[0] / deltaY);
destData[1] = static_cast<uchar>(totalSum[1] / deltaY);
destData[2] = static_cast<uchar>(totalSum[2] / deltaY);
destData[0] = static_cast<uchar>(totalSum[0] / deltaXY);
destData[1] = static_cast<uchar>(totalSum[1] / deltaXY);
destData[2] = static_cast<uchar>(totalSum[2] / deltaXY);
}
}
delete columnData;
}
// Flyod-Steinberg Dithering
// Filter (distribution of error to adjacent pixels, X is current pixel):
// 0 X 7
// 3 5 1
typedef struct {
intType error[3];
} DitheringColumnData;
uchar
Scaler::Limit(intType value)
{
if (value < 0) {
value = 0;
} if (value > 255) {
value = 255;
}
return value;
}
void
Scaler::Run(int i, int n)
Scaler::Dither(int32 fromRow, int32 toRow)
{
BBitmap* src;
BBitmap* dest;
intType destW, destH;
intType x, y;
uchar* srcBits;
intType srcBPR;
uchar* srcDataRow;
uchar* srcData;
uchar* destBits;
intType destBPR;
uchar* destDataRow;
uchar* destData;
const int32 kBPP = 4;
DitheringColumnData* columnData0;
DitheringColumnData* columnData;
DitheringColumnData* cd;
BScreen screen;
intType error[3], err[3];
src = fScaledImage;
dest = GetDestImage();
ASSERT(src->ColorSpace() == B_RGB32 || src->ColorSpace() == B_RGBA32);
ASSERT(dest->ColorSpce() == B_CMAP8);
ASSERT(src->Bounds().IntegerWidth() == dest->Bounds().IntegerWidth());
ASSERT(src->Bounds().IntegerHeight() == dest->Bounds().IntegerHeight());
destW = dest->Bounds().IntegerWidth();
destH = dest->Bounds().IntegerHeight();
srcBits = (uchar*)src->Bits();
srcBPR = src->BytesPerRow();
destBits = (uchar*)dest->Bits();
destBPR = dest->BytesPerRow();
// Allocate space for sentinel at left and right bounds,
// so that columnData[-1] and columnData[destW+1] can be safely accessed
columnData0 = new DitheringColumnData[destW+3];
columnData = columnData0 + 1;
// clear error
cd = columnData;
for (x = destW; x >= 0; x --, cd ++) {
cd->error[0] = cd->error[1] = cd->error[2] =0;
}
srcDataRow = srcBits + fromRow * srcBPR;
destDataRow = destBits + fromRow * destBPR;
for (y = fromRow; IsRunning() && y <= toRow; y ++, srcDataRow += srcBPR, destDataRow += destBPR) {
// left to right
error[0] = error[1] = error[2] = 0;
srcData = srcDataRow;
destData = destDataRow;
for (x = 0; x <= destW; x ++, srcData += kBPP, destData += 1) {
rgb_color color, actualColor;
uint8 index;
color.red = Limit(srcData[2] + error[0] / 16);
color.green = Limit(srcData[1] + error[1] / 16);
color.blue = Limit(srcData[0] + error[2] / 16);
index = screen.IndexForColor(color);
actualColor = screen.ColorForIndex(index);
*destData = index;
err[0] = color.red - actualColor.red;
err[1] = color.green -actualColor.green;
err[2] = color.blue -actualColor.blue;
// distribute error
// get error for next pixel
cd = &columnData[x+1];
error[0] = cd->error[0] + 7 * err[0];
error[1] = cd->error[1] + 7 * err[1];
error[2] = cd->error[2] + 7 * err[2];
// set error for right pixel below current pixel
cd->error[0] = err[0];
cd->error[1] = err[1];
cd->error[2] = err[2];
// add error for pixel below current pixel
cd --;
cd->error[0] += 5 * err[0];
cd->error[1] += 5 * err[1];
cd->error[2] += 5 * err[2];
// add error for left pixel below current pixel
cd --;
cd->error[0] += 3 * err[0];
cd->error[1] += 3 * err[1];
cd->error[2] += 3 * err[2];
}
// Note: Alogrithm has good results with "left to right" already
// Optionally remove code to end of block:
y ++;
srcDataRow += srcBPR; destDataRow += destBPR;
if (y > toRow) break;
// right to left
error[0] = error[1] = error[2] = 0;
srcData = srcDataRow + destW * kBPP;
destData = destDataRow + destW;
for (x = 0; x <= destW; x ++, srcData -= kBPP, destData -= 1) {
rgb_color color, actualColor;
uint8 index;
color.red = Limit(srcData[2] + error[0] / 16);
color.green = Limit(srcData[1] + error[1] / 16);
color.blue = Limit(srcData[0] + error[2] / 16);
index = screen.IndexForColor(color);
actualColor = screen.ColorForIndex(index);
*destData = index;
err[0] = color.red - actualColor.red;
err[1] = color.green -actualColor.green;
err[2] = color.blue -actualColor.blue;
// distribute error
// get error for next pixel
cd = &columnData[x-1];
error[0] = cd->error[0] + 7 * err[0];
error[1] = cd->error[1] + 7 * err[1];
error[2] = cd->error[2] + 7 * err[2];
// set error for left pixel below current pixel
cd->error[0] = err[0];
cd->error[1] = err[1];
cd->error[2] = err[2];
// add error for pixel below current pixel
cd ++;
cd->error[0] += 5 * err[0];
cd->error[1] += 5 * err[1];
cd->error[2] += 5 * err[2];
// add error for right pixel below current pixel
cd ++;
cd->error[0] += 3 * err[0];
cd->error[1] += 3 * err[1];
cd->error[2] += 3 * err[2];
}
}
delete columnData0;
}
int32
Scaler::GetNumberOfUnits()
{
return fRect.IntegerHeight() + 1;
}
void
Scaler::Run(int32 i, int32 n)
{
int32 from, to, height;
height = (GetDestImage()->Bounds().IntegerHeight()+1)/n;
int32 from, to, height, imageHeight;
imageHeight = GetDestImage()->Bounds().IntegerHeight() + 1;
height = imageHeight / n;
from = i * height;
if (i+1 == n) {
to = (int32)GetDestImage()->Bounds().bottom;
to = imageHeight - 1;
} else {
to = from + height - 1;
}
@ -594,4 +863,190 @@ Scaler::Run(int i, int n)
} else {
DownScaleBilinear(from, to);
}
if (fDither) {
Dither(from, to);
}
}
void
Scaler::Completed()
{
if (GetDestImage() != fScaledImage) {
delete fScaledImage; fScaledImage = NULL;
}
}
// Implementation of ImageProcessor
ImageProcessor::ImageProcessor(enum operation op, BBitmap* image, BMessenger listener, uint32 what)
: Filter(image, listener, what)
, fOp(op)
{
}
BBitmap*
ImageProcessor::CreateDestImage(BBitmap* srcImage)
{
color_space cs;
BBitmap* bm;
BRect rect;
if (GetSrcImage() == NULL) return NULL;
cs = GetSrcImage()->ColorSpace();
fBPP = BytesPerPixel(cs);
if (fBPP < 1) return NULL;
fWidth = GetSrcImage()->Bounds().IntegerWidth();
fHeight = GetSrcImage()->Bounds().IntegerHeight();
if (fOp == kRotateClockwise || fOp == kRotateAntiClockwise) {
rect.Set(0, 0, fHeight, fWidth);
} else {
rect.Set(0, 0, fWidth, fHeight);
}
bm = new BBitmap(rect, cs);
if (bm == NULL) return NULL;
fSrcBPR = GetSrcImage()->BytesPerRow();
fDestBPR = bm->BytesPerRow();
return bm;
}
int32
ImageProcessor::GetNumberOfUnits()
{
return GetSrcImage()->Bounds().IntegerHeight() + 1;
}
int32
ImageProcessor::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
ImageProcessor::CopyPixel(uchar* dest, int32 destX, int32 destY, const uchar* src, int32 x, int32 y)
{
// Note: On my systems (Dual Intel P3 866MHz and AMD Athlon 1GHz), replacing
// the multiplications below with pointer arithmethics showed no speedup at all!
dest += fDestBPR * destY + destX * fBPP;
src += fSrcBPR * y + x *fBPP;
// Replacing memcpy with this switch statement is slightly faster
switch (fBPP) {
case 4:
dest[3] = src[3];
case 3:
dest[2] = src[2];
case 2:
dest[1] = src[1];
case 1:
dest[0] = src[0];
break;
}
}
// Note: For B_CMAP8 InvertPixel inverts the color index not the color value!
void
ImageProcessor::InvertPixel(int32 x, int32 y, uchar* dest, const uchar* src)
{
dest += fDestBPR * y + x * fBPP;
src += fSrcBPR * y + x * fBPP;
switch (fBPP) {
case 4:
// dest[3] = ~src[3]; DON'T invert alpha channel
case 3:
dest[2] = ~src[2];
case 2:
dest[1] = ~src[1];
case 1:
dest[0] = ~src[0];
break;
}
}
// Note: On my systems, the operation kInvert shows a speedup on multiple CPUs only!
void
ImageProcessor::Run(int32 i, int32 n)
{
int32 from, to;
int32 height = (fHeight+1) / n;
from = i * height;
if (i+1 == n) {
to = fHeight;
} else {
to = from + height - 1;
}
int32 x, y, destX, destY;
const uchar* src = (uchar*)GetSrcImage()->Bits();
uchar* dest = (uchar*)GetDestImage()->Bits();
switch (fOp) {
case kRotateClockwise:
for (y = from; y <= to; y ++) {
for (x = 0; x <= fWidth; x ++) {
destX = fHeight - y;
destY = x;
CopyPixel(dest, destX, destY, src, x, y);
}
}
break;
case kRotateAntiClockwise:
for (y = from; y <= to; y ++) {
for (x = 0; x <= fWidth; x ++) {
destX = y;
destY = fWidth - x;
CopyPixel(dest, destX, destY, src, x, y);
}
}
break;
case kMirrorHorizontal:
for (y = from; y <= to; y ++) {
for (x = 0; x <= fWidth; x ++) {
destX = x;
destY = fHeight - y;
CopyPixel(dest, destX, destY, src, x, y);
}
}
break;
case kMirrorVertical:
for (y = from; y <= to; y ++) {
for (x = 0; x <= fWidth; x ++) {
destX = fWidth - x;
destY = y;
CopyPixel(dest, destX, destY, src, x, y);
}
}
break;
case kInvert:
for (y = from; y <= to; y ++) {
for (x = 0; x <= fWidth; x ++) {
InvertPixel(x, y, dest, src);
}
}
break;
}
}

View File

@ -63,15 +63,15 @@ const int32 kFPOne = to_fixed_point(1);
// Used by class Filter
class FilterThread {
public:
FilterThread(Filter* filter, int i);
FilterThread(Filter* filter, int32 i, int32 n, bool runInCurrentThread = false);
~FilterThread();
private:
status_t Run();
static status_t worker_thread(void* data);
thread_id fWorkerThread;
Filter* fFilter;
int fI;
int32 fI;
int32 fN;
};
class Filter {
@ -80,53 +80,71 @@ public:
// for an operation executed in Run() method which
// writes into the destination image, that can be
// retrieve using GetBitmap() method.
// GetBitmap() can be called any time, but it
// may contain "invalid" pixels.
// GetBitmap() must be called either before Start(),
// or after Start() and IsRunning() returns false.
// To start the operation Start() method has to
// be called. The operation is executed in as many
// threads as CPUs are active.
// threads as GetMaxNumberOfThreads() returns.
// The implementation of GetMaxNumberOfThreads()
// can use CPUCount() to retrieve the number of
// active CPUs at the time the Filter was created.
// IsRunning() is true as long as there are any
// threads running.
// The operation is complete when IsRunning() is false
// and Stop() has not been called.
// To abort an operation Stop() method has to
// be called. Stop() has to be called after Start().
// When the operation is done (and has not been aborted).
// Then listener receives a message with the specified "what" value.
// When the operation is done Completed() is called.
// and only if it has not been aborted, then the listener
// receives a message with the specified "what" value.
Filter(BBitmap* image, BMessenger listener, uint32 what);
virtual ~Filter();
// The bitmap the filter writes into
BBitmap* GetBitmap();
// Removes the destination image from Filter (caller is new owner of image)
BBitmap* DetachBitmap();
// Starts one or more FilterThreads
void Start();
// Starts one or more FilterThreads. Returns immediately if async is true.
// Either Wait() or Stop() has to be called if async is true!
void Start(bool async = true);
// Wait for completion of operation
void Wait();
// Has to be called after Start() (even if IsRunning() is false)
void Stop();
// Are there any running FilterThreads?
bool IsRunning() const;
// To be implemented by inherited class
// To be implemented by inherited class (methods are called in this order):
virtual BBitmap* CreateDestImage(BBitmap* srcImage) = 0;
// The number of processing units
virtual int32 GetNumberOfUnits() = 0;
// Should calculate part i of n of the image. i starts with zero
virtual void Run(int i, int n) = 0;
virtual void Run(int32 i, int32 n) = 0;
// Completed() is called when the last FilterThread has completed its work.
virtual void Completed();
// Used by FilterThread only!
void Done();
int32 CPUCount() const { return fCPUCount; }
protected:
// Number of threads to be used to perform the operation
int32 NumberOfThreads();
BBitmap* GetSrcImage();
BBitmap* GetDestImage();
private:
// Returns the number of active CPUs
int32 CPUCount() const { return fCPUCount; }
BMessenger fListener;
uint32 fWhat;
int32 fCPUCount;
bool fStarted;
sem_id fWaitForThreads;
volatile int32 fNumberOfThreads;
volatile bool fIsRunning;
int32 fCPUCount; // the number of active CPUs
bool fStarted; // has Start() been called?
sem_id fWaitForThreads; // to exit
int32 fN; // the number of used filter threads
volatile int32 fNumberOfThreads; // the current number of FilterThreads
volatile bool fIsRunning; // FilterThreads should process data as long as it is true
BBitmap* fSrcImage;
BBitmap* fDestImage;
#if TIME_FILTER
@ -134,22 +152,57 @@ private:
#endif
};
// Scales and optionally dithers an image
class Scaler : public Filter {
public:
Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what);
Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what, bool dither);
~Scaler();
BBitmap* CreateDestImage(BBitmap* srcImage);
void Run(int i, int n);
bool Matches(BRect rect) const;
int32 GetNumberOfUnits();
void Run(int32 i, int32 n);
void Completed();
bool Matches(BRect rect, bool dither) const;
private:
void ScaleBilinear(int32 fromRow, int32 toRow);
void ScaleBilinearFP(int32 fromRow, int32 toRow);
inline void RowValues(float* sum, const uchar* srcData, intType srcW, intType fromX, intType toX, const float a0X, const float a1X, const float deltaX, const int32 kBPP);
inline void RowValues(float* sum, const uchar* srcData, intType srcW, intType fromX, intType toX, const float a0X, const float a1X, const int32 kBPP);
void DownScaleBilinear(int32 fromRow, int32 toRow);
static inline uchar Limit(intType value);
void Dither(int32 fromRow, int32 toRow);
BBitmap* fScaledImage;
BRect fRect;
bool fDither;
};
// Rotates, mirrors or inverts an image
class ImageProcessor : public Filter {
public:
enum operation {
kRotateClockwise,
kRotateAntiClockwise,
kMirrorVertical,
kMirrorHorizontal,
kInvert,
kNumberOfAffineTransformations = 4
};
ImageProcessor(enum operation op, BBitmap* image, BMessenger listener, uint32 what);
BBitmap* CreateDestImage(BBitmap* srcImage);
int32 GetNumberOfUnits();
void Run(int32 i, int32 n);
private:
int32 BytesPerPixel(color_space cs) const;
inline void CopyPixel(uchar* dest, int32 destX, int32 destY, const uchar* src, int32 x, int32 y);
inline void InvertPixel(int32 x, int32 y, uchar* dest, const uchar* src);
enum operation fOp;
int32 fBPP;
int32 fWidth, fHeight;
int32 fSrcBPR, fDestBPR;
};
#endif

View File

@ -90,6 +90,8 @@ ShowImageApp::StartPulse()
void
ShowImageApp::Pulse()
{
// Bug: The BFilePanel is automatically closed if the volume that
// is displayed is unmounted.
if (!IsLaunching() && CountWindows() <= WINDOWS_TO_IGNORE)
// If the application is not launching and
// all windows are closed except for the file open panel,
@ -112,8 +114,10 @@ ShowImageApp::ArgvReceived(int32 argc, char **argv)
pmsg->AddRef("refs", &ref);
}
}
if (pmsg)
if (pmsg) {
RefsReceived(pmsg);
delete pmsg;
}
}
void
@ -133,10 +137,6 @@ ShowImageApp::MessageReceived(BMessage *pmsg)
StartPulse();
break;
case MSG_UPDATE_RECENT_DOCUMENTS:
BroadcastToWindows(MSG_UPDATE_RECENT_DOCUMENTS);
break;
case B_CLIPBOARD_CHANGED:
CheckClipboard();
break;
@ -169,17 +169,6 @@ ShowImageApp::Open(const entry_ref *pref)
new ShowImageWindow(pref);
}
void
ShowImageApp::BroadcastToWindows(uint32 what)
{
const int32 n = CountWindows();
for (int32 i = 0; i < n ; i ++) {
// BMessenger checks for us if BWindow is still a valid object
BMessenger msgr(WindowAt(i));
msgr.SendMessage(what);
}
}
void
ShowImageApp::BroadcastToWindows(BMessage *pmsg)
{

View File

@ -51,7 +51,6 @@ public:
private:
void StartPulse();
void Open(const entry_ref *pref);
void BroadcastToWindows(uint32 what);
void BroadcastToWindows(BMessage *pmsg);
void CheckClipboard();

View File

@ -61,7 +61,6 @@ const uint32 MSG_INVERT = 'mINV';
const uint32 MSG_SLIDE_SHOW = 'mSSW';
const uint32 MSG_SLIDE_SHOW_DELAY = 'mSSD';
const uint32 MSG_FULL_SCREEN = 'mFSC';
const uint32 MSG_UPDATE_RECENT_DOCUMENTS = 'mURD';
const uint32 MSG_SHOW_CAPTION = 'mSCP';
const uint32 MSG_PAGE_SETUP = 'mPSU';
const uint32 MSG_PREPARE_PRINT = 'mPPT';

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);
}

View File

@ -32,6 +32,7 @@
#include <View.h>
#include <Bitmap.h>
#include <Entry.h>
#include <NodeInfo.h>
#include <String.h>
#include <TranslatorRoster.h>
@ -46,6 +47,8 @@ public:
void Pulse();
void SetImage(const entry_ref *pref);
void SetDither(bool dither);
bool GetDither() const { return fDither; }
void SetShowCaption(bool show);
void SetShrinkToBounds(bool enable);
bool GetShrinkToBounds() const { return fShrinkToBounds; }
@ -105,13 +108,19 @@ public:
void Mirror(bool vertical);
void Invert();
void SetIcon(bool clear);
private:
enum image_operation {
kRotateClockwise,
kRotateAntiClockwise,
kMirrorVertical,
kMirrorHorizontal,
kInvert
enum image_orientation {
k0, // 0
k90, // 1
k180, // 2
k270, // 3
k0V, // 4
k90V, // 5
k0H, // 6
k270V, // 7
kNumberOfOrientations,
};
void InitPatterns();
void RotatePatterns();
@ -128,7 +137,7 @@ private:
int32 BytesPerPixel(color_space cs) const;
inline void CopyPixel(uchar* dest, int32 destX, int32 destY, int32 destBPR, uchar* src, int32 x, int32 y, int32 bpr, int32 bpp);
inline void InvertPixel(int32 x, int32 y, uchar* dest, int32 destBPR, uchar* src, int32 bpr, int32 bpp);
void DoImageOperation(image_operation op);
void DoImageOperation(enum ImageProcessor::operation op, bool quiet = false);
BRect AlignBitmap();
void Setup(BRect r);
BPoint ImageToView(BPoint p) const;
@ -166,8 +175,10 @@ private:
void MouseWheelChanged(BMessage* msg);
void ShowPopUpMenu(BPoint screen);
void SettingsSetBool(const char* name, bool value);
void SetIcon(bool clear, icon_size which);
entry_ref fCurrentRef; // of the image
bool fDither; // dither the image
int32 fDocumentIndex; // of the image in the file
int32 fDocumentCount; // number of images in the file
BBitmap *fBitmap; // to be displayed
@ -201,6 +212,10 @@ private:
bool fShowCaption; // display caption?
BString fCaption; // caption text
bool fInverted;
enum image_orientation fImageOrientation;
static enum image_orientation fTransformation[ImageProcessor::kNumberOfAffineTransformations][kNumberOfOrientations];
};
#endif /* _ShowImageView_h */

View File

@ -53,6 +53,46 @@
#include "ShowImageStatusView.h"
#include "EntryMenuItem.h"
// Implementation of RecentDocumentsMenu
RecentDocumentsMenu::RecentDocumentsMenu(const char *title, menu_layout layout = B_ITEMS_IN_COLUMN)
: BMenu(title, layout)
{
}
bool
RecentDocumentsMenu::AddDynamicItem(add_state s)
{
if (s != B_INITIAL_ADD) return false;
BMenuItem *item;
BMessage list, *msg;
entry_ref ref;
char name[B_FILE_NAME_LENGTH];
while ((item = RemoveItem((int32)0)) != NULL) {
delete item;
}
be_roster->GetRecentDocuments(&list, 20, NULL, APP_SIG);
for (int i = 0; list.FindRef("refs", i, &ref) == B_OK; i++) {
BEntry entry(&ref);
if (entry.Exists() && entry.GetName(name) == B_OK) {
msg = new BMessage(B_REFS_RECEIVED);
msg->AddRef("refs", &ref);
item = new EntryMenuItem(&ref, name, msg, 0, 0);
AddItem(item);
item->SetTarget(be_app, NULL);
}
}
return false;
}
// Implementation of ShowImageWindow
ShowImageWindow::ShowImageWindow(const entry_ref *pref)
: BWindow(BRect(50, 50, 350, 250), "", B_DOCUMENT_WINDOW, 0)
{
@ -119,7 +159,7 @@ ShowImageWindow::ShowImageWindow(const entry_ref *pref)
BMenu* pmenu = new BMenu("View");
BuildViewMenu(pmenu);
fBar->AddItem(pmenu);
MarkMenuItem(fBar, MSG_DITHER_IMAGE, fImageView->GetDither());
UpdateTitle();
SetPulseRate(100000); // every 1/10 second; ShowImageView needs it for marching ants
@ -165,31 +205,6 @@ ShowImageWindow::UpdateTitle()
SetTitle(path.String());
}
void
ShowImageWindow::UpdateRecentDocumentsMenu()
{
BMenuItem *item;
BMessage list, *msg;
entry_ref ref;
char name[B_FILE_NAME_LENGTH];
while ((item = fOpenMenu->RemoveItem((int32)0)) != NULL) {
delete item;
}
be_roster->GetRecentDocuments(&list, 20, NULL, APP_SIG);
for (int i = 0; list.FindRef("refs", i, &ref) == B_OK; i++) {
BEntry entry(&ref);
if (entry.Exists() && entry.GetName(name) == B_OK) {
msg = new BMessage(B_REFS_RECEIVED);
msg->AddRef("refs", &ref);
item = new EntryMenuItem(&ref, name, msg, 0, 0);
fOpenMenu->AddItem(item);
item->SetTarget(be_app, NULL);
}
}
}
void
ShowImageWindow::BuildViewMenu(BMenu *pmenu)
{
@ -247,7 +262,7 @@ void
ShowImageWindow::LoadMenus(BMenuBar *pbar)
{
BMenu *pmenu = new BMenu("File");
fOpenMenu = new BMenu("Open");
fOpenMenu = new RecentDocumentsMenu("Open");
pmenu->AddItem(fOpenMenu);
fOpenMenu->Superitem()->SetTrigger('O');
fOpenMenu->Superitem()->SetMessage(new BMessage(MSG_FILE_OPEN));
@ -304,8 +319,6 @@ ShowImageWindow::LoadMenus(BMenuBar *pbar)
pmenu->AddSeparatorItem();
AddItemMenu(pmenu, "Invert", MSG_INVERT, 0, 0, 'W', true);
pbar->AddItem(pmenu);
UpdateRecentDocumentsMenu();
}
BMenuItem *
@ -350,6 +363,7 @@ ShowImageWindow::WindowRedimension(BBitmap *pbitmap)
BRect r(pbitmap->Bounds());
float width, height;
float maxWidth, maxHeight;
float minW, maxW, minH, maxH;
const float windowBorderWidth = 5;
const float windowBorderHeight = 5;
@ -364,8 +378,16 @@ ShowImageWindow::WindowRedimension(BBitmap *pbitmap)
maxWidth = screen.Frame().Width() + 1 - windowBorderWidth - Frame().left;
maxHeight = screen.Frame().Height() + 1 - windowBorderHeight - Frame().top;
// We have to check size limits manually, otherwise
// menu bar will be too short for small images.
GetSizeLimits(&minW, &maxW, &minH, &maxH);
if (maxWidth > maxW) maxWidth = maxW;
if (maxHeight > maxH) maxHeight = maxH;
if (width < minW) width = minW;
if (height < minH) height = minH;
if (width > maxWidth) width = maxWidth;
if (height > maxHeight) height = maxHeight;
if (height > maxHeight) height = maxHeight;
ResizeTo(width, height);
}
@ -607,7 +629,7 @@ ShowImageWindow::MessageReceived(BMessage *pmsg)
break;
case MSG_DITHER_IMAGE:
ToggleMenuItem(pmsg->what);
fImageView->SetDither(ToggleMenuItem(pmsg->what));
break;
case MSG_SHRINK_TO_WINDOW:
@ -672,10 +694,6 @@ ShowImageWindow::MessageReceived(BMessage *pmsg)
}
break;
case MSG_UPDATE_RECENT_DOCUMENTS:
UpdateRecentDocumentsMenu();
break;
case MSG_PAGE_SETUP:
PageSetup();
break;

View File

@ -29,6 +29,7 @@
#ifndef _ShowImageWindow_h
#define _ShowImageWindow_h
#include <Menu.h>
#include <Window.h>
#include <FilePanel.h>
#include <TranslationDefs.h>
@ -43,6 +44,16 @@ class ShowImageStatusView;
#define TRANSLATOR_FLD "be:translator"
#define TYPE_FLD "be:type"
class RecentDocumentsMenu : public BMenu
{
public:
RecentDocumentsMenu(const char *title, menu_layout layout = B_ITEMS_IN_COLUMN);
bool AddDynamicItem(add_state s);
private:
void UpdateRecentDocumentsMenu();
};
class ShowImageWindow : public BWindow {
public:
ShowImageWindow(const entry_ref *pref);
@ -67,7 +78,6 @@ private:
char target, bool enabled);
BMenuItem* AddDelayItem(BMenu *pmenu, char *caption, float value);
void UpdateRecentDocumentsMenu();
bool ToggleMenuItem(uint32 what);
void EnableMenuItem(BMenu *menu, uint32 what, bool enable);