Поворот изображения на 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]