Идёт загрузка страницы...

htp://aptem.net.ru





Delphi 2

Поворот изображения на 90 градусов

Новый модуль имеет три программы: RotateBitmap90DegreesClockwise, RotateBitmap90DegreesCounterClockwise, и RotateBitmap180Degrees. Все три используют TBitmap как переменную и вращают его согласно своему названию.

Два предостережения: Это все еще не совсем работает в Delphi3. Появляется какой-то шум на краях изображения. Мне кажется это из-за какой-то ошибки в методе LoadFromStream объекта TBitmap, но это может быть и моей ошибкой. Тем не менее есть другие решения, связанные с использованием свойства ScanLine, так что эта проблема решается. Во-вторых, этот алгоритм не работает с сжатыми RLE-алгоритмом изображениями. 4- и 8-битные (по разрешению) изображения могут быть декодированы и хранится в памяти: на случай, если они потребуются, у нас есть их дескриптор. К тому же, если изображение сжато, можно просто получить дескриптор канвы с нормальным изображением:


    ABitmap.Canvas.Handle;

Этим мы также назначаем контекст устройства (то есть экрана), и, вероятно, сможем обрабатывать изображения вплоть до 24-битного формата. Что-то вроде компромисного решения.

Во всяком случае это работает у меня в Delphi 1 и 2 с черно-белыми, 4-, 8-, 16-, 24-, и 32-битными изображениями (но не с 4- и 8-битными изображениями, сжатыми RLE-алгоритмом, как я уже говорил выше).


    unit bmpRot;

interface

uses
(*$IFDEF Win32*) Windows, (*$ELSE*) WinTypes, WinProcs, (*$ENDIF*)
Classes, Graphics;

procedure RotateBitmap90DegreesCounterClockwise(var ABitmap: TBitmap);
procedure RotateBitmap90DegreesClockwise(var ABitmap: TBitmap);
procedure RotateBitmap180Degrees(var ABitmap: TBitmap);

implementation

uses
Dialogs;

(*$IFNDEF Win32*)

type
DWORD = LongInt;
TSelOfs = record
L, H: Word;
end;

procedure Win16Dec(var P: Pointer; const N: LongInt); forward;

procedure Win16Inc(var P: Pointer; const N: LongInt);
begin
if N < 0 then
Win16Dec(P, -N)
else if N > 0 then begin
Inc( TSelOfs(P).H, TSelOfs(N).H * SelectorInc );
Inc( TSelOfs(P).L, TSelOfs(N).L );
if TSelOfs(P).L < TSelOfs(N).L then Inc( TSelOfs(P).H, SelectorInc );
end;
end;

procedure Win16Dec(var P: Pointer; const N: LongInt);
begin
if N < 0 then
Win16Inc(P, -N)
else if N > 0 then begin
if TSelOfs(N).L > TSelOfs(P).L then Dec( TSelOfs(P).H, SelectorInc );
Dec( TSelOfs(P).L, TSelOfs(N).L );
Dec( TSelOfs(P).H, TSelOfs(N).H * SelectorInc );
end;
end;

(*
procedure HugeShift; far; external 'KERNEL' index 113;
procedure Win16Dec(var P: Pointer; const N: LongInt); forward;

procedure Win16Inc(var HugePtr: Pointer; Amount: LongInt);
procedure HugeInc; assembler;
asm
mov ax, Amount.Word[0]        { Сохраняем сумму в DX:AX. }
mov dx, Amount.Word[2]
les bx, HugePtr               { Получаем ссылку на HugePtr. }
add ax, es:[bx]               { Добавление коррекции. }
adc dx, 0                     { Распространяем перенос на наибольшую величину суммы. }
mov cx, Offset HugeShift
shl dx,                       { Перемещаем наибольшую величину суммы для сегмента. }
add es:[bx+2], dx             { Увеличиваем сегмент HugePtr. }
mov es:[bx], ax
end;

begin
if Amount > 0 then HugeInc else if Amount < 0 then Win16Dec(HugePtr, -Amount);
end;

procedure Win16Dec(var P: Pointer; const N: LongInt);
begin
if N < 0 then
Win16Inc(P, -N)
else if N > 0 then begin
if TSelOfs(N).L > TSelOfs(P).L then Dec( TSelOfs(P).H, SelectorInc );
Dec( TSelOfs(P).L, TSelOfs(N).L );
Dec( TSelOfs(P).H, TSelOfs(N).H * SelectorInc );
end;
end;
*)

(*$ENDIF*)


procedure RotateBitmap90DegreesCounterClockwise(var ABitmap: TBitmap);
const
BitsPerByte = 8;

