diff --git a/src/apps/showimage/Filter.cpp b/src/apps/showimage/Filter.cpp index cdb8c5fa10..427e5f3801 100644 --- a/src/apps/showimage/Filter.cpp +++ b/src/apps/showimage/Filter.cpp @@ -27,20 +27,30 @@ /*****************************************************************************/ #include +#include +#include + #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(totalSum[0] / deltaY); - destData[1] = static_cast(totalSum[1] / deltaY); - destData[2] = static_cast(totalSum[2] / deltaY); + destData[0] = static_cast(totalSum[0] / deltaXY); + destData[1] = static_cast(totalSum[1] / deltaXY); + destData[2] = static_cast(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; + } + } diff --git a/src/apps/showimage/Filter.h b/src/apps/showimage/Filter.h index f09d72450e..7b028e1088 100644 --- a/src/apps/showimage/Filter.h +++ b/src/apps/showimage/Filter.h @@ -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 diff --git a/src/apps/showimage/ShowImageApp.cpp b/src/apps/showimage/ShowImageApp.cpp index 52d8fda596..fb5db74b9e 100644 --- a/src/apps/showimage/ShowImageApp.cpp +++ b/src/apps/showimage/ShowImageApp.cpp @@ -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) { diff --git a/src/apps/showimage/ShowImageApp.h b/src/apps/showimage/ShowImageApp.h index 10322bc43b..c2d2c6a19a 100644 --- a/src/apps/showimage/ShowImageApp.h +++ b/src/apps/showimage/ShowImageApp.h @@ -51,7 +51,6 @@ public: private: void StartPulse(); void Open(const entry_ref *pref); - void BroadcastToWindows(uint32 what); void BroadcastToWindows(BMessage *pmsg); void CheckClipboard(); diff --git a/src/apps/showimage/ShowImageConstants.h b/src/apps/showimage/ShowImageConstants.h index 27ee6b3512..d8552dd7c1 100644 --- a/src/apps/showimage/ShowImageConstants.h +++ b/src/apps/showimage/ShowImageConstants.h @@ -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'; diff --git a/src/apps/showimage/ShowImageView.cpp b/src/apps/showimage/ShowImageView.cpp index 25a362dd44..b9a318bab9 100644 --- a/src/apps/showimage/ShowImageView.cpp +++ b/src/apps/showimage/ShowImageView.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #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(s * (rect.Height()+1.0))-1; // center vertically - rect.OffsetBy(0, (height - rect.Height()) / 2); + rect.OffsetBy(0, static_cast((height - rect.Height()) / 2)); } else { s = height / (rect.Height()+1.0); rect.right = static_cast(s * (rect.Width()+1.0))-1; rect.bottom = height-1; // center horizontally - rect.OffsetBy((width - rect.Width()) / 2, 0); + rect.OffsetBy(static_cast((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(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(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); } diff --git a/src/apps/showimage/ShowImageView.h b/src/apps/showimage/ShowImageView.h index c7dc301d7e..3acd38dc54 100644 --- a/src/apps/showimage/ShowImageView.h +++ b/src/apps/showimage/ShowImageView.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -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 */ diff --git a/src/apps/showimage/ShowImageWindow.cpp b/src/apps/showimage/ShowImageWindow.cpp index 8a4f5fd12f..c117f32801 100644 --- a/src/apps/showimage/ShowImageWindow.cpp +++ b/src/apps/showimage/ShowImageWindow.cpp @@ -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; diff --git a/src/apps/showimage/ShowImageWindow.h b/src/apps/showimage/ShowImageWindow.h index 608b81e2d2..61205664e0 100644 --- a/src/apps/showimage/ShowImageWindow.h +++ b/src/apps/showimage/ShowImageWindow.h @@ -29,6 +29,7 @@ #ifndef _ShowImageWindow_h #define _ShowImageWindow_h +#include #include #include #include @@ -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);