Skip to content

Commit 2fe2d87

Browse files
committed
New chapter and a button to toggle histogram.
1 parent d99586a commit 2fe2d87

File tree

16 files changed

+405
-63
lines changed

16 files changed

+405
-63
lines changed

.idea/codeStyles/codeStyleConfig.xml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

FanOfAscii.xcodeproj/project.pbxproj

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
5E551EC52371FC3F00784365 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5E551EC42371FC3F00784365 /* Assets.xcassets */; };
1212
5E6F7E592367B529008CC191 /* UserModules in Copy Book Contents */ = {isa = PBXBuildFile; fileRef = 5E6F7E582367B51E008CC191 /* UserModules */; };
1313
5E6F7E6C2368FC01008CC191 /* BookAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E6F7E5C2367B64A008CC191 /* BookAPI.swift */; };
14-
5E6F7E7D2368FF29008CC191 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7067A323678E220094BDEF /* Image.swift */; };
14+
5E6F7E7D2368FF29008CC191 /* RawImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5E7067A323678E220094BDEF /* RawImage.swift */; };
1515
5E70679F23678DC30094BDEF /* Modules in Copy Book Contents */ = {isa = PBXBuildFile; fileRef = 5E70679E23678DC00094BDEF /* Modules */; };
1616
5EA2E3CA2056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5EA2E3C82056F35B00416A35 /* LiveViewTestAppLaunchScreen.storyboard */; };
1717
5EA2E3D42056F36800416A35 /* libBookCore.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5EF2F9AB2054BBF300191409 /* libBookCore.a */; };
@@ -35,6 +35,7 @@
3535
7174D3BB23F7D0D100A09FFB /* lantern.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7174D3AF23F7D0D100A09FFB /* lantern.jpg */; };
3636
7174D3BC23F7D0D100A09FFB /* woman.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 7174D3B023F7D0D100A09FFB /* woman.jpg */; };
3737
7174D3BD23F7D0D100A09FFB /* imagePickerList.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7174D3B123F7D0D100A09FFB /* imagePickerList.plist */; };
38+
71BBEDB923F8F22600771781 /* EventMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1AA23F79FB20091A700 /* EventMessage.swift */; };
3839
71DD3E6323F7991F001510FB /* LiveViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD3E6223F7991F001510FB /* LiveViewSupport.swift */; };
3940
71DD3E6523F79B2E001510FB /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD3E6423F79B2E001510FB /* BaseViewController.swift */; };
4041
71DD3E6723F79B42001510FB /* ImagePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD3E6623F79B42001510FB /* ImagePickerViewController.swift */; };
@@ -43,7 +44,6 @@
4344
71DD3E7223F79BA7001510FB /* ShowcaseImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD3E7123F79BA6001510FB /* ShowcaseImageView.swift */; };
4445
71DD3E7523F79BCC001510FB /* UIColor+StateColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD3E7423F79BCC001510FB /* UIColor+StateColors.swift */; };
4546
71DD3E7823F79C12001510FB /* ImagePickerDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD3E7723F79C12001510FB /* ImagePickerDataSource.swift */; };
46-
71DEC1AC23F79FB20091A700 /* EventMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1AA23F79FB20091A700 /* EventMessage.swift */; };
4747
71DEC1B023F7A21E0091A700 /* IntroductionLiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1AF23F7A21E0091A700 /* IntroductionLiveViewController.swift */; };
4848
71DEC1CB23F7A2650091A700 /* GrayscaleHistogramEqualization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1B523F7A2650091A700 /* GrayscaleHistogramEqualization.swift */; };
4949
71DEC1CC23F7A2650091A700 /* HistogramView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1B823F7A2650091A700 /* HistogramView.swift */; };
@@ -52,6 +52,7 @@
5252
71DEC1CF23F7A2650091A700 /* HowImagesComposedLiveViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1C623F7A2650091A700 /* HowImagesComposedLiveViewController.swift */; };
5353
71DEC1D023F7A2650091A700 /* MagnifierView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1C923F7A2650091A700 /* MagnifierView.swift */; };
5454
71DEC1D123F7A2650091A700 /* MagnivierContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DEC1CA23F7A2650091A700 /* MagnivierContainerView.swift */; };
55+
B99F6F92FC79077F0AF4C414 /* HistogramToolBarButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99F60BCF4AD4401FD95EBC5 /* HistogramToolBarButtonView.swift */; };
5556
/* End PBXBuildFile section */
5657