var
{
Целая куча переменных. Некоторые имеют дело только с одно- и четырех-битовыми
изображениями, другие только с восемью- и 24-битовыми, а некоторые с обоими.
Любая переменная, оканчивающаяся символом 'R', имеет отношение к вращению изображения,
например если MemoryStream содержит исходное изображение, то MemoryStreamR - повернутое.
}
PbmpInfoR: PBitmapInfoHeader;
bmpBuffer, bmpBufferR: PByte;
MemoryStream, MemoryStreamR: TMemoryStream;
PbmpBuffer, PbmpBufferR: PByte;
BytesPerPixel, PixelsPerByte: LongInt;
BytesPerScanLine, BytesPerScanLineR: LongInt;
PaddingBytes: LongInt;
BitmapOffset: LongInt;
BitCount: LongInt;
WholeBytes, ExtraPixels: LongInt;
SignificantBytes, SignificantBytesR: LongInt;
ColumnBytes: LongInt;
AtLeastEightBitColor: Boolean;
T: LongInt;

procedure NonIntegralByteRotate; (* вложение *)
{
Эта программа осуществляет поворот изображений с разрешением меньшим, чем 8 бит на пиксел,
а имеено: черно-белые (1-бит) и 16-цветные (4-бит) изображения. Имейте в виду, что
такие вещи, как 2-битные изображения также могли бы вращаться, но Microsoft не включил
данный формат в свои спецификации и не поддерживает его.
}

var
X, Y: LongInt;
I: LongInt;
MaskBits, CurrentBits: Byte;
FirstMask, LastMask: Byte;
PFirstScanLine: PByte;
FirstIndex, CurrentBitIndex: LongInt;
ShiftRightAmount, ShiftRightStart: LongInt;

begin
(*$IFDEF Win32*)
Inc(PbmpBuffer, BytesPerScanLine * (PbmpInfoR^.biHeight - 1) );
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), BytesPerScanLine * (PbmpInfoR^.biHeight - 1) );
(*$ENDIF*)

{ PFirstScanLine движется вдоль первой линии чередования bmpBufferR. }
PFirstScanLine := bmpBufferR;

{ Устанавливаем индексирование. }
FirstIndex := BitsPerByte - BitCount;

{
Устанавливаем битовые маски:

Для черно-белого изображения,
LastMask  := 00000001    и
FirstMask := 10000000

Для 4-битного изображения,
LastMask  := 00001111    и
FirstMask := 11110000

Зададим значения CurrentBits и MaskBits, так как мы будем перемещаться по ним:
Для монохромных изображений:
10000000, 01000000, 00100000, 00010000, 00001000, 00000100, 00000010, 00000001
Для 4-битных изображений:
11110000, 00001111

CurrentBitIndex определяет расстояние от крайнего правого бита
до позиции CurrentBits. Например, если мы находимся в одиннадцатой
колонке черно-белого изображения, CurrentBits равен
11 mod 8 := 3, или 3-й самый левый бит. Таким образом, крайне правый
бит должен переместиться на четыре позиции, чтобы попасть на позицию
CurrentBits. CurrentBitIndex как раз и хранит такое значение.
}
LastMask := 1 shl BitCount - 1;
FirstMask := LastMask shl FirstIndex;

CurrentBits := FirstMask;
CurrentBitIndex := FirstIndex;

ShiftRightStart := BitCount * (PixelsPerByte - 1);

{ Вот мясо. Перебираем в цикле все пиксели и соответственно вращаем. }
{ Помните что DIB'ы имеют происхождение противоположное DDB'сам. }

{ Счетчик Y указывает на текущую строчку исходного изображения. }
for Y := 1 to PbmpInfoR^.biHeight do begin
PbmpBufferR := PFirstScanLine;

{
Счетчик X указывает на текущую колонку пикселей исходного изображения.
Здесь мы имеем дело только с полностью заполненными байтами. Обработка
'частично заполненных' байтов происходит ниже.
}
for X := 1 to WholeBytes do begin
{
Выбираем биты, начиная с 10000000 для черно-белых и
заканчивая 11110000 для 4-битных изображений.
}
MaskBits := FirstMask;
{
ShiftRightAmount - сумма, необходимая для перемещения текущего байта
через весь путь (помните, я об этом говорил выше) в правую часть.
}
ShiftRightAmount := ShiftRightStart;
for I := 1 to PixelsPerByte do begin
{
Вот гарнир. Берем текущий байт вращаемого изображения и маскируем его
с not CurrentBits. Гасятся нулями только биты CurrentBits, сам байт перемещается
без изменений. Пример: Для черно-белого изображения, если бы мы
находились в 11-й колонке (см. пример выше), мы должны нулем погасить
3-й слева бит, то есть мы должны использовать PbmpBufferR^ и 11011111.

Теперь рассмотрим наш текущий исходный байт. Для черно-белых изображений
мы организуем цикл с шагом через бит, в общей сложности для восьми пикселей.
Для 4-битных изображений мы делаем цикл с обработкой четырех битов за проход для
двух пикселей. В любом случае мы делаем это через маскирование с
MaskBits ('PbmpBuffer^ и MaskBits'). Теперь нам нужно получить бит(ы)
из той колонки(ок), на которую отобразится CurrentBits. Мы это делаем с помощью
перемещения их в крайне правую часть байта ('shr ShiftRightAmount'),
затем сдвигая их налево с помощью вышеупомянутого
CurrentBitIndex ('shl CurrentBitIndex'). Дело в том, что хотя перемещение
вправо с параметром -n должно быть просто перемещением налево с параметром +n,
в Delphi это не работает. Итак, мы начинаем с первого байта, перемещая пиксели
в правую часть насколько это возможно незанятыми позициями.

Наконец, мы имеем наш исходный бит(ы), перемещенный на нужное место
с погашенными нулями битами. Последнее делаем непосредственно или с
помощью PbmpBufferR^ (гасим биты в CurrentBits, помните?).

Мда... "Просто". Ладно, поехали дальше.
}

