- 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:
+506
-51
@@ -27,19 +27,29 @@
|
|||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
#include <scheduler.h>
|
#include <scheduler.h>
|
||||||
|
#include <Debug.h>
|
||||||
|
#include <Screen.h>
|
||||||
|
|
||||||
#include "Filter.h"
|
#include "Filter.h"
|
||||||
|
|
||||||
|
extern "C" int _kget_cpu_state_(int cpu);
|
||||||
|
|
||||||
// Implementation of FilterThread
|
// Implementation of FilterThread
|
||||||
FilterThread::FilterThread(Filter* filter, int i)
|
FilterThread::FilterThread(Filter* filter, int32 i, int32 n, bool runInCurrentThread)
|
||||||
: fWorkerThread(-1)
|
: fFilter(filter)
|
||||||
, fFilter(filter)
|
|
||||||
, fI(i)
|
, fI(i)
|
||||||
|
, fN(n)
|
||||||
{
|
{
|
||||||
fWorkerThread = spawn_thread(worker_thread, "filter", suggest_thread_priority(B_STATUS_RENDERING), this);
|
if (runInCurrentThread) {
|
||||||
if (fWorkerThread >= 0) {
|
Run();
|
||||||
resume_thread(fWorkerThread);
|
|
||||||
} else {
|
} 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +68,17 @@ FilterThread::worker_thread(void* data)
|
|||||||
status_t
|
status_t
|
||||||
FilterThread::Run()
|
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;
|
delete this;
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
@@ -68,15 +88,29 @@ Filter::Filter(BBitmap* image, BMessenger listener, uint32 what)
|
|||||||
: fListener(listener)
|
: fListener(listener)
|
||||||
, fWhat(what)
|
, fWhat(what)
|
||||||
, fStarted(false)
|
, fStarted(false)
|
||||||
|
, fN(0)
|
||||||
, fNumberOfThreads(0)
|
, fNumberOfThreads(0)
|
||||||
, fIsRunning(false)
|
, fIsRunning(false)
|
||||||
, fSrcImage(image)
|
, fSrcImage(image)
|
||||||
, fDestImage(NULL)
|
, fDestImage(NULL)
|
||||||
{
|
{
|
||||||
|
// get the number of active CPUs
|
||||||
|
int count;
|
||||||
system_info info;
|
system_info info;
|
||||||
get_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");
|
fWaitForThreads = create_sem(0, "wait_for_threads");
|
||||||
|
|
||||||
#if TIME_FILTER
|
#if TIME_FILTER
|
||||||
fStopWatch = NULL;
|
fStopWatch = NULL;
|
||||||
#endif
|
#endif
|
||||||
@@ -97,37 +131,53 @@ Filter::GetBitmap()
|
|||||||
return fDestImage;
|
return fDestImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
BBitmap*
|
||||||
Filter::Start()
|
Filter::DetachBitmap()
|
||||||
{
|
{
|
||||||
GetBitmap();
|
BBitmap* image = fDestImage;
|
||||||
if (fStarted || fSrcImage == NULL || fDestImage == NULL) return;
|
fDestImage = NULL;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Filter::Start(bool async)
|
||||||
|
{
|
||||||
|
if (fStarted || fSrcImage == NULL) return;
|
||||||
|
|
||||||
#if TIME_FILTER
|
#if TIME_FILTER
|
||||||
fStopWatch = new BStopWatch("Filter Time");
|
fStopWatch = new BStopWatch("Filter Time");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
fNumberOfThreads = fCPUCount;
|
fN = NumberOfThreads();
|
||||||
|
fNumberOfThreads = fN;
|
||||||
fIsRunning = true;
|
fIsRunning = true;
|
||||||
fStarted = true;
|
fStarted = true;
|
||||||
|
|
||||||
// start filter threads
|
// start first filter thread
|
||||||
for (int i = 0; i < fCPUCount; i ++) {
|
new FilterThread(this, 0, fN, !async);
|
||||||
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
|
void
|
||||||
Filter::Stop()
|
Filter::Stop()
|
||||||
{
|
{
|
||||||
if (fStarted) {
|
// tell FilterThreads to stop calculations
|
||||||
// tell FilterThreads to stop calculations
|
fIsRunning = false;
|
||||||
fIsRunning = false;
|
Wait();
|
||||||
// wait for threads to exit
|
|
||||||
while (acquire_sem_etc(fWaitForThreads, fCPUCount, 0, 0) == B_INTERRUPTED);
|
|
||||||
// ready to start again
|
|
||||||
fStarted = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -137,6 +187,7 @@ Filter::Done()
|
|||||||
#if TIME_FILTER
|
#if TIME_FILTER
|
||||||
delete fStopWatch; fStopWatch = NULL;
|
delete fStopWatch; fStopWatch = NULL;
|
||||||
#endif
|
#endif
|
||||||
|
Completed();
|
||||||
if (fIsRunning) {
|
if (fIsRunning) {
|
||||||
fListener.SendMessage(fWhat);
|
fListener.SendMessage(fWhat);
|
||||||
}
|
}
|
||||||
@@ -151,6 +202,25 @@ Filter::IsRunning() const
|
|||||||
return fIsRunning;
|
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*
|
BBitmap*
|
||||||
Filter::GetSrcImage()
|
Filter::GetSrcImage()
|
||||||
{
|
{
|
||||||
@@ -164,14 +234,19 @@ Filter::GetDestImage()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Implementation of (bilinear) Scaler
|
// 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)
|
: Filter(image, listener, what)
|
||||||
|
, fScaledImage(NULL)
|
||||||
, fRect(rect)
|
, fRect(rect)
|
||||||
|
, fDither(dither)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Scaler::~Scaler()
|
Scaler::~Scaler()
|
||||||
{
|
{
|
||||||
|
if (GetDestImage() != fScaledImage) {
|
||||||
|
delete fScaledImage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BBitmap*
|
BBitmap*
|
||||||
@@ -179,14 +254,22 @@ Scaler::CreateDestImage(BBitmap* srcImage)
|
|||||||
{
|
{
|
||||||
if (srcImage == NULL || srcImage->ColorSpace() != B_RGB32 && srcImage->ColorSpace() !=B_RGBA32) return NULL;
|
if (srcImage == NULL || srcImage->ColorSpace() != B_RGB32 && srcImage->ColorSpace() !=B_RGBA32) return NULL;
|
||||||
BRect dest(0, 0, fRect.IntegerWidth(), fRect.IntegerHeight());
|
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;
|
return destImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
Scaler::Matches(BRect rect) const {
|
Scaler::Matches(BRect rect, bool dither) const
|
||||||
|
{
|
||||||
return fRect.IntegerWidth() == rect.IntegerWidth() &&
|
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;
|
const int32 kBPP = 4;
|
||||||
|
|
||||||
src = GetSrcImage();
|
src = GetSrcImage();
|
||||||
dest = GetDestImage();
|
dest = fScaledImage;
|
||||||
|
|
||||||
srcW = src->Bounds().IntegerWidth();
|
srcW = src->Bounds().IntegerWidth();
|
||||||
srcH = src->Bounds().IntegerHeight();
|
srcH = src->Bounds().IntegerHeight();
|
||||||
@@ -244,7 +327,11 @@ Scaler::ScaleBilinear(intType fromRow, int32 toRow)
|
|||||||
intType srcRow;
|
intType srcRow;
|
||||||
float alpha0, alpha1;
|
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;
|
srcRow = (intType)row;
|
||||||
alpha1 = row - srcRow;
|
alpha1 = row - srcRow;
|
||||||
alpha0 = 1.0 - alpha1;
|
alpha0 = 1.0 - alpha1;
|
||||||
@@ -313,7 +400,7 @@ Scaler::ScaleBilinear(intType fromRow, int32 toRow)
|
|||||||
|
|
||||||
// Scale bilinear using fixed point calculations
|
// Scale bilinear using fixed point calculations
|
||||||
// Is already more than two times faster than floating point version
|
// 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 {
|
typedef struct {
|
||||||
int32 srcColumn;
|
int32 srcColumn;
|
||||||
@@ -340,7 +427,7 @@ Scaler::ScaleBilinearFP(intType fromRow, int32 toRow)
|
|||||||
const int32 kBPP = 4;
|
const int32 kBPP = 4;
|
||||||
|
|
||||||
src = GetSrcImage();
|
src = GetSrcImage();
|
||||||
dest = GetDestImage();
|
dest = fScaledImage;
|
||||||
|
|
||||||
srcW = src->Bounds().IntegerWidth();
|
srcW = src->Bounds().IntegerWidth();
|
||||||
srcH = src->Bounds().IntegerHeight();
|
srcH = src->Bounds().IntegerHeight();
|
||||||
@@ -373,7 +460,11 @@ Scaler::ScaleBilinearFP(intType fromRow, int32 toRow)
|
|||||||
intType srcRow;
|
intType srcRow;
|
||||||
fixed_point alpha0, alpha1;
|
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);
|
srcRow = from_fixed_point(row);
|
||||||
alpha1 = tail_value(row); // weight for row y+1
|
alpha1 = tail_value(row); // weight for row y+1
|
||||||
alpha0 = kFPOne - alpha1; // weight for row y
|
alpha0 = kFPOne - alpha1; // weight for row y
|
||||||
@@ -442,7 +533,7 @@ Scaler::ScaleBilinearFP(intType fromRow, int32 toRow)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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[0] = a0X * src[0];
|
||||||
sum[1] = a0X * src[1];
|
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[1] += a1X * src[1];
|
||||||
sum[2] += a1X * src[2];
|
sum[2] += a1X * src[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
sum[0] /= deltaX;
|
|
||||||
sum[1] /= deltaX;
|
|
||||||
sum[2] /= deltaX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -492,7 +579,7 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
|
|||||||
DownScaleColumnData* columnData;
|
DownScaleColumnData* columnData;
|
||||||
|
|
||||||
src = GetSrcImage();
|
src = GetSrcImage();
|
||||||
dest = GetDestImage();
|
dest = fScaledImage;
|
||||||
|
|
||||||
srcW = src->Bounds().IntegerWidth();
|
srcW = src->Bounds().IntegerWidth();
|
||||||
srcH = src->Bounds().IntegerHeight();
|
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 deltaX = (srcW + 1.0) / (destW + 1.0);
|
||||||
const float deltaY = (srcH + 1.0) / (destH + 1.0);
|
const float deltaY = (srcH + 1.0) / (destH + 1.0);
|
||||||
|
const float deltaXY = deltaX * deltaY;
|
||||||
|
|
||||||
columnData = new DownScaleColumnData[destW+1];
|
columnData = new DownScaleColumnData[destW+1];
|
||||||
DownScaleColumnData* cd = columnData;
|
DownScaleColumnData* cd = columnData;
|
||||||
@@ -548,7 +636,7 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
|
|||||||
float totalSum[3];
|
float totalSum[3];
|
||||||
float sum[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[0] = a0Y * sum[0];
|
||||||
totalSum[1] = a0Y * sum[1];
|
totalSum[1] = a0Y * sum[1];
|
||||||
totalSum[2] = a0Y * sum[2];
|
totalSum[2] = a0Y * sum[2];
|
||||||
@@ -556,36 +644,217 @@ Scaler::DownScaleBilinear(intType fromRow, int32 toRow)
|
|||||||
srcData += srcBPR;
|
srcData += srcBPR;
|
||||||
|
|
||||||
for (int32 r = fromY+1; r < toY; r ++, 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[0] += sum[0];
|
||||||
totalSum[1] += sum[1];
|
totalSum[1] += sum[1];
|
||||||
totalSum[2] += sum[2];
|
totalSum[2] += sum[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toY <= srcH) {
|
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[0] += a1Y * sum[0];
|
||||||
totalSum[1] += a1Y * sum[1];
|
totalSum[1] += a1Y * sum[1];
|
||||||
totalSum[2] += a1Y * sum[2];
|
totalSum[2] += a1Y * sum[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
destData[0] = static_cast<uchar>(totalSum[0] / deltaY);
|
destData[0] = static_cast<uchar>(totalSum[0] / deltaXY);
|
||||||
destData[1] = static_cast<uchar>(totalSum[1] / deltaY);
|
destData[1] = static_cast<uchar>(totalSum[1] / deltaXY);
|
||||||
destData[2] = static_cast<uchar>(totalSum[2] / deltaY);
|
destData[2] = static_cast<uchar>(totalSum[2] / deltaXY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
delete columnData;
|
delete columnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
// Flyod-Steinberg Dithering
|
||||||
Scaler::Run(int i, int n)
|
// 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)
|
||||||
{
|
{
|
||||||
int32 from, to, height;
|
if (value < 0) {
|
||||||
height = (GetDestImage()->Bounds().IntegerHeight()+1)/n;
|
value = 0;
|
||||||
|
} if (value > 255) {
|
||||||
|
value = 255;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
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, imageHeight;
|
||||||
|
imageHeight = GetDestImage()->Bounds().IntegerHeight() + 1;
|
||||||
|
height = imageHeight / n;
|
||||||
from = i * height;
|
from = i * height;
|
||||||
if (i+1 == n) {
|
if (i+1 == n) {
|
||||||
to = (int32)GetDestImage()->Bounds().bottom;
|
to = imageHeight - 1;
|
||||||
} else {
|
} else {
|
||||||
to = from + height - 1;
|
to = from + height - 1;
|
||||||
}
|
}
|
||||||
@@ -594,4 +863,190 @@ Scaler::Run(int i, int n)
|
|||||||
} else {
|
} else {
|
||||||
DownScaleBilinear(from, to);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+75
-22
@@ -63,15 +63,15 @@ const int32 kFPOne = to_fixed_point(1);
|
|||||||
// Used by class Filter
|
// Used by class Filter
|
||||||
class FilterThread {
|
class FilterThread {
|
||||||
public:
|
public:
|
||||||
FilterThread(Filter* filter, int i);
|
FilterThread(Filter* filter, int32 i, int32 n, bool runInCurrentThread = false);
|
||||||
~FilterThread();
|
~FilterThread();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
status_t Run();
|
status_t Run();
|
||||||
static status_t worker_thread(void* data);
|
static status_t worker_thread(void* data);
|
||||||
thread_id fWorkerThread;
|
|
||||||
Filter* fFilter;
|
Filter* fFilter;
|
||||||
int fI;
|
int32 fI;
|
||||||
|
int32 fN;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Filter {
|
class Filter {
|
||||||
@@ -80,53 +80,71 @@ public:
|
|||||||
// for an operation executed in Run() method which
|
// for an operation executed in Run() method which
|
||||||
// writes into the destination image, that can be
|
// writes into the destination image, that can be
|
||||||
// retrieve using GetBitmap() method.
|
// retrieve using GetBitmap() method.
|
||||||
// GetBitmap() can be called any time, but it
|
// GetBitmap() must be called either before Start(),
|
||||||
// may contain "invalid" pixels.
|
// or after Start() and IsRunning() returns false.
|
||||||
// To start the operation Start() method has to
|
// To start the operation Start() method has to
|
||||||
// be called. The operation is executed in as many
|
// 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
|
// IsRunning() is true as long as there are any
|
||||||
// threads running.
|
// threads running.
|
||||||
// The operation is complete when IsRunning() is false
|
// The operation is complete when IsRunning() is false
|
||||||
// and Stop() has not been called.
|
// and Stop() has not been called.
|
||||||
// To abort an operation Stop() method has to
|
// To abort an operation Stop() method has to
|
||||||
// be called. Stop() has to be called after Start().
|
// be called. Stop() has to be called after Start().
|
||||||
// When the operation is done (and has not been aborted).
|
// When the operation is done Completed() is called.
|
||||||
// Then listener receives a message with the specified "what" value.
|
// 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);
|
Filter(BBitmap* image, BMessenger listener, uint32 what);
|
||||||
virtual ~Filter();
|
virtual ~Filter();
|
||||||
|
|
||||||
// The bitmap the filter writes into
|
// The bitmap the filter writes into
|
||||||
BBitmap* GetBitmap();
|
BBitmap* GetBitmap();
|
||||||
|
// Removes the destination image from Filter (caller is new owner of image)
|
||||||
|
BBitmap* DetachBitmap();
|
||||||
|
|
||||||
// Starts one or more FilterThreads
|
// Starts one or more FilterThreads. Returns immediately if async is true.
|
||||||
void Start();
|
// 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)
|
// Has to be called after Start() (even if IsRunning() is false)
|
||||||
void Stop();
|
void Stop();
|
||||||
// Are there any running FilterThreads?
|
// Are there any running FilterThreads?
|
||||||
bool IsRunning() const;
|
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;
|
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
|
// 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!
|
// Used by FilterThread only!
|
||||||
void Done();
|
void Done();
|
||||||
int32 CPUCount() const { return fCPUCount; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Number of threads to be used to perform the operation
|
||||||
|
int32 NumberOfThreads();
|
||||||
BBitmap* GetSrcImage();
|
BBitmap* GetSrcImage();
|
||||||
BBitmap* GetDestImage();
|
BBitmap* GetDestImage();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Returns the number of active CPUs
|
||||||
|
int32 CPUCount() const { return fCPUCount; }
|
||||||
|
|
||||||
BMessenger fListener;
|
BMessenger fListener;
|
||||||
uint32 fWhat;
|
uint32 fWhat;
|
||||||
int32 fCPUCount;
|
int32 fCPUCount; // the number of active CPUs
|
||||||
bool fStarted;
|
bool fStarted; // has Start() been called?
|
||||||
sem_id fWaitForThreads;
|
sem_id fWaitForThreads; // to exit
|
||||||
volatile int32 fNumberOfThreads;
|
int32 fN; // the number of used filter threads
|
||||||
volatile bool fIsRunning;
|
volatile int32 fNumberOfThreads; // the current number of FilterThreads
|
||||||
|
volatile bool fIsRunning; // FilterThreads should process data as long as it is true
|
||||||
BBitmap* fSrcImage;
|
BBitmap* fSrcImage;
|
||||||
BBitmap* fDestImage;
|
BBitmap* fDestImage;
|
||||||
#if TIME_FILTER
|
#if TIME_FILTER
|
||||||
@@ -134,22 +152,57 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Scales and optionally dithers an image
|
||||||
class Scaler : public Filter {
|
class Scaler : public Filter {
|
||||||
public:
|
public:
|
||||||
Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what);
|
Scaler(BBitmap* image, BRect rect, BMessenger listener, uint32 what, bool dither);
|
||||||
~Scaler();
|
~Scaler();
|
||||||
|
|
||||||
BBitmap* CreateDestImage(BBitmap* srcImage);
|
BBitmap* CreateDestImage(BBitmap* srcImage);
|
||||||
void Run(int i, int n);
|
int32 GetNumberOfUnits();
|
||||||
bool Matches(BRect rect) const;
|
void Run(int32 i, int32 n);
|
||||||
|
void Completed();
|
||||||
|
bool Matches(BRect rect, bool dither) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ScaleBilinear(int32 fromRow, int32 toRow);
|
void ScaleBilinear(int32 fromRow, int32 toRow);
|
||||||
void ScaleBilinearFP(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);
|
void DownScaleBilinear(int32 fromRow, int32 toRow);
|
||||||
|
static inline uchar Limit(intType value);
|
||||||
|
void Dither(int32 fromRow, int32 toRow);
|
||||||
|
|
||||||
|
BBitmap* fScaledImage;
|
||||||
BRect fRect;
|
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
|
#endif
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ ShowImageApp::StartPulse()
|
|||||||
void
|
void
|
||||||
ShowImageApp::Pulse()
|
ShowImageApp::Pulse()
|
||||||
{
|
{
|
||||||
|
// Bug: The BFilePanel is automatically closed if the volume that
|
||||||
|
// is displayed is unmounted.
|
||||||
if (!IsLaunching() && CountWindows() <= WINDOWS_TO_IGNORE)
|
if (!IsLaunching() && CountWindows() <= WINDOWS_TO_IGNORE)
|
||||||
// If the application is not launching and
|
// If the application is not launching and
|
||||||
// all windows are closed except for the file open panel,
|
// all windows are closed except for the file open panel,
|
||||||
@@ -112,8 +114,10 @@ ShowImageApp::ArgvReceived(int32 argc, char **argv)
|
|||||||
pmsg->AddRef("refs", &ref);
|
pmsg->AddRef("refs", &ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pmsg)
|
if (pmsg) {
|
||||||
RefsReceived(pmsg);
|
RefsReceived(pmsg);
|
||||||
|
delete pmsg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -133,10 +137,6 @@ ShowImageApp::MessageReceived(BMessage *pmsg)
|
|||||||
StartPulse();
|
StartPulse();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_UPDATE_RECENT_DOCUMENTS:
|
|
||||||
BroadcastToWindows(MSG_UPDATE_RECENT_DOCUMENTS);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case B_CLIPBOARD_CHANGED:
|
case B_CLIPBOARD_CHANGED:
|
||||||
CheckClipboard();
|
CheckClipboard();
|
||||||
break;
|
break;
|
||||||
@@ -169,17 +169,6 @@ ShowImageApp::Open(const entry_ref *pref)
|
|||||||
new ShowImageWindow(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
|
void
|
||||||
ShowImageApp::BroadcastToWindows(BMessage *pmsg)
|
ShowImageApp::BroadcastToWindows(BMessage *pmsg)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ public:
|
|||||||
private:
|
private:
|
||||||
void StartPulse();
|
void StartPulse();
|
||||||
void Open(const entry_ref *pref);
|
void Open(const entry_ref *pref);
|
||||||
void BroadcastToWindows(uint32 what);
|
|
||||||
void BroadcastToWindows(BMessage *pmsg);
|
void BroadcastToWindows(BMessage *pmsg);
|
||||||
void CheckClipboard();
|
void CheckClipboard();
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ const uint32 MSG_INVERT = 'mINV';
|
|||||||
const uint32 MSG_SLIDE_SHOW = 'mSSW';
|
const uint32 MSG_SLIDE_SHOW = 'mSSW';
|
||||||
const uint32 MSG_SLIDE_SHOW_DELAY = 'mSSD';
|
const uint32 MSG_SLIDE_SHOW_DELAY = 'mSSD';
|
||||||
const uint32 MSG_FULL_SCREEN = 'mFSC';
|
const uint32 MSG_FULL_SCREEN = 'mFSC';
|
||||||
const uint32 MSG_UPDATE_RECENT_DOCUMENTS = 'mURD';
|
|
||||||
const uint32 MSG_SHOW_CAPTION = 'mSCP';
|
const uint32 MSG_SHOW_CAPTION = 'mSCP';
|
||||||
const uint32 MSG_PAGE_SETUP = 'mPSU';
|
const uint32 MSG_PAGE_SETUP = 'mPSU';
|
||||||
const uint32 MSG_PREPARE_PRINT = 'mPPT';
|
const uint32 MSG_PREPARE_PRINT = 'mPPT';
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
#include <Path.h>
|
#include <Path.h>
|
||||||
#include <PopUpMenu.h>
|
#include <PopUpMenu.h>
|
||||||
#include <Region.h>
|
#include <Region.h>
|
||||||
|
#include <Screen.h>
|
||||||
|
|
||||||
|
|
||||||
#include "ShowImageApp.h"
|
#include "ShowImageApp.h"
|
||||||
@@ -62,10 +63,25 @@
|
|||||||
#define max(a,b) ((a)>(b)?(a):(b))
|
#define max(a,b) ((a)>(b)?(a):(b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
|
||||||
#define BORDER_WIDTH 16
|
#define BORDER_WIDTH 16
|
||||||
#define BORDER_HEIGHT 16
|
#define BORDER_HEIGHT 16
|
||||||
#define PEN_SIZE 1.0f
|
#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
|
// use patterns to simulate marching ants for selection
|
||||||
void
|
void
|
||||||
@@ -157,6 +173,7 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
|
|||||||
settings = my_app->Settings();
|
settings = my_app->Settings();
|
||||||
|
|
||||||
InitPatterns();
|
InitPatterns();
|
||||||
|
fDither = false;
|
||||||
fBitmap = NULL;
|
fBitmap = NULL;
|
||||||
fSelBitmap = NULL;
|
fSelBitmap = NULL;
|
||||||
fDocumentIndex = 1;
|
fDocumentIndex = 1;
|
||||||
@@ -177,6 +194,7 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
|
|||||||
fScaler = NULL;
|
fScaler = NULL;
|
||||||
|
|
||||||
if (settings->Lock()) {
|
if (settings->Lock()) {
|
||||||
|
fDither = settings->GetBool("Dither", fDither);
|
||||||
fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds);
|
fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds);
|
||||||
fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds);
|
fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds);
|
||||||
fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay);
|
fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay);
|
||||||
@@ -185,7 +203,7 @@ ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
|
|||||||
}
|
}
|
||||||
|
|
||||||
SetViewColor(B_TRANSPARENT_COLOR);
|
SetViewColor(B_TRANSPARENT_COLOR);
|
||||||
SetHighColor(kborderColor);
|
SetHighColor(kBorderColor);
|
||||||
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
|
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
|
||||||
SetPenSize(PEN_SIZE);
|
SetPenSize(PEN_SIZE);
|
||||||
}
|
}
|
||||||
@@ -236,7 +254,6 @@ void
|
|||||||
ShowImageView::AddToRecentDocuments()
|
ShowImageView::AddToRecentDocuments()
|
||||||
{
|
{
|
||||||
be_roster->AddToRecentDocuments(&fCurrentRef, APP_SIG);
|
be_roster->AddToRecentDocuments(&fCurrentRef, APP_SIG);
|
||||||
be_app_messenger.SendMessage(MSG_UPDATE_RECENT_DOCUMENTS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -302,6 +319,45 @@ ShowImageView::SetImage(const entry_ref *pref)
|
|||||||
return;
|
return;
|
||||||
fCurrentRef = ref;
|
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
|
// get the number of documents (pages) if it has been supplied
|
||||||
int32 documentCount = 0;
|
int32 documentCount = 0;
|
||||||
if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
|
if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
|
||||||
@@ -321,6 +377,16 @@ ShowImageView::SetImage(const entry_ref *pref)
|
|||||||
Notify(info.name);
|
Notify(info.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ShowImageView::SetDither(bool dither)
|
||||||
|
{
|
||||||
|
if (fDither != dither) {
|
||||||
|
SettingsSetBool("Dither", dither);
|
||||||
|
fDither = dither;
|
||||||
|
Invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ShowImageView::SetShowCaption(bool show)
|
ShowImageView::SetShowCaption(bool show)
|
||||||
{
|
{
|
||||||
@@ -449,13 +515,13 @@ ShowImageView::AlignBitmap()
|
|||||||
rect.right = width-1;
|
rect.right = width-1;
|
||||||
rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
|
rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
|
||||||
// center vertically
|
// center vertically
|
||||||
rect.OffsetBy(0, (height - rect.Height()) / 2);
|
rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2));
|
||||||
} else {
|
} else {
|
||||||
s = height / (rect.Height()+1.0);
|
s = height / (rect.Height()+1.0);
|
||||||
rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
|
rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
|
||||||
rect.bottom = height-1;
|
rect.bottom = height-1;
|
||||||
// center horizontally
|
// center horizontally
|
||||||
rect.OffsetBy((width - rect.Width()) / 2, 0);
|
rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// zoom image
|
// zoom image
|
||||||
@@ -611,10 +677,10 @@ ShowImageView::UpdateCaption()
|
|||||||
Scaler*
|
Scaler*
|
||||||
ShowImageView::GetScaler(BRect rect)
|
ShowImageView::GetScaler(BRect rect)
|
||||||
{
|
{
|
||||||
if (fScaler == NULL || !fScaler->Matches(rect)) {
|
if (fScaler == NULL || !fScaler->Matches(rect, fDither)) {
|
||||||
DeleteScaler();
|
DeleteScaler();
|
||||||
BMessenger msgr(this, Window());
|
BMessenger msgr(this, Window());
|
||||||
fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE);
|
fScaler = new Scaler(fBitmap, rect, msgr, MSG_INVALIDATE, fDither);
|
||||||
fScaler->Start();
|
fScaler->Start();
|
||||||
}
|
}
|
||||||
return fScaler;
|
return fScaler;
|
||||||
@@ -623,12 +689,14 @@ ShowImageView::GetScaler(BRect rect)
|
|||||||
void
|
void
|
||||||
ShowImageView::DrawImage(BRect rect)
|
ShowImageView::DrawImage(BRect rect)
|
||||||
{
|
{
|
||||||
if (fScaleBilinear) {
|
if (fScaleBilinear || fDither) {
|
||||||
Scaler* scaler = GetScaler(rect);
|
Scaler* scaler = GetScaler(rect);
|
||||||
if (scaler != NULL && scaler->GetBitmap() != NULL && !scaler->IsRunning()) {
|
if (scaler != NULL && !scaler->IsRunning()) {
|
||||||
BBitmap* bitmap = scaler->GetBitmap();
|
BBitmap* bitmap = scaler->GetBitmap();
|
||||||
DrawBitmap(bitmap, BPoint(rect.left, rect.top));
|
if (bitmap) {
|
||||||
return;
|
DrawBitmap(bitmap, BPoint(rect.left, rect.top));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DrawBitmap(fBitmap, fBitmap->Bounds(), rect);
|
DrawBitmap(fBitmap, fBitmap->Bounds(), rect);
|
||||||
@@ -1215,6 +1283,9 @@ ShowImageView::KeyDown (const char * bytes, int32 numBytes)
|
|||||||
BMessenger msgr(Window());
|
BMessenger msgr(Window());
|
||||||
msgr.SendMessage(MSG_SLIDE_SHOW);
|
msgr.SendMessage(MSG_SLIDE_SHOW);
|
||||||
}
|
}
|
||||||
|
case 'T':
|
||||||
|
case 't':
|
||||||
|
SetIcon(*bytes == 't');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1594,7 +1665,7 @@ ShowImageView::FirstFile()
|
|||||||
void
|
void
|
||||||
ShowImageView::SetZoom(float zoom)
|
ShowImageView::SetZoom(float zoom)
|
||||||
{
|
{
|
||||||
if (fScaleBilinear && fZoom != zoom) {
|
if ((fScaleBilinear || fDither) && fZoom != zoom) {
|
||||||
DeleteScaler();
|
DeleteScaler();
|
||||||
}
|
}
|
||||||
fZoom = zoom;
|
fZoom = zoom;
|
||||||
@@ -1620,11 +1691,20 @@ void
|
|||||||
ShowImageView::SetSlideShowDelay(float seconds)
|
ShowImageView::SetSlideShowDelay(float seconds)
|
||||||
{
|
{
|
||||||
ShowImageSettings* settings;
|
ShowImageSettings* settings;
|
||||||
fSlideShowDelay = (int)(seconds * 10.0);
|
int32 delay = (int)(seconds * 10.0);
|
||||||
settings = my_app->Settings();
|
if (fSlideShowDelay != delay) {
|
||||||
if (settings->Lock()) {
|
// update counter
|
||||||
settings->SetInt32("SlideShowDelay", fSlideShowDelay);
|
fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown);
|
||||||
settings->Unlock();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1640,152 +1720,56 @@ ShowImageView::StopSlideShow()
|
|||||||
fSlideShow = false;
|
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
|
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;
|
BMessenger msgr;
|
||||||
src += bpr * y + x * bpp;
|
ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
|
||||||
memcpy(dest, src, bpp);
|
imageProcessor.Start(false);
|
||||||
}
|
BBitmap* bm = imageProcessor.DetachBitmap();
|
||||||
|
|
||||||
// FIXME: In color space with alpha channel the alpha value should not be inverted!
|
// update orientation state
|
||||||
// This could be a problem when image is saved and opened in another application.
|
if (op != ImageProcessor::kInvert) {
|
||||||
// Note: For B_CMAP8 InvertPixel inverts the color index not the color value!
|
// Note: If one of these fails, check its definition in class ImageProcessor.
|
||||||
void
|
ASSERT(ImageProcessor::kRotateClockwise < ImageProcessor::kNumberOfAffineTransformations);
|
||||||
ShowImageView::InvertPixel(int32 x, int32 y, uchar* dest, int32 destBPR, uchar* src, int32 bpr, int32 bpp)
|
ASSERT(ImageProcessor::kRotateAntiClockwise < ImageProcessor::kNumberOfAffineTransformations);
|
||||||
{
|
ASSERT(ImageProcessor::kMirrorVertical < ImageProcessor::kNumberOfAffineTransformations);
|
||||||
dest += destBPR * y + x * bpp;
|
ASSERT(ImageProcessor::kMirrorHorizontal < ImageProcessor::kNumberOfAffineTransformations);
|
||||||
src += bpr * y + x * bpp;
|
fImageOrientation = fTransformation[op][fImageOrientation];
|
||||||
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;
|
|
||||||
|
|
||||||
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);
|
|
||||||
} else {
|
} else {
|
||||||
rect.Set(0, 0, width, height);
|
fInverted = !fInverted;
|
||||||
}
|
}
|
||||||
|
|
||||||
bm = new BBitmap(rect, cs);
|
if (!quiet) {
|
||||||
if (bm == NULL) return;
|
// write orientation state
|
||||||
|
BNode node(&fCurrentRef);
|
||||||
src = (uchar*)fBitmap->Bits();
|
int32 orientation = fImageOrientation;
|
||||||
dest = (uchar*)bm->Bits();
|
if (fInverted) orientation += 256;
|
||||||
bpr = fBitmap->BytesPerRow();
|
if (orientation != k0) {
|
||||||
destBPR = bm->BytesPerRow();
|
node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0, &orientation, sizeof(orientation));
|
||||||
|
} else {
|
||||||
switch (op) {
|
node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set new bitmap
|
// set new bitmap
|
||||||
DeleteBitmap();
|
DeleteBitmap();
|
||||||
fBitmap = bm;
|
fBitmap = bm;
|
||||||
// remove selection
|
|
||||||
SetHasSelection(false);
|
if (!quiet) {
|
||||||
Notify(NULL);
|
// remove selection
|
||||||
|
SetHasSelection(false);
|
||||||
|
Notify(NULL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ShowImageView::Rotate(int degree)
|
ShowImageView::Rotate(int degree)
|
||||||
{
|
{
|
||||||
if (degree == 90) {
|
if (degree == 90) {
|
||||||
DoImageOperation(kRotateClockwise);
|
DoImageOperation(ImageProcessor::kRotateClockwise);
|
||||||
} else if (degree == 270) {
|
} else if (degree == 270) {
|
||||||
DoImageOperation(kRotateAntiClockwise);
|
DoImageOperation(ImageProcessor::kRotateAntiClockwise);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1793,14 +1777,83 @@ void
|
|||||||
ShowImageView::Mirror(bool vertical)
|
ShowImageView::Mirror(bool vertical)
|
||||||
{
|
{
|
||||||
if (vertical) {
|
if (vertical) {
|
||||||
DoImageOperation(kMirrorVertical);
|
DoImageOperation(ImageProcessor::kMirrorVertical);
|
||||||
} else {
|
} else {
|
||||||
DoImageOperation(kMirrorHorizontal);
|
DoImageOperation(ImageProcessor::kMirrorHorizontal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
ShowImageView::Invert()
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
#include <View.h>
|
#include <View.h>
|
||||||
#include <Bitmap.h>
|
#include <Bitmap.h>
|
||||||
#include <Entry.h>
|
#include <Entry.h>
|
||||||
|
#include <NodeInfo.h>
|
||||||
#include <String.h>
|
#include <String.h>
|
||||||
#include <TranslatorRoster.h>
|
#include <TranslatorRoster.h>
|
||||||
|
|
||||||
@@ -46,6 +47,8 @@ public:
|
|||||||
void Pulse();
|
void Pulse();
|
||||||
|
|
||||||
void SetImage(const entry_ref *pref);
|
void SetImage(const entry_ref *pref);
|
||||||
|
void SetDither(bool dither);
|
||||||
|
bool GetDither() const { return fDither; }
|
||||||
void SetShowCaption(bool show);
|
void SetShowCaption(bool show);
|
||||||
void SetShrinkToBounds(bool enable);
|
void SetShrinkToBounds(bool enable);
|
||||||
bool GetShrinkToBounds() const { return fShrinkToBounds; }
|
bool GetShrinkToBounds() const { return fShrinkToBounds; }
|
||||||
@@ -105,13 +108,19 @@ public:
|
|||||||
void Mirror(bool vertical);
|
void Mirror(bool vertical);
|
||||||
void Invert();
|
void Invert();
|
||||||
|
|
||||||
|
void SetIcon(bool clear);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum image_operation {
|
enum image_orientation {
|
||||||
kRotateClockwise,
|
k0, // 0
|
||||||
kRotateAntiClockwise,
|
k90, // 1
|
||||||
kMirrorVertical,
|
k180, // 2
|
||||||
kMirrorHorizontal,
|
k270, // 3
|
||||||
kInvert
|
k0V, // 4
|
||||||
|
k90V, // 5
|
||||||
|
k0H, // 6
|
||||||
|
k270V, // 7
|
||||||
|
kNumberOfOrientations,
|
||||||
};
|
};
|
||||||
void InitPatterns();
|
void InitPatterns();
|
||||||
void RotatePatterns();
|
void RotatePatterns();
|
||||||
@@ -128,7 +137,7 @@ private:
|
|||||||
int32 BytesPerPixel(color_space cs) const;
|
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 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);
|
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();
|
BRect AlignBitmap();
|
||||||
void Setup(BRect r);
|
void Setup(BRect r);
|
||||||
BPoint ImageToView(BPoint p) const;
|
BPoint ImageToView(BPoint p) const;
|
||||||
@@ -166,8 +175,10 @@ private:
|
|||||||
void MouseWheelChanged(BMessage* msg);
|
void MouseWheelChanged(BMessage* msg);
|
||||||
void ShowPopUpMenu(BPoint screen);
|
void ShowPopUpMenu(BPoint screen);
|
||||||
void SettingsSetBool(const char* name, bool value);
|
void SettingsSetBool(const char* name, bool value);
|
||||||
|
void SetIcon(bool clear, icon_size which);
|
||||||
|
|
||||||
entry_ref fCurrentRef; // of the image
|
entry_ref fCurrentRef; // of the image
|
||||||
|
bool fDither; // dither the image
|
||||||
int32 fDocumentIndex; // of the image in the file
|
int32 fDocumentIndex; // of the image in the file
|
||||||
int32 fDocumentCount; // number of images in the file
|
int32 fDocumentCount; // number of images in the file
|
||||||
BBitmap *fBitmap; // to be displayed
|
BBitmap *fBitmap; // to be displayed
|
||||||
@@ -201,6 +212,10 @@ private:
|
|||||||
|
|
||||||
bool fShowCaption; // display caption?
|
bool fShowCaption; // display caption?
|
||||||
BString fCaption; // caption text
|
BString fCaption; // caption text
|
||||||
|
|
||||||
|
bool fInverted;
|
||||||
|
enum image_orientation fImageOrientation;
|
||||||
|
static enum image_orientation fTransformation[ImageProcessor::kNumberOfAffineTransformations][kNumberOfOrientations];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _ShowImageView_h */
|
#endif /* _ShowImageView_h */
|
||||||
|
|||||||
@@ -53,6 +53,46 @@
|
|||||||
#include "ShowImageStatusView.h"
|
#include "ShowImageStatusView.h"
|
||||||
#include "EntryMenuItem.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)
|
ShowImageWindow::ShowImageWindow(const entry_ref *pref)
|
||||||
: BWindow(BRect(50, 50, 350, 250), "", B_DOCUMENT_WINDOW, 0)
|
: 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");
|
BMenu* pmenu = new BMenu("View");
|
||||||
BuildViewMenu(pmenu);
|
BuildViewMenu(pmenu);
|
||||||
fBar->AddItem(pmenu);
|
fBar->AddItem(pmenu);
|
||||||
|
MarkMenuItem(fBar, MSG_DITHER_IMAGE, fImageView->GetDither());
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
|
|
||||||
SetPulseRate(100000); // every 1/10 second; ShowImageView needs it for marching ants
|
SetPulseRate(100000); // every 1/10 second; ShowImageView needs it for marching ants
|
||||||
@@ -165,31 +205,6 @@ ShowImageWindow::UpdateTitle()
|
|||||||
SetTitle(path.String());
|
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
|
void
|
||||||
ShowImageWindow::BuildViewMenu(BMenu *pmenu)
|
ShowImageWindow::BuildViewMenu(BMenu *pmenu)
|
||||||
{
|
{
|
||||||
@@ -247,7 +262,7 @@ void
|
|||||||
ShowImageWindow::LoadMenus(BMenuBar *pbar)
|
ShowImageWindow::LoadMenus(BMenuBar *pbar)
|
||||||
{
|
{
|
||||||
BMenu *pmenu = new BMenu("File");
|
BMenu *pmenu = new BMenu("File");
|
||||||
fOpenMenu = new BMenu("Open");
|
fOpenMenu = new RecentDocumentsMenu("Open");
|
||||||
pmenu->AddItem(fOpenMenu);
|
pmenu->AddItem(fOpenMenu);
|
||||||
fOpenMenu->Superitem()->SetTrigger('O');
|
fOpenMenu->Superitem()->SetTrigger('O');
|
||||||
fOpenMenu->Superitem()->SetMessage(new BMessage(MSG_FILE_OPEN));
|
fOpenMenu->Superitem()->SetMessage(new BMessage(MSG_FILE_OPEN));
|
||||||
@@ -304,8 +319,6 @@ ShowImageWindow::LoadMenus(BMenuBar *pbar)
|
|||||||
pmenu->AddSeparatorItem();
|
pmenu->AddSeparatorItem();
|
||||||
AddItemMenu(pmenu, "Invert", MSG_INVERT, 0, 0, 'W', true);
|
AddItemMenu(pmenu, "Invert", MSG_INVERT, 0, 0, 'W', true);
|
||||||
pbar->AddItem(pmenu);
|
pbar->AddItem(pmenu);
|
||||||
|
|
||||||
UpdateRecentDocumentsMenu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BMenuItem *
|
BMenuItem *
|
||||||
@@ -350,6 +363,7 @@ ShowImageWindow::WindowRedimension(BBitmap *pbitmap)
|
|||||||
BRect r(pbitmap->Bounds());
|
BRect r(pbitmap->Bounds());
|
||||||
float width, height;
|
float width, height;
|
||||||
float maxWidth, maxHeight;
|
float maxWidth, maxHeight;
|
||||||
|
float minW, maxW, minH, maxH;
|
||||||
const float windowBorderWidth = 5;
|
const float windowBorderWidth = 5;
|
||||||
const float windowBorderHeight = 5;
|
const float windowBorderHeight = 5;
|
||||||
|
|
||||||
@@ -364,6 +378,14 @@ ShowImageWindow::WindowRedimension(BBitmap *pbitmap)
|
|||||||
maxWidth = screen.Frame().Width() + 1 - windowBorderWidth - Frame().left;
|
maxWidth = screen.Frame().Width() + 1 - windowBorderWidth - Frame().left;
|
||||||
maxHeight = screen.Frame().Height() + 1 - windowBorderHeight - Frame().top;
|
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 (width > maxWidth) width = maxWidth;
|
||||||
if (height > maxHeight) height = maxHeight;
|
if (height > maxHeight) height = maxHeight;
|
||||||
|
|
||||||
@@ -607,7 +629,7 @@ ShowImageWindow::MessageReceived(BMessage *pmsg)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_DITHER_IMAGE:
|
case MSG_DITHER_IMAGE:
|
||||||
ToggleMenuItem(pmsg->what);
|
fImageView->SetDither(ToggleMenuItem(pmsg->what));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_SHRINK_TO_WINDOW:
|
case MSG_SHRINK_TO_WINDOW:
|
||||||
@@ -672,10 +694,6 @@ ShowImageWindow::MessageReceived(BMessage *pmsg)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_UPDATE_RECENT_DOCUMENTS:
|
|
||||||
UpdateRecentDocumentsMenu();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MSG_PAGE_SETUP:
|
case MSG_PAGE_SETUP:
|
||||||
PageSetup();
|
PageSetup();
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#ifndef _ShowImageWindow_h
|
#ifndef _ShowImageWindow_h
|
||||||
#define _ShowImageWindow_h
|
#define _ShowImageWindow_h
|
||||||
|
|
||||||
|
#include <Menu.h>
|
||||||
#include <Window.h>
|
#include <Window.h>
|
||||||
#include <FilePanel.h>
|
#include <FilePanel.h>
|
||||||
#include <TranslationDefs.h>
|
#include <TranslationDefs.h>
|
||||||
@@ -43,6 +44,16 @@ class ShowImageStatusView;
|
|||||||
#define TRANSLATOR_FLD "be:translator"
|
#define TRANSLATOR_FLD "be:translator"
|
||||||
#define TYPE_FLD "be:type"
|
#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 {
|
class ShowImageWindow : public BWindow {
|
||||||
public:
|
public:
|
||||||
ShowImageWindow(const entry_ref *pref);
|
ShowImageWindow(const entry_ref *pref);
|
||||||
@@ -67,7 +78,6 @@ private:
|
|||||||
char target, bool enabled);
|
char target, bool enabled);
|
||||||
|
|
||||||
BMenuItem* AddDelayItem(BMenu *pmenu, char *caption, float value);
|
BMenuItem* AddDelayItem(BMenu *pmenu, char *caption, float value);
|
||||||
void UpdateRecentDocumentsMenu();
|
|
||||||
|
|
||||||
bool ToggleMenuItem(uint32 what);
|
bool ToggleMenuItem(uint32 what);
|
||||||
void EnableMenuItem(BMenu *menu, uint32 what, bool enable);
|
void EnableMenuItem(BMenu *menu, uint32 what, bool enable);
|
||||||
|
|||||||
Reference in New Issue
Block a user