5758
/* Begin PBXBuildRule section */
@@ -173,7 +174,7 @@
173174
5E6F7E6D2368FC9A008CC191 /* ModuleOverridingBuildSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = ModuleOverridingBuildSettings.xcconfig; sourceTree = "<group>"; };
174175
5E6F7E742368FF15008CC191 /* libUserModule.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUserModule.a; sourceTree = BUILT_PRODUCTS_DIR; };
175176
5E70679E23678DC00094BDEF /* Modules */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Modules; sourceTree = "<group>"; };
176-
5E7067A323678E220094BDEF /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = "<group>"; };
177+
5E7067A323678E220094BDEF /* RawImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawImage.swift; sourceTree = "<group>"; };
177178
5E80DF292342971E00595EB4 /* LiveViewTestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = LiveViewTestApp.entitlements; sourceTree = "<group>"; };
178179
5EA2E3BD2056F35A00416A35 /* LiveViewTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LiveViewTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
179180
5EA2E3C92056F35B00416A35 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LiveViewTestAppLaunchScreen.storyboard; sourceTree = "<group>"; };
@@ -216,6 +217,7 @@
216217
71DEC1C623F7A2650091A700 /* HowImagesComposedLiveViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HowImagesComposedLiveViewController.swift; sourceTree = "<group>"; };
217218
71DEC1C923F7A2650091A700 /* MagnifierView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagnifierView.swift; sourceTree = "<group>"; };
218219
71DEC1CA23F7A2650091A700 /* MagnivierContainerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MagnivierContainerView.swift; sourceTree = "<group>"; };
220+
B99F60BCF4AD4401FD95EBC5 /* HistogramToolBarButtonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HistogramToolBarButtonView.swift; sourceTree = "<group>"; };
219221
/* End PBXFileReference section */
220222

221223
/* Begin PBXFrameworksBuildPhase section */
@@ -380,7 +382,7 @@
380382
5E7067A223678E0D0094BDEF /* Sources */ = {
381383
isa = PBXGroup;
382384
children = (
383-
5E7067A323678E220094BDEF /* Image.swift */,
385+
5E7067A323678E220094BDEF /* RawImage.swift */,
384386
);
385387
path = Sources;
386388
sourceTree = "<group>";
@@ -517,8 +519,8 @@
517519
71DEC1A723F79FA70091A700 /* Core */ = {
518520
isa = PBXGroup;
519521
children = (
520-
71DD3E6223F7991F001510FB /* LiveViewSupport.swift */,
521522
71DEC1AA23F79FB20091A700 /* EventMessage.swift */,
523+
71DD3E6223F7991F001510FB /* LiveViewSupport.swift */,
522524
);
523525
path = Core;
524526
sourceTree = "<group>";
@@ -556,6 +558,7 @@
556558
isa = PBXGroup;
557559
children = (
558560
71DEC1B823F7A2650091A700 /* HistogramView.swift */,
561+
B99F60BCF4AD4401FD95EBC5 /* HistogramToolBarButtonView.swift */,
559562
);
560563
path = Views;
561564
sourceTree = "<group>";
@@ -805,7 +808,7 @@
805808
isa = PBXSourcesBuildPhase;
806809
buildActionMask = 2147483647;
807810
files = (
808-
5E6F7E7D2368FF29008CC191 /* Image.swift in Sources */,
811+
5E6F7E7D2368FF29008CC191 /* RawImage.swift in Sources */,
809812
);
810813
runOnlyForDeploymentPostprocessing = 0;
811814
};
@@ -828,16 +831,17 @@
828831
71DD3E7223F79BA7001510FB /* ShowcaseImageView.swift in Sources */,
829832
71DEC1D123F7A2650091A700 /* MagnivierContainerView.swift in Sources */,
830833
71DEC1CE23F7A2650091A700 /* AsciificationLiveViewController.swift in Sources */,
834+
71BBEDB923F8F22600771781 /* EventMessage.swift in Sources */,
831835
71DD3E7823F79C12001510FB /* ImagePickerDataSource.swift in Sources */,
832836
71DD3E6723F79B42001510FB /* ImagePickerViewController.swift in Sources */,
833-
71DEC1AC23F79FB20091A700 /* EventMessage.swift in Sources */,
834837
71DEC1D023F7A2650091A700 /* MagnifierView.swift in Sources */,
835838
71DEC1CB23F7A2650091A700 /* GrayscaleHistogramEqualization.swift in Sources */,
836839
71DD3E7523F79BCC001510FB /* UIColor+StateColors.swift in Sources */,
837840
71DD3E6323F7991F001510FB /* LiveViewSupport.swift in Sources */,
838841
71DD3E6B23F79B61001510FB /* ImagePickerCollectionViewCell.swift in Sources */,
839842
71DD3E6523F79B2E001510FB /* BaseViewController.swift in Sources */,
840843
71DEC1B023F7A21E0091A700 /* IntroductionLiveViewController.swift in Sources */,
844+
B99F6F92FC79077F0AF4C414 /* HistogramToolBarButtonView.swift in Sources */,
841845
);
842846
runOnlyForDeploymentPostprocessing = 0;
843847
};