PbmpBufferR^ := ( PbmpBufferR^ and not CurrentBits ) or
( (PbmpBuffer^ and MaskBits) shr ShiftRightAmount shl CurrentBitIndex );

{ Сдвигаем MaskBits для следующей итерации. }
MaskBits := MaskBits shr BitCount;
(*$IFDEF Win32*)
{ Перемещаем наш указатель на буфер вращаемого изображения на одну линию чередования. }
Inc(PbmpBufferR, BytesPerScanLineR);
{ Нам не нужно перемещаться непосредственно вправо в течение некоторого времени. }
Dec(ShiftRightAmount, BitCount);
(*$ELSE*)
Win16Inc( Pointer(PbmpBufferR), BytesPerScanLineR );
Win16Dec( Pointer(ShiftRightAmount), BitCount );
(*$ENDIF*)
end;
(*$IFDEF Win32*)
Inc(PbmpBuffer);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), 1 );
(*$ENDIF*)
end;

{ Если есть "частично заполненный" байт, самое время о нем позаботиться. }
if ExtraPixels <> 0 then begin
{ Делаем такие же манипуляции, как в цикле выше. }
MaskBits := FirstMask;
ShiftRightAmount := ShiftRightStart;
for I := 1 to ExtraPixels do begin
PbmpBufferR^ := ( PbmpBufferR^ and not CurrentBits ) or
( (PbmpBuffer^ and MaskBits) shr ShiftRightAmount shl CurrentBitIndex );

MaskBits := MaskBits shr BitCount;
(*$IFDEF Win32*)
Inc(PbmpBufferR, BytesPerScanLineR);
(*$ELSE*)
Win16Inc( Pointer(PbmpBufferR), BytesPerScanLineR );
(*$ENDIF*)
Dec(ShiftRightAmount, BitCount);
end;
(*$IFDEF Win32*)
Inc(PbmpBuffer);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), 1 );
(*$ENDIF*)
end;

(*$IFDEF Win32*)
{ Пропускаем заполнение. }
Inc(PbmpBuffer, PaddingBytes);
{
Сохраняем только что просмотренную линию чередования и переходим к следующей
для получения набора очередной строки.
}
Dec(PbmpBuffer, BytesPerScanLine shl 1);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), PaddingBytes );
Win16Dec( Pointer(PbmpBuffer), BytesPerScanLine shl 1 );
(*$ENDIF*)

if CurrentBits = LastMask then begin
{ Мы в конце этого байта. Начинаем с другой колонки. }
CurrentBits := FirstMask;
CurrentBitIndex := FirstIndex;
{ Идем вниз колонки вращаемого изображения , но одну колонку пропускаем. }
(*$IFDEF Win32*)
Inc(PFirstScanLine);
(*$ELSE*)
Win16Inc( Pointer(PFirstScanLine), 1 );
(*$ENDIF*)
end
else begin
{ Продолжаем заполнять этот байт. }
CurrentBits := CurrentBits shr BitCount;
Dec(CurrentBitIndex, BitCount);
end;
end;
end; { procedure NonIntegralByteRotate (* вложение *) }

procedure IntegralByteRotate; (* вложение *)
var
X, Y: LongInt;
(*$IFNDEF Win32*)
I: Integer;
(*$ENDIF*)

begin
{ Перемещаем PbmpBufferR в последнюю колонку первой линии чередования bmpBufferR. }
(*$IFDEF Win32*)
Inc(PbmpBufferR, SignificantBytesR - BytesPerPixel);
(*$ELSE*)
Win16Inc( Pointer(PbmpBufferR), SignificantBytesR - BytesPerPixel );
(*$ENDIF*)

{ Вот мясо. Перебираем в цикле все пиксели и соответственно вращаем. }
{ Помните что DIB'ы имеют происхождение противоположное DDB'сам. }
for Y := 1 to PbmpInfoR^.biHeight do begin
for X := 1 to PbmpInfoR^.biWidth do begin
{ Копируем пиксели. }
(*$IFDEF Win32*)
Move(PbmpBuffer^, PbmpBufferR^, BytesPerPixel);
Inc(PbmpBuffer, BytesPerPixel);
Inc(PbmpBufferR, BytesPerScanLineR);
(*$ELSE*)
for I := 1 to BytesPerPixel do begin
PbmpBufferR^ := PbmpBuffer^;
Win16Inc( Pointer(PbmpBuffer), 1 );
Win16Inc( Pointer(PbmpBufferR), 1 );
end;
Win16Inc( Pointer(PbmpBufferR), BytesPerScanLineR - BytesPerPixel);
(*$ENDIF*)
end;
(*$IFDEF Win32*)
{ Пропускаем заполнение. }
Inc(PbmpBuffer, PaddingBytes);
{ Идем вверх колонки вращаемого изображения , но одну колонку пропускаем. }
Dec(PbmpBufferR, ColumnBytes + BytesPerPixel);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), PaddingBytes);
Win16Dec( Pointer(PbmpBufferR), ColumnBytes + BytesPerPixel);
(*$ENDIF*)
end;
end;

