1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
385.
386.
387.
388.
389.
390.
391.
392.
393.
394.
395.
396.
397.
398.
399.
400.
401.
402.
403.
404.
405.
406.
407.
408.
409.
410.
411.
412.
413.
414.
415.
416.
417.
418.
419.
420.
421.
422.
423.
424.
425.
426.
427.
428.
429.
430.
431.
432.
433.
434.
435.
436.
437.
438.
439.
440.
441.
442.
443.
444.
445.
446.
447.
448.
449.
450.
451.
452.
453.
454.
455.
456.
457.
458.
459.
460.
461.
462.
463.
464.
465.
466.
467.
468.
469.
470.
471.
472.
473.
474.
475.
476.
477.
478.
479.
480.
481.
482.
483.
484.
485.
486.
487.
488.
489.
490.
491.
492.
493.
494.
495.
496.
497.
498.
499.
500.
501.
502.
503.
504.
505.
506.
507.
508.
509.
510.
511.
512.
513.
514.
515.
516.
517.
518.
519.
520.
521.
522.
523.
524.
525.
526.
527.
528.
529.
530.
531.
532.
533.
534.
535.
536.
537.
538.
539.
540.
541.
542.
543.
544.
545.
546.
547.
548.
549.
550.
551.
552.
553.
554.
555.
556.
557.
558.
559.
560.
561.
562.
563.
564.
565.
566.
567.
568.
569.
570.
571.
572.
573.
574.
575.
576.
577.
578.
579.
580.
581.
582.
583.
584.
585.
586.
587.
588.
589.
590.
591.
592.
593.
594.
595.
596.
597.
598.
599.
600.
601.
602.
603.
604.
605.
606.
607.
608.
609.
610.
611.
612.
613.
614.
615.
616.
617.
618.
619.
620.
621.
622.
623.
624.
625.
626.
627.
628.
629.
630.
631.
632.
633.
634.
635.
636.
637.
638.
639.
640.
641.
642.
643.
644.
645.
646.
647.
648.
649.
650.
651.
652.
653.
654.
655.
656.
657.
658.
659.
660.
661.
662.
663.
664.
665.
666.
667.
668.
669.
670.
671.
672.
673.
674.
675.
676.
677.
678.
679.
680.
681.
682.
683.
684.
685.
686.
687.
688.
689.
690.
691.
692.
693.
694.
695.
696.
697.
698.
699.
700.
701.
702.
703.
704.
705.
706.
707.
708.
709.
710.
711.
712.
713.
714.
715.
716.
717.
718.
719.
720.
721.
722.
723.
724.
725.
726.
727.
728.
729.
730.
731.
732.
733.
734.
735.
736.
737.
738.
739.
740.
741.
742.
743.
744.
745.
746.
747.
748.
749.
750.
751.
752.
753.
754.
755.
756.
757.
758.
759.
760.
761.
762.
763.
764.
765.
766.
767.
768.
769.
770.
771.
772.
773.
774.
775.
776.
777.
778.
779.
780.
781.
782.
783.
784.
785.
786.
787.
788.
789.
790.
791.
792.
793.
794.
795.
796.
797.
798.
799.
800.
801.
802.
803.
804.
805.
806.
807.
808.
809.
810.
811.
812.
813.
814.
815.
816.
817.
818.
819.
820.
821.
822.
823.
824.
825.
826.
827.
828.
829.
830.
831.
832.
833.
834.
835.
836.
837.
838.
839.
840.
841.
842.
843.
844.
845.
846.
847.
848.
849.
850.
851.
852.
853.
854.
855.
856.
857.
858.
859.
860.
861.
862.
863.
864.
865.
866.
867.
868.
869.
870.
871.
872.
873.
874.
875.
876.
877.
878.
879.
880.
881.
882.
883.
884.
885.
886.
887.
888.
889.
890.
891.
892.
893.
894.
895.
896.
897.
898.
899.
900.
901.
902.
903.
904.
905.
906.
907.
908.
909.
910.
911.
912.
913.
914.
915.
916.
917.
918.
919.
920.
921.
922.
923.
924.
925.
926.
927.
928.
929.
930.
931.
932.
933.
934.
935.
936.
937.
938.
939.
940.
941.
942.
943.
944.
945.
946.
947.
948.
949.
950.
951.
952.
953.
954.
955.
956.
957.
958.
959.
960.
961.
962.
963.
964.
965.
966.
967.
968.
969.
970.
971.
972.
973.
974.
975.
976.
977.
978.
979.
980.
981.
982.
983.
984.
985.
986.
987.
988.
989.
990.
991.
992.
993.
994.
995.
996.
997.
998.
999.
1000.
1001.
1002.
1003.
1004.
1005.
1006.
1007.
1008.
1009.
1010.
1011.
1012.
1013.
1014.
1015.
1016.
1017.
1018.
1019.
1020.
1021.
1022.
1023.
1024.
1025.
1026.
1027.
1028.
1029.
1030.
1031.
1032.
1033.
1034.
1035.
1036.
1037.
1038.
1039.
Новый модуль имеет три программы: 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.