FanOfAscii/Chapters/01-FanOfAscii.playgroundchapter/Pages/02-HowImagesComposed.playgroundpage/main.swift

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,36 @@ even see lighting units in these three colors with a close-up look.
4646
**Filters** are used as the general technique for image processing. Mysterious it seems to be, a filter is more like a
4747
mathematical function, receiving colors per pixel, recalculates them, producing a new image as output.
4848

49-
### 🔨Build Your First Image Filter
49+
### 🔨Your First Image Filter
5050

5151
* Experiment:
5252
* In this experiment, we'll build a simple filter which takes red, green or blue component out of the source
5353
image.
54-
* Try to read and complete the following code snippet. When you finish it, run your code and tap the **R, G
55-
and B** button below the image to see whether it works.
54+
* Try to read and complete the following code snippet. When you finish, run your code and tap the *R, G
55+
and B* button below the image to see whether it works.
5656
*/
5757

5858
//#-code-completion(everything, hide)
5959
//#-code-completion(literal, show, float, integer)
60-
//#-code-completion(identifier, show, coefficientRed, coefficientGreen, coefficientBlue)
60+
//#-code-completion(identifier, show, factorRed, factorGreen, factorBlue)
61+
//#-editable-code
6162
func applyRGBFilter(redEnabled: Bool,
6263
greenEnabled: Bool,
6364
blueEnabled: Bool,
64-
image: Image) {
65-
let coefficientRed, coefficientGreen, coefficientBlue: Float
66-
coefficientRed = (redEnabled ? 1 : 0)
67-
coefficientGreen = (greenEnabled ? 1 : 0)
68-
coefficientBlue = (blueEnabled ? 1 : 0)
65+
rawImage: RawImage) {
66+
let factorRed, factorGreen, factorBlue: Float
67+
factorRed = (redEnabled ? 1 : 0)
68+
factorGreen = (greenEnabled ? 1 : 0)
69+
factorBlue = (blueEnabled ? 1 : 0)
6970
var filterMatrix: [Float] = [
70-
/*#-editable-code*/<#T##Red##Float#>/*#-end-editable-code*/, 0, 0, 0,
71-
0, /*#-editable-code*/<#T##Green##Float#>/*#-end-editable-code*/, 0, 0,
72-
0, 0, /*#-editable-code*/<#T##Blue##Float#>/*#-end-editable-code*/, 0,
73-
0, 0, 0, /*#-editable-code*/<#T##Alpha##Float#>/*#-end-editable-code*/
71+
<#T##Red##Float#>, 0, 0, 0,
72+
0, <#T##Green##Float#>, 0, 0,
73+
0, 0, <#T##Blue##Float#>, 0,
74+
0, 0, 0, <#T##Alpha##Float#>
7475
]
75-
image.multiplyByMatrix(matrix4x4: filterMatrix)
76+
rawImage.multiplyByMatrix(matrix4x4: filterMatrix)
7677
}
78+
//#-end-editable-code
7779