{ Это тело процедуры RotateBitmap90DegreesCounterClockwise. }
begin
{ Никогда сами не вызывайте GetDIBSizes! Это испортит ваше изображение. }

MemoryStream := TMemoryStream.Create;

{
Для работы: Прежде всего установим размер. Это устранит перераспределение памяти
для MemoryStream. Вызов GetDIBSizes будет к месту, но, как отмечалось выше,
это может исказить ваше изображение. Вызов некоторых API функций вероятно
позаботился бы об этом, но это тема отдельного разговора.
}

{ Недокументированный метод. Все же программист иногда сродни шаману. }
ABitmap.SaveToStream(MemoryStream);

{ Изображение больше не нужно. Создадим новое когда понадобится. }
ABitmap.Free;

bmpBuffer := MemoryStream.Memory;
{ Получаем биты компенсации. Они могут содержать информацию о палитре. }
BitmapOffset := PBitmapFileHeader(bmpBuffer)^.bfOffBits;

{ Устанавливаем PbmpInfoR на указатель информационного заголовка исходного изображения. }
{ Эти заголовки могут немного раздражать, но они необходимы для работы. }
(*$IFDEF Win32*)
Inc( bmpBuffer, SizeOf(TBitmapFileHeader) );
(*$ELSE*)
Win16Inc( Pointer(bmpBuffer), SizeOf(TBitmapFileHeader) );
(*$ENDIF*)
PbmpInfoR := PBitmapInfoHeader(bmpBuffer);

{ Устанавливаем bmpBuffer и PbmpBuffer так, чтобы они указывали на биты оригинального изображения. }
bmpBuffer := MemoryStream.Memory;
(*$IFDEF Win32*)
Inc(bmpBuffer, BitmapOffset);
(*$ELSE*)
Win16Inc( Pointer(bmpBuffer), BitmapOffset );
(*$ENDIF*)
PbmpBuffer := bmpBuffer;

{
Имейте в виду, что нам не нужно беспокоиться о совместимости изображений версии 4 и 3,
поскольку области, которые мы используем, а именно -- biWidth, biHeight, и biBitCount --
располагаются на один и тех же местах в обоих структурах. Итак, одной проблемой меньше.
Изображения версии OS/2, между прочим, при этом гнусно рушатся. Обидно.
}
with PbmpInfoR^ do begin
{ ShowMessage('Компрессия := ' + IntToStr(biCompression)); }
BitCount := biBitCount;
{ ShowMessage('BitCount := ' + IntToStr(BitCount)); }

{ ScanLines - "выровненный" DWORD. }
BytesPerScanLine := ((((biWidth * BitCount) + 31) div 32) * SizeOf(DWORD));
BytesPerScanLineR := ((((biHeight * BitCount) + 31) div 32) * SizeOf(DWORD));

AtLeastEightBitColor := BitCount >= BitsPerByte;
if AtLeastEightBitColor then begin
{ Нас не должен волновать бит-тильда. Классно. }
BytesPerPixel := biBitCount shr 3;
SignificantBytes := biWidth * BitCount shr 3;
SignificantBytesR := biHeight * BitCount shr 3;
{ Дополнительные байты необходимы для выравнивания DWORD. }
PaddingBytes := BytesPerScanLine - SignificantBytes;
ColumnBytes := BytesPerScanLineR * biWidth;
end
else begin
{ Одно- или четырех-битовое изображение. Уфф. }
PixelsPerByte := SizeOf(Byte) * BitsPerByte div BitCount;
{ Все количество байтов полностью заполняется информацией о пикселе. }
WholeBytes := biWidth div PixelsPerByte;
{
Обрабатываем любые дополнительные биты, которые могут частично заполнять байт.
Например, черно-белое изображение, у которого 14 пикселей описываются каждый
соответственно своим байтом, плюс одним дополнительным, у которого на самом
деле используются 6 битов, остальное мусор.
}
ExtraPixels := biWidth mod PixelsPerByte;
{
Все дополнительные байты -- если имеются -- требуется DWORD-выровнять по
линии чередования.
}
PaddingBytes := BytesPerScanLine - WholeBytes;
{
Если есть дополнительные биты (то есть имеется 'дополнительный байт'),
то один из заполненных байтов уже был принят во внимание.
}
if ExtraPixels <> 0 then Dec(PaddingBytes);
end; { if AtLeastEightBitColor then }

