diff --git a/src/apps/showimage/Filter.cpp b/src/apps/showimage/Filter.cpp index 62f04e65f9..dbc6d5912c 100644 --- a/src/apps/showimage/Filter.cpp +++ b/src/apps/showimage/Filter.cpp @@ -28,21 +28,62 @@ #include #include "Filter.h" -#include "Scale.h" +// Implementation of FilterThread +FilterThread::FilterThread(Filter* filter, int i) + : fWorkerThread(-1) + , fFilter(filter) + , fI(i) +{ + fWorkerThread = spawn_thread(worker_thread, "filter", suggest_thread_priority(B_STATUS_RENDERING), this); + if (fWorkerThread >= 0) { + resume_thread(fWorkerThread); + } +} + +FilterThread::~FilterThread() +{ + fFilter->Done(); +} + +status_t +FilterThread::worker_thread(void* data) +{ + FilterThread* thread = (FilterThread*)data; + return thread->Run(); +} + +status_t +FilterThread::Run() +{ + fFilter->Run(fI, fFilter->CPUCount()); + delete this; + return B_OK; +} + +// Implementation of Filter Filter::Filter(BBitmap* image, BMessenger listener, uint32 what) : fListener(listener) , fWhat(what) + , fStarted(false) + , fNumberOfThreads(0) , fIsRunning(false) - , fWorkerThread(-1) , fSrcImage(image) , fDestImage(NULL) { + system_info info; + get_system_info(&info); + fCPUCount = info.cpu_count; + fWaitForThreads = create_sem(0, "wait_for_threads"); + #if TIME_FILTER + fStopWatch = NULL; + #endif } Filter::~Filter() { delete fDestImage; + delete_sem(fWaitForThreads); } BBitmap* @@ -54,40 +95,54 @@ Filter::GetBitmap() return fDestImage; } -status_t -Filter::worker_thread(void* data) -{ - Filter* filter = (Filter*)data; - filter->Run(); - if (filter->fIsRunning) { - filter->fListener.SendMessage(filter->fWhat); - filter->fIsRunning = false; - } - return B_OK; -} - void Filter::Start() { GetBitmap(); - if (fSrcImage == NULL || fDestImage == NULL) return; - fWorkerThread = spawn_thread(worker_thread, "filter", suggest_thread_priority(B_STATUS_RENDERING), this); - if (fWorkerThread >= 0) { - fIsRunning = true; - resume_thread(fWorkerThread); - } + if (fStarted || fSrcImage == NULL || fDestImage == NULL) return; + + #if TIME_FILTER + fStopWatch = new BStopWatch("Filter Time"); + #endif + + fNumberOfThreads = fCPUCount; + fIsRunning = true; + fStarted = true; + + // start filter threads + for (int i = 0; i < fCPUCount; i ++) { + new FilterThread(this, i); + } } void Filter::Stop() { - if (fIsRunning) { + if (fStarted) { + // tell FilterThreads to stop calculations fIsRunning = false; - status_t st; - wait_for_thread(fWorkerThread, &st); + // wait for threads to exit + acquire_sem_etc(fWaitForThreads, fCPUCount, 0, 0); + // ready to start again + fStarted = false; } } +void +Filter::Done() +{ + if (atomic_add(&fNumberOfThreads, -1) == 1) { + #if TIME_FILTER + delete fStopWatch; fStopWatch = NULL; + #endif + if (fIsRunning) { + fListener.SendMessage(fWhat); + } + fIsRunning = false; + } + release_sem(fWaitForThreads); +} + bool Filter::IsRunning() const { @@ -106,9 +161,10 @@ Filter::GetDestImage() return fDestImage; } -Scaler::Scaler(BBitmap* image, float scale, BMessenger listener, uint32 what) +// Implementation of (bilinear) Scaler +Scaler::Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what) : Filter(image, listener, what) - , fScale(scale) + , fRect(rect) { } @@ -120,14 +176,301 @@ BBitmap* Scaler::CreateDestImage(BBitmap* srcImage) { if (srcImage == NULL || srcImage->ColorSpace() != B_RGB32 && srcImage->ColorSpace() !=B_RGBA32) return NULL; - BRect src(srcImage->Bounds()); - BRect dest(0, 0, (src.Width()+1)*fScale - 1, (src.Height()+1)*fScale - 1); + BRect dest(0, 0, fRect.IntegerWidth(), fRect.IntegerHeight()); BBitmap* destImage = new BBitmap(dest, srcImage->ColorSpace()); return destImage; } -void -Scaler::Run() -{ - scale(GetSrcImage(), GetDestImage(), IsRunningAddr(), fScale, fScale); +bool +Scaler::Matches(BRect rect) const { + return fRect.IntegerWidth() == rect.IntegerWidth() && + fRect.IntegerHeight() == rect.IntegerHeight(); +} + + +// Scale bilinear using floating point calculations +typedef int32 intType; + +typedef struct { + intType srcColumn; + float alpha0; + float alpha1; +} ColumnData; + +void +Scaler::ScaleBilinear(intType fromRow, int32 toRow) +{ + BBitmap* src; + BBitmap* dest; + intType srcW, srcH; + intType destW, destH; + intType x, y, i; + ColumnData* columnData; + ColumnData* cd; + const uchar* srcBits; + uchar* destBits; + intType srcBPR, destBPR; + const uchar* srcData; + uchar* destDataRow; + uchar* destData; + const int32 kBPP = 4; + + src = GetSrcImage(); + dest = GetDestImage(); + + srcW = src->Bounds().IntegerWidth(); + srcH = src->Bounds().IntegerHeight(); + destW = dest->Bounds().IntegerWidth(); + destH = dest->Bounds().IntegerHeight(); + + srcBits = (uchar*)src->Bits(); + destBits = (uchar*)dest->Bits(); + srcBPR = src->BytesPerRow(); + destBPR = dest->BytesPerRow(); + + columnData = new ColumnData[destW]; + cd = columnData; + for (i = 0; i < destW; i ++, cd++) { + float column = (float)i * (float)srcW / (float)destW; + cd->srcColumn = (intType)column; + cd->alpha1 = column - cd->srcColumn; + cd->alpha0 = 1.0 - cd->alpha1; + } + + destDataRow = destBits + fromRow * destBPR; + + for (y = fromRow; IsRunning() && y <= toRow; y ++, destDataRow += destBPR) { + float row; + intType srcRow; + float alpha0, alpha1; + + row = (float)y * (float)srcH / (float)destH; + srcRow = (intType)row; + alpha1 = row - srcRow; + alpha0 = 1.0 - alpha1; + + srcData = srcBits + srcRow * srcBPR; + destData = destDataRow; + + if (y < destH) { + float a0, a1; + const uchar *a, *b, *c, *d; + + for (x = 0; x < destW; x ++, destData += kBPP) { + a = srcData + columnData[x].srcColumn * kBPP; + b = a + kBPP; + c = a + srcBPR; + d = c + kBPP; + + a0 = columnData[x].alpha0; + a1 = columnData[x].alpha1; + + destData[0] = static_cast( + (a[0] * a0 + b[0] * a1) * alpha0 + + (c[0] * a0 + d[0] * a1) * alpha1); + destData[1] = static_cast( + (a[1] * a0 + b[1] * a1) * alpha0 + + (c[1] * a0 + d[1] * a1) * alpha1); + destData[2] = static_cast( + (a[2] * a0 + b[2] * a1) * alpha0 + + (c[2] * a0 + d[2] * a1) * alpha1); + } + + // right column + a = srcData + srcW * kBPP; + c = a + srcBPR; + + destData[0] = static_cast(a[0] * alpha0 + c[0] * alpha1); + destData[1] = static_cast(a[1] * alpha0 + c[1] * alpha1); + destData[2] = static_cast(a[2] * alpha0 + c[2] * alpha1); + } else { + float a0, a1; + const uchar *a, *b; + for (x = 0; x < destW; x ++, destData += kBPP) { + a = srcData + columnData[x].srcColumn * kBPP; + b = a + kBPP; + + a0 = columnData[x].alpha0; + a1 = columnData[x].alpha1; + + destData[0] = static_cast(a[0] * a0 + b[0] * a1); + destData[1] = static_cast(a[1] * a0 + b[1] * a1); + destData[2] = static_cast(a[2] * a0 + b[2] * a1); + } + + // bottom, right pixel + a = srcData + srcW * kBPP; + + destData[0] = a[0]; + destData[1] = a[1]; + destData[2] = a[2]; + } + + } + + delete[] columnData; +} + +// 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. +typedef int64 long_fixed_point; +typedef int32 fixed_point; + +// Could use shift operator instead of multiplication and division, +// but compiler will optimize it for use anyway. +#define to_fixed_point(number) static_cast((number) * kFPPrecisionFactor) +#define from_fixed_point(number) ((number) / kFPPrecisionFactor) +#define to_float(number) from_fixed_point(static_cast(number)) + +#define int_value(number) ((number) & kFPInverseMask) +#define tail_value(number) ((number) & kFPPrecisionMask) + +// Has to be called after muliplication of two fixed point values +#define mult_correction(number) ((number) / kFPPrecisionFactor) + +const int32 kFPPrecision = 8; // (32-kFPPrecision).kFPPrecision +const int32 kFPPrecisionFactor = (1 << kFPPrecision); +const int32 kFPPrecisionMask = ((kFPPrecisionFactor)-1); +const int32 kFPInverseMask = (~kFPPrecisionMask); +const int32 kFPOne = to_fixed_point(1); + +typedef struct { + int32 srcColumn; + fixed_point alpha0; + fixed_point alpha1; +} ColumnDataFP; + +void +Scaler::ScaleBilinearFP(intType fromRow, int32 toRow) +{ + BBitmap* src; + BBitmap* dest; + intType srcW, srcH; + intType destW, destH; + intType x, y, i; + ColumnDataFP* columnData; + ColumnDataFP* cd; + const uchar* srcBits; + uchar* destBits; + intType srcBPR, destBPR; + const uchar* srcData; + uchar* destDataRow; + uchar* destData; + const int32 kBPP = 4; + + src = GetSrcImage(); + dest = GetDestImage(); + + srcW = src->Bounds().IntegerWidth(); + srcH = src->Bounds().IntegerHeight(); + destW = dest->Bounds().IntegerWidth(); + destH = dest->Bounds().IntegerHeight(); + + srcBits = (uchar*)src->Bits(); + destBits = (uchar*)dest->Bits(); + srcBPR = src->BytesPerRow(); + destBPR = dest->BytesPerRow(); + + fixed_point fpSrcW = to_fixed_point(srcW); + fixed_point fpDestW = to_fixed_point(destW); + fixed_point fpSrcH = to_fixed_point(srcH); + fixed_point fpDestH = to_fixed_point(destH); + + columnData = new ColumnDataFP[destW]; + cd = columnData; + for (i = 0; i < destW; i ++, cd++) { + fixed_point column = to_fixed_point(i) * (long_fixed_point)fpSrcW / fpDestW; + cd->srcColumn = from_fixed_point(column); + cd->alpha1 = tail_value(column); // weigth for left pixel value + cd->alpha0 = kFPOne - cd->alpha1; // weigth for right pixel value + } + + destDataRow = destBits + fromRow * destBPR; + + for (y = fromRow; IsRunning() && y <= toRow; y ++, destDataRow += destBPR) { + fixed_point row; + intType srcRow; + fixed_point alpha0, alpha1; + + 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 + + srcData = srcBits + srcRow * srcBPR; + destData = destDataRow; + + // Need mult_correction for "outer" multiplication only + #define I4(i) from_fixed_point(mult_correction(\ + (a[i] * a0 + b[i] * a1) * alpha0 + \ + (c[i] * a0 + d[i] * a1) * alpha1)) + #define V2(i) from_fixed_point(a[i] * alpha0 + c[i] * alpha1); + #define H2(i) from_fixed_point(a[i] * a0 + b[i] * a1); + + if (y < destH) { + fixed_point a0, a1; + const uchar *a, *b, *c, *d; + + for (x = 0; x < destW; x ++, destData += kBPP) { + a = srcData + columnData[x].srcColumn * kBPP; + b = a + kBPP; + c = a + srcBPR; + d = c + kBPP; + + a0 = columnData[x].alpha0; + a1 = columnData[x].alpha1; + + destData[0] = I4(0); + destData[1] = I4(1); + destData[2] = I4(2); + } + + // right column + a = srcData + srcW * kBPP; + c = a + srcBPR; + + destData[0] = V2(0); + destData[1] = V2(1); + destData[2] = V2(2); + } else { + fixed_point a0, a1; + const uchar *a, *b; + for (x = 0; x < destW; x ++, destData += kBPP) { + a = srcData + columnData[x].srcColumn * kBPP; + b = a + kBPP; + + a0 = columnData[x].alpha0; + a1 = columnData[x].alpha1; + + destData[0] = H2(0); + destData[1] = H2(1); + destData[2] = H2(2); + } + + // bottom, right pixel + a = srcData + srcW * kBPP; + + destData[0] = a[0]; + destData[1] = a[1]; + destData[2] = a[2]; + } + + } + + delete[] columnData; +} + +void +Scaler::Run(int i, int n) +{ + int32 from, to, height; + height = (GetDestImage()->Bounds().IntegerHeight()+1)/n; + from = i * height; + if (i+1 == n) { + to = (int32)GetDestImage()->Bounds().bottom; + } else { + to = from + height - 1; + } + ScaleBilinearFP(from, to); } diff --git a/src/apps/showimage/Filter.h b/src/apps/showimage/Filter.h index 52da652497..2b970899cf 100644 --- a/src/apps/showimage/Filter.h +++ b/src/apps/showimage/Filter.h @@ -32,21 +32,66 @@ #include #include #include +#include + +#define TIME_FILTER 0 + +class Filter; + +// Used by class Filter +class FilterThread { +public: + FilterThread(Filter* filter, int i); + ~FilterThread(); + +private: + status_t Run(); + static status_t worker_thread(void* data); + thread_id fWorkerThread; + Filter* fFilter; + int fI; +}; class Filter { public: + // The filter uses the input "image" as source image + // 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. + // To start the operation Start() method has to + // be called. The operation is executed in as many + // threads as CPUs are active. + // 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. Filter(BBitmap* image, BMessenger listener, uint32 what); virtual ~Filter(); + // The bitmap the filter writes into BBitmap* GetBitmap(); + // Starts one or more FilterThreads void Start(); + // Has to be called after Start() (even if IsRunning() is false) void Stop(); + // Are there any running FilterThreads? bool IsRunning() const; - volatile bool *IsRunningAddr() { return &fIsRunning; } + // To be implemented by inherited class virtual BBitmap* CreateDestImage(BBitmap* srcImage) = 0; - virtual void Run() = 0; + // Should calculate part i of n of the image. i starts with zero + virtual void Run(int i, int n) = 0; + + // Used by FilterThread only! + void Done(); + int32 CPUCount() const { return fCPUCount; } protected: BBitmap* GetSrcImage(); @@ -55,23 +100,32 @@ protected: private: BMessenger fListener; uint32 fWhat; - static status_t worker_thread(void* data); + int32 fCPUCount; + bool fStarted; + sem_id fWaitForThreads; + volatile int32 fNumberOfThreads; volatile bool fIsRunning; - thread_id fWorkerThread; BBitmap* fSrcImage; BBitmap* fDestImage; +#if TIME_FILTER + BStopWatch* fStopWatch; +#endif }; class Scaler : public Filter { public: - Scaler(BBitmap* image, float scale, BMessenger listener, uint32 what); + Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what); ~Scaler(); BBitmap* CreateDestImage(BBitmap* srcImage); - void Run(); - float Scale() const { return fScale; } + void Run(int i, int n); + bool Matches(BRect rect) const; + private: - float fScale; + void ScaleBilinear(int32 fromRow, int32 toRow); + void ScaleBilinearFP(int32 fromRow, int32 toRow); + + BRect fRect; }; #endif diff --git a/src/apps/showimage/ShowImageConstants.h b/src/apps/showimage/ShowImageConstants.h index 3aaeff1669..197f4d831a 100644 --- a/src/apps/showimage/ShowImageConstants.h +++ b/src/apps/showimage/ShowImageConstants.h @@ -50,7 +50,8 @@ const uint32 MSG_PAGE_PREV = 'mPGP'; const uint32 MSG_GOTO_PAGE = 'mGTP'; const uint32 MSG_FILE_NEXT = 'mFLN'; const uint32 MSG_FILE_PREV = 'mFLP'; -const uint32 MSG_FIT_TO_WINDOW_SIZE = 'mFWS'; +const uint32 MSG_SHRINK_TO_WINDOW = 'mSTW'; +const uint32 MSG_ZOOM_TO_WINDOW = 'mZTW'; const uint32 MSG_ROTATE_90 = 'mR90'; const uint32 MSG_ROTATE_270 = 'mR27'; const uint32 MSG_MIRROR_VERTICAL = 'mMIV'; diff --git a/src/apps/showimage/ShowImageView.cpp b/src/apps/showimage/ShowImageView.cpp index fe3b80667b..752f3b76ea 100644 --- a/src/apps/showimage/ShowImageView.cpp +++ b/src/apps/showimage/ShowImageView.cpp @@ -159,7 +159,9 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode, fDocumentCount = 1; fAnimateSelection = true; fbHasSelection = false; - fResizeToViewBounds = false; + fShrinkToBounds = false; + fZoomToBounds = false; + fHasBorder = true; fHAlignment = B_ALIGN_LEFT; fVAlignment = B_ALIGN_TOP; fSlideShow = false; @@ -317,10 +319,30 @@ ShowImageView::SetShowCaption(bool show) } void -ShowImageView::ResizeToViewBounds(bool resize) +ShowImageView::SetShrinkToBounds(bool enable) { - if (fResizeToViewBounds != resize) { - fResizeToViewBounds = resize; + if (fShrinkToBounds != enable) { + fShrinkToBounds = enable; + FixupScrollBars(); + Invalidate(); + } +} + +void +ShowImageView::SetZoomToBounds(bool enable) +{ + if (fZoomToBounds != enable) { + fZoomToBounds = enable; + FixupScrollBars(); + Invalidate(); + } +} + +void +ShowImageView::SetBorder(bool hasBorder) +{ + if (fHasBorder != hasBorder) { + fHasBorder = hasBorder; FixupScrollBars(); Invalidate(); } @@ -402,29 +424,27 @@ ShowImageView::AlignBitmap() width = Bounds().Width()-2*PEN_SIZE+1; height = Bounds().Height()-2*PEN_SIZE+1; if (width == 0 || height == 0) return rect; - if (fResizeToViewBounds) { + if (fShrinkToBounds && (rect.Width() >= Bounds().Width() || rect.Height() >= Bounds().Height()) || + fZoomToBounds && rect.Width() < Bounds().Width() && rect.Height() < Bounds().Height()) { float s; - s = width / (rect.Width()+1); + s = width / (rect.Width()+1.0); - if (s * rect.Height() <= height) { - // XXX temporary solution, fZoom should not be changed here - fZoom = s; + if (s * (rect.Height()+1.0) <= height) { rect.right = width-1; - rect.bottom = static_cast(s * (rect.Height()+1))-1; + rect.bottom = static_cast(s * (rect.Height()+1.0))-1; // center vertically rect.OffsetBy(0, (height - rect.Height()) / 2); } else { - // XXX temporary solution, fZoom should not be changed here - fZoom = height / (rect.Height()+1); - rect.right = static_cast(fZoom * (rect.Width()+1))-1; + 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); } } else { // zoom image - rect.right = static_cast((rect.right+1)*fZoom)-1; - rect.bottom = static_cast((rect.bottom+1)*fZoom)-1; + rect.right = static_cast((rect.right+1.0)*fZoom)-1; + rect.bottom = static_cast((rect.bottom+1.0)*fZoom)-1; // align switch (fHAlignment) { case B_ALIGN_CENTER: @@ -435,7 +455,9 @@ ShowImageView::AlignBitmap() // fall through default: case B_ALIGN_LEFT: - rect.OffsetBy(BORDER_WIDTH, 0); + if (fHasBorder) { + rect.OffsetBy(BORDER_WIDTH, 0); + } break; } switch (fVAlignment) { @@ -447,7 +469,9 @@ ShowImageView::AlignBitmap() // fall through default: case B_ALIGN_TOP: - rect.OffsetBy(0, BORDER_WIDTH); + if (fHasBorder) { + rect.OffsetBy(0, BORDER_WIDTH); + } break; } } @@ -460,8 +484,8 @@ ShowImageView::Setup(BRect rect) { fLeft = rect.left; fTop = rect.top; - fScaleX = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0); - fScaleY = (rect.Height()+1.0) / (fBitmap->Bounds().Height()+1.0); + fScaleX = rect.Width() / fBitmap->Bounds().Width(); + fScaleY = rect.Height() / fBitmap->Bounds().Height(); } BPoint @@ -572,12 +596,12 @@ ShowImageView::EraseCaption() } Scaler* -ShowImageView::GetScaler() +ShowImageView::GetScaler(BRect rect) { - if (fScaler == NULL || fScaler->Scale() != fZoom) { + if (fScaler == NULL || !fScaler->Matches(rect)) { DeleteScaler(); BMessenger msgr(this, Window()); - fScaler = new Scaler(fBitmap, fZoom, msgr, MSG_INVALIDATE); + fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE); fScaler->Start(); } return fScaler; @@ -587,7 +611,7 @@ void ShowImageView::DrawImage(BRect rect) { if (fScaleBilinear) { - Scaler* scaler = GetScaler(); + Scaler* scaler = GetScaler(rect); if (scaler != NULL && scaler->GetBitmap() != NULL && !scaler->IsRunning()) { BBitmap* bitmap = scaler->GetBitmap(); DrawBitmap(bitmap, BPoint(rect.left, rect.top)); @@ -847,10 +871,6 @@ ShowImageView::HandleDrop(BMessage* msg) sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK; - fprintf(stderr, "HandleDrop saveToFile %s, sendInMessage %s\n", - saveToFile ? "yes" : "no", - sendInMessage ? "yes" : "no"); - bitmap = CopySelection(); if (bitmap == NULL) return; @@ -1109,8 +1129,6 @@ ShowImageView::LimitToRange(float v, orientation o, bool absolute) void ShowImageView::ScrollRestricted(float x, float y, bool absolute) { - if (fResizeToViewBounds) return; - if (x != 0) { x = LimitToRange(x, B_HORIZONTAL, absolute); } @@ -1247,46 +1265,41 @@ ShowImageView::MessageReceived(BMessage *pmsg) } void -ShowImageView::FixupScrollBars() +ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength) { + float prop, range; BScrollBar *psb; - if (fResizeToViewBounds) { - psb = ScrollBar(B_HORIZONTAL); - if (psb) psb->SetRange(0, 0); - psb = ScrollBar(B_VERTICAL); - if (psb) psb->SetRange(0, 0); - return; + psb = ScrollBar(o); + if (psb) { + if (fHasBorder) { + bitmapLength += BORDER_WIDTH*2; + } + range = bitmapLength - viewLength; + if (range < 0.0) { + range = 0.0; + } + prop = viewLength / bitmapLength; + if (prop > 1.0) { + prop = 1.0; + } + psb->SetRange(0, range); + psb->SetProportion(prop); + psb->SetSteps(10, 100); } +} +void +ShowImageView::FixupScrollBars() +{ BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0); if (fBitmap) { BRect rect(AlignBitmap()); rctbitmap.Set(0, 0, rect.Width(), rect.Height()); } - float prop, range; - psb = ScrollBar(B_HORIZONTAL); - if (psb) { - range = rctbitmap.Width() + (BORDER_WIDTH * 2) - rctview.Width(); - if (range < 0) range = 0; - prop = rctview.Width() / (rctbitmap.Width() + (BORDER_WIDTH * 2)); - if (prop > 1.0f) prop = 1.0f; - psb->SetRange(0, range); - psb->SetProportion(prop); - psb->SetSteps(10, 100); - } - - psb = ScrollBar(B_VERTICAL); - if (psb) { - range = rctbitmap.Height() + (BORDER_HEIGHT * 2) - rctview.Height(); - if (range < 0) range = 0; - prop = rctview.Height() / (rctbitmap.Height() + (BORDER_HEIGHT * 2)); - if (prop > 1.0f) prop = 1.0f; - psb->SetRange(0, range); - psb->SetProportion(prop); - psb->SetSteps(10, 100); - } + FixupScrollBar(B_HORIZONTAL, rctbitmap.Width(), rctview.Width()); + FixupScrollBar(B_VERTICAL, rctbitmap.Height(), rctview.Height()); } int32 diff --git a/src/apps/showimage/ShowImageView.h b/src/apps/showimage/ShowImageView.h index dfee3ed558..e264c33ac6 100644 --- a/src/apps/showimage/ShowImageView.h +++ b/src/apps/showimage/ShowImageView.h @@ -47,8 +47,12 @@ public: void SetImage(const entry_ref *pref); void SetShowCaption(bool show); - void ResizeToViewBounds(bool resize); - bool GetResizeToViewBounds() const { return fResizeToViewBounds; } + void SetShrinkToBounds(bool enable); + bool GetShrinkToBounds() const { return fShrinkToBounds; } + void SetZoomToBounds(bool enable); + bool GetZoomToBounds() const { return fZoomToBounds; } + void SetBorder(bool hasBorder); + bool HasBorder() const { return fHasBorder; } void SetAlignment(alignment horizontal, vertical_alignment vertical); BBitmap *GetBitmap(); void GetName(BString *name); @@ -67,6 +71,7 @@ public: virtual void MessageReceived(BMessage *pmsg); + void FixupScrollBar(orientation o, float bitmapLength, float viewLength); void FixupScrollBars(); int32 CurrentPage(); @@ -148,7 +153,7 @@ private: void DrawCaption(); void EraseCaption(); void DrawSelectionBox(BRect &rect); - Scaler* GetScaler(); + Scaler* GetScaler(BRect rect); void DrawImage(BRect rect); float LimitToRange(float v, orientation o, bool absolute); void ScrollRestricted(float x, float y, bool absolute); @@ -157,22 +162,24 @@ private: void MouseWheelChanged(BMessage* msg); void ShowPopUpMenu(BPoint screen); - entry_ref fCurrentRef; - int32 fDocumentIndex; - int32 fDocumentCount; - BBitmap *fBitmap; - BBitmap *fSelBitmap; - float fZoom; - bool fScaleBilinear; - Scaler* fScaler; - bool fResizeToViewBounds; - alignment fHAlignment; - vertical_alignment fVAlignment; + entry_ref fCurrentRef; // of the image + int32 fDocumentIndex; // of the image in the file + int32 fDocumentCount; // number of images in the file + BBitmap *fBitmap; // to be displayed + BBitmap *fSelBitmap; // the bitmap in the selection + float fZoom; // factor to be used to display the image + bool fScaleBilinear; // use bilinear scaling? + Scaler* fScaler; // holds the scaled image if bilinear scaling is enabled + bool fShrinkToBounds; // shrink images to view bounds that are larger than the view + bool fZoomToBounds; // zoom images to view bounds that are smaller than the view + bool fHasBorder; // should the image have a border? + alignment fHAlignment; // horizontal alignment (left and centered only) + vertical_alignment fVAlignment; // vertical alignment (left and centered only) float fLeft; // the origin of the image in the view float fTop; - float fScaleX; + float fScaleX; // to convert image from/to view coordinates float fScaleY; - bool fMovesImage; + bool fMovesImage; // is the image being moved with the mouse bool fMakesSelection; // is a selection being made BPoint fFirstPoint; // first point in image space of selection bool fAnimateSelection; // marching ants @@ -182,12 +189,12 @@ private: // the portion of the background bitmap the selection is made from pattern fPatternUp, fPatternDown, fPatternLeft, fPatternRight; - bool fSlideShow; - int fSlideShowDelay; - int fSlideShowCountDown; + bool fSlideShow; // is slide show enabled? + int fSlideShowDelay; // in pulse rate units + int fSlideShowCountDown; // shows next image if it reaches zero - bool fShowCaption; - BString fCaption; + bool fShowCaption; // display caption? + BString fCaption; // caption text }; #endif /* _ShowImageView_h */ diff --git a/src/apps/showimage/ShowImageWindow.cpp b/src/apps/showimage/ShowImageWindow.cpp index f72ffad26a..c06223f993 100644 --- a/src/apps/showimage/ShowImageWindow.cpp +++ b/src/apps/showimage/ShowImageWindow.cpp @@ -212,19 +212,24 @@ ShowImageWindow::BuildViewMenu(BMenu *pmenu) AddItemMenu(pmenu, "Zoom Out", MSG_ZOOM_OUT, '-', 0, 'W', true); AddItemMenu(pmenu, "Scale Bilinear", MSG_SCALE_BILINEAR, 0, 0, 'W', true); pmenu->AddSeparatorItem(); - AddItemMenu(pmenu, "Fit To Window Size", MSG_FIT_TO_WINDOW_SIZE, 0, 0, 'W', true); + AddItemMenu(pmenu, "Shrink to Window", MSG_SHRINK_TO_WINDOW, 0, 0, 'W', true); + AddItemMenu(pmenu, "Zoom to Window", MSG_ZOOM_TO_WINDOW, 0, 0, 'W', true); AddItemMenu(pmenu, "Full Screen", MSG_FULL_SCREEN, B_ENTER, 0, 'W', true); MarkMenuItem(pmenu, MSG_FULL_SCREEN, fFullScreen); AddItemMenu(pmenu, "Show Caption in Full Screen Mode", MSG_SHOW_CAPTION, 0, 0, 'W', true); MarkMenuItem(pmenu, MSG_SHOW_CAPTION, fShowCaption); if (fpImageView) { + bool shrink, zoom, enabled; MarkMenuItem(pmenu, MSG_SCALE_BILINEAR, fpImageView->GetScaleBilinear()); - bool resize = fpImageView->GetResizeToViewBounds(); - MarkMenuItem(pmenu, MSG_FIT_TO_WINDOW_SIZE, resize); - EnableMenuItem(pmenu, MSG_ORIGINAL_SIZE, !resize); - EnableMenuItem(pmenu, MSG_ZOOM_IN, !resize); - EnableMenuItem(pmenu, MSG_ZOOM_OUT, !resize); + shrink = fpImageView->GetShrinkToBounds(); + zoom = fpImageView->GetZoomToBounds(); + MarkMenuItem(pmenu, MSG_SHRINK_TO_WINDOW, shrink); + MarkMenuItem(pmenu, MSG_ZOOM_TO_WINDOW, zoom); + enabled = !(shrink || zoom); + EnableMenuItem(pmenu, MSG_ORIGINAL_SIZE, enabled); + EnableMenuItem(pmenu, MSG_ZOOM_IN, enabled); + EnableMenuItem(pmenu, MSG_ZOOM_OUT, enabled); } } @@ -413,6 +418,23 @@ ShowImageWindow::MarkSlideShowDelay(float value) } } + +void +ShowImageWindow::ResizeToWindow(bool shrink, uint32 what) +{ + bool enabled; + enabled = ToggleMenuItem(what); + if (shrink) { + fpImageView->SetShrinkToBounds(enabled); + } else { + fpImageView->SetZoomToBounds(enabled); + } + enabled = !(fpImageView->GetShrinkToBounds() || fpImageView->GetZoomToBounds()); + EnableMenuItem(fpBar, MSG_ORIGINAL_SIZE, enabled); + EnableMenuItem(fpBar, MSG_ZOOM_IN, enabled); + EnableMenuItem(fpBar, MSG_ZOOM_OUT, enabled); +} + void ShowImageWindow::MessageReceived(BMessage *pmsg) { @@ -571,15 +593,11 @@ ShowImageWindow::MessageReceived(BMessage *pmsg) ToggleMenuItem(pmsg->what); break; - case MSG_FIT_TO_WINDOW_SIZE: - { - bool resize; - resize = ToggleMenuItem(pmsg->what); - fpImageView->ResizeToViewBounds(resize); - EnableMenuItem(fpBar, MSG_ORIGINAL_SIZE, !resize); - EnableMenuItem(fpBar, MSG_ZOOM_IN, !resize); - EnableMenuItem(fpBar, MSG_ZOOM_OUT, !resize); - } + case MSG_SHRINK_TO_WINDOW: + ResizeToWindow(true, pmsg->what); + break; + case MSG_ZOOM_TO_WINDOW: + ResizeToWindow(false, pmsg->what); break; case MSG_FILE_PREV: @@ -768,6 +786,7 @@ ShowImageWindow::ToggleFullScreen() SetFlags(Flags() & ~(B_NOT_RESIZABLE | B_NOT_MOVABLE)); fpImageView->SetAlignment(B_ALIGN_LEFT, B_ALIGN_TOP); } + fpImageView->SetBorder(!fFullScreen); fpImageView->SetShowCaption(fFullScreen && fShowCaption); MoveTo(frame.left, frame.top); ResizeTo(frame.Width(), frame.Height()); diff --git a/src/apps/showimage/ShowImageWindow.h b/src/apps/showimage/ShowImageWindow.h index 5775c22c24..fd79466595 100644 --- a/src/apps/showimage/ShowImageWindow.h +++ b/src/apps/showimage/ShowImageWindow.h @@ -73,6 +73,7 @@ private: void EnableMenuItem(BMenu *menu, uint32 what, bool enable); void MarkMenuItem(BMenu *menu, uint32 what, bool marked); void MarkSlideShowDelay(float value); + void ResizeToWindow(bool shrink, uint32 what); void SaveAs(BMessage *pmsg); // Handle Save As submenu choice