@@ -10,109 +10,81 @@ import (
1010 "github.com/github/gh-skyline/types"
1111)
1212
13- // Common configuration for rendered elements
14- type renderConfig struct {
15- startX float64
16- startY float64
17- startZ float64
18- voxelScale float64
19- depth float64
20- }
13+ const (
14+ baseWidthVoxelResolution = 2000 // Number of voxels across the skyline face
15+ voxelDepth = 1.0 // Distance to come out of face
2116
22- // TextConfig holds parameters for text rendering
23- type textRenderConfig struct {
24- renderConfig
25- text string
26- contextWidth int
27- contextHeight int
28- fontSize float64
29- }
17+ logoScale = 0.4 // Percent
18+ logoTopOffset = 0.15 // Percent
19+ logoLeftOffset = 0.03 // Percent
3020
31- // ImageConfig holds parameters for image rendering
32- type imageRenderConfig struct {
33- renderConfig
34- imagePath string
35- height float64
36- }
21+ usernameFontSize = 120.0
22+ usernameJustification = "left" // "left", "center", "right"
23+ usernameLeftOffset = 0.1 // Percent
3724
38- const (
39- imagePosition = 0.025
40- usernameOffset = - 0.01
41- yearPosition = 0.77
42-
43- defaultContextWidth = 800
44- defaultContextHeight = 200
45- textVoxelSize = 1.0
46- textDepthOffset = 2.0
47- frontEmbedDepth = 1.5
48-
49- usernameContextWidth = 1000
50- usernameContextHeight = 200
51- usernameFontSize = 48.0
52- usernameZOffset = 0.7
53-
54- yearContextWidth = 800
55- yearContextHeight = 200
56- yearFontSize = 56.0
57- yearZOffset = 0.4
58-
59- defaultImageHeight = 9.0
60- defaultImageScale = 0.8
61- imageLeftMargin = 10.0
25+ yearFontSize = 100.0
26+ yearJustification = "right" // "left", "center", "right"
27+ yearLeftOffset = 0.97 // Percent
6228)
6329
6430// Create3DText generates 3D text geometry for the username and year.
65- func Create3DText (username string , year string , innerWidth , baseHeight float64 ) ([]types.Triangle , error ) {
31+ func Create3DText (username string , year string , baseWidth float64 , baseHeight float64 ) ([]types.Triangle , error ) {
6632 if username == "" {
6733 username = "anonymous"
6834 }
6935
70- usernameConfig := textRenderConfig {
71- renderConfig : renderConfig {
72- startX : innerWidth * usernameOffset ,
73- startY : - textDepthOffset / 2 ,
74- startZ : baseHeight * usernameZOffset ,
75- voxelScale : textVoxelSize ,
76- depth : frontEmbedDepth ,
77- },
78- text : username ,
79- contextWidth : usernameContextWidth ,
80- contextHeight : usernameContextHeight ,
81- fontSize : usernameFontSize ,
82- }
83-
84- yearConfig := textRenderConfig {
85- renderConfig : renderConfig {
86- startX : innerWidth * yearPosition ,
87- startY : - textDepthOffset / 2 ,
88- startZ : baseHeight * yearZOffset ,
89- voxelScale : textVoxelSize * 0.75 ,
90- depth : frontEmbedDepth ,
91- },
92- text : year ,
93- contextWidth : yearContextWidth ,
94- contextHeight : yearContextHeight ,
95- fontSize : yearFontSize ,
96- }
97-
98- usernameTriangles , err := renderText (usernameConfig )
36+ usernameTriangles , err := renderText (
37+ username ,
38+ usernameJustification ,
39+ usernameLeftOffset ,
40+ usernameFontSize ,
41+ baseWidth ,
42+ baseHeight ,
43+ )
9944 if err != nil {
10045 return nil , err
10146 }
10247
103- yearTriangles , err := renderText (yearConfig )
48+ yearTriangles , err := renderText (
49+ year ,
50+ yearJustification ,
51+ yearLeftOffset ,
52+ yearFontSize ,
53+ baseWidth ,
54+ baseHeight ,
55+ )
10456 if err != nil {
10557 return nil , err
10658 }
10759
10860 return append (usernameTriangles , yearTriangles ... ), nil
10961}
11062
111- // renderText generates 3D geometry for the given text configuration.
112- func renderText (config textRenderConfig ) ([]types.Triangle , error ) {
113- dc := gg .NewContext (config .contextWidth , config .contextHeight )
63+ // renderText places text on the face of a skyline, offset from the left and vertically-aligned.
64+ // The function takes the text to be displayed, offset from left, and font size.
65+ // It returns an array of types.Triangle.
66+ //
67+ // Parameters:
68+ //
69+ // text (string): The text to be displayed on the skyline's front face.
70+ // leftOffsetPercent (float64): The percentage distance from the left to start displaying the text.
71+ // fontSize (float64): How large to make the text. Note: It scales with the baseWidthVoxelResolution.
72+ //
73+ // Returns:
74+ //
75+ // ([]types.Triangle, error): A slice of triangles representing text.
76+ func renderText (text string , justification string , leftOffsetPercent float64 , fontSize float64 , baseWidth float64 , baseHeight float64 ) ([]types.Triangle , error ) {
77+ // Create a rendering context for the face of the skyline
78+ faceWidthRes := baseWidthVoxelResolution
79+ faceHeightRes := int (float64 (faceWidthRes ) * baseHeight / baseWidth )
80+
81+ // Create image representing the skyline face
82+ dc := gg .NewContext (faceWidthRes , faceHeightRes )
83+ dc .SetRGB (0 , 0 , 0 )
84+ dc .Clear ()
85+ dc .SetRGB (1 , 1 , 1 )
11486
115- // Get temporary font file
87+ // Load font into context
11688 fontPath , cleanup , err := writeTempFont (PrimaryFont )
11789 if err != nil {
11890 // Try fallback font
@@ -121,31 +93,42 @@ func renderText(config textRenderConfig) ([]types.Triangle, error) {
12193 return nil , errors .New (errors .IOError , "failed to load any fonts" , err )
12294 }
12395 }
124-
125- if err := dc .LoadFontFace (fontPath , config .fontSize ); err != nil {
96+ if err := dc .LoadFontFace (fontPath , fontSize ); err != nil {
12697 return nil , errors .New (errors .IOError , "failed to load font" , err )
12798 }
12899
129- dc .SetRGB (0 , 0 , 0 )
130- dc .Clear ()
131- dc .SetRGB (1 , 1 , 1 )
132- dc .DrawStringAnchored (config .text , float64 (config .contextWidth )/ 8 , float64 (config .contextHeight )/ 2 , 0.0 , 0.5 )
133-
100+ // Draw text on image at desired location
134101 var triangles []types.Triangle
135102
136- for y := 0 ; y < config .contextHeight ; y ++ {
137- for x := 0 ; x < config .contextWidth ; x ++ {
103+ // Convert justification to a number
104+ var justificationPercent float64
105+ switch justification {
106+ case "center" :
107+ justificationPercent = 0.5
108+ case "right" :
109+ justificationPercent = 1.0
110+ default :
111+ justificationPercent = 0.0
112+ }
113+
114+ dc .DrawStringAnchored (
115+ text ,
116+ float64 (faceWidthRes )* leftOffsetPercent , // Offset from right
117+ float64 (faceHeightRes )* 0.5 , // Offset from top
118+ justificationPercent , // Justification (0.0=left, 0.5=center, 1.0=right)
119+ 0.5 , // Vertically aligned
120+ )
121+
122+ // Convert context image pixels into voxels
123+ for x := 0 ; x < faceWidthRes ; x ++ {
124+ for y := 0 ; y < faceHeightRes ; y ++ {
138125 if isPixelActive (dc , x , y ) {
139- xPos := config .startX + float64 (x )* config .voxelScale / 8
140- zPos := config .startZ - float64 (y )* config .voxelScale / 8
141-
142- voxel , err := CreateCube (
143- xPos ,
144- config .startY ,
145- zPos ,
146- config .voxelScale / 2 ,
147- config .depth ,
148- config .voxelScale / 2 ,
126+ voxel , err := createVoxelOnFace (
127+ float64 (x ),
128+ float64 (y ),
129+ voxelDepth ,
130+ baseWidth ,
131+ baseHeight ,
149132 )
150133 if err != nil {
151134 return nil , errors .New (errors .STLError , "failed to create cube" , err )
@@ -161,34 +144,78 @@ func renderText(config textRenderConfig) ([]types.Triangle, error) {
161144 return triangles , nil
162145}
163146
147+ // createVoxelOnFace creates a voxel on the face of a skyline by generating a cube at the specified coordinates.
148+ // The function takes in the x, y coordinates and height.
149+ // It returns a slice of types.Triangle representing the cube and an error if the cube creation fails.
150+ //
151+ // Parameters:
152+ //
153+ // x (float64): The x-coordinate on the skyline face (left to right).
154+ // y (float64): The y-coordinate on the skyline face (top to bottom).
155+ // height (float64): Distance coming out of the face.
156+ //
157+ // Returns:
158+ //
159+ // ([]types.Triangle, error): A slice of triangles representing the cube and an error if any.
160+ func createVoxelOnFace (x float64 , y float64 , height float64 , baseWidth float64 , baseHeight float64 ) ([]types.Triangle , error ) {
161+ // Mapping resolution
162+ xResolution := float64 (baseWidthVoxelResolution )
163+ yResolution := xResolution * baseHeight / baseWidth
164+
165+ // Pixel size
166+ voxelSize := 1.0
167+
168+ // Scale coordinate to face resolution
169+ x = (x / xResolution ) * baseWidth
170+ y = (y / yResolution ) * baseHeight
171+ voxelSizeX := (voxelSize / xResolution ) * baseWidth
172+ voxelSizeY := (voxelSize / yResolution ) * baseHeight
173+
174+ cube , err := CreateCube (
175+ // Location (from top left corner of skyline face)
176+ x , // x - Left to right
177+ - height , // y - Negative comes out of face. Positive goes into face.
178+ - voxelSizeY - y , // z - Bottom to top
179+
180+ // Size
181+ voxelSizeX , // x length - left to right from specified point
182+ height , // thickness - distance coming out of face
183+ voxelSizeY , // y length - bottom to top from specified point
184+ )
185+
186+ return cube , err
187+ }
188+
164189// GenerateImageGeometry creates 3D geometry from the embedded logo image.
165- func GenerateImageGeometry (innerWidth , baseHeight float64 ) ([]types.Triangle , error ) {
190+ func GenerateImageGeometry (baseWidth float64 , baseHeight float64 ) ([]types.Triangle , error ) {
166191 // Get temporary image file
167192 imgPath , cleanup , err := getEmbeddedImage ()
168193 if err != nil {
169194 return nil , err
170195 }
171196
172- config := imageRenderConfig {
173- renderConfig : renderConfig {
174- startX : innerWidth * imagePosition ,
175- startY : - frontEmbedDepth / 2.0 ,
176- startZ : - 0.85 * baseHeight ,
177- voxelScale : defaultImageScale ,
178- depth : frontEmbedDepth ,
179- },
180- imagePath : imgPath ,
181- height : defaultImageHeight ,
182- }
183-
184197 defer cleanup ()
185198
186- return renderImage (config )
199+ return renderImage (
200+ imgPath ,
201+ logoScale ,
202+ voxelDepth ,
203+ logoLeftOffset ,
204+ logoTopOffset ,
205+ baseWidth ,
206+ baseHeight ,
207+ )
187208}
188209
189210// renderImage generates 3D geometry for the given image configuration.
190- func renderImage (config imageRenderConfig ) ([]types.Triangle , error ) {
191- reader , err := os .Open (config .imagePath )
211+ func renderImage (filePath string , scale float64 , height float64 , leftOffsetPercent float64 , topOffsetPercent float64 , baseWidth float64 , baseHeight float64 ) ([]types.Triangle , error ) {
212+
213+ // Get voxel resolution of base face
214+ faceWidthRes := baseWidthVoxelResolution
215+ faceHeightRes := int (float64 (faceWidthRes ) * baseHeight / baseWidth )
216+
217+ // Load image from file
218+ reader , err := os .Open (filePath )
192219 if err != nil {
193220 return nil , errors .New (errors .IOError , "failed to open image" , err )
194221 }
@@ -199,34 +226,32 @@ func renderImage(config imageRenderConfig) ([]types.Triangle, error) {
199226 fmt .Println (closeErr )
200227 }
201228 }()
202-
203229 img , err := png .Decode (reader )
204230 if err != nil {
205231 return nil , errors .New (errors .IOError , "failed to decode PNG" , err )
206232 }
207233
234+ // Get image size
208235 bounds := img .Bounds ()
209- width := bounds .Max .X
210- height := bounds .Max .Y
211-
212- scale := config .height / float64 (height )
236+ logoWidth := bounds .Max .X
237+ logoHeight := bounds .Max .Y
213238
239+ // Transfer image pixels onto face of skyline as voxels
214240 var triangles []types.Triangle
215-
216- for y := height - 1 ; y >= 0 ; y -- {
217- for x := 0 ; x < width ; x ++ {
241+ for x := 0 ; x < logoWidth ; x ++ {
242+ for y := logoHeight - 1 ; y >= 0 ; y -- {
243+ // Get pixel color and alpha
218244 r , _ , _ , a := img .At (x , y ).RGBA ()
245+
246+ // If pixel is active (white) and not fully transparent, create a voxel
219247 if a > 32768 && r > 32768 {
220- xPos := config .startX + float64 (x )* config .voxelScale * scale
221- zPos := config .startZ + float64 (height - 1 - y )* config .voxelScale * scale
222-
223- voxel , err := CreateCube (
224- xPos ,
225- config .startY ,
226- zPos ,
227- config .voxelScale * scale ,
228- config .depth ,
229- config .voxelScale * scale ,
248+
249+ voxel , err := createVoxelOnFace (
250+ (leftOffsetPercent * float64 (faceWidthRes ))+ float64 (x )* scale ,
251+ (topOffsetPercent * float64 (faceHeightRes ))+ float64 (y )* scale ,
252+ height ,
253+ baseWidth ,
254+ baseHeight ,
230255 )
231256
232257 if err != nil {
0 commit comments