{ TMemoryStream, обслуживающий вращаемые биты. }
MemoryStreamR := TMemoryStream.Create;
{
Устанавливаем размер вращаемого изображения. Может отличаться
от исходного из-за выравнивания DWORD.
}
MemoryStreamR.SetSize(BitmapOffset + BytesPerScanLineR * biWidth);
end; { with PbmpInfoR^ do }

{ Копируем заголовки исходного изображения. }
MemoryStream.Seek(0, soFromBeginning);
MemoryStreamR.CopyFrom(MemoryStream, BitmapOffset);

{ Вот буфер, который мы будем "вращать". }
bmpBufferR := MemoryStreamR.Memory;
{ Пропускаем заголовки, yadda yadda yadda... }
(*$IFDEF Win32*)
Inc(bmpBufferR, BitmapOffset);
(*$ELSE*)
Win16Inc( Pointer(bmpBufferR), BitmapOffset );
(*$ENDIF*)
PbmpBufferR := bmpBufferR;

{ Едем дальше. }
if AtLeastEightBitColor then
IntegralByteRotate
else
NonIntegralByteRotate;

{ Удовлетворяемся исходными битами. }
MemoryStream.Free;

{ Теперь устанавливаем PbmpInfoR, чтобы он указывал на информационный заголовок вращаемого изображения. }
PbmpBufferR := MemoryStreamR.Memory;
(*$IFDEF Win32*)
Inc( PbmpBufferR, SizeOf(TBitmapFileHeader) );
(*$ELSE*)
Win16Inc( Pointer(PbmpBufferR), SizeOf(TBitmapFileHeader) );
(*$ENDIF*)
PbmpInfoR := PBitmapInfoHeader(PbmpBufferR);

{ Меняем ширину с высотой в информационном заголовке вращаемого изображения. }
with PbmpInfoR^ do begin
T := biHeight;
biHeight := biWidth;
biWidth := T;
biSizeImage := 0;
end;

ABitmap := TBitmap.Create;

{ Вращение с самого начала. }
MemoryStreamR.Seek(0, soFromBeginning);
{ Загружаем это снова в ABitmap. }
ABitmap.LoadFromStream(MemoryStreamR);

MemoryStreamR.Free;
end;

procedure RotateBitmap90DegreesClockwise(var ABitmap: TBitmap);
const
BitsPerByte = 8;

var
{
Целая куча переменных. Некоторые имеют дело только с одно- и четырех-битовыми
изображениями, другие только с восемью- и 24-битовыми, а некоторые с обоими.
Любая переменная, оканчивающаяся символом 'R', имеет отношение к вращению изображения,
например если MemoryStream содержит исходное изображение, то MemoryStreamR - повернутое.
}
PbmpInfoR: PBitmapInfoHeader;
bmpBuffer, bmpBufferR: PByte;
MemoryStream, MemoryStreamR: TMemoryStream;
PbmpBuffer, PbmpBufferR: PByte;
BytesPerPixel, PixelsPerByte: LongInt;
BytesPerScanLine, BytesPerScanLineR: LongInt;
PaddingBytes: LongInt;
BitmapOffset: LongInt;
BitCount: LongInt;
WholeBytes, ExtraPixels: LongInt;
SignificantBytes: LongInt;
ColumnBytes: LongInt;
AtLeastEightBitColor: Boolean;
T: LongInt;

procedure NonIntegralByteRotate; (* вложение *)
{
Эта программа осуществляет поворот изображений с разрешением меньшим, чем 8 бит на пиксел,
а имеено: черно-белые (1-бит) и 16-цветные (4-бит) изображения. Имейте в виду, что
такие вещи, как 2-битные изображения также могли бы вращаться, но Microsoft не включил
данный формат в свои спецификации и не поддерживает его.
}
var
X, Y: LongInt;
I: LongInt;
MaskBits, CurrentBits: Byte;
FirstMask, LastMask: Byte;
PLastScanLine: PByte;
FirstIndex, CurrentBitIndex: LongInt;
ShiftRightAmount, ShiftRightStart: LongInt;

begin
{ Перемещаем PLastScanLine в первую колонку последней линии чередования bmpBufferR. }
PLastScanLine := bmpBufferR; (*$IFDEF Win32*) Inc(PLastScanLine, BytesPerScanLineR *
(PbmpInfoR^.biWidth - 1) ); (*$ELSE*) Win16Inc( Pointer(PLastScanLine),
BytesPerScanLineR * (PbmpInfoR^.biWidth - 1) ); (*$ENDIF*)

{ Устанавливаем индексирование. }
FirstIndex := BitsPerByte - BitCount;

{
Устанавливаем битовые маски:

Для черно-белого изображения,
LastMask  := 00000001    и
FirstMask := 10000000

Для 4-битного изображения,
LastMask  := 00001111    и
FirstMask := 11110000

Зададим значения CurrentBits и MaskBits, так как мы будем перемещаться по ним:
Для черно-белых изображений:
10000000, 01000000, 00100000, 00010000, 00001000, 00000100, 00000010, 00000001
Для 4-битных изображений:
11110000, 00001111

CurrentBitIndex определяет расстояние от крайнего правого бита
до позиции CurrentBits. Например, если мы находимся в одиннадцатой
колонке черно-белого изображения, CurrentBits равен
11 mod 8 := 3, или 3-й самый левый бит. Таким образом, крайне правый
бит должен переместиться на четыре позиции, чтобы попасть на позицию
CurrentBits. CurrentBitIndex как раз и хранит такое значение.
}
LastMask := 1 shl BitCount - 1;
FirstMask := LastMask shl FirstIndex;

