Два предостережения: Это все еще не совсем работает в 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 = recordL, H: Word;end; procedure Win16Dec(var P: Pointer; const N: LongInt); forward; procedure Win16Inc(var P: Pointer; const N: LongInt); begin if N < 0 thenWin16Dec(P, -N)else if N > 0 then beginInc( 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 thenWin16Inc(P, -N)else if N > 0 then beginif 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;asmmov 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 HugeShiftshl dx, { Перемещаем наибольшую величину суммы для сегмента. }add es:[bx+2], dx { Увеличиваем сегмент HugePtr. }mov es:[bx], axend; 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 thenWin16Inc(P, -N)else if N > 0 then beginif 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 beginPbmpBufferR := 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 beginPbmpBufferR^ := ( 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*)endelse 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 beginfor 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 beginPbmpBufferR^ := 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;endelse 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 thenIntegralByteRotateelseNonIntegralByteRotate; { Удовлетворяемся исходными битами. }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 beginT := 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 beginPbmpBufferR := 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 beginPbmpBufferR^ := ( 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*)endelse 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 beginfor 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 beginPbmpBufferR^ := 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;endelse 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 thenIntegralByteRotateelseNonIntegralByteRotate; { Удовлетворяемся исходными битами. }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 beginT := 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 beginWidth := ABitmap.Width;Height := ABitmap.Height;Canvas.StretchDraw( Rect(ABitmap.Width, ABitmap.Height, 0, 0), ABitmap );end;ABitmap.Free;ABitmap := RotatedBitmap;end; end. |
[000122]