Smalltalk80LanguageImplementationKor:Chapter 18
- 제 18 장 그래픽 커널
그래픽 커널
Object
Magnitude
Character
Date
Time
Number
Float
Fraction
Integer
LargeNegativeInteger
LargePositiveInteger
SmallInteger
LookupKey
Association
Link
Process
Collection
SequenceableCollection
LinkedList
Semaphore
ArrayedCollection
Array
Bitmap***
DisplayBitmap***
RunArray
String
Symbol
Text
ByteArray
Interval
OrderedCollection
SortedCollection
Bag
MappedCollection
Set
Dictionary
IdentifyDictionary
Stream
PositionableStream
ReadStream
WriteStream
ReadWriteStream
ExternalStream
FileStream
Random
File
FileDirectory
FilePage
UndefinedObject
Boolean
False
True
ProcessorScheduler
Delay
SharedQueue
Behavior
ClassDescription
Class
MetaClass
Point***
Rectangle***
BitBit***
CharacterScanner***
Pen
DisplayObject
DisplayMedium
Form
Cursor
DisplayScreen
InfiniteForm
OpaqueForm
Path
Arc
Circle
Curve
Line
LinearFit
Spline
그래픽 표현(Graphical Representation)
그림 18.1은 Smalltalk-80 시스템의 표시화면 모습이다. 이는 시스템에 이용 가능한 광범위한 그래픽 용어를 보여준다. 임의의 크기로 된 직사각형 영역은 흰색, 검정색, 하프톤 패턴으로 채워져 있다. 다양한 서체로 된 텍스트가 각 문자의 저장된 이미지로부터 화면에 위치된다. 하프톤 그림자는 사용자에 의해 "brushed"(그려져) 손으로 그린 그림이 생성된다. 그리고 출력된 페이지에는 표시되지 않았지만 디스플레이에 이미지를 이동하거나 시간별로 배열하여 애니메이션을 제공할 수도 있다.
앞 장에 실린 시스템과 상호작용의 예제를 통해 객체를 관찰하고 그래픽하게 조작하는 방법을 몇 가지 소개한 바 있다. 시스템에서 의미가 있는 객체의 표현을 위해서는 디스플레이 매체에 대해 최대한으로 통제할 수 있어야 한다. 필요한 유연성을 제공하는 한 가지 접근법은 표시된 이미지에 모든 discernible point의 밝기를 독자적으로 통제할 수 있도록 허용하는 것을 들 수 있다. 이러한 접근법의 구현으로는 이미지를 표시할 때 각 "bit"(비트)의 설정을 해당하는 그림 요소의 illumination 또는 "pixel"(픽셀)로 매핑하는 contiguous block of storage를 들 수 있겠다. block of storage는 “bitmap”(비트맵)이라고 부른다. 이러한 디스플레이 유형을 “bitmapped display”(비트맵 방식의 디스플레이)라고 부른다. 가장 단순한 형태의 비트맵은 두 개의 밝기 수준, 흰색과 검정색만 허용하는데, 이는 저장된 비트 값 0과 1에 해당한다. Smalltalk-80 그래픽 시스템은 이러한 디스플레이 매체 모델을 위주로 구축된다.
그래픽 저장(Graphical Storage)
이미지는 Form 클래스의 인스턴스에 의해 표현된다. Form은 높이와 너비를 갖고 있고, 비트맵은 표현되는 특정 이미지의 흰색과 검정색 영역을 나타낸다. 예를 들어, 그림 18.2에 나타낸 사람 모양을 살펴보자. Form의 높이는 비트맵에서 40, 너비는 14, 모양은 1과 0의 패턴으로 설명된다 (흰색과 검정색 사각형으로 표시). Form의 높이와 너비는 비트맵에서 구조화되지 않은 데이터에 적절한 2차원 정렬을 이용하는 역할을 한다. 본 장의 뒷부분에서 Forms의 표현을 좀 더 자세히 살펴보도록 하겠다.
새로운 Forms는 여러 개의 Forms를 결합하여 생성된다. 그림 18.1의 중앙에 손으로 그린 그림은 Smalltalk-80 애플리케이션 시스템에서 "paint brushes"(페인트 브러쉬) 역할을 하는 Forms 여러 개를 결합 및 반복하여 생성된 큰 Form의 예라고 할 수 있겠다. 그림 18.1에 있는 텍스트는 문자를 표현하는 Forms의 구조화된 결합이다.
Form은 디스플레이 화면에 표시할 이미지 또는 실제 데이터를 기억하여 버퍼로서 디스플레이 하드웨어에 제시할 수 있다. 하드웨어에 대한 인터페이스는 Form을 통하여 이루어지기 때문에 이미지를 내부적으로 결합하는 것이나 이미지를 화면에 표시하는 것에 차이가 없다. 하나의 Form은 디스플레이 Form으로 사용하면서 다음으로 표시할 이미지를 두 번째 Form에서 준비시키면 애니메이션을 부드럽게 표시할 수 있다. 각 이미지가 완성되면 두 개의 Forms가 역할을 교환하여 새로운 이미지가 표시되고 기존 이미지의 Form을 시퀀스 내 다른 이미지를 빌드하는 데 이용할 수 있도록 한다.
디스플레이 화면에 표시할 데이터에 대한 버퍼로 사용한 Forms는 Form의 서브클래스인 DisplayScreen 클래스의 인스턴스다. 비트의 연속 기억장치(continuous storage)는 Bitmap 클래스의 인스턴스에 의해 제공된다. DisplayScreen의 비트맵은 Bitmap의 서브클래스인 DisplayBitmap의 인스턴스다. DisplayScreen과 DisplayBitmap은 실제 하드웨어에 특정적인, 그리고 Form이 디스플레이 화면의 잠재적인 일부보다는 전체를 표현한다는 사실에 특정적인 프로토콜을 제공한다.
그래픽 조작(Graphical Manipulation)
BitBIt으로 불리는 Forms에 대한 기본 연산은 광범위한 그래픽 표현을 지원한다. 시스템 내 모든 텍스트 및 그래픽 객체들은 이러한 단일 그래픽 연산을 이용해 생성 및 수정된다. "BitBIt"이란 이름은 임의의 비트 위치 또는 픽셀로의 데이터 전송을 일반화하는 데서 비롯되었다. Smalltalk 시스템이 구현되었던 초창기 한 컴퓨터에서는 16-bit 워드의 "block transfer"(블록 전송)용 BLT라 불리던 명령어가 있었는데, 그에 따라 "bit block transfer"(비트 블록 전송)은 BitBIt으로 알려지게 되었다.
연산은 메시지에 의해 객체에게 표현된다. 따라서 BitBIt은 Form 클래스로 전송되는 메시지를 이용해 구현할 수 있어야 한다. 하지만 BitBIts는 명시하기에 꽤 복잡한 연산이기 때문에 객체에 의해 표현된다. 이러한 객체들은 BitBIt이라는 클래스의 인스턴스다. 기본 연산은 적절히 초기화된 BitBIt의 인스턴스에 copyBits 메시지를 전송하여 실행된다. BitBIt 연산은 의도적으로 매우 일반적인 연산이지만 그 애플리케이션은 대부분 “이 직사각형 픽셀을 여기서 저기로 이동시켜라” 식으로 그래픽적으로 단순하다.
그림 18.3은 텍스트의 문자를 디스플레이 상에 영역으로 복사하는 과정을 보여준다. 이러한 연산은 남은 절에서 소개할 BitBIt 특성의 대부분을 설명하게 될 것이다.
소스와 목적지 Forms(Source and Destination Forms)
BitBlt 복사 연산에는 두 개의 Forms, 즉 소스와 목적지가 수반된다. 그림 18.3의 예제에서 소스는 font로, 획일화된 스타일과 크기로 묘시된 문자 글리프를 포함하며 가로로 패킹되어 있다. 예제에서 목적지는 디스플레이 버퍼로 사용되는 Form일 것으로 추측된다. 픽셀은 소스로부터 복사되어 목적지로 저장된다. 전송의 너비와 높이는 문자 크기에 해당한다. 소스 x와 y 좌표는 font에서 문자의 위치를 제공하고, 목적지 좌표는 디스플레이에서 그 복사본이 나타나게 될 위치를 명시한다.
클리핑 직사각형
BitBlt은 그 명세(specification)에 다른 목적지 매개변수와 상관없이 자체의 연산의 영향을 받을 수 있는 목적지의 영역을 제한하는 직사각형을 포함하고 있다. 이 영역을 "clipping rectangle"(클리핑 직사각형)이라고 부른다. 때로는 부분적인 "window"(창)을 더 큰 화면에 표시하는 것이 바람직한데, 클리핑 직사각형은 모든 그림 요소가 창의 경계 안으로 들어가도록 보장한다. BitBIt 프리미티브에 포함시키면 클리핑 기능을 모든 애플리케이션 프로그램에 복사하는 대신 한 곳에서 효율적으로 실행할 수 있다.
그림 18.4는 그림 18.3의 예제에 클리핑 직사각형을 이용한 결과를 보여준다. 클리핑 직사각형 밖에 위치하였을 픽셀은 ("N"의 왼쪽 가장자리와 "the"라는 단어 절반) 전송되지 않았을 것이다. 이 직사각형의 위나 아래에 다른 문자가 있었다면 비슷하게 깎일 것이다.
하프톤 형식(HalfTone Form)
때로는 회색 톤이나 텍스처의 효과를 제공하는 정규 패턴으로 영역을 채워야 하는 경우가 있다. 이를 위해 BitBIt은 바람직한 패턴을 포함하는 세 번째 Form으로 참조를 제공한다. 이러한 Form을 하프톤 또는 "mask"(마스크)라고 부른다. 높이와 너비는 16으로 제한된다. 하프토닝(halftoning)이 명시되면 이 패턴은 전체 목적지에 걸쳐 수평으로 그리고 수직으로 16 units마다 효과적으로 반복된다.
하프톤 형식이나 소스 형식에 대해 nil을 제공함으로써 통제되는 하프톤 및 소스로부터 픽셀을 제공하는 "방식"이 네 가지가 있다.
- 소스 없음, 하프톤 없음(검정색 실선 제공)
- 하프톤만 있음(하프톤 패턴 제공)
- 소스만 있음(소스 픽셀 제공)
- 소스와 하프톤 모두 있음(하프톤 패턴으로 가려진 소스 비트 제공)
그림 18.5는 동일한 소스 및 목적지와 정규 회색 하프톤을 적용한 네 가지 방식의 효과를 보여준다.
결합 규칙(Combination Rule)
위의 예제들은 모두 목적지의 새로운 내용이 소스와 하프톤에만 기반으로 하여 목적지에 바로 저장하기로 결정하였다. 목적지의 이전 내용은 복사 결과를 결정 시 고려하기도 한다.
각 소스 요소 S를 그에 상응하는 목적지 요소 D와 결합하여 새로운 목적지 요소 D' 를 생성하는 데에 가능한 규칙으로 16개가 있다. 각 규칙은 소스의 네 가지 경우 각각에 대한 흰색 또는 검정색 결과를 흰색이나 검정색, 그리고 목적지를 흰색이나 검정색으로 명시해야 한다.
그림 18.6은 소스 S와 목적지 D를 결합할 때 직면할 수 있는 사례 네 가지에 해당하는 네 가지 셀로 된 상자를 보여준다. 가령, 2번 셀은 소스가 검정색이고 목적지가 흰색인 경우에 해당한다. 네 가지 셀을 흰색이나 검정색으로 적절하게 채움으로써 상자는 어떠한 결합 규칙이든 묘사하도록 만들 수 있다. 네 개의 셀에 있는 숫자는 해당 규칙을 선택하는 정수값으로 그래픽하게 묘사하도록 규칙을 만든다. 예를 들어, 소스 또는 목적지(아니면 둘 다)가 검정색인 곳은 결과도 검정색이어야 함을 명시하기 위해서는 4, 2, 1번 셀을 검정색으로 칠해야 할 것이다. 이러한 규칙을 명시하는 데에 연관된 정수는 검정색으로 된 셀의 번호를 합한 값, 즉 4+2+1=7이 된다.
그림 18.7은 네 가지 공통된 결합 규칙을 그래픽하게 설명한다. 또한 각 규칙은 결합도(combination diagram), 정수 규칙 번호, 적용되는 실제 논리 함수로 설명된다. 16개 결합 규칙의 전체 집합은 BitBIt에 대한 세부적인 시뮬레이션의 일부로 본장 뒷부분에서 싣고 있다.
Form과 Bitmap 클래스
그림 18.8은 그림 18.2에 표시된 Form에 관한 추가 정보를 표시한다. 너비와 높이는 Integers로 저장된다. 실제 픽셀은 Bitmap 클래스의 인스턴스에 해당하는 구분된 객체에 저장된다. Bitmaps의 목적은 Forms에 대한 저장공간(storage)을 제공하는 것이기 때문에 프로토콜을 거의 가지지 않는다. 또한 그것을 소유하는 Form이 제공하는 수치를 제외하면 내재적인 너비와 높이도 갖고 있지 않은데, 그림에서는 명료성을 위해 구조를 그대로 유지하고 있다. Bitmap에는 16 너비의 공간이 제공되고 있음을 확인할 수 있는데, 이는 저장공간의 하드웨어 조직(hardware organization)과 16-bit "words"(워드)로의 처리를 나타내는 것이다. Bitmaps는 각 픽셀 행에 대한 워드의 진정수로 할당된다. 이러한 행의 크기를 "raster size"(래스터 크기)라고 부른다. 래스터 크기에서 정수 워드(integral word)의 제약은 BitBIt의 연산 내에서, 그리고 하드웨어가 디스플레이 화면을 스캔하는 동안 한 행에서 다음 행으로 이동을 촉진시킨다. 메모리를 워드로 나누는 것은 원초적인 수준에서 중요하며, 시스템 요구 내 어떠한 고수준의 그래픽 구성요소도 워드 크기의 문제를 고려하지 않도록 캡슐화된다.
두 개의 클래스, Rectangle과 Point는 저장된 이미지와 작업하는 데에 광범위하게 사용된다. Point는 x와 y 좌표값을 포함하고 Form에서 픽셀 위치를 참조하는 데에 사용되는데, Rectangle은 상단 좌측 모서리와 하단 우측 모서리에 있는 Point 두 개를 포함하며, 직사각형 영역의 Form을 정의하는 데에 사용된다.
Form 클래스는 검정색과 흰색 점으로 된 직사각형 패턴을 관리하는 프로토콜을 포함한다. Form의 비트맵은 디스플레이 화면의 직사각형 영역에서 비트를 복사하여 (fromDisplay:) (재)설정 가능하고, Form의 범위와 오프셋도 설정할 수 있다 (extent:, extent:offset:). 두 개의 메시지가 각 비트로 접근을 제공한다 (valueAt: 과 valueAt:put:). 모드와 마스크에 대한 상수는 Form 클래스에 알려져 있으며, 다음과 같은 Form의 클래스 메시지로 얻을 수 있다.
초기화-해제(initialize-release) | |
fromDisplay: aRectangle | 인자 aRectangle이 정의한 경계 내의 디스플레이 화면에서 비트를 수신자의 비트로 복사한다. |
접근하기(accessing) | |
extent: aPoint | 수신자의 너비와 높이를 인자 aPoint의 좌표로 설정한다. |
extent: extentPoint offset: offsetPoint | 수신자의 너비와 높이를 인자 extentPoint의 좌표로 설정하고 오프셋을 offsetPoint로 설정한다. |
패턴(pattern) | |
valueAt: aPoint | 수신자의 비트맵 내에 aPoint 위치에 해당하는 0 또는 1 비트를 응답한다. |
valueAt: aPoint put: bitCode | 수신자의 비트맵 내에 aPoint 위치에 해당하는 비트를 bitCde, 즉 0 또는 1로 설정한다. |
Form 인스턴스 프로토콜 |
인스턴스 생성(instance creation) | |
fromDisplay: aRectangle | 인자 aRectangle이 정의한 경계 내의 디스플레이 화면에서 수신자의 비트맵으로 비트를 복사한 새로운 Form을 응답한다. |
모드 상수(mode constants) | |
erase | Erase 모드를 나타내는 Integer를 응답한다. |
over | Over 모드를 나타내는 Integer를 응답한다. |
reverse | Reverse 모드를 나타내는 Integer를 응답한다. |
under | Under 모드를 나타내는 Integer를 응답한다. |
마스크 상수(mask constants) | |
black | 검정색 마스크를 나타내는 Form을 응답한다. |
darkGray | 짙은 회색 마스크를 나타내는 Form을 응답한다. |
gray | 회색 마스크를 나타내는 Form을 응답한다. |
lightGray | 연한 회색 마스크를 나타내는 Form을 응답한다. |
veryLightGray | 매우 연한 회색 마스크를 나타내는 Form을 응답한다. |
white | 흰색 마스크를 나타내는 Form을 응답한다. |
Form 클래스 프로토콜 |
공간 참조(Spatial Reference)
Forms가 표현하는 이미지는 본래 2차원이기 때문에 이미지 조작은 2차원 위치와 면적을 표현하는 객체를 제공함으로써 단순화된다. Point 클래스의 인스턴스들은 위치를 나타내고 Rectangle 클래스의 인스턴스들은 면적을 나타낸다.
Point 클래스
Point는 주로 Form에서 픽셀을 지정하는 숫자의 x-y 쌍을 나타낸다. Points는 Form의 상단 좌측 모서리를 기준으로 한 (또는 다른 참조점) 픽셀 위치를 참조한다. 페이지의 레이아웃이나 디스플레이 스캐닝의 방향과 마찬가지로 본래 x는 우측으로 증가하고 y는 하향으로 증가한다. 이는 y가 상향으로 갈수록 증가하는 "오른손" 좌표계와는 상반된다.
Point는 주로 Number로 이항 메시지 @를 전송하여 생성된다. 가령, 아래의 표현식을 평가하면
200 @ 150
x와 y 좌표가 각각 200과 150인 Point가 된다. 또 Point의 클래스 프로토콜은 x: xInteger y: yInteger라는 인스턴스 생성 메시지를 지원한다.
Point x: 200 y: 150
위는 동일한 200@150 위치를 나타낸다. Point에 대한 인스턴스 프로토콜은 두 개의 Points를 비교하기 위한 메시지와 접근 메시지를 지원한다.
접근하기(accessing) | |
x | x 좌표를 응답한다. |
x: aNumber | x 좌표를 인자 aNumber로 설정한다. |
y | y 좌표를 응답한다. |
y: aNumber | y 좌표를 인자 aNumber로 설정한다. |
비교하기(comparing) | |
< aPoint | 수신자가 인자 aPoint보다 위와 좌측에 있는지 응답한다. |
< = aPoint | 수신자가 인자 aPoint의 아래쪽이나 우측에 없는지 응답한다. |
> aPoint | 수신자가 인자 aPoint의 아래와 우측에 있는지 응답한다. |
> = aPoint | 수신자가 인자 aPoint의 위쪽이나 좌측에 없는지 응답한다. |
max: aPoint | 수신자와 인자 aPoint가 유일하게 정의한 정사각형의 하단 우측 모서리를 응답한다. |
min: aPoint | 수신자와 인자 aPoint가 유일하게 정의한 정사각형의 상단 좌측 모서리를 응답한다. |
Point 인스턴스 프로토콜 |
그림 18.9에 표시된 좌표와 관련된 표현식 예제는 다음과 같다.
표현식 | 결과 |
(45@230) < (175@270) | true |
(45@230) < (175@200) | false |
(45@230) > (175@200) | false |
(175@270) > (45@230) | true |
(45@230) max: (175@200) | 175@230 |
(45@230) min: (175@200) | 45@200 |
두 개의 Points 또는 Point와 Number(환산 계수) 간에 산술 연산을 실행할 수 있다. 산술 메시지는 각각 Point 또는 Number(스칼라)를 인자로 취하고, 결과로 새로운 Point를 리턴한다. 버림(truncation) 및 반올림(round off) 메시지는 Numbers에 해당하는 메시지와 비슷하며 Point의 인스턴스 프로토콜에 제공된다.
산술(arithmetic) | |
* scale | 수신자와 인자 scale의 곱인 새로운 Point를 응답한다. |
+ delta | 수신자와 인자 delta의 합인 새로운 Point를 응답한다. |
- delta | 수신자와 인자 delta의 차인 새로운 Point를 응답한다. |
/ scale | 수신자와 인자 scale의 몫인 새로운 Point를 응답한다. |
// scale | 수신자와 인자 scale의 몫인 (음의 무한대로 자른 나눗셈으로 정의된) 새로운 Point를 응답한다. |
abs | 수신자의 x와 y의 절대값을 x와 y로 가진 새로운 Point를 응답한다. |
버림과 반올림(truncation and round off) | |
rounded | 수신자의 x와 y가 반올림된 새로운 Point를 응답한다. |
truncateTo: grid | 수신자의 x와 y를 인자 grid로 자른 새로운 Point를 응답한다. |
Point 인스턴스 프로토콜 |
아래와 같이 예를 들 수 있겠다.
표현식 | 결과 |
(45@230) + (175@300) | 220@530 |
(45@230) + 175 | 220@405 |
(45@230) - (175@300) | -130@-70 |
(160@240) / 50 | (16/5)@(24/5) |
(160@240) // 50 | 3@4 |
(160@240) // (50@50) | 3@4 |
((45@230) - (175@300)) abs | 130@70 |
(120.5 @ 220.7) rounded | 121@221 |
(160 @ 240) truncateTo: 50 | 150@200 |
그 외 두 개의 Points 간 거리를 계산하거나, 두 개의 Points의 내적(dot product)을 계산하거나, 점을 뒤바꾼다거나, 격자무늬로 된 범위 내에서 Points를 결정하는 일을 포함해 Points에서는 여러 연산을 실행할 수 있다.
point 함수(point functions) | |
dist: aPoint | 인자 aPoint와 수신자 간 거리를 응답한다. |
dotProduct: aPoint | 수신자의 인자 aPoint의 내적에 해당하는 Number를 응답한다. |
grid: aPoint | 인자 aPoint가 명시한 것과 가장 가깝게 반올림된 그리드 모듈로 Point를 응답한다. |
normal | 시계 방향으로 90도 회전한 단위 벡터를 나타내는 Point를 응답한다. |
transpose | x가 수신자의 y이고 y가 수신자의 x인 Point를 응답한다. |
truncatedGrid: aPoint | 인자 aPoint가 명시한 것과 가장 가깝게 자른(truncated) 그리드 모듈로 Point를 응답한다. |
Point 인스턴스 프로토콜 |
예를 들자면 아래와 같다.
표현식 | 결과 |
(45@230) dist: 175@270 | 136.015 |
(160@240) dotProduct: 50@50 | 20000 |
(160@240) grid: 50@50 | 150@250 |
(160@240) normal | -0.83105@0.5547 |
(160@240) truncatedGrid: 50@50 | 150@200 |
(175@300) transpose | 300@175 |
Points와 Rectangles는 그래픽한 조작을 지원하기 위해 함께 사용된다. Rectangle은 두 개의 Points를 포함하는데, 하나는 상단 좌측 모서리를 명시하는 origin이고, 나머지는 설명된 영역의 하단 우측 모서리를 나타내는 corner다. Rectangle 클래스는 관련된 모든 좌표로 접근을 위한 프로토콜과, 다른 rectangles와의 교차점 을 비롯해 다른 연산을 제공한다. Point로 전송되는 메시지는 Point를 원점으로 한 Rectangle을 생성하는 방법을 제공한다.
변환하기(converting) | |
corner: aPoint | 원점이 수신자이고 모서리가 인자 aPoint인 Rectangle을 응답한다. |
extent: aPoint | 원점이 수신자이고 범위가 인자 aPoint인 Rectangle을 응답한다. |
Point 인스턴스 프로토콜 |
따라서 (45@200) corner: (175@270) 는 디스플레이 좌표의 이미지에서 앞서 소개한 직사각형 면적을 나타낸다.
Rectangle 클래스
Rectangle의 인스턴스는 직사각형 픽셀 영역을 나타낸다. 산술 연산은 포인트를 인자로 취하고, 크기 조정 및 이동 연산을 실행하여 새로운 Rectangles를 생성한다. Rectangle 함수는 Rectangles로 Rectangles의 교차점을 결정하여 새로운 Rectangles를 생성한다.
Rectangles를 생성할 수 있는 Point로 전송되는 메시지 외에도 Rectangle에 대한 클래스 프로토콜은 인스턴스를 생성하는 메시지 세 가지를 지원한다. 이러한 메시지는 직사각형 면적의 경계, 원점, 모서리 좌표를 명시하거나, 면적의 원점과 너비 및 높이를 명시한다.
인스턴스 생성(instance creation) | |
left: leftNumber right: rightNumber top: topNumber bottom: bottomNumber | 좌측, 우측, 상단, 하단 좌표가 인자에 의해 결정되는 Rectangle을 응답한다. |
origin: originPoint corner: cornerPoint | 상단 좌측과 하단 우측 모서리가 인자 originPoint와 cornerPoint에 의해 결정되는 Rectangle을 응답한다. |
origin: originPoint extent: extentPoint | 상단 좌측 모서리가 originPoint이고 너비에 높이를 곱한 값이 extentPoint인 Rectangle을 응답한다. |
Rectangle 클래스 프로토콜 |
Rectangle에 대한 접근 프로토콜은 꽤 광범위하다. 이는 Rectangle의 경계에 8개의 중요한 위치를 참조하는 상세한 방법을 지원한다. 이러한 점은 그림 18.10에 싣겠다.
이러한 위치로 접근하기 위한 메시지들은 그림에 표시된 이름을 가진 선택자를 갖는다.
접근하기(accessing) | |
topLeft | 수신자의 상단 좌측 모서리에 있는 Point를 응답한다. |
topCenter | 수신자의 상단 수평선 중앙에 있는 Point를 응답한다. |
topRight | 수신자의 상단 우측 모서리에 있는 Point를 응답한다. |
rightCenter | 수신자의 우측 수직선 중앙에 있는 Point를 응답한다. |
bottomRight | 수신자의 하단 우측 모서리에 있는 Point를 응답한다. |
bottomCenter | 수신자의 하단 수평선 중앙에 있는 Point를 응답한다. |
bottomLeft | 수신자의 하단 좌측 모서리에 있는 Point를 응답한다. |
leftCenter | 수신자의 좌측 수직선 중앙에 있는 Point를 응답한다. |
center | 수신자의 중앙에 있는 Point를 응답한다. |
area | 너비에 높이를 곱한 값인 수신자의 면적을 응답한다. |
width | 수신자의 너비를 응답한다. |
height | 수신자의 높이를 응답한다. |
extent | Point 수신자의 너비 @ 수신자의 높이를 응답한다. |
top | 수신자의 상단 수평선 위치를 응답한다. |
right | 수신자의 우측 수직선 위치를 응답한다. |
bottom | 수신자의 하단 수평선 위치를 응답한다. |
left | 수신자의 좌측 수직선 위치를 응답한다. |
origin | 수신자의 상단 좌측 모서리에 있는 Point를 응답한다. |
corner | 수신자의 하단 우측 모서리에 있는 Point를 응답한다. |
Rectangle 인스턴스 프로토콜 |
frame으로 참조되는 Rectangle은 아래와 같은 표현식으로 생성된다고 가정하자.
frame ← Rectangle origin: 100@100 extent: 150@150
아래같은 예를 들 수 있다.
표현식 | 결과 |
frame topLeft | 100@100 |
frame top | 100 |
frame rightCenter | 250@175 |
frame bottom | 250 |
frame center | 175@175 |
frame extent | 150@150 |
frame area | 22500 |
Rectangle의 위치 각각은 그림 18.10에 명명된 위치 중 하나를 키워드로 가진 접근 메시지를 이용해 수정이 가능하다. 또한 너비와 높이는 각각 width: 과 height: 메시지를 이용해 설정할 수 있다. 두 메시지는 아래 열거되어 있는데, Rectangle의 변수를 리셋하기 위해 시스템 프로그래밍 인터페이스의 구현에서 흔히 사용된다.
접근하기(accessing) | |
origin: originPoint corner: cornerPoint | 수신자의 상단 좌측 모서리와 하단 우측 모서리에 있는 점을 설정한다. |
origin: originPoint extent: extentPoint | 수신자의 상단 좌측 모서리에 있는 점을 originPoint로 설정하고 수신자의 너비와 높이는 extentPoint로 설정한다. |
Rectangle 인스턴스 프로토콜 |
Rectangle 함수는 새로운 Rectangles를 생성하고, 두 Rectangles 간 관계를 계산한다.
rectangle 함수(rectangle functions) | |
amountToTranslateWithin: a Rectangle | delta가 수신자를 이동 시 수신자를 인자 aRectangle 이내에 강제로 위치시키는 Point, delta를 응답한다. |
areasOutside: aRectangle | 인자 aRectangle 이내에 위치하지 않는 수신자의 부분들로 구성된 Rectangles의 컬렉션을 응답한다. |
expandBy: delta | delta가 Rectangle, Point 또는 스칼라에 해당하고, 수신자로부터 delta만큼 떨어진 outset인 Rectangle을 응답한다. |
insetBy: delta | detla가 Rectangle, Point 또는 스칼라에 해당하고, 수신자로부터 delta만큼 떨어진 inset인 Rectangle을 응답한다. |
insetOriginBy: originDeltaPoint cornerBy: cornerDeltaPoint | 원점에서 originDeltaPoint만큼, 모서리에서 cornerDeltaPoint만큼 수신자로부터 떨어진 inset인 Rectangle을 응답한다. |
intersect: aRectangle | 수신자가 인자 aRectangle과 겹치는 면적인 Rectangle을 응답한다. |
merge: aRectangle | 수신자와 인자 aRectangle을 모두 포함하는 가장 작은 Rectangle을 응답한다. |
Rectangle 인스턴스 프로토콜 |
그림 18.11은 아래와 같이 생성된 세 개의 Rectangles, A, B, C를 표시한다.
A ← 50@50 corner: 200@200.
B ← 120@120 corner: 260@240.
C ← 100@300 corner: 300@400
그리고 세 개의 Rectangles를 이용한 표현식은 아래 열거되어 있다. Rectangles는 originPoint corner: cornerPoint의 형태로 출력됨을 주목한다.
표현식 | 결과 |
A amountToTranslateWithin: C | 50@250 |
A areasOutside: B | OrderedCollection((50@50 corner: 200@120) (50@120 corner: 120@200)) |
C expandBy: 10 | 90@290 corner: 310@410 |
C insetBy: 10@20 | 110@320 corner: 290@380 |
A intersect: B | 120@120 corner:200@200 |
B merge: C | 100@120 corner: 300@400 |
Rectangles를 검사하는 프로토콜은 Point 또는 다른 Rectangle가 Rectangle의 경계 내에 포함되는지, 혹은 두 개의 Rectangles가 교차하는지 결정하는 메시지를 포함한다.
검사하기(testing) | |
contains: aRectangle | 인자 aRectangle이 포함하는 Points를 모두 수신자가 포함하는지 여부를 응답한다. |
containsPoint: aPoint | 인자 aPoint가 수신자 내에 있는지 여부를 응답한다. |
intersects: aRectangle | 인자 aRectangle이 포함하는 Point를 수신자가 포함하는지 여부를 응답한다. |
Rectangle 인스턴스 프로토콜 |
위의 예를 이어가자면 다음과 같다.
표현식 | 결과 |
A contains: B | false |
C containsPoint: 200@320 | true |
A intersects: B | true |
Point에 대한 메시지와 마찬가지로 Rectangle의 좌표는 가장 가까운 정수로 반올림이 가능하다. Rectangle은 특정 양만큼 이동이 가능하고, 특정 위치로 이동이 가능하며, 좌표는 특정 양만큼 크기를 조정하거나 옮길 수 있다. Rectangles는 크기조정(scaling) 및 위치조정(translating) 메시지에도 응답하는데, 이는 스스로를 디스플레이 매체에 표시할 수 있는 모든 객체에 대한 프로토콜의 일부이다.
버림과 반올림(truncation and round off) | |
rounded | 원점과 모서리 좌표가 가장 가까운 정수로 반올림되는 Rectangle을 응답한다. |
변환하기(transforming) | |
moveBy: aPoint | 수신자의 모서리 위치를 변경하여 그 면적이 인자 aPoint가 정의한 양만큼 이동하도록 한다. |
moveTo: aPoint | 수신자의 모서리를 변경하여 그 상단 좌측 위치가 인자 aPoint가 되도록 한다. |
scaleBy: scale | 인자 scale만큼 위치가 조정되고 scale이 Point 또는 스칼라에 해당하는 Rectangle을 응답한다. |
translateBy: factor | factor가 Point 또는 스칼라에 해당하고 인자 factor만큼 위치가 이동된 Rectangle을 응답한다. |
Rectangle 인스턴스 프로토콜 |
아래와 같은 예를 들 수 있겠다.
expression | result |
A moveBy: 50@50 | 100@100 corner: 250@250 |
A moveTo: 200@300 | 200@300 corner: 350@450 |
A scaleBy: 2 | 400@600 corner: 700@900 |
A translateBy: -100 | 100@200 corner: 250@350 |
BitBIt 클래스
BitBIt에 대한 가장 기본적인 인터페이스는 동일한 이름으로 된 클래스를 통한다. BitBIt의 각 인스턴스는 BitBIt 연산을 명시하는 데 필요한 변수를 포함한다. BitBIt의 구체적인 적용은 다음을 포함한 매개변수의 목록으로 조정된다:
destForm | 픽셀을 저장하게 될 Form(목적지 Form) |
sourceForm | 픽셀을 복사하게 될 소스 Form |
halftoneForm | 공간 halftone pattern을 포함하는 Form |
combinationRule | sourceForm과 destForm에 상응하는 픽셀을 결합하기 위한 규칙을 명시하는 Integer |
destX, destY, width, height | (목적지 면적 x, y, 너비, 높이) 목적지에서 채워질 직사각형 하위영역을 명시하는 Integers |
clipX, clipY,
|
(클리핑 직사각형 면적 x, y, 너비, 높이) 영향을 받는 목적지 영역을 제한하는 직사각형의 면적을 명시한 Integers |
sourceX, sourceY | 소스로부터 복사할 하위영역의 (상단 좌측 모서리) 위치를 명시하는 Integers |
BitBlt 클래스 프로토콜은 인스턴스를 생성하기 위한 하나의 메시지로 구성되는데, 이 메시지는 각 BitBIt 변수에 대한 인자와 키워드를 포함한다. BitBIt 인스턴스 프로토콜은 변수의 초기화를 위한 메시지와 copyBits라는 메시지를 포함하는데, 후자는 프리미티브 연산의 발생을 야기한다. 또 두 개의 Points가 정의하는 직선을 그리는 drawFrom: startPoint to: stopPoint 메시지를 포함한다.
BitBlt class protocol
instance creationg
destForm: destination
sourceForm: source
halftoneForm: halftone
combinationRule: rule
destOrigin: destOrigin
sourceOrigin: sourceOrigin
extent: extent
clipRect: clipRect
|
각 인자에 따라 값이 설정된 BitBIt을 응답하되, rule은 Integer이고, destination, source, halftone은 Forms이며, destOrigin, sourceOrigin, extent는 Points이고, clipRect는 Rectangle이다. |
접근하기(accessing) | |
sourceForm: aForm | 수신자의 소스 form 이 인자 aForm이 되도록 설정한다. |
destForm: aForm | 수신자의 목적지 form 이 인자 aForm이 되도록 설정한다. |
mask: aForm | 수신자의 halftone mask form 이 인자 aForm이 되도록 설정한다. |
combinationRule: anInteger | 수신자의 조합 규칙이 0과 15 사이 정수인 인자 anInteger가 되도록 설정한다. |
clipHeight: anInteger | 수신자의 클리핑 면적 높이가 인자 anInteger가 되도록 설정한다. |
clipWidth: anInteger | 수신자의 클리핑 면적 너비가 인자 anInteger가 되도록 설정한다. |
clipRect | 수신자의 클리핑 직사각형을 응답한다. |
clipRect: aRectangle | 수신자의 클리핑 직사각형이 인자 aRectangle이 되도록 설정한다. |
clipX: anInteger | 수신자의 클리핑 직사각형 상단 좌측 x 좌표가 인자 anInteger가 되도록 설정한다. |
clipY: anInteger | 수신자의 클리핑 직사각형 상단 좌측 y 좌표가 인자 anInteger가 되도록 설정한다. |
sourceRect: aRectangle | 수신자의 소스 form 직사각형 면적이 인자 aRectangle이 되도록 설정한다. |
sourceOrigin: aPoint | 수신자의 소스 form 상단 좌측 좌표가 인자 aPoint가 되도록 설정한다. |
sourceX: anInteger | 수신자의 소스 form 상단 좌측 x 좌표가 인자 anInteger가 되도록 설정한다. |
sourceY: anInteger | 수신자의 소스 form 상단 좌측 y 좌표가 인자 anInteger가 되도록 설정한다. |
destRect: aRectangle | 수신자의 목적지 form 직사각형 면적이 인자 aRectangle이 되도록 설정한다. |
destOrigin: aPoint | 수신자의 목적지 form 상단 좌측 좌표가 인자 aPoint가 되도록 설정한다. |
destX: anInteger | 수신자의 목적지 form 상단 좌측 x 좌표가 인자 anInteger가 되도록 설정한다. |
destY: anInteger | 수신자의 목적지 form 상단 좌측 y 좌표가 인자 anInteger가 되도록 설정한다. |
height: anInteger | 수신자의 목적지 form 높이가 인자 anInteger가 되도록 설정한다. |
width: anInteger | 수신자의 목적지 form 너비가 인자 anInteger가 되도록 설정한다. |
복사하기(copying) | |
copyBits | 소스 폼에서 목적지 폼으로 비트의 이동을 실행한다. 어떤 변수든 올바른 타입이 (Integer 또는 Form) 아닌 경우, 또는 조합 규칙이 0과 15까지 범위에 있지 않은 경우 오류를 보고한다. 변수를 리셋하고 다시 시도한다. |
Bitblt 인스턴스 프로토콜 |
BitBIt의 인스턴스에 보관된 상태는 초기화를 모두 반복할 필요 없이 관련된 컨텍스트에 있는 다수의 연산을 실행할 수 있도록 해준다. 가령, 디스플레이 창에 어떤 장면을 표시하면서 목적지 폼과 클리핑 직사각형은 한 연산에서 다음 연산으로 변경하지 않을 것이다. 따라서 각 변수를 수정하기 위한 인스턴스 프로토콜을 이용해 효율성을 얻을 수 있다.
직선 그리기(Line Drawing)
Smalltalk-80 시스템 내 그래픽 대다수는 직선과 텍스트로 구성되어 있다. 이러한 개체들은 BitBIt의 반복 호출로 통합된다.
BitBlt 프로토콜은 끝점이 startPoint와 stopPoint 인자에 해당하는 직선을 그리기 위해 drawFrom: startPoint to: stopPoint 메시지들을 포함한다.
직선 그리기(line drawing) | |
drawFrom: startPoint to: stopPoint | 끝점이 인자 startPoint와 stopPoint인 직선을 그린다. 직선은 수신자의 하프톤 마스크와 조합 규칙에 따라 현재 소스 폼의 복사본을 표시하여 형성된다. |
Bitblt 인스턴스 프로토콜 |
BitBIt을 사용하면 하나의 알고리즘만으로 다양한 너비, 여러 하프톤, 어떤 조합 규칙의 직선이든 그릴 수 있다. 직선을 그리기 위해서는 적절한 목적지 Form과 클리핑 직사각형, 그리고 직선을 따라 펜 또는 “브러시” 모양으로 적용될 수 있는 Form으로 된 소스를 이용해 BitBIt의 인스턴스가 초기화된다. 이제 Points를 두 개의 인자로 가진 drawFrom:to: 메시지가 인스턴스로 전송된다. 그림 18.12는 BitBIt의 조합 규칙이 6 또는 7일 경우 그것이 형성하는 직선과 수많은 펜 모양을 보여준다.
drawFrom: startPoint to: stopPoint 메시지는 destX와 destY 값을 저장한다. 그 다음으로 표시된 직선 그리기 루프 drawLoopX: xDelta Y: yDelta는 이렇게 저장된 값에서 시작해 x와 y delta 값을 수용하고, x와 y step 값을 계산하여 직선을 따라가는 점을 결정한 후 copyBits를 호출하여 각 점에 적절한 이미지를 표시한다. 여기서 사용된 메서드는 Bresenham plotting 알고리즘(IBM Systems Journal, 4권, 1번, 1965)이다. 이는 주곡률 방향을 선택하고 변수 p를 유지한다. p의 부호가 변경되면 작은(minor) 방향으로도 움직일 때가 된 것이다. 이 방법은 프리미티브 메서드로서 구현되어야 할 자연 단위인데, 계산이 매우 간단한데다가 copyBits에서 셋업은 호출마다 거의 변화가 없기 때문이다.
BitBIt 클래스에서 drawLoopX: xDelta Y: yDelta에 대한 메서드는 다음과 같다.
drawLoopX: xDelta Y: yDelta
| dx dy px py p |
dx ← xDelta sign.
dy ← yDelta sign.
px ← yDelta abs.
py ← xDelta abs.
self copyBits. "first point"
py > px
ifTure: "more horizontal"
[p ← py // 2.
1 to: py do:
[ :i | destx ← destx + dx.
(p ← p - px) < 0
ifTrue: [desty ← desty + dy. p ← p + py].
self copyBits]]
ifFalse: "more vertical"
[p ← px//2.
1 to: px do:
[ :i | desty ← desty + dy.
(p ← p - py) < 0
ifTrue: [destx ← destx + dx. p ← p + px].
self copyBits]]
텍스트 표시(Text Display)
BitBIt에서 발생하는 장점 중 하나는 폰트를 축소하여 저장하여 다양한 조합 규칙을 이용해 폰트를 표시하는 능력이다. 소형 저장공간(compact storage)은 문자를 나란히 가로로 패킹할 수 있는 가능성에서 비롯되는데 (앞서 그림 18.3에서 보인 바와 같이), BitBIt은 모든 문자의 좌측 x 좌표로 된 표와 함께 제공될 때 관련된 비트를 추출할 수 있기 때문이다. 이것을 출판 용어로 strike 포맷이라 부르는데, 하나의 폰트로 된 모든 문자를 근접하게 표시함을 의미한다.
Smalltalk-80 시스템에서 텍스트의 스캐닝과 표시는 CharacterScanner라고 불리는 BitBIt의 서브클래스에 의해 실행된다. 이 서브클래스는 일반 상태를 모두 상속하며, destForm은 텍스트가 표시될 Form을 나타내고 sourceForm은 모든 문자 글리프를 나란히 포함시킨 Form을 의미한다(그림 18.3에서와 같이). 또한 CharacterScanner는 다음을 포함한 상태를 정의한다:
text | 표시할 Characters의 String |
textPos | 텍스트로 현재 위치를 제공하는 Integer |
xTable | sourceForm에서 각 문자의 좌측 x 위치를 제공하는 Integers로 이루어진 Array |
stopX | 내부 루프가 스캐닝을 멈춰야 하는 우측 경계를 설정하는 Integer |
exceptions | Symbols로 이루어진 Array로, nil이 아닌 경우 해당하는 문자를 특별히 처리하기 위한 메시지를 나타낸다 |
printing | 문자를 출력해야 하는지 여부를 나타내는 Boolean |
주어진 폰트와 텍스트 위치로 인스턴스를 초기화하고 나면 아래의 scanWord: 루프가 어떤 수평 위치(stopX)를 통과하거나 특수 문자가 (exceptions로부터 결정) 발견되거나 텍스트 범위의 끝에 (endRun) 도달할 때까지 텍스트를 스캐닝 또는 출력할 것이다. 이러한 조건은 각각 stopXCode, exceptions (Symbols의 Array), endRunCode라는 상징적 코드로 표기된다.
scanword: endRun
| charIndex |
[textPos < endRun] whileTrue:
["pick character" charIndex ← text at: textPos.
"check exceptions"
(exceptions at: charIndex) > 0
ifTrue: [↑exceptions at: charIndex].
"left x of character in font" sourceX ← xTable at: charIndex.
"up to left of next char"
width ← (xTable at: charIndex + 1) - sourceX.
"print the character" printing ifTrue: [self copyBits].
"advance by width of character" destX ← destX + width.
destX > stopX ifTrue: [↑stopXCode]. "passed right boundary"
"advance to next character"
textPos ← textPos + 1].
textPos ← textPos - 1.
↑endRunCode
exceptions에 대한 검사는 한 가지 연산에서 많은 가능성을 처리한다. 공백 문자는 flush right(오른끝 맞추기) 여백을 얻기 위해 패딩되는 텍스트의 경우 예외적으로 처리되어야 할 것이다. 탭(tabs)은 주로 너비를 결정하기 위해 계산 또는 테이블 검색(table lookup)을 필요로 한다. 캐리지 리턴(carriage return) 또한 exceptions 검사에서 식별된다. 폰트에서 주어진 범위 외의 문자 코드는 이와 유사하게 감지되고, 주로 눈에 띄어 수정되도록 작은 번개모양과 같은 예외 문자를 표시함으로써 처리된다.
printing 플래그는 false로 설정하여 동일한 코드가 직선을 측정하거나 (워드 경계에서 자르거나) 커서가 가리키는 문자를 찾을 수 있도록 해준다. 이러한 규칙은 과도하게 일반적인 것으로 보이지만 이러한 일반성으로 인한 장점이 두 가지가 있다 (소형이라는 점 이외에도). 첫째, 기본적인 스캐닝 알고리즘으로 변경할 경우 측정, 출력, 커서 추적의 유사한 기능들이 처음부터 동기화된다. 둘째, 루프에 프리미티브 구현이 제공될 경우 시스템 성능에 3배의 영향을 미친다.
scanword: 루프는 그러한 프리미티브 구현에 수정 가능하게 설계되는데, 그에 따라 인터프리터는 그것을 인터럽트하여 표시된 Smalltalk-80 코드 대신 프리미티브 코드를 실행할 수 있다. 이렇게 각 문자와 전체 단어에서 copyBits에 대한 셋업 오버헤드 대다수를 피하거나 더 많은 것을 직접 표시할 수도 있다. 반대로 Smalltalk 텍스트와 그래픽 시스템은 전체 기능을 제공하기 위해 하나의 프리미티브 연산의 (BitBIt) 구현만 필요로 한다.
BitBIt 시뮬레이션
여기서는 BitBItSimulation이라 불리는 BitBIt의 서브클래스에 copyBits의 구현에 대한 시뮬레이션을 제공하겠다. 해당 시뮬레이션에 포함된 메서드는 구현자에 대한 지침 역할을 하도록 고의적으로 머신 코드 스타일로 작성된 것이다. 16 비트 워드 크기는 굳이 숨기지 않았다. copyBits 메서드는 BitBItSimulation에서 Smalltalk-80 메서드로 표현되긴 하지만 사실 머신 코드에서 BitBIt 클래스 내 프리미티브 메서드로 구현되며, 시뮬레이션은 약간 더 느리긴 하지만 똑같은 일을 수행한다.
클래스명 | BitbltSimulation |
슈퍼클래스 | Bitblt |
인스턴스 변수명 | sourceBits sourceRaster destBits destRaster halftoneBits skew skewMask mask1 mask2 preload nWords hDir vDir sourceIndex sourceDelta destIndex destDelta sx sy dx dy w h |
클래스 변수명 | AllOnes RightMasks |
클래스 메서드 | initialize
"Initialize a table of bit masks"
RightMasks ←
#(0 16r1 16r3 16r7 16rF
16r1F 16r3F 16r7F 16rFF
16r1FF 16r3FF 16r7FF 16rFFF
16r1FFF 16r3FFF 16r7FFF 16rFFFF).
AllOnes ← 16rFFFF
|
인스턴스 메서드 | operations
copyBits
"sets w and h"
self clipRange.
(w < = or: [h < = 0]) ifTrue: [↑self]. "null range"
self computeMasks.
self checkOverlap.
self calculateOffsets.
self copyLoop
private
clipRange
"clip and adjust source origin and extent appropriately"
"first in x"
destX > = clipX
ifTrue: [sx ← sourceX. dx ← destX. w ← width]
ifFalse: [sx ← sourceX + (clipX - destX).
w ← width - (clipX - destX).
dx ← clipX].
(dx + w) > (clipX + clipWidth)
ifTrue: [w ← w - ((dx + w) - (clipX + clipWidth))].
"then in y"
destY > = clipY
ifTrue: [sy ← sourceY. dy ← destY. h ← height]
ifFalse: [sy ← sourceY + clipY - destY.
h ← height - clipY + destY.
dy ← clipY].
(dy + h) > (clipY + clipHeight)
ifTrue: [h ← h - ((dy + h) - (clipY + clipHeight))].
sx < 0
ifTrue: [dx ← dx - sx. w ← w + sx. sx ← 0].
sx + w > sourceForm width
ifTrue: [w ← w - (sx + w - sourceForm width)].
sy < 0
ifTrue: [dy ← dy - sy. h ← h + sy. sy ← 0].
sy + h > sourceForm height
ifTrue: [h ← h - (sy + h - sourceForm height)]
|
클리핑은 먼저 목적지 x가 클리핑 직사각형의 좌측에 위치하는지 확인하고, 위치할 경우 목적지 x와 너비를 모두 조정한다. 앞서 언급했듯이 이렇게 조정된 직사각형에 복사될 자료는 소스의 전환된 영역에서 오기 때문에 소스 x 또한 조정되어야 한다. 다음으로, 가장 오른쪽의 목적지 x가 클리핑 직사각형과 비교되고 필요 시 너비가 다시 감소된다. 전체 과정은 y와 높이에도 반복된다. 이후 높이와 너비는 소스 폼의 크기로 클리핑된다. 조정된 매개변수는 변수 sx, sy, dx, dy, w, h에 저장된다. width와 height 중 하나라도 0으로 줄어든 경우 BitBIt에 대한 모든 호출은 즉시 리턴할 것이다.
ComputeMasks
| startBits endBits |
"calculate skew and edge masks"
distBits ← destForm bits.
destRaster ← destForm width - 1 // 16 + 1.
sourceForm notNil
ifTrue: [sourceBits ← sourceForm bits.
sourceRaster ← sourceForm width - 1 // 16 + 1].
halftoneForm notNil
ifTrue: [halftoneBits ← halftoneForm bits].
skew ← (sx - dx) bitAnd: 15.
"how many bits source gets skewed to right"
startBits ← 16 - (dx bitAnd: 15).
"how many bits in first word"
mask1 ← RightMasks at: startBits + 1.
endBits ← 15 - ((dx + w - 1) bitAnd: 15).
"how many bits in last word"
mask2 ← (RightMasks at: endBits + 1) bitInvert.
skewMask ←
(skew = 0
ifTrue: [0]
ifFalse: [RightMasks at: 16 - skew + 1]).
"determine number of words stored per line; merge masks if necessary"
w < startBits
ifTrue: [mask1 ← mask1 bitAnd: mask2.
mask2 ← 0.
nWords ← 1]
ifFalse: [nWords ← (w - startBits - 1) // 16 + 2].
데이터의 실제 전송을 준비하면서 여러 매개변수가 준비된다. 첫 번째는 소스부터 목적지까지 데이터의 수평 오프셋인 skew이다. 이것은 목적지에서 최종 위치와 일직선이 되기 위해 소스로부터 로딩한 후 자료를 회전시킬 비트 수를 나타낸다. 그림 18.3 예제에서 skew는 5가 되는데, 문자 "e"에 대한 glyph는 목적지도 저장되기 전에 5 비트만큼 좌측으로 전환되어야 하기 때문이다. skew로부터 회전에 사용되도록 skewMask가 저장된다 (회전 워드 명령의 머신에서는 불필요하다). 이후 목적지에서 각 스캔 직선의 첫 부분 단어와 마지막 부분 단어의 비트를 선택하기 위해 mask1과 mask2가 계산된다. 이러한 masks는 그림 18.3 예제에서 각각 16r1FFF와 16rFFC0이 되는데, startBits=13이고 endBits=6이기 때문이다. 이와 같이 각 목적지 직선의 한 단어만 영향을 받는 경우 masks가 합쳐져 해당 단어 내에서 범위를 선택하는데, 여기서는 16rFC0이 되겠다.
checkOverlap
| t |
"check for possible overlap of source and destination"
hDir ← vDir ← 1. "defaults for no overlap"
(sourceForm = = destForm and: [dy > = sy])
ifTrue:
[dy > sy "have to start at bottom"
ifTrue: [vDir ← - 1. sy ← sy + h - 1. dy ← dy + h - 1]
ifFalse: [dx > sx "y's are equal, but x's are backward"
ifTrue: [hDir ← -1.
sx ← sx + w - 1.
"start at right"
dx ← dx + w - 1.
"and fix up masks"
skewMask ← skewMask bitInvert.
t ← mask1.
mask1 ← mask2.
mask2 ← t]]]
검사는 겹치는 소스와 목적지를 대상으로 실행되어야 한다. 소스와 목적지가 동일한 비트맵에 있을 경우 데이터를 이동하면서 복사 연산이 데이터를 제거할 가능성이 있다. 따라서 데이터를 아래로 이동하는 동안에는 복사가 바닥부터 시작해 위로 진행되어야 한다. 이와 비슷하게, 수직 이동이 없을 경우 수평 이동은 오른쪽으로 이루어져야 하고 복사는 오른쪽에서 시작해 왼쪽으로 진행되어야 한다. 이를 제외한 경우는 위에서 아래로, 왼쪽에서 오른쪽으로 복사를 진행할 수 있다.
calculateOffsets
"check if need to preload buffer
(i.e., two words of source needed for first word of destination)"
preload ← (sourceForm notNil) and:
[skew ,~= 0 and: [skew < = (sx bitAnd: 15)]].
hDir < 0 ifTrue: [preload ← preload == false].
" calculate starting offsets"
sourceIndex ← sy * sourceRaster + (sx// 16).
destIndex ← dy * destRaster + (dx// 16).
" calculate increments from end of 1 line to start of next"
sourceDelta ←
(sourceRaster * vDir) -
(nWords + (preload ifTrue: [1] ifFalse: [0]) * hDir).
destDelta ← (destRaster * vDir) - (nWords * hDir)
첫 번째 워드를 목적지로 저장하기 위해 소스의 두 워드가 필요한 경우, preload 플래그가 설정되면서 복사본의 내부 루프 전에 32-bit shifter를 프리로드(preload)할 필요가 있음을 나타낸다 (이는 최적화를 위한 것으로, 추가 워드를 단순히 처음부터 로딩하는 방법도 있다). 내부 루프에 필요한 로프셋은 소스 및 목적지 bases로부터 워드로 된 시작 오프셋이며, 하나의 스캔라인에 있는 데이터의 끝에서 시작해 다음 스캔라인에 있는 데이터의 시작까지 건너뛰기 위한 델타(deltas)도 계산된다.
inner loop
copyLoop
I prevWord thisWord skewWord mergeMask
halftoneWord mergeWord word |
1 to: h do: " here is the vertical loop:"
[:i |
(halftoneForm notNil)
ifTrue:
[halftoneWord ← halftoneBits at: (1 + (dy bitAnd: 15)).
dy ← dy + vDir]
ifFalse: [halftoneWord ← AllOnes].
skewWord ← halftoneWord.
preload
ifTrue: [prevWord ← sourceBits at: sourceIndex + 1.
"load the 32-bit shifter"
sourceIndex ← sourceIndex + hDir]
ifFalse: [prevWord ← 0].
mergeMask ← mask1.
1 to to: nWords do: "here is the inner horizontal loop"
[ :word |
sourceForm notNil " if source is used"
ifTrue:
[prevWord ← prevWord bitAnd: skewMask.
thisWord ← sourceBits at: sourceIndex + 1.
"pick up next word"
skewWord ←
prevWord bitOr: (this Word bitAnd: skewMask bitInvert).
prevWord ← thisWord.
skewWord ← (skewWord bitShift: skew) bitOr:
(skewWord bitShift: skew - 16)]. "
"16-bit rotate"
mergeWord ← self merge: (skewWord bitAnd: halftoneWord)
with: (destBits at: destIndex + 1).
destBits
at: destIndex + 1
put: ((mergeMask bitAnd: mergeWord)
bitOr: (mergeMask bitInvert
bitAnd: (destBits at: destIndex + 1))).
sourceIndex ← sourceIndex + hDir.
destIndex ← destIndex + hDir.
word = (nWords - 1)
ifTrue: [mergeMask ← mask2]
ifFalse: [mergeMask ← AllOnes]].
sourceIndex ← sourceIndex + sourceDelta.
destIndex ← destIndex + destDelta]
외부 또는 수직 루프는 각 직선에 대한 오버헤드를 포함하여 적절한 하프톤 회색 직선을 선택하고, 필요 시 shifter를 프리로드하며, 내부 루프 다음의 다음 스캔라인으로 소스 및 목적지 포인터를 움직인다. 하프톤 패턴을 목적지 y로 색인을 만드는 이유는 모든 연산에서 하프톤이 이러한 방식으로 조정되지 않을 경우 발생할 수 있는 "seams"를 제거하기 위함이라는 것을 주목해야 한다.
내부 또는 수평 루프는 새로운 소스 워드를 골라서 이전의 것과 함께 회전시키고, 그 결과를 목적지의 워드와 합한다. 목적지에 대한 저장은 각 스캔라인에서 첫 번째와 마지막 부분 워드에 대해 마스킹되어야 하지만 중간에서는 사실상 어떠한 마스킹도 필요하지 않다.
merge: sourceWord with: destinationWord
"These are the 16 combination rules:"
combinationRule = 0
ifTrue: [↑O].
combinationRule = 1
ifTrue: [↑sourceWord bitAnd: destinationWord].
combinationRule = 2
ifTrue: [↑sourceWold bitAnd: destinationWord bitInvert].
combinationRule= 3
ifTrue: [↑sourceWord].
combinationRule= 4
ifTrue: [↑sourceWord bitInvert bitAnd: destinationWord].
combinationRule = 5
ifTrue: [↑destinationWord].
combinationRule =6
ifTrue:[↑sourceWord bitXor: destinationWord].
combinationRule= 7
ifTrue: [↑sourceWord bitOr: destinationWord].
combinationRule = 8
ifTrue: [↑sourceWord bitInvert bitAnd: destinationWord bitInvert].
combinationRule = 9
ifTrue: [↑sourceWord bitInvert bitXor: destinationWord].
combinationRule = 10
ifTrue: [↑destinationWord bitInvert].
combinationRule = 11
ifTrue: [↑sourceWord bitOr: destinationWord bitInvert].
combinationRule = 12
ifTrue: [↑sourceWord bitInvert].
combinationRule= 13
ifTrue: [↑sourceWord bitInvert bitOr: destinationWord].
combinationRule= 14
ifTrue: [↑sourceWord bitInvert bitOr: destinationWord bitInvert].
combinationRule = 15
ifTrue: [↑AllOnes]
효율성과 관련된 고찰사항(Efficiency Considerations)
경험에 따르면 BitBIt의 일반성은 그만한 가치가 있다. 이 하나의 프리미티브는 프로그래밍 인터페이스에 너무나 중요하여 그 성능이 어떻게 향상되든 시스템 전체의 상호작용 질에 엄청난 영향을 미친다. Smalltalk-80 시스템의 일반적인 사용에서 BitBIt의 호출 대부분은 극히 미시적이거나 거시적인 범위로 이루어진다.
거시적 범위에서 전송 너비는 많은 워드에 거쳐 이루어진다. 수평 스캔라인에 걸친 내부 루프는 여러 번 실행되고, 요청된 연산은 간단한 이동(move)이나 일정한 저장(store)으로 이루어지는 경향을 보인다. 이러한 예로 다음을 들 수 있다.
- clearing a line of text to white
- clearing an entire window to white
- scrolling a block of text up or down
대부분의 프로세서는 빠른 블록 이동 및 저장 수단을 제공하며, 이는 위의 역할을 하도록 만들 수 있다. BitBIt의 수평 루프를 다음과 같이 구성한다고 가정해보자.
- 좌측 부분 워드를 이동,
- 여러 개의 전체 워드를 이동 (또는 어떤 것도 이동시키지 않음),
- 우측 부분 워드를 이동 (또는 어떤 것도 이동시키지 않음).
2번에서 만약 연산이 소스로부터 목적지까지 어떠한 경사도 없는 (수평적 비트 오프셋) 단순한 복사 또는 단순한 저장일 경우라면 특별한 사례가 된다. 이를 통해 중간 정도의 power를 가진 프로세서에서도 BitBIt의 거시적 적용을 대부분 빠르게 만들 수 있다.
BitBIt의 거시적 범위에서는 내부 루프에 대한 0을 특징으로 들 수 있다. 각 스캔라인의 작업에는 최소 두 개의 부분 워드에서의 작업이 수반되고, 이러한 경우 전체적인 셋업과 수직 루프 오버헤드 모두 상당히 감소할 수 있다. 문자는 워드 너비보다 작은 경향이 있고 직선은 워드 두께보다 작은 경향이 있기 때문에 거의 모든 텍스트와 직선 그리기는 이 범주에 속한다. 그러한 효율성을 편리하게 제공하는 방법은 미시적 매개변수를 가정하되 이를 충족하지 않을 때마다 일반 BitBIt로 되돌아가는 BitBIt의 특수 사례를 작성하는 것이다. 이러한 통계로 인해 (많은 작은 연산과 소수의 큰 연산) 드물게 발생하는 호출에 대해 잘못된 가정을 내려서 대가를 치르는 것도 나쁘지 않다. 클리핑에서도 동일한 수법을 이용해, 어떠한 클리핑도 발생하지 않을 것이라 가정하고 이러한 가정이 실패할 때에만 일반 코드를 실행하는 방법도 있겠다.