CurrentBits := FirstMask;
CurrentBitIndex := FirstIndex;

ShiftRightStart := BitCount * (PixelsPerByte - 1);

{ Вот мясо. Перебираем в цикле все пиксели и соответственно вращаем. }
{ Помните что DIB'ы имеют происхождение противоположное DDB'сам. }

{ Счетчик Y указывает на текущую строчку исходного изображения. }
for Y := 1 to PbmpInfoR^.biHeight do begin
PbmpBufferR := PLastScanLine;

{
Счетчик X указывает на текущую колонку пикселей исходного изображения.
Здесь мы имеем дело только с полностью заполненными байтами. Обработка
'частично заполненных' байтов происходит ниже.
}
for X := 1 to WholeBytes do begin
{
Выбираем биты, начиная с 10000000 для черно-белых и
заканчивая 11110000 для 4-битных изображений.
}
MaskBits := FirstMask;
{
ShiftRightAmount - сумма, необходимая для перемещения текущего байта
через весь путь (помните, я об этом говорил выше) в правую часть.
}
ShiftRightAmount := ShiftRightStart;
for I := 1 to PixelsPerByte do begin
{
Вот гарнир. Берем текущий байт вращаемого изображения и маскируем его
с not CurrentBits. Гасятся нулями только биты CurrentBits, сам байт перемещается
без изменений. Пример: Для черно-белого изображения, если бы мы
находились в 11-й колонке (см. пример выше), мы должны нулем погасить
3-й слева бит, то есть мы должны использовать PbmpBufferR^ и 11011111.

Теперь рассмотрим наш текущий исходный байт. Для черно-белых изображений
мы организуем цикл с шагом через бит, в общей сложности для восьми пикселей.
Для 4-битных изображений мы делаем цикл с обработкой четырех битов за проход для
двух пикселей. В любом случае мы делаем это через маскирование с
MaskBits ('PbmpBuffer^ и MaskBits'). Теперь нам нужно получить бит(ы)
из той колонки(ок), на которую отобразится CurrentBits. Мы это делаем с помощью
перемещения их в крайне правую часть байта ('shr ShiftRightAmount'),
затем сдвигая их налево с помощью вышеупомянутого
CurrentBitIndex ('shl CurrentBitIndex'). Дело в том, что хотя перемещение
вправо с параметром -n должно быть просто перемещением налево с параметром +n,
в Delphi это не работает. Итак, мы начинаем с первого байта, перемещая пиксели
в правую часть насколько это возможно незанятыми позициями.

Наконец, мы имеем наш исходный бит(ы), перемещенный на нужное место
с погашенными нулями битами. Последнее делаем непосредственно или с
помощью PbmpBufferR^ (гасим биты в CurrentBits, помните?).

Мда... "Просто". Ладно, поехали дальше.
}

PbmpBufferR^ := ( PbmpBufferR^ and not CurrentBits ) or
( (PbmpBuffer^ and MaskBits) shr ShiftRightAmount shl CurrentBitIndex );

{ Сдвигаем MaskBits для следующей итерации. }
MaskBits := MaskBits shr BitCount;
(*$IFDEF Win32*)
{ Перемещаем наш указатель на буфер вращаемого изображения на одну линию чередования. }
Dec(PbmpBufferR, BytesPerScanLineR);
(*$ELSE*)
Win16Dec( Pointer(PbmpBufferR), BytesPerScanLineR );
(*$ENDIF*)
{ Нам не нужно перемещаться непосредственно вправо в течение некоторого времени. }
Dec(ShiftRightAmount, BitCount);
end;
(*$IFDEF Win32*)
Inc(PbmpBuffer);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), 1 );
(*$ENDIF*)
end;

{ Если есть "частично заполненный" байт, самое время о нем позаботиться. }
if ExtraPixels <> 0 then begin
{ Делаем такие же манипуляции, как в цикле выше. }
MaskBits := FirstMask;
ShiftRightAmount := ShiftRightStart;
for I := 1 to ExtraPixels do begin
PbmpBufferR^ := ( PbmpBufferR^ and not CurrentBits ) or
( (PbmpBuffer^ and MaskBits) shr ShiftRightAmount shl CurrentBitIndex );

MaskBits := MaskBits shr BitCount;
(*$IFDEF Win32*)
Dec(PbmpBufferR, BytesPerScanLineR);
(*$ELSE*)
Win16Dec( Pointer(PbmpBufferR), BytesPerScanLineR );
(*$ENDIF*)
Dec(ShiftRightAmount, BitCount);
end;
(*$IFDEF Win32*)
Inc(PbmpBuffer);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), 1 );
(*$ENDIF*)
end;

