Smalltalk80LanguageImplementationKor:Chapter 20
- 제 20 장 Display 객체
Display 객체
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***
DiDisplayMedium***
Form***
Cursor***
DisplayScreen***
InfiniteForm***
OpaqueForm***
Path***
Arc***
Circle***
Curve***
Line***
LinearFit***
Spline***
Smalltalk-80 시스템에서 그래픽은 BitBIt의 명세에서 시작된다. Points, Rectangles, Forms, Pens, Text의 지원을 받아 광범위한 형상을 생성할 수 있다. 그림 20.1의 이미지는 이러한 5가지 객체 유형의 사용을 확장시킬 때 가능한 그래픽 개체를 몇 가지 보여준다.
그림 20.2와 20.3에 실린 좀 더 미술적인 이미지는 Smalltalk-80 시스템에서 이용할 수 있는 추가 디스플레이 객체를 이용해 생성하였다. 이러한 이미지를 생성하는 데 사용된 메서드는 후에 설명하겠다. 이번 장에서는 이용 가능한 디스플레이 객체 유형과 이를 조작하는 다양한 방법을 설명하겠다.
DisplayObject 클래스
Form은 일종의 디스플레이 객체다. 시스템에는 다른 객체들도 존재한다. 이러한 객체들이 구현되는 방식은 DisplayObject라는 슈퍼클래스를 가진 클래스들의 계층구조가 된다. Form은 이러한 계층구조에서 서브클래스이다.
디스플레이 객체는 너비, 높이, 그리고 0@0으로 가정된 원점, 이 원점으로부터 이미지가 표시되어야 하는 오프셋을 가진 이미지를 표현한다. 모든 디스플레이 객체는 그들의 이미지를 다른 이미지로 복사하고 크기를 조정하며 이동시키는 능력에 있어 비슷하다고 할 수 있다. 단, 객체들의 이미지를 어떻게 생성하느냐에 따라 차이가 난다.
DisplayObject에는 세 개의 주요 서브클래스가 있다. 이는 DisplayMedium, DisplayText, Path이다.
- DisplayMedium은 "colored"(색칠)할 수 있고 (회색 톤으로 채울 수 있는) 테두리를 그릴 수 있는 (직사각형 윤곽을 색으로 칠할 수 있는) 이미지를 표현한다.
- DisplayText는 텍스트로 된 이미지를 표현한다.
- Path는 이미지의 컬렉션으로 구성된 이미지를 표현한다.
Form은 DisplayMedium의 서브클래스로, 이미지의 비트맵 표현을 추가한다. 모든 DisplayObjects는 이미지에 대한 소스 정보를 제공하고, Forms는 소스와 목적지에 대한 정보를 모두 제공한다.
DisplayObject 클래스는 이미지의 다양한 측면을 조작하기 위한 접근 메시지를 지원한다.
접근하기(accessing) | |
width | 수신자의 이미지의 테두리를 나타내는 직사각형인 바운딩 박스(bounding box)의 너비를 응답한다. |
height | 수신자의 바운딩 박스의 높이를 응답한다. |
extent | 수신자의 바운딩 박스의 너비와 높이를 나타내는 Point를 응답한다. |
offset | 수신자를 표시하거나 그 위치를 검사할 때 오프셋해야 하는 양을 나타내는 Point를 응답한다. |
offset: aPoint | 수신자의 오프셋을 설정한다. |
rounded | 수신자의 오프셋을 가장 가까운 정수양으로 설정한다. |
DisplayObject 인스턴스 프로토콜 |
DisplayObject는 이미지의 변형, 이미지의 표시, 이미지를 표시하기 위한 영역의 테두리를 나타내는 직사각형 영역인 디스플레이 상자의 계산을 지원하기 위해 세 가지 유형의 메시지를 제공하기도 한다.
변형하기(transforming) | |
scaleBy: aPoint | 수신자의 오프셋을 aPoint만큼 크기 변경한다. |
translateBy: aPoint | 수신자의 오프셋을 aPoint만큼 이동한다. |
align: alignmentPoint with: relativePoint | alignmentPoint가 relativePoint와 일렬이 되도록 수신자의 오프셋을 이동한다. |
디스플레이 상자 접근(display box access) | |
boundingBox | 수신자의 정보 공간 테두리를 나타내는 직사각형 영역을 응답한다. |
표시하기(displaying) | |
displayOn: aDiDisplayMedium
at: aDisplayPoint
clippingBox: clipRectangle
rule: ruleInteger
mask: aForm||
|
규칙 ruleInteger와 halftone mask인 aForm으로 aDisplayPoint 위치에서 수신자를 표시한다. 표시할 정보는 clipRectangle과 교차하는 영역으로 제한되어야 한다. |
DisplayObject 인스턴스 프로토콜 |
위에서 표시되지 않은 표시 메시지가 몇 가지 있다. 대안적 표시 메시지는 키워드를 계속해서 (마지막부터 시작하여) 누락시키고 기본 마스크, 규칙, 클리핑 직사각형, 그리고 필요 시 위치를 제공한다. 기본적으로 디스플레이 화면 자체가 기본 클리핑 직사각형이기 때문에 0@0은 기본 디스플레이 위치가 되고, 시스템 디스플레이 화면을 나타내는 객체 Display(전역 변수)가 기본 디스플레이 매체가 된다.
displayAt: aDisplayPoint 메시지는 보통 디폴트되지 않은 유일한 매개변수에 이미지가 위치되어야 할 때 유용한 메시지를 제공한다. display 메시지는 디스플레이 위치를 0@0으로 가정한다.
displayAt: aDisplayPoint | "over" 또는 "storing" 규칙; 하프톤 마스크, 검정색 Form; 디스플레이 화면의 클리핑 직사각형;을 규칙으로 하여 aDisplayPoint 위치의 수신자를 디스플레이 화면에 표시한다(Display). |
display | 0@0 위치에 수신자를 표시한다. |
DisplayObject 인스턴스 프로토콜 |
마지막 두 개의 표시 메시지는 String이나 Text와 같은 텍스트로 된 객체에도 제공되니 프로그래머는 아래와 같은 표현식을 평가하여 화면에 문자를 위치시킬 수도 있다.
'This is text to be displayed' displayAt: 100@100
locomotive가 아래와 같은 모양의 Form이라고 가정하면,
아래와 같은 표현식을 평가하여 50@150 위치에 상단 좌측 모서리를 두고 화면에 표시할 수 있겠다.
locomotive displayAt: 50@150
DisplayMedium 클래스
DisplayMedium은 이미지를 복사할 수 있는 객체를 나타내는 DisplayObject의 서브클래스다. DisplayMedium은 그 슈퍼클래스로부터 상속받은 메시지 외에도 이미지의 내부에 색을 칠하고 이미지 디스플레이 상자 주위에 테두리를 위치시키기 위한 프로토콜을 제공한다. "Colors"(색상)는 시스템에서 이미 이용할 수 있는 Forms이다. 이러한 색상으로는 black(비트맵이 모두 1), 흰색(모두 0), gray, veryLightGray, lightGray, darkGray와 같은 다양한 회색 톤이 (0과 1이 섞임) 있다. 이러한 색상의 이미지는 아래에 제공되어 있다. 아래의 메시지를 이용하면 DisplayMedium의 모든 영역 또는 일부 영역을 이러한 색상 중 하나로 변경할 수 있다.
색상관련(coloring) | |
black | 수신자의 영역을 모두 검정색으로 변경한다. |
black: aRectangle | 인자 aRectangle이 정의한 수신자의 영역을 검정색으로 변경한다. |
white | 수신자의 영역을 모두 흰색으로 변경한다. |
white: aRectangle | 인자 aRectangle이 정의한 수신자의 영역을 흰색으로 변경한다. |
gray | 수신자의 영역을 모두 회색으로 변경한다. |
gray: aRectangle | 인자 aRectangle이 정의한 수신자의 영역을 회색으로 변경한다. |
veryLightGray | 수신자의 영역을 모두 매우 옅은 회색으로 변경한다. |
veryLightGray: aRectangle | 인자 aRectangle이 정의한 수신자의 영역을 매우 옅은 회색으로 변경한다. |
lightGray | 수신자의 영역을 모두 옅은 회색으로 변경한다. |
lightGray: aRectangle | 인자 aRectangle이 정의한 수신자의 영역을 옅은 회색으로 변경한다. |
darkGray | 수신자의 영역을 모두 짙은 회색으로 변경한다. |
darkGray: aRectangle | 인자 aRectangle이 정의한 수신자의 영역을 짙은 회색으로 변경한다. |
DisplayMedium 인스턴스 프로토콜 |
위의 메시지에서 인자 aRectangle의 원점은 수신자의 좌표체계에 있다.
picture이 너비가 100 픽셀이고 높이가 100 픽셀인 DisplayMedium 유형이고, box가 30@30에 원점을 두고 너비와 높이가 40인 Rectangle의 인스턴스라고 가정하자. 그렇다면 box가 표현하는 picture의 하위영역을 채우는 프로토콜은 아래와 같은 시퀀스로 나타낼 수 있다.
표현식 | 결과 |
picture black: box | |
picture white: box | |
picture gray: box | |
picture lightGray: box | |
picture veryLightGray:box | |
picture darkGray: box |
특정 하위영역을 halftone 패턴으로 채우기 위한 메시지를 DisplayMedium으로 전송함으로써 이미지 일부를 패턴으로 채울 수 있다. 그 외 색칠 메시지도 그 구현에서 이러한 채우기 메시지를 이용한다.
fill: aRectangle mask: aHalftoneForm | 인자 aRectangle이 정의한 수신자의 영역을 16x16-bit 패턴 aHalftoneForm으로 채워 흰색으로 변경한다. Mask를 수신자로 복사하기 위한 조합 규칙은 3이다 (Form over). |
fill: aRectangle rule: anInteger mask: aHalftoneForm | 인자 aRectangle이 정의한 수신자의 영역을 16x16-bit 패턴 aHalftoneForm으로 채워 흰색으로 변경한다. Mask를 수신자로 복사하기 위한 조합 규칙은 anInteger이다. |
DisplayMedium 인스턴스 프로토콜 |
가령, 아래의 표현식을 평가 할 때
box ← 16@16 extent: 64@64.
picture fill: box mask: locomotive
locomotive가 16x16-bit Form인 경우, 결과는 다음과 같다.
다음 순서로 두 표현식을 평가한 결과는
picture lightGray: box.
picture fill: box rule: Form under mask: locomotive
다음과 같다.
위에서 Form under 규칙은 Integer 조합 규칙을 나타낸다는 사실을 주목한다. 조합 규칙과 halftone masks로 접근하기 위해 Form으로 전송되는 메시지는 제 18장에 정의한 바 있다.
이미지의 "reversing"(반전)은 영역 내 흰색 비트를 모두 검정색으로, 검정색 비트를 모두 흰색으로 변경함을 의미한다. 이미지의 반전은 일부 또는 전체에 적용할 수 있다.
reverse: aRectangle mask: aHalftoneForm | 수신자 내에 인자 aRectangle이 정의한 영역을 변경하여 aHalftoneForm mask가 검정색인 비트 중에서 수신자 내 흰색 비트는 검정색이, 검정색은 흰색이 되도록 한다. |
reverse: aRectangle | 수신자 내에 인자 aRectangle이 정의한 영역을 변경하여 흰색이 검정색이 되도록 하고 검정색이 흰색이 되도록 한다. 기본 마스크는 Form black이다. |
reverse | 수신자의 모든 영역을 변경하여 흰색은 검정색이, 검정색은 흰색이 되도록 한다. |
DisplayMedium 인스턴스 프로토콜 |
마지막 이미지에서 아래의 결과는
picture reverse: box
다음과 같다.
"Bordering"(테두리 칠하기)이란 직사각형 윤곽을 색칠하는 것을 의미한다. 테두리 칠하기는 소스 Form과 마스크를 이용해 이루어진다. 세 가지 메시지가 이미지의 테두리 칠을 위한 메서드를 제공한다.
bordering | |
border: aRectangle widthRectangle: insets mask: aHalftoneForm | 수신자 내에서 인자 aRectangle이 정의한 영역 주위에 윤곽을 칠한다. 색상은 aHalftoneForm 마스크가 결정한다. 윤곽의 너비는 Rectangle, insets가 결정하여 원점 x는 좌측면의 너비, 원점 y는 상단면의 너비, 모서리 x는 우측면의 너비, 모서리 y는 하단면의 너비가 된다. |
border: aRectangle width: borderWidth mask: aHalftoneForm | 수신자 내에서 인자 aRectangle이 정의한 영역 주위에 윤곽을 칠한다. 색상은 aHalftoneForm mask가 결정한다. 모든 면의 너비는 borderWidth이다. |
border: aRectangle width: borderWidth | 수신자 내에서 인자 aRectangle이 정의한 영역 주위에 윤곽을 칠한다. 색상은 Form black이다. 모든 면의 너비는 borderWidth이다. |
DisplayMedium 인스턴스 프로토콜 |
예를 들자면 다음과 같다.
다음 이미지 순서는 picture 내에서 어떤 영역을 변경해야 하는지 지정 시 사용된 직사각형 크기를 조작함으로써 테두리 칠하기를 실행하는 방법을 보여준다.
Forms
Form 클래스는 표준 Smalltalk-80 시스템에서 DisplayMedium의 유일한 서브클래스다. 앞서 18장에서 마스크와 조합 규칙(모드)를 표현하는 상수로 접근하게 해주는 메시지를 정의하면서 소개한 바 있다. 복잡한 이미지를 생성 시 Forms의 사용을 설명하는 다음 순서의 표현식은 본 장의 시작 부분에 그림 20.2에서 보인 바와 같은 이미지를 생성한다.
두 개의 Forms가 있고, 각 Form이 120 bits의 너비와 180 bits의 높이로 되어 있다고 가정하자. 이를 face25와 face75로 명명한다. 이러한 이미지들은 어떤 신사의 20대 사진과 75세 생일을 맞았을 때 사진을 디지털화하기 위해 스캐너를 이용해 만들어졌다.
스캔된 이미지는 적정한 크기로 조정된 후 다음과 같은 방식으로 하프톤 마스크와 결합되었다. 각각의 크기가 8인 두 개의 Arrays는 최종 이미지의 각 부분을 생성 시 사용된 Forms(forms)와 하프톤 마스크(masks)에 대한 참조를 포함한다.
masks ← Array new: 8.
masks at: 1 put: Form black.
masks at: 2 put: Form darkGray.
masks at: 3 put: Form gray.
masks at: 4 put: Form lightGray.
masks at: 5 put: Form veryLightGray.
masks at: 6 put: Form lightGray.
masks at: 7 put: Form gray.
masks at: 8 put: Form black.
forms ← Array new: 8.
forms at: 1 put: face25.
forms at: 2 put: face25.
forms at: 3 put: face25.
forms at: 4 put: face25.
forms at: 5 put: face75.
forms at: 6 put: face75.
forms at: 7 put: face75.
forms at: 8 put: face75
변수 i는 각 행의 첫 번째 하위 이미지를 형성하는 데 사용된 첫 번째 Form과 첫 번째 하프톤의 첫 색인이다. 전체 행이 표시될 때마다 i는 1씩 증가한다. 각 행은 5개의 요소로 구성된다. 변수 index는 5개의 하프톤과 5개의 Forms를 색인하는 데 사용되고, index는 각 행의 시작에서 i로 설정된다. 따라서 첫 행은 masks와 forms의 요소 1, 2, 3, 4, 5를 결합하여 구성되고, 두 번째 행은 masks와 forms의 요소 2, 3, 4, 5, 6을 결합하여 구성되는 식이다. 각 행의 y 좌표는 매번 180 픽셀씩 변경되고, 각 열의 x 좌표는 120 픽셀씩 변경된다.
i ← 1.
0 to: 540 by: 180 do:
[ :y | index ←.
0 to: 480 by: 120 do:
[ :x | (forms at: index)
displayOn: Display
at: x@y
clippingBox: Display boundingBox
rule: Form over
mask: (masks at: index).
index ← index + 1].
i ← i + 1]
그외의 폼
시스템에는 또 다른 두 가지 유형의 폼이 존재하는데, 바로 InfiniteForm과 OpaqueForm이다. 이 두 클래스는 DisplayMedium보다는 DisplayObject의 서브클래스들이다. 따라서 Form이 상속 받은 색칠하기와 테두리 칠하기 능력을 공유하지 않는다. InfiniteForm은 Form 패턴을 모든 방향에서 무기한으로 복제함으로써 얻은 Form을 나타낸다. 보통 Smalltalk-80 프로그래밍 인터페이스에 표시된 중복 뷰는 (17장에 표시) 옅은 회색 배경으로 위치하고, 이 배경은 중복 패턴이 Form gray인 InfiniteForm에 의해 정의된다. OpaqueForms는 그림 Form뿐만 아니라 도형도 나타낸다. 도형은 이미지를 표시하는 데에 어떤 배경 부분을 가려야 그림에서 검정색을 제외한 패턴이 불투명하게 나타날 것인지를 나타낸다. OpaqueForm의 인스턴스들은 애니메이션의 생성을 지원한다. InfiniteForm과 OpaqueForm 모두 새로운 프로토콜을 추가하지 않는다.
커서(Cursors)
Form에는 두 가지 흥미로운 서브클래스가 있는데, 클래스 Cursor와 클래스 DisplayScreen이 그것들이다. Smalltalk-80 시스템은 하드웨어 지시장치의 현재 위치와 시스템의 현재 상태를 표시하기 위해 Forms를 광범위하게 이용한다. 이렇게 사용되는 Form을 "cursor"(커서)라고 부르는데, 화면 좌표를 위치시키기 위해 화면에서 이리저리 움직이는 것이 주 사용 목적이라 그렇게 부른다.
Cursor 클래스의 인스턴스들은 너비와 높이가 16 픽셀인 Forms이다. Cursor 클래스는 DisplayObject로부터 상속받는 새로운 메시지 3개를 표시 프로토콜에 추가한다.
표시하기(displaying) | |
show | 수신자를 현재 커서 모양으로 만든다. |
showGridded: gridPoint | 수신자를 현재 커서 모양으로 만들어 커서의 위치를 gridPoint 위치에서 가장 가까운 위치로 강제 이동한다. |
showWhile: aBlock | 인자 aBlock을 평가하는 동안 수신자를 커서 모양으로 만든다. |
Cursor 인스턴스 프로토콜 |
표준 Smalltalk-80 시스템에는 여러 가지의 커서가 제공된다. 이러한 커서의 비트맵을 보여주도록 그림 20.4에 작은 커서와 확대된 커서를 모두 실었다. 이미지 아래에 표시된 각 커서의 이름은 특정 Cursor로 접근하는 Cursor 클래스로 전송되는 메시지의 이름과 동일하다. 가령, 다음 표현식은 화면에서 안경처럼 보이는 커서를 표시하는 동안 시스템은 50 계승을 계산한다. 이후 원래 커서 모양을 표시하도록 돌아간다.
Cursor read showWhile: [50 factorial]
커서 모양의 변경은 사용자와 매우 효과적인 의사소통 방식에 해당한다. 주의는 항상 커서로 집중되며 그 모양을 변경한다고 해서 디스플레이의 외형이 변경되지는 않는다.
디스플레이 화면(The Display Screen)
DisplayScreen는 Form의 또 다른 서브클래스다. 주로 시스템에서 DisplayScreen의 유일한 인스턴스에 해당한다. 이는 전체 디스플레이 화면을 처리하라는 일반 사용자 요청을 처리하는 데 사용되는 전역 변수인 Display라고 불린다. 그 슈퍼클래스, DisplayObject, DisplayMedium, Form에서 상속받는 메시지 외에도 DisplayScreen은 너비, 높이, 화면에 표시되는 이미지를 리셋하는 클래스 프로토콜을 제공한다.
DisplayScreen의 여러 인스턴스가 존재할 수 있는 한 가지 경우는 DisplayScreen의 어떤 인스턴스들이 디스플레이 하드웨어에 비트를 제공할 것인지 수정함으로써 (이중 버퍼) 전체 화면 애니메이션을 실행할 때다. 보통 전체 화면 애니메이션이 사용되지 않고 애니메이션이 작은 직사각형 영역 내에서 실행된다. 비트의 숨겨진 버퍼를 이용해 다음 이미지가 형성된다. 새로운 이미지는 각각 BitBIt의 copyBits: 메시지를 이용해 직사각형 영역으로 비트를 복사함으로써 표시된다.
DisplayText
DisplayObject의 두 번째 서브클래스는 DisplayText 클래스다. Text의 인스턴스는 폰트 색인(1부터 10까지)과 String의 인스턴스 각 문자에 대한 강조(이탤릭체, 볼드체, 밑줄)를 제공한다. DisplayText는 Text와 TextStyle로 구성된다. TextStyle은 각 폰트 색인을 실제 폰트(글리프 집합)와 연관시킨다. DisplayText는 폰트 집합으로 이러한 매핑을 나타내는 것을 비롯해 문자를 화면에 표시하는 기능을 지원한다. 단, 강조와 폰트의 선택 또는 문자를 편집하기 위한 사용자 인터페이스를 생성하는 데 필요한 프로토콜은 지원하지 않는데, 이러한 프로토콜은 DisplayText의 서브클래스에서 제공해야 한다.
Paths
DisplayObject의 세 번째 서브클래스는 Path 클래스다. Path는 각 Point에 표시되어야 하는 Form과 Points의 OrderedCollection이다. Points가 나타내는 궤도를 따라 Form을 복사하면 복잡한 이미지를 생성할 수 있다.
Path 클래스는 궤도를 나타내는 그래픽 디스플레이 객체의 기본 슈퍼클래스다. Path의 인스턴스들은 OrderedCollection과 Form을 참조한다. 컬렉션의 요소들은 Points에 해당한다. 이는 Path로 추가 가능하며(add:), 특정 기준에 따라 설명되는 Points들은 모두 Path로부터 제거 가능하고 (removeAllSuchThat:), Points는 열거, 수집, 선택할 수 있다 (do:, collect, select:).
접근하기(accessing) | |
form | 수신자가 참조하는 Form을 응답한다. |
form: aForm | 수신자가 참조하는 Form을 aForm으로 설정한다. |
at: index | 수신자의 컬렉션에서 index번째 요소인 Point를 응답한다. |
at: index put: aPoint | 인자 aPoint를 수신자의 컬렉션에서 index번째 요소가 되도록 설정한다. |
size | 수신자의 컬렉션에서 Points의 개수를 응답한다. |
검사하기(testing) | |
isEmpty | 수신자가 Points를 포함하는지 응답한다. |
추가하기(adding) | |
add: aPoint | 인자 aPoint를 수신자의 Points 컬렉션의 마지막 요소로 추가한다. |
제거하기(removing) | |
removeAllSuchThat: aBlock | 수신자 내에 Point마다 인자 aBlock을 평가한다. aBlock을 true로 평가하는 Points를 제거한다. |
열거하기(enumerating) | |
do: aBlock | 수신자 내에 Point마다 인자 aBlock을 평가한다. |
collect: aBlock | 수신자 내에 Point마다 인자 aBlock을 평가한다. 결과 값을 OrderedCollection에 수집하고 새로운 컬렉션을 응답한다. |
select: aBlock | 수신자 내에 Point마다 인자 aBlock을 평가한다. aBlock을 true로 평가하는 Points를 OrderedCollection에 수집한다. 새로운 컬렉션을 응답한다. |
Path 인스턴스 프로토콜 |
예를 들어, "star" Path를 생성하고, 그 Path 상에서 각 점마다 dot 라는 이름으로 불리는 점 모양의 Form을 표시한다.
aPath ← Path new form: dot.
aPath add: 150 @ 285.
aPath add: 400 @ 285.
aPath add: 185 @ 430.
aPath add: 280 @ 200.
aPath add: 375 @ 430.
aPath add: 150 @ 285.
aPath display
결과 이미지는 그림 20.5에서 첫 번째 경로로 표시되어 있다.
그림 20.5에는 세 개의 경로가 있다.
- 위에 나타난 바와 같이 생성된 Path의 인스턴스
- 동일한 Points의 컬렉션을 이용한 LinearFit의 인스턴스
- 동일한 Points의 컬렉션을 이용한 Spline의 인스턴스
LinearFit은 컬렉션 내에 Points를 순서대로 연결시켜 표시된다.
aPath ← LinearFit new form: dot.
aPath add: 150 @ 285.
aPath add: 400 @ 285.
aPath add: 185 @ 430.
aPath add: 280 @ 200.
aPath add: 375 @ 430.
aPath add: 150 @ 285.
aPath display
Spline은 Points를 통해 cubic spline 곡선을 다시 순서대로 맞춰 얻는다. Points가 Path로 추가되는 순서는 결과에 상당한 영향을 미친다.
aPath ← Spline new form: dot.
aPath add: 150 @ 285.
aPath add: 400 @ 285.
aPath add: 185 @ 430.
aPath add: 280 @ 200.
aPath add: 375 @ 430.
aPath add: 150 @ 285.
aPath computeCurve.
aPath display
LinearFit과 Spline은 Path의 서브클래스로서 정의된다. DisplayObject의 프로토콜을 지원하기 위해 이러한 서브클래스 각각은 displayOn:at:clippingBox:rule:mask: 메시지를 구현한다.
Paths와 관련해 직선을 정의할 수 있다. Line은 두 개의 점으로 명시되는 Path이다. Arc는 원의 사분면(쿼터)으로 정의된다. Arc 클래스의 인스턴스들은 가능한 네 가지 쿼터 중 하나로 명시되고, 그들의 중심 Point와 원의 반지름을 알고 있다. 그리고 Circle은 네 개의 쿼터를 모두 표현하는 Arc 유형이다. 다시 말하지만, DisplayObject의 프로토콜을 지원하기 위해 세 클래스 각각은 (Line, Arc, Circle) displayOn:at:clippingBox:rule:mask: 메시지를 구현한다.
Curve 클래스는 Path의 서브클래스다. 이는 Points p1, p2, p3이 결정하는 직선에 접하고 Points p1, p3을 통과하는 쌍곡선을 나타낸다. Curve의 표시 메시지는 아래의 메서드에 표시된 바와 같이 정의된다.
displayOn: aDiDisplayMedium
at: aPoint
clippingBox: aRectangle
rule: anInteger
mask: aForm
| pa pb k s p1 p2 p3 line |
line ← Line new.
line form: self form.
self size < 3 ifTrue: [self error: 'Curves are defined by three points'].
p1 ← self at: 1.
p2 ← self at: 2.
p3 ← self at: 3.
s ← Path new.
s add: p1.
pa ← p2 - p1.
pb ← p3 - p2.
k ← 5 max: pa x abs + pa y abs + pb x abs + pb y abs // 20.
"k is a guess as to how many line segments to use to approximate the curve."
1 to: k do:
[ :i | s add:
pa*i//k + p1*(k-i) + (pb*(i-1)//k + p2*(i-1))//(k-1)].
s add: p3.
1 to: s size do:
[ :i |
line at: 1 put: (s at: i).
line at: 2 put: (s at: i + 1).
line displayOn: aDiDisplayMedium
at: aPoint
clippingBox: aRectangle
rule: anInteger
mask: aForm]
알고리즘은 Ted Kaehler가 수정하였다. 기본적으로는 선분 p1, p2, p3을 10개의 구간으로 나누고자 하였다. 도표에 표시된 바와 같이 구간에 번호를 붙이고, p1, p2에서 점 1을 p2, p3의 점 1으로 연결하는 직선을 그리며, p1, p2에서 점 2를 p2, p3의 점 2로 연결하는 직선을 그리는 식으로 나아간다. 쌍곡선은 외부 셸(shell)에 형성된 선분을 따라 삽입하여 p1부터 p3까지 형성된 경로이다.
그림 20.6에는 여러 개의 곡선이 표시되어 있다. 곡선은 검정색 직선이고, 회색 직선은 곡선을 정의하는 데 사용된 점을 연결하는 직선을 나타낸다.
그림 20.3에 표시된 이미지를 생성하기 위해 두 개의 Curves가 사용되었다. Form은 그림 20.2에 사용된 신사 이미지 두 개 중 하나에 해당한다.
Forms를 이용한 이미지 조작
BitBIt이 도형을 어떻게 복사하고, 반복 호출이 어떻게 텍스트나 직선과 같은 복잡한 이미지를 합할 수 있는지는 18장에서 설명하였다. BitBIt은 기존 이미지를 조작 시에도 유용하다. 가령, 텍스트를 그 위에 ORing만큼 볼드체로 보이도록 만들 수도 있고, 1 픽셀씩 우측으로 전환할 수도 있다. 단순한 이미지에서 복잡한 이미지를 빌드하듯이 간단한 연산을 반복하여 적용하면 복잡한 프로세싱을 얻을 수 있다. DisplayObject 프로토콜에 분명히 나타나는 BitBIt의 힘은 copy:from:in:rule: 와 같은 메시지를 통해 이미지를 조작 시에도 이용 가능하다.
본문에서는 그러한 구조적 조작의 네 가지 예, 즉 확대, 회전, 채우기, 인생 게임을 소개하겠다.
확대(Magnification)
저장된 Form을 간단하게 확대하는 방법은 그것을 더 큰 Form에 복사하여 원본에서 작은 점을 크게 보이도록 만드는 방법일 것이다. 높이 h와 너비 w의 경우 h*w 연산이 필요하겠다. 본문에 제시된 알고리즘은 (Form 클래스로 전송되는 두 개의 메시지) h + w 연산을 비롯해 소수의 연산만 추가로 이용한다.
magnify: aRectangle by: scale
| wideForm bigForm spacing |
spacing ← 0 @ 0.
wideForm ←
Form new
extent: aRectangle width * scale x @ aRectangle height.
wideForm
spread: aRectangle
from: self
by: scale x
spacing: spacing x
direction: 1 @ 0.
bigForm ← Form new extent: aRectangle extent * scale.
bigForm
spread: wideForm boundingBox
from: wideForm
by: scale y
spacing: spacing y
direction: 0 @ 1.
↑bigForm
spread: rectangle
from: aForm
by: scale
spacing: spacing
direction: dir
| slice sourcePt |
slice ← 0 @ 0 corner: dir transpose * self extent + dir.
sourcePt ← rectangle origin.
1 to: (rectangle extent dotProduct: dir) do:
[ :i |
"slice up original area"
self copy: slice
from: sourcePt
in: aForm
rule: 3.
sourcePt ← sourcePt + dir.
slice moveBy: dir * scale].
1 to: scale - spacing - 1 do:
[ :i |
"smear out the slices, leave white space"
self copy: (dir corner: self extent)
from: 0 @ 0
in: self
rule: 7]
확대는 두 단계로 진행된다. 첫째, 이미지를 wideForm에서 확대비율과 동일한 공백으로 구분된 수직 사선으로 나눈다. 이후 Oring 함수를 이용해 사이에 오는 영역 위가 얼룩지고 수평적 확대가 이루어진다. 수평적 슬라이스가 구분되고 수직 방향으로 얼룩져 이 과정은 wideForm으로부터 bigForm으로 반복되고 원하는 만큼 확대시킨다. 그림 20.7은 확대된 "7"을 생성하는 데에 위의 알고리즘을 사용하는 과정을 보여준다.
회전(Rotation)
이미지 상에서 또 유용한 연산으로는 90도씩 회전하는 연산을 들 수 있겠다. 회전은 종종 이동(translation)과 기본적으로 다른 연산으로 간주되는데, 이러한 관점은 BitBIt을 이용해 이미지를 회전하는 가능성을 간과한다. 하지만 그림 20.8에서 보인 첫 번째 변형(transformation)은 표시된 이미지를 회전시키기 위한 단계임은 확실하며, 변경된 네 개의 셀 내부를 회전시키는 일만 남았다. 그림의 나머지 부분은 이러한 셀이 더 분할되고 그 셀이 또 비슷하게 치환되는 모습을 보여준다. 결국 하나의 셀마다 하나의 픽셀만 포함한다. 이 시점에서 더 이상의 분할은 불필요하며, 이미지도 정확히 회전되었다.
그림 20.8에 표시된 각 변형은 계속해서 더 많은 계산을 필요로 하고 마지막은 h*w 연산보다 많은 계산을 필요로 하는 것처럼 보인다. 아래 소개된 알고리즘에서 약간 까다로운 점이 하나 있는데, 바로 분할된 셀의 하위부분을 한 번에 치환시켜 연속된 시간 log2(h) 연산에서 전체 회전을 실행한다는 점이다. 다수의 셀을 동시에 교환하는 것은 두 개의 보조 Forms의 도움을 받아 이루어진다. 첫 번째는 마스크로서 모든 셀의 상단 좌측 사분면을 선택하는 마스크를 이동하고, 두 번째 temp는 임시 저장소로 사용된다. 일련의 연산이 모든 셀의 우측과 좌측 절반을 치환하고, 또 다른 일련의 연산은 대각선 사분면을 교환하여 원하는 치환을 성공한다.
rotate
| mask temp quad all |
all ← self boundingBox.
mask ← Form extent: self extent.
temp ← Form extent: self extent.
mask white. "set up the first mask"
mask black: (0@0 extent: mask extent // 2).
quad ← self width // 2.
[quad > = 1] whileTrue:
["First exchange left and right halves"
temp copy: all from: 0@0 in: mask rule: 3.
temp copy: all from: 0@quad negated in: mask rule: 7.
temp copy: all from: 0@0 in: self rule: 1.
self copy: all from: 0@0 in: temp rule: 6.
temp copy: all from: quad@0 in: self rule: 6.
self copy: all from: quad@0 in: self rule: 7.
self copy: all from: quad negated@0 in: temp rule: 6.
"then flip the diagonals"
temp copy: all from: 0@0 in: self rule: 3.
temp copy: all from: quad@quad in: self rule: 6.
temp copy: all from: 0@0 in: mask rule: 1.
self copy: all from: 0@0 in: temp rule: 6.
self copy: all from: quad negated@quad negated in: temp rule: 6.
"Now refine the mask"
mask copy: all from: (quad//2)@(quad//2) in: mask rule: 1.
mask copy: all from: 0@quad negated in: mask rule: 7.
mask copy: all from: quad negated@0 in: mask rule: 7.
quad ← quad // 2]
그림 20.9는 연속하는 연산 이후마다 temp와 self의 상태를 추적한다.
그림 20.9에서 각 연산의 오프셋은 표시되지 않으나 프로그램 리스팅에서는 제공된다. 12개의 연산 이후 원하는 치환을 얻었다. 이 시점에서 마스크는 더 세부적으로 발전하고, 더 작은 셀을 대상으로 과정이 반복된다. 그림 20.10은 세분화(refinement)의 첫 번째 단계에서 두 번째 단계까지 마스크의 발전을 보여준다.
본문에서 회전에 사용된 알고리즘은 크기가 2의 제곱인 사각형 모양에만 적용 가능하다. 이러한 기법을 임의의 직사각형으로 확장하기 위해서는 많은 노력이 필요하다. 위의 기법을 직사각형 중앙에 대한 수평 및 수직 반사(reflection)에 적용하는 것이 좀 더 단순한 연습이 되겠다.
영역 채우기
Forms 상에서 유용한 연산은 윤곽이 그려진 영역의 내부를 하프톤 마스크로 채우는 기능이다. 여기서 제공하는 메서드는 영역의 내부에 어떤 위치를 표시하는 Point를 하나의 인자로 취한다. 마크는 이 위치에 "seed"(시드)로 위치하고, 시드는 영역 테두리로 확장될 때까지 조금 더 큰 방울(blob)으로 얼룩진다(네 방향 모두). 얼룩 과정의 단계마다 "erase"(제거) 규칙을 이용해 방울 위에 원본 Form이 복사된다. 이는 영역 테두리를 넘어 커지는 것을 트리밍(trimming)하는 효과가 있다. 또한 문지름을 10회 하고 나서 결과가 되는 얼룩을 이전 버전과 비교한다. 변경된 내용이 없다면 얼룩이 영역을 채우고 전체적으로 하프토닝이 적용된다.
shapeFill: aMask interiorPoint: interiorPoint
| dirs smearForm previousSmear all cycle noChange |
all ← self boundingBox.
smearForm ← Form extent: self extent.
"Place a seed in the interior"
smearForm valueAt: interiorPoint put: 1.
previousSmear ← smearForm deepCopy.
dirs ← Array with: 1@0 with: -1@0 with: 0@1 with: 0@-1.
cycle ← 0.
["check for no change every 10 smears"
(cycle ← cycle + 1)\\10 = 0 and:
[previousSmear copy: all
from: 0@0
in: smearForm
rule: Form reverse.
noChange ← previousSmear isAllWhite.
previousSmear copy: all from: 0@0 in: smearForm rule: Form over.
noChange]]
whileFalse:
[dirs do:
[dir |
"smear in each of the four directions"
smearForm copy: all
from: dir
in: smearForm
rule: Form under.
"After each smear, trim around the region border"
smearForm copy: all from: 0@0 in: self rule: Form erase]].
"Now paint the filled region in me with aMask"
smearForm displayOn: self
at: 0@0
clippingBox: self boundingBox
rule: Form under
mask: aMask
그림 20.11은 꽃 모양의 영역을 채워야 하는 Form을 표시한다. 얼룩을 연속하여 적용한 모습과 함께 최종 결과를 표시하였다.
인생 게임(The Game of Life)
콘웨이의 인생 게임은 비트맵의 번식에 대한 간단한 규칙이다. 규칙은 각 셀(세포)마다 주위 8개 셀 중 몇 개가 차지되는지를 중심으로 한다. 각 셀 주위에 살아있는 셀이 정확히 3개이거나 해당 셀이 살아 있고 주위에 2개의 셀이 살아 있다면 해당 셀은 다음 세대에도 살아 남는다. 이는 다음과 같이 설명할 수 있다. 세 개의 주위 유기조직은 빈 셀에 생명을 부여할 수 있고, 주위에 살아 있는 셀의 개수가 2개 미만이거나 3개를 초과하여 과잉을 보일 경우 기존의 유기조직은 죽는다. BitBIt은 추가를 할 수 없기 때문에 이 애플리케이션에서는 쓸모가 없는 듯 보인다. 하지만 Form 연산에서 이용할 수 있는 BitBIt의 조합 규칙은 부분합(XOR)과 올림(AND)을 포함한다. 약간의 독창성과 어느 정도의 저장공간을 이용하면 일정한 수의 BitBIt 연산을 이용해 어떤 크기의 비트맵이든 다음 세대를 계산할 수 있다.
nextLifeGeneration
| nbr1 nbr2 nbr4 carry2 carry4 all delta |
nbr1 ← Form extent: self extent.
nbr2 ← Form extent: self extent.
nbr4 ← Form extent: self extent.
carry2 ← Form extent: self extent.
carry4 ← Form extent: self extent.
all ← self boundingBox.
1 to: 8 do:
[ :i |
"delta is the offset of the eight neighboring cells"
delta ← ((#(-1 0 1 1 1 0 -1 -1) at: i)
@ (#(-1 -1 -1 0 1 1 1 0) at: i)).
carry2 copy: all from: 0@0 in: nbr1 rule: 3.
carry2 copy: all from: delta in: self rule: 1. "AND for carry into 2"
nbr1 copy: all from: delta in: self rule: 6. "XOR for sum 1"
carry4 copy: all from: 0@0 in: nbr2 rule: 3.
carry4 copy: all from: 0@0 in: carry2 rule: 1. "AND for carry into 4"
nbr2 copy: all from: 0@0 in: carry2 rule: 6. "XOR for sum 2"
nbr4 copy: all from: 0@0 in: carry4 rule: 6].
"XOR for sum 4(ignore carry into 8)"
self copy: all from: 0@0 in: nbr2 rule: 1.
nbr1 copy: all from: 0@0 in: nbr2 rule: 1.
self copy: all from: 0@0 in: nbr1 rule: 7.
self copy: all from: 0@0 in: nbr4 rule: 4
"compute next generation"
그림 20.12에서 표시된 바와 같이 주변 셀의 개수는 이항으로 된 숫자의 1의 비트, 2의 비트, 4의 비트에 대한 세 개의 이미지 평면을 이용해 표현된다. 8의 비트는 생존자가 없기 때문에 0과 같으므로 (8의 비트를 무시한 결과) 무시할 수 있다. 어떤 것도 최소 4번째 주위 셀까지 4-면으로 전파되지는 않겠지만 이러한 Smalltalk-80 메서드는 각 주위 셀마다 full carry propagation(전체적인 올림수 전달)을 실행하기 때문에 다소 낭비적이기도 하다.