Sender).Tag));
end;
…
procedure TFePaint.Doing(const Action: TDrawAction);
var Fon, AGrid: Boolean;
UCount: Byte;
Size: TPoint;
Bmp: TBitmap;
begin
if not GetCan(Action) then Exit;
Ended;
case Action of
daClear: begin
CanChangeImage;
ClearImage;
ChangedImage;
end;
daCopyToFile: Save(True, False);
…
end;
end;
Нестандартный способ решения задачи применён и к алгоритму отмены и повтора действий пользователя. Создан специальный класс TBmpMemory, который хранит изображения в памяти. После каждого действия, будь то ри-сование прямоугольника или увеличение контрастности, изображение сохра-няется в отдельной ячейке (Cell) динамического массива, длина которого равна числу возможных отмен + 1. Данный массив представляет собой дву-связный список. Тип каждой ячейки определён следующим образом:
PBitmapCell = ^TBitmapCell;
TBitmapCell = record
NextCell: PBitmapCell; // указатель на следующую ячейку
PrevCell: PBitmapCell; // указатель на предыдущую ячейку
Bitmap: TBitmap;
end;
Таким образом, каждый элемент списка имеет указатель на предыдущий и на следующий элемент. Указатель PrevCell первой ячейки указывает на послед-нюю, а указатель NextCell последней ячейки указывает на первую ячейку. Получается замкнутое кольцо без входа и выхода. Это сделано по той причи-не, что после отмены действия, для восстановления требуется то изображе-ние, которое было отменено. Например: пользователь нарисовал круг, а затем линию. В первой ячейке записано изображение, которое было до рисования круга, во второй – после рисования круга, а в третьей – после рисования ли-нии. После отмены последнего действия указатель перемещается на преды-дущую ячейку (ячейка с кругом, но без линии):
procedure TBmpMemory.Undo(Bitmap: TBitmap);
begin
if CanUndo then begin
Dec(FNumUndo); //уменьшаем число возможных отмен
Bitmap.Assign(FActiveCell^.Bitmap); //передаём изображение с активной ячейки
FActiveCell := FActiveCell^.PrevCell;//переносим указатель на предыдущую ячейку
end;
end;
Если же пользователь передумал и решил вернуть нарисованное, то он может вызвать повтор последнего отменённого действия (если после отмены ничего не было сделано):
procedure TBmpMemory.Redo(Bitmap: TBitmap);
begin
if CanRedo then begin
Inc(FNumUndo); //увеличиваем число возможных отмен
FActiveCell := FActiveCell^.NextCell; //переносим указатель на следующую ячейку
Bitmap.Assign(FActiveCell^.NextCell^.Bitmap); //передаём изображение
//со следующей после активной ячейки, т. к. активная ячейка та,
//которая должна быть отменена
end;
end;
Вся программа построена на основе большого количества классов, каж-дый из которых отвечает за отдельную функцию. Это помогает локализовать, разбить по частям всю программу, а не создавать огромные процедуры, об-работчики событий, что, конечно же, идет на пользу и является подтвержде-нием преимуществ объективно-ориентированного метода программирования.
Принципы графических алгоритмов.
Поскольку программа является растровым (точечным) редактором, то изображение находится в виде объекта графического класса TBitmap и рабо-та всех эффектов основана на работе с этим классом. TBitmap содержит дву-мерный массив, в каждой ячейке которого записан определённый цвет. То есть мы имеем дело с двумерной (плоскостной) системой координат: каждая точка рисунка описывается двумя координатами, например (45, 87).
Доступ к определённой точке можно получить двумя способами: через свойство Canvas.Pixels[x, y] (x и y – координаты точки), которое имеет тип TColor, или с помощью свойства ScanLine[y], которое возвращает указатель на начало строки под номером 'y'. Первый способ работает гораздо медлен-нее второго, поэтому он почти не используется, хотя и более удобен.
Каждый цвет (в компьютерном представлении) состоит из трёх оттен-ков: красного (red), зелёного (green) и синего (blue) – RGB. При этом каждый из оттенков может принимать значения от 0 до 255, то есть, всего компьютер (монитор) может отображать 256*256*256=16777216 разных цветов.
Все функции и процедуры, имеющие отношение к созданию эффектов, находятся в файле FeProcs.pas, который занимает 69 КБ, так как этих функ-ций очень много.
Рассмотрим пример реализации эффекта 'Негатив':
type
TRGB = record
B, G, R: Byte;
end;
pRGB = ^TRGB;
procedure InvertBitmap(Bitmap: TBitmap);
var x, y: Integer;
Dest: pRGB;
begin
for y := 0 to Bitmap.Height - 1 do begin
Dest := Bitmap.ScanLine[y]; //помещаем указатель в начало строки y
for x := 0 to Bitmap.Width - 1 do begin
with Dest^ do begin
R := 255 - R; //чтобы обратить цвет,
G := 255 - G; //нужно обратить на противоположные
B := 255 - B; //его составляющие
end;
Inc(Dest); //перемещаем указатель вправо
end;
end;
end;
Цикл начинается с 0 и заканчивается на Bitmap.Height – 1 (Bitmap.Width – 1), так как в системе координат Windows отсчёт начинается с точки (0, 0), а точ-ки (Width – ширина, Height – высота) не существует. Внешний цикл прохо-дит по порядку все строки (координата Y), потомучто свойство Bitmap ScanLine передаёт указатель на строку, а не на столбец.
Интересной (с точки зрения программирования) является функция под-счёта цветов рисунка:
function HowManyColors(Bitmap: TBitmap): Integer;
var i: Byte;
x, y: Integer;
Dest: pRGB;
RGBArray: array [Byte, Byte] of array of Byte;
begin
Result := 0;
for y := 0 to Bitmap.Height - 1 do begin
Dest := Bitmap.ScanLine[y];
for x := 0 to Bitmap.Width - 1 do begin
with Dest^ do
if RGBArray[r, g] nil then
for i := 0 to High(RGBArray[r, g]) do begin
if RGBArray[r, g] [i] = b then Break; //такой цвет уже есть – выход из цикла
if i = High(RGBArray[r, g]) then begin //если это последний 'круг' цикла, то
//такого цвета нет и его нужно записать
Inc(Result); //прибавляем еще один цвет
SetLength(RGBArray[r, g], Length(RGBArray[r, g]) + 1);
RGBArray[r, g] [High(RGBArray[r, g])] := b;
end;
end
else begin
Inc(Result); //прибавляем еще один цвет
SetLength(RGBArray[r, g], 1);
RGBArray[r, g] [0] := b;
end;
Inc(Dest);
end;
end;
end;
Изначально, для решения задачи подсчёта цветов применялся следующий ал-горитм: использовалась глобальная переменная, которая являлась трёхмер-ным массивом - «ColorsArray: array [0..255, 0..255, 0..255] of Boolean», то есть имел для каждого существующего цвета отдельную ячейку типа Boolean – всего 16777216 ячеек; в начале функции значения всех ячеек устанавливались в False (ложный), это означало, что нет ни одного цвета; затем цвет каждой точки разлагался на составляющие R, G и B, и проверялось значение соответ-ствующей ячейки - «ColorsArray[R, G, B]», если оно было ложным, то изменя-лось на True (истинный), а количество цветов рисунка увеличивалось на один. Но, естественно, такой подход крайне иррационален, так как компиля-тор не позволяет выделять так много памяти внутри процедуры, а выделение 16 Мбайт памяти при загрузке тормозит не только саму программу, но и дру-гие процессы системы. По этой причине пришлось отказаться от такого ре-шения и разработать новый алгоритм, что и было сделано. Функция, реали-зующая новый подход изложена выше, а сам он заключается в динамическом выделении памяти. Переменная RGBArray – это двумерный массив, каждая ячейка которого, в свою очередь, является динамическим массивом типа Byte (0..255). Фактически изначально вообще не выделяется памяти, она выделя-ется по мере увеличения числа цветов.
Системные требования.
Микропроцессор – Pentium 100 и выше.
Оперативная память – 16 Mb RAM.
Операционная система – Windows 9x/Me.
Свободное место на диске – 1 Mb.
Аппаратное обеспечение – мышь.
Заключение.
По мере написания программы возникали всё новые задачи и проблемы, которые было бы невозможно или нецелесообразно решать обычными мето-дами, поэтому приходилось изобретать или где-то заимствовать нестандарт-ные алгоритмы решения задач, коих в программе довольно много. Это по-могло сохранить небольшой размер и высокую производительность про-граммы при её широких возможностях, что делает её весьма привлекатель-ной для пользователей как опытных, так и не очень.
Умелое сочетание применения
|
|