{ Пропускаем заполнение. }
(*$IFDEF Win32*)
Inc(PbmpBuffer, PaddingBytes);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), PaddingBytes );
(*$ENDIF*)

if CurrentBits = LastMask then begin
{ Мы в конце этого байта. Начинаем с другой колонки. }
CurrentBits := FirstMask;
CurrentBitIndex := FirstIndex;
{ Идем вниз колонки вращаемого изображения , но одну колонку пропускаем. }
(*$IFDEF Win32*)
Inc(PLastScanLine);
(*$ELSE*)
Win16Inc( Pointer(PLastScanLine), 1 );
(*$ENDIF*)
end
else begin
{ Продолжаем заполнять этот байт. }
CurrentBits := CurrentBits shr BitCount;
Dec(CurrentBitIndex, BitCount);
end;
end;
end; { procedure NonIntegralByteRotate (* вложение *) }

procedure IntegralByteRotate; (* вложение *)
var
X, Y: LongInt;
(*$IFNDEF Win32*)
I: Integer;
(*$ENDIF*)

begin
{ Перемещаем PbmpBufferR в первую колонку последней линии чередования bmpBufferR. }
(*$IFDEF Win32*)
Inc( PbmpBufferR, BytesPerScanLineR * (PbmpInfoR^.biWidth - 1) );
(*$ELSE*)
Win16Inc( Pointer(PbmpBufferR) , BytesPerScanLineR * (PbmpInfoR^.biWidth - 1) );
(*$ENDIF*)

{ Вот мясо. Перебираем в цикле все пиксели и соответственно вращаем. }
{ Remember that DIBs have their origins opposite from DDBs. }
for Y := 1 to PbmpInfoR^.biHeight do begin
for X := 1 to PbmpInfoR^.biWidth do begin
{ Копируем пиксели. }
(*$IFDEF Win32*)
Move(PbmpBuffer^, PbmpBufferR^, BytesPerPixel);
Inc(PbmpBuffer, BytesPerPixel);
Dec(PbmpBufferR, BytesPerScanLineR);
(*$ELSE*)
for I := 1 to BytesPerPixel do begin
PbmpBufferR^ := PbmpBuffer^;
Win16Inc( Pointer(PbmpBuffer), 1 );
Win16Inc( Pointer(PbmpBufferR), 1 );
end;
Win16Dec( Pointer(PbmpBufferR), BytesPerScanLineR + BytesPerPixel);
(*$ENDIF*)
end;
(*$IFDEF Win32*)
{ Пропускаем заполнение. }
Inc(PbmpBuffer, PaddingBytes);
{ Идем вверх колонки вращаемого изображения , но одну колонку пропускаем. }
Inc(PbmpBufferR, ColumnBytes + BytesPerPixel);
(*$ELSE*)
Win16Inc( Pointer(PbmpBuffer), PaddingBytes );
Win16Inc( Pointer(PbmpBufferR), ColumnBytes + BytesPerPixel );
(*$ENDIF*)
end;
end;

{ Это тело процедуры RotateBitmap90DegreesCounterClockwise. }
begin
{ Никогда сами не вызывайте GetDIBSizes! Это испортит ваше изображение. }

MemoryStream := TMemoryStream.Create;

{
Для работы: Прежде всего установим размер. Это устранит перераспределение памяти
для MemoryStream. Вызов GetDIBSizes будет к месту, но, как отмечалось выше,
это может исказить ваше изображение. Вызов некоторых API функций вероятно
позаботился бы об этом, но это тема отдельного разговора.
}

{ Недокументированный метод. Все же программист иногда сродни шаману. }
ABitmap.SaveToStream(MemoryStream);

{ Don't need you anymore. We'll make a new one when the time comes. }
ABitmap.Free;

bmpBuffer := MemoryStream.Memory;
{ Get the offset bits. This may or may not include palette information. }
BitmapOffset := PBitmapFileHeader(bmpBuffer)^.bfOffBits;

{ Устанавливаем PbmpInfoR на указатель информационного заголовка исходного изображения. }
{ Эти заголовки могут немного раздражать, но они необходимы для работы. }
(*$IFDEF Win32*)
Inc( bmpBuffer, SizeOf(TBitmapFileHeader) );
(*$ELSE*)
Win16Inc( Pointer(bmpBuffer), SizeOf(TBitmapFileHeader) );
(*$ENDIF*)
PbmpInfoR := PBitmapInfoHeader(bmpBuffer);

{ Устанавливаем bmpBuffer и PbmpBuffer так, чтобы они указывали на биты оригинального изображения. }
bmpBuffer := MemoryStream.Memory;
(*$IFDEF Win32*)
Inc(bmpBuffer, BitmapOffset);
(*$ELSE*)
Win16Inc( Pointer(bmpBuffer), BitmapOffset );
(*$ENDIF*)
PbmpBuffer := bmpBuffer;