7880
/*:
7981
* Note:
@@ -88,18 +90,16 @@ let eventListener = EventListener(proxy: remoteView) { message in
8890
case .rgbFilterRequest(let redEnabled,
8991
let greenEnabled,
9092
let blueEnabled,
91-
let uiImage):
92-
guard let uiImage = uiImage,
93-
let image = Image(uiImage: uiImage) else {
93+
let image):
94+
guard let rawImage = RawImage(uiImage: image) else {
9495
return
9596
}
9697
applyRGBFilter(redEnabled: redEnabled,
9798
greenEnabled: greenEnabled,
9899
blueEnabled: blueEnabled,
99-
image: image);
100-
100+
rawImage: rawImage);
101101
let destinationBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
102-
if let destCGImage = image.cgImage(bitmapInfo: destinationBitmapInfo),
102+
if let destCGImage = rawImage.cgImage(bitmapInfo: destinationBitmapInfo),
103103
let destImage = try? UIImage(cgImage: destCGImage) {
104104
remoteView?.send(EventMessage.imageProcessingResponse(image: destImage).playgroundValue)
105105
}

FanOfAscii/Chapters/01-FanOfAscii.playgroundchapter/Pages/03-GrayscaleHistogramEqualization.playgroundpage/main.swift

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,142 @@
11
//#-hidden-code
22
//
3-
// See LICENSE folder for this template’s licensing information.
4-
//
5-
// Abstract:
6-
// The Swift file containing the source code edited by the user of this playground book.
3+
// Copyright © 2020 Bunny Wong
4+
// Created on 2019/12/18.
75
//
6+
7+
import UIKit
8+
import PlaygroundSupport
9+
import Accelerate
10+
11+
import BookCore
12+
13+
PlaygroundPage.current.needsIndefiniteExecution = true
14+
15+
//#-end-hidden-code
16+
/*:
17+
# Grayscale, Histogram & Equalization
18+
19+
## Grayscale
20+
21+
To achieve the final result, the first thing we need to do is converting the image into a *grayscaled* version.
22+
We can achieve this by setting it's red, green and blue scalar to the same value, which is the average. With prior
23+
knowledge,this task should be simple now. Just construct a grayscale matrix and… it's done.
24+
25+
However, according to scientific researches, our eyes are more sensitive to green color than red and blue (actually,
26+
green takes up almost 70%). To achieve better result, we can modify our coefficients a little bit, taking this into
27+
consideration.
28+
29+
### 🔨Yet Another Image Filter
30+
31+
* Experiment:
32+
* In this experiment, we'll build another filter to turn a image into grayscaled version, with consideration.
33+
* Try to read and complete the following code snippet. When you finish, run your code and tap the *Switch to
34+
Grayscale* button below the image to see whether it works.
35+
*/
36+
37+
//#-code-completion(everything, hide)
38+
//#-code-completion(literal, show, float, integer)
39+
//#-code-completion(identifier, show, coefficientRed, coefficientGreen, coefficientBlue)
40+
//#-editable-code
41+
func applyGrayscaleFilter(rawImage: RawImage) {
42+
let coefficientRed: Float = 0.2126
43+
let coefficientGreen: Float = 0.7152
44+
let coefficientBlue: Float = 0.0722
45+
var filterMatrix: [Float] = [
46+
<#T##Red##Float#>, <#T##Red##Float#>, <#T##Red##Float#>, 0,
47+
<#T##Green##Float#>, <#T##Green##Float#>, <#T##Green##Float#>, 0,
48+
<#T##Blue##Float#>, <#T##Blue##Float#>, <#T##Blue##Float#>, 0,
49+
0, 0, 0, 1
50+
]
51+
rawImage.multiplyByMatrix(matrix4x4: filterMatrix)
52+
}
53+
54+
//#-end-editable-code
55+
/*:
56+
## Histograms
57+
58+
Now we've turned our image into grayscale and here comes another problem we have to solve: for some images, which are
59+
too bright or too dark, the resulting ASCII arts may be hard to recognize, as the following figure shows.
60+
61+
**Histogram** is an effective tool for image processing. It visualizes the distribution of tones in an image. The X axis
62+
represents the brightness, while the Y axis represents the relative number of pixels at that brightness value. The
63+
following figure shows the same images as previous figure, along with histograms representing them.
64+
65+
### 🔬Demystification of Histograms
66+
67+
* Experiment:
68+
* Choose an image and then tap the *Show Histogram* icon below the image to show the histogram. Tap it again to
69+
see a histogram with separated red, green, and blue value.
70+
* Try to understand these graphs by associating them with tone and color distributions of images.
71+
*/
72+
//#-editable-code
73+
func applyGrayscaleFilterx(rawImage: RawImage) {
74+
let coefficientRed: Float = 0.2126
75+
let coefficientGreen: Float = 0.7152
76+
let coefficientBlue: Float = 0.0722
77+
var filterMatrix: [Float] = [
78+
<#T##Red##Float#>, <#T##Red##Float#>, <#T##Red##Float#>, 0,
79+
<#T##Green##Float#>, <#T##Green##Float#>, <#T##Green##Float#>, 0,
80+
<#T##Blue##Float#>, <#T##Blue##Float#>, <#T##Blue##Float#>, 0,
81+
0, 0, 0, 1
82+
]
83+
rawImage.multiplyByMatrix(matrix4x4: filterMatrix)
84+
}
85+
86+
//#-end-editable-code
87+
/*:
88+
## Tone Equalization
89+
90+
In this section we'll use a technique called **Tone Equalization** to solve the previous problem. This technique
91+
equalizes an image by *expanding light part to lightest and dark part to darkest*. For histogram's perspective, it
92+
*widens* a histogram to its maximum width.
93+
94+
### 🔨Equalize the Images
95+
96+
* Experiment:
97+
* In this experiment, we'll build a filter for **tone equalization**.
98+
* Try to read and complete the following code snippet. When you finish it, run your code and tap the **Equalization**
99+
button below the image to see whether it works.
100+
*/
101+
//#-editable-code
102+
func applyGrayscaleFilterxx(rawImage: RawImage) {
103+
let coefficientRed: Float = 0.2126
104+
let coefficientGreen: Float = 0.7152
105+
let coefficientBlue: Float = 0.0722
106+
var filterMatrix: [Float] = [
107+
<#T##Red##Float#>, <#T##Red##Float#>, <#T##Red##Float#>, 0,
108+
<#T##Green##Float#>, <#T##Green##Float#>, <#T##Green##Float#>, 0,
109+
<#T##Blue##Float#>, <#T##Blue##Float#>, <#T##Blue##Float#>, 0,
110+
0, 0, 0, 1
111+
]
112+
rawImage.multiplyByMatrix(matrix4x4: filterMatrix)
113+
}
114+
115+
//#-end-editable-code
116+
/*:
117+
* Note:
118+
In this code snippet, we transform the image by multiplying it with a custom filter matrix. If you're not familiar
119+
with limier algebra, the following figure will explain how this transform matrix works.
120+
*/
121+
122+
//#-hidden-code
123+
let remoteView = remoteViewAsLiveViewProxy()
124+
let eventListener = EventListener(proxy: remoteView) { message in
125+
switch message {
126+
case .grayscaleFilterRequest(let enabled, let image):
127+
guard let rawImage = RawImage(uiImage: image) else {
128+
return
129+
}
130+
if enabled == true {
131+
applyGrayscaleFilter(rawImage: rawImage);
132+
}
133+
let destinationBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
134+
if let destCGImage = rawImage.cgImage(bitmapInfo: destinationBitmapInfo),
135+
let destImage = try? UIImage(cgImage: destCGImage) {
136+
remoteView?.send(EventMessage.imageProcessingResponse(image: destImage).playgroundValue)
137+
}
138+
default:
139+
break
140+
}
141+
}
8142
//#-end-hidden-code
9-
let str = "Hello, playground"

0 commit comments

Comments
 (0)