{
Имейте в виду, что нам не нужно беспокоиться о совместимости изображений версии 4 и 3,
поскольку области, которые мы используем, а именно -- biWidth, biHeight, и biBitCount --
располагаются на один и тех же местах в обоих структурах. Итак, одной проблемой меньше.
Изображения версии OS/2, между прочим, при этом гнусно рушатся. Обидно.
}
with PbmpInfoR^ do begin
{ ShowMessage('Компрессия := ' + IntToStr(biCompression)); }
BitCount := biBitCount;
{ ShowMessage('BitCount := ' + IntToStr(BitCount)); }

{ ScanLines - "выровненный" DWORD. }
BytesPerScanLine := ((((biWidth * BitCount) + 31) div 32) * SizeOf(DWORD));
BytesPerScanLineR := ((((biHeight * BitCount) + 31) div 32) * SizeOf(DWORD));

AtLeastEightBitColor := BitCount >= BitsPerByte;
if AtLeastEightBitColor then begin
{ Нас не должен волновать бит-тильда. Классно. }
BytesPerPixel := biBitCount shr 3;
SignificantBytes := biWidth * BitCount shr 3;
{ Дополнительные байты необходимы для выравнивания DWORD. }
PaddingBytes := BytesPerScanLine - SignificantBytes;
ColumnBytes := BytesPerScanLineR * biWidth;
end
else begin
{ Одно- или четырех-битовое изображение. Уфф. }
PixelsPerByte := SizeOf(Byte) * BitsPerByte div BitCount;
{ Все количество байтов полностью заполняется информацией о пикселе. }
WholeBytes := biWidth div PixelsPerByte;
{
Обрабатываем любые дополнительные биты, которые могут частично заполнять байт.
Например, черно-белое изображение, у которого 14 пикселей описываются каждый
соответственно своим байтом, плюс одним дополнительным, у которого на самом
деле используются 6 битов, остальное мусор.
}
ExtraPixels := biWidth mod PixelsPerByte;
{
Все дополнительные байты -- если имеются -- требуется DWORD-выровнять по
линии чередования.
}
PaddingBytes := BytesPerScanLine - WholeBytes;
{
Если есть дополнительные биты (то есть имеется 'дополнительный байт'),
то один из заполненных байтов уже был принят во внимание.
}
if ExtraPixels <> 0 then Dec(PaddingBytes);
end; { if AtLeastEightBitColor then }

{ TMemoryStream, обслуживающий вращаемые биты. }
MemoryStreamR := TMemoryStream.Create;
{
Устанавливаем размер вращаемого изображения. Может отличаться
от исходного из-за выравнивания DWORD.
}
MemoryStreamR.SetSize(BitmapOffset + BytesPerScanLineR * biWidth);
end; { with PbmpInfoR^ do }

{ Копируем заголовки исходного изображения. }
MemoryStream.Seek(0, soFromBeginning);
MemoryStreamR.CopyFrom(MemoryStream, BitmapOffset);

{ Вот буфер, который мы будем "вращать". }
bmpBufferR := MemoryStreamR.Memory;
{ Пропускаем заголовки, yadda yadda yadda... }
(*$IFDEF Win32*)
Inc(bmpBufferR, BitmapOffset);
(*$ELSE*)
Win16Inc( Pointer(bmpBufferR), BitmapOffset );
(*$ENDIF*)
PbmpBufferR := bmpBufferR;

{ Едем дальше. }
if AtLeastEightBitColor then
IntegralByteRotate
else
NonIntegralByteRotate;

{ Удовлетворяемся исходными битами. }
MemoryStream.Free;

{ Теперь устанавливаем PbmpInfoR, чтобы он указывал на информационный заголовок вращаемого изображения. }
PbmpBufferR := MemoryStreamR.Memory;
(*$IFDEF Win32*)
Inc( PbmpBufferR, SizeOf(TBitmapFileHeader) );
(*$ELSE*)
Win16Inc( Pointer(PbmpBufferR), SizeOf(TBitmapFileHeader) );
(*$ENDIF*)
PbmpInfoR := PBitmapInfoHeader(PbmpBufferR);

{ Меняем ширину с высотой в информационном заголовке вращаемого изображения. }
with PbmpInfoR^ do begin
T := biHeight;
biHeight := biWidth;
biWidth := T;
biSizeImage := 0;
end;

ABitmap := TBitmap.Create;

{ Вращение с самого начала. }
MemoryStreamR.Seek(0, soFromBeginning);
{ Загружаем это снова в ABitmap. }
ABitmap.LoadFromStream(MemoryStreamR);

MemoryStreamR.Free;
end;

procedure RotateBitmap180Degrees(var ABitmap: TBitmap);
var
RotatedBitmap: TBitmap;

begin
RotatedBitmap := TBitmap.Create;
with RotatedBitmap do begin
Width := ABitmap.Width;
Height := ABitmap.Height;
Canvas.StretchDraw( Rect(ABitmap.Width, ABitmap.Height, 0, 0), ABitmap );
end;
ABitmap.Free;
ABitmap := RotatedBitmap;
end;

end.

[000122]