├── .gitignore ├── CardGenerator ├── function.json ├── image-lib.csx └── run.csx ├── CoderCards.funproj ├── CoderCards.sln ├── LICENSE ├── README.md ├── assets ├── angry.png ├── happy.png ├── neutral.png └── surprised.png ├── azuredeploy.json ├── azuredeploy.parameters.json ├── client ├── index.html ├── package.json └── public │ ├── css │ └── main.css │ └── js │ └── codercards.js ├── host.json └── local.settings.json /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/visualstudiocode,windows,osx,linux,visualstudio,csharp 3 | 4 | ### VisualStudioCode ### 5 | .vscode/* 6 | !.vscode/settings.json 7 | !.vscode/tasks.json 8 | !.vscode/launch.json 9 | 10 | 11 | ### Windows ### 12 | # Windows image file caches 13 | Thumbs.db 14 | ehthumbs.db 15 | 16 | # Folder config file 17 | Desktop.ini 18 | 19 | # Recycle Bin used on file shares 20 | $RECYCLE.BIN/ 21 | 22 | # Windows Installer files 23 | *.cab 24 | *.msi 25 | *.msm 26 | *.msp 27 | 28 | # Windows shortcuts 29 | *.lnk 30 | 31 | 32 | ### OSX ### 33 | *.DS_Store 34 | .AppleDouble 35 | .LSOverride 36 | 37 | # Icon must end with two \r 38 | Icon 39 | 40 | 41 | # Thumbnails 42 | ._* 43 | 44 | # Files that might appear in the root of a volume 45 | .DocumentRevisions-V100 46 | .fseventsd 47 | .Spotlight-V100 48 | .TemporaryItems 49 | .Trashes 50 | .VolumeIcon.icns 51 | .com.apple.timemachine.donotpresent 52 | 53 | # Directories potentially created on remote AFP share 54 | .AppleDB 55 | .AppleDesktop 56 | Network Trash Folder 57 | Temporary Items 58 | .apdisk 59 | 60 | 61 | ### Linux ### 62 | *~ 63 | 64 | # temporary files which can be created if a process still has a handle open of a deleted file 65 | .fuse_hidden* 66 | 67 | # KDE directory preferences 68 | .directory 69 | 70 | # Linux trash folder which might appear on any partition or disk 71 | .Trash-* 72 | 73 | 74 | ### Csharp ### 75 | ## Ignore Visual Studio temporary files, build results, and 76 | ## files generated by popular Visual Studio add-ons. 77 | 78 | # User-specific files 79 | *.suo 80 | *.user 81 | *.userosscache 82 | *.sln.docstates 83 | 84 | # User-specific files (MonoDevelop/Xamarin Studio) 85 | *.userprefs 86 | 87 | # Build results 88 | [Dd]ebug/ 89 | [Dd]ebugPublic/ 90 | [Rr]elease/ 91 | [Rr]eleases/ 92 | x64/ 93 | x86/ 94 | bld/ 95 | [Bb]in/ 96 | [Oo]bj/ 97 | [Ll]og/ 98 | 99 | # Visual Studio 2015 cache/options directory 100 | .vs/ 101 | # Uncomment if you have tasks that create the project's static files in wwwroot 102 | #wwwroot/ 103 | 104 | # MSTest test Results 105 | [Tt]est[Rr]esult*/ 106 | [Bb]uild[Ll]og.* 107 | 108 | # NUNIT 109 | *.VisualState.xml 110 | TestResult.xml 111 | 112 | # Build Results of an ATL Project 113 | [Dd]ebugPS/ 114 | [Rr]eleasePS/ 115 | dlldata.c 116 | 117 | # DNX 118 | project.lock.json 119 | project.fragment.lock.json 120 | artifacts/ 121 | 122 | *_i.c 123 | *_p.c 124 | *_i.h 125 | *.ilk 126 | *.meta 127 | *.obj 128 | *.pch 129 | *.pdb 130 | *.pgc 131 | *.pgd 132 | *.rsp 133 | *.sbr 134 | *.tlb 135 | *.tli 136 | *.tlh 137 | *.tmp 138 | *.tmp_proj 139 | *.log 140 | *.vspscc 141 | *.vssscc 142 | .builds 143 | *.pidb 144 | *.svclog 145 | *.scc 146 | 147 | # Chutzpah Test files 148 | _Chutzpah* 149 | 150 | # Visual C++ cache files 151 | ipch/ 152 | *.aps 153 | *.ncb 154 | *.opendb 155 | *.opensdf 156 | *.sdf 157 | *.cachefile 158 | *.VC.db 159 | *.VC.VC.opendb 160 | 161 | # Visual Studio profiler 162 | *.psess 163 | *.vsp 164 | *.vspx 165 | *.sap 166 | 167 | # TFS 2012 Local Workspace 168 | $tf/ 169 | 170 | # Guidance Automation Toolkit 171 | *.gpState 172 | 173 | # ReSharper is a .NET coding add-in 174 | _ReSharper*/ 175 | *.[Rr]e[Ss]harper 176 | *.DotSettings.user 177 | 178 | # JustCode is a .NET coding add-in 179 | .JustCode 180 | 181 | # TeamCity is a build add-in 182 | _TeamCity* 183 | 184 | # DotCover is a Code Coverage Tool 185 | *.dotCover 186 | 187 | # Visual Studio code coverage results 188 | *.coverage 189 | *.coveragexml 190 | 191 | # NCrunch 192 | _NCrunch_* 193 | .*crunch*.local.xml 194 | nCrunchTemp_* 195 | 196 | # MightyMoose 197 | *.mm.* 198 | AutoTest.Net/ 199 | 200 | # Web workbench (sass) 201 | .sass-cache/ 202 | 203 | # Installshield output folder 204 | [Ee]xpress/ 205 | 206 | # DocProject is a documentation generator add-in 207 | DocProject/buildhelp/ 208 | DocProject/Help/*.HxT 209 | DocProject/Help/*.HxC 210 | DocProject/Help/*.hhc 211 | DocProject/Help/*.hhk 212 | DocProject/Help/*.hhp 213 | DocProject/Help/Html2 214 | DocProject/Help/html 215 | 216 | # Click-Once directory 217 | publish/ 218 | 219 | # Publish Web Output 220 | *.[Pp]ublish.xml 221 | *.azurePubxml 222 | # TODO: Comment the next line if you want to checkin your web deploy settings 223 | # but database connection strings (with potential passwords) will be unencrypted 224 | *.pubxml 225 | *.publishproj 226 | 227 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 228 | # checkin your Azure Web App publish settings, but sensitive information contained 229 | # in these scripts will be unencrypted 230 | PublishScripts/ 231 | 232 | # NuGet Packages 233 | *.nupkg 234 | # The packages folder can be ignored because of Package Restore 235 | **/packages/* 236 | # except build/, which is used as an MSBuild target. 237 | !**/packages/build/ 238 | # Uncomment if necessary however generally it will be regenerated when needed 239 | #!**/packages/repositories.config 240 | # NuGet v3's project.json files produces more ignoreable files 241 | *.nuget.props 242 | *.nuget.targets 243 | 244 | # Microsoft Azure Build Output 245 | csx/ 246 | *.build.csdef 247 | 248 | # Microsoft Azure Emulator 249 | ecf/ 250 | rcf/ 251 | 252 | # Windows Store app package directories and files 253 | AppPackages/ 254 | BundleArtifacts/ 255 | Package.StoreAssociation.xml 256 | _pkginfo.txt 257 | 258 | # Visual Studio cache files 259 | # files ending in .cache can be ignored 260 | *.[Cc]ache 261 | # but keep track of directories ending in .cache 262 | !*.[Cc]ache/ 263 | 264 | # Others 265 | ClientBin/ 266 | ~$* 267 | *~ 268 | *.dbmdl 269 | *.dbproj.schemaview 270 | *.jfm 271 | *.pfx 272 | *.publishsettings 273 | node_modules/ 274 | orleans.codegen.cs 275 | 276 | # Since there are multiple workflows, uncomment next line to ignore bower_components 277 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 278 | #bower_components/ 279 | 280 | # RIA/Silverlight projects 281 | Generated_Code/ 282 | 283 | # Backup & report files from converting an old project file 284 | # to a newer Visual Studio version. Backup files are not needed, 285 | # because we have git ;-) 286 | _UpgradeReport_Files/ 287 | Backup*/ 288 | UpgradeLog*.XML 289 | UpgradeLog*.htm 290 | 291 | # SQL Server files 292 | *.mdf 293 | *.ldf 294 | 295 | # Business Intelligence projects 296 | *.rdl.data 297 | *.bim.layout 298 | *.bim_*.settings 299 | 300 | # Microsoft Fakes 301 | FakesAssemblies/ 302 | 303 | # GhostDoc plugin setting file 304 | *.GhostDoc.xml 305 | 306 | # Node.js Tools for Visual Studio 307 | .ntvs_analysis.dat 308 | 309 | # Visual Studio 6 build log 310 | *.plg 311 | 312 | # Visual Studio 6 workspace options file 313 | *.opt 314 | 315 | # Visual Studio LightSwitch build output 316 | **/*.HTMLClient/GeneratedArtifacts 317 | **/*.DesktopClient/GeneratedArtifacts 318 | **/*.DesktopClient/ModelManifest.xml 319 | **/*.Server/GeneratedArtifacts 320 | **/*.Server/ModelManifest.xml 321 | _Pvt_Extensions 322 | 323 | # Paket dependency manager 324 | .paket/paket.exe 325 | paket-files/ 326 | 327 | # FAKE - F# Make 328 | .fake/ 329 | 330 | # JetBrains Rider 331 | .idea/ 332 | *.sln.iml 333 | 334 | # CodeRush 335 | .cr/ 336 | 337 | # Python Tools for Visual Studio (PTVS) 338 | __pycache__/ 339 | *.pyc 340 | 341 | 342 | ### VisualStudio ### 343 | ## Ignore Visual Studio temporary files, build results, and 344 | ## files generated by popular Visual Studio add-ons. 345 | 346 | # User-specific files 347 | *.suo 348 | *.user 349 | *.userosscache 350 | *.sln.docstates 351 | 352 | # User-specific files (MonoDevelop/Xamarin Studio) 353 | *.userprefs 354 | 355 | # Build results 356 | [Dd]ebug/ 357 | [Dd]ebugPublic/ 358 | [Rr]elease/ 359 | [Rr]eleases/ 360 | x64/ 361 | x86/ 362 | bld/ 363 | [Bb]in/ 364 | [Oo]bj/ 365 | [Ll]og/ 366 | 367 | # Visual Studio 2015 cache/options directory 368 | .vs/ 369 | # Uncomment if you have tasks that create the project's static files in wwwroot 370 | #wwwroot/ 371 | 372 | # MSTest test Results 373 | [Tt]est[Rr]esult*/ 374 | [Bb]uild[Ll]og.* 375 | 376 | # NUNIT 377 | *.VisualState.xml 378 | TestResult.xml 379 | 380 | # Build Results of an ATL Project 381 | [Dd]ebugPS/ 382 | [Rr]eleasePS/ 383 | dlldata.c 384 | 385 | # DNX 386 | project.lock.json 387 | project.fragment.lock.json 388 | artifacts/ 389 | 390 | *_i.c 391 | *_p.c 392 | *_i.h 393 | *.ilk 394 | *.meta 395 | *.obj 396 | *.pch 397 | *.pdb 398 | *.pgc 399 | *.pgd 400 | *.rsp 401 | *.sbr 402 | *.tlb 403 | *.tli 404 | *.tlh 405 | *.tmp 406 | *.tmp_proj 407 | *.log 408 | *.vspscc 409 | *.vssscc 410 | .builds 411 | *.pidb 412 | *.svclog 413 | *.scc 414 | 415 | # Chutzpah Test files 416 | _Chutzpah* 417 | 418 | # Visual C++ cache files 419 | ipch/ 420 | *.aps 421 | *.ncb 422 | *.opendb 423 | *.opensdf 424 | *.sdf 425 | *.cachefile 426 | *.VC.db 427 | *.VC.VC.opendb 428 | 429 | # Visual Studio profiler 430 | *.psess 431 | *.vsp 432 | *.vspx 433 | *.sap 434 | 435 | # TFS 2012 Local Workspace 436 | $tf/ 437 | 438 | # Guidance Automation Toolkit 439 | *.gpState 440 | 441 | # ReSharper is a .NET coding add-in 442 | _ReSharper*/ 443 | *.[Rr]e[Ss]harper 444 | *.DotSettings.user 445 | 446 | # JustCode is a .NET coding add-in 447 | .JustCode 448 | 449 | # TeamCity is a build add-in 450 | _TeamCity* 451 | 452 | # DotCover is a Code Coverage Tool 453 | *.dotCover 454 | 455 | # Visual Studio code coverage results 456 | *.coverage 457 | *.coveragexml 458 | 459 | # NCrunch 460 | _NCrunch_* 461 | .*crunch*.local.xml 462 | nCrunchTemp_* 463 | 464 | # MightyMoose 465 | *.mm.* 466 | AutoTest.Net/ 467 | 468 | # Web workbench (sass) 469 | .sass-cache/ 470 | 471 | # Installshield output folder 472 | [Ee]xpress/ 473 | 474 | # DocProject is a documentation generator add-in 475 | DocProject/buildhelp/ 476 | DocProject/Help/*.HxT 477 | DocProject/Help/*.HxC 478 | DocProject/Help/*.hhc 479 | DocProject/Help/*.hhk 480 | DocProject/Help/*.hhp 481 | DocProject/Help/Html2 482 | DocProject/Help/html 483 | 484 | # Click-Once directory 485 | publish/ 486 | 487 | # Publish Web Output 488 | *.[Pp]ublish.xml 489 | *.azurePubxml 490 | # TODO: Comment the next line if you want to checkin your web deploy settings 491 | # but database connection strings (with potential passwords) will be unencrypted 492 | *.pubxml 493 | *.publishproj 494 | 495 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 496 | # checkin your Azure Web App publish settings, but sensitive information contained 497 | # in these scripts will be unencrypted 498 | PublishScripts/ 499 | 500 | # NuGet Packages 501 | *.nupkg 502 | # The packages folder can be ignored because of Package Restore 503 | **/packages/* 504 | # except build/, which is used as an MSBuild target. 505 | !**/packages/build/ 506 | # Uncomment if necessary however generally it will be regenerated when needed 507 | #!**/packages/repositories.config 508 | # NuGet v3's project.json files produces more ignoreable files 509 | *.nuget.props 510 | *.nuget.targets 511 | 512 | # Microsoft Azure Build Output 513 | csx/ 514 | *.build.csdef 515 | 516 | # Microsoft Azure Emulator 517 | ecf/ 518 | rcf/ 519 | 520 | # Windows Store app package directories and files 521 | AppPackages/ 522 | BundleArtifacts/ 523 | Package.StoreAssociation.xml 524 | _pkginfo.txt 525 | 526 | # Visual Studio cache files 527 | # files ending in .cache can be ignored 528 | *.[Cc]ache 529 | # but keep track of directories ending in .cache 530 | !*.[Cc]ache/ 531 | 532 | # Others 533 | ClientBin/ 534 | ~$* 535 | *~ 536 | *.dbmdl 537 | *.dbproj.schemaview 538 | *.jfm 539 | *.pfx 540 | *.publishsettings 541 | node_modules/ 542 | orleans.codegen.cs 543 | 544 | # Since there are multiple workflows, uncomment next line to ignore bower_components 545 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 546 | #bower_components/ 547 | 548 | # RIA/Silverlight projects 549 | Generated_Code/ 550 | 551 | # Backup & report files from converting an old project file 552 | # to a newer Visual Studio version. Backup files are not needed, 553 | # because we have git ;-) 554 | _UpgradeReport_Files/ 555 | Backup*/ 556 | UpgradeLog*.XML 557 | UpgradeLog*.htm 558 | 559 | # SQL Server files 560 | *.mdf 561 | *.ldf 562 | 563 | # Business Intelligence projects 564 | *.rdl.data 565 | *.bim.layout 566 | *.bim_*.settings 567 | 568 | # Microsoft Fakes 569 | FakesAssemblies/ 570 | 571 | # GhostDoc plugin setting file 572 | *.GhostDoc.xml 573 | 574 | # Node.js Tools for Visual Studio 575 | .ntvs_analysis.dat 576 | 577 | # Visual Studio 6 build log 578 | *.plg 579 | 580 | # Visual Studio 6 workspace options file 581 | *.opt 582 | 583 | # Visual Studio LightSwitch build output 584 | **/*.HTMLClient/GeneratedArtifacts 585 | **/*.DesktopClient/GeneratedArtifacts 586 | **/*.DesktopClient/ModelManifest.xml 587 | **/*.Server/GeneratedArtifacts 588 | **/*.Server/ModelManifest.xml 589 | _Pvt_Extensions 590 | 591 | # Paket dependency manager 592 | .paket/paket.exe 593 | paket-files/ 594 | 595 | # FAKE - F# Make 596 | .fake/ 597 | 598 | # JetBrains Rider 599 | .idea/ 600 | *.sln.iml 601 | 602 | # CodeRush 603 | .cr/ 604 | 605 | # Python Tools for Visual Studio (PTVS) 606 | __pycache__/ 607 | *.pyc 608 | 609 | ### VisualStudio Patch ### 610 | build/ 611 | 612 | ### ARM ### 613 | azuredeploy.parameters.json -------------------------------------------------------------------------------- /CardGenerator/function.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": [ 3 | { 4 | "name": "image", 5 | "type": "blobTrigger", 6 | "direction": "in", 7 | "path": "%input-container%/{filename}.jpg", 8 | "connection": "AzureWebJobsDashboard" 9 | }, 10 | { 11 | "type": "blob", 12 | "name": "outputBlob", 13 | "path": "%output-container%/{filename}.jpg", 14 | "connection": "AzureWebJobsDashboard", 15 | "direction": "out" 16 | } 17 | ], 18 | "disabled": false 19 | } -------------------------------------------------------------------------------- /CardGenerator/image-lib.csx: -------------------------------------------------------------------------------- 1 | #r "Newtonsoft.Json" 2 | #r "System.Drawing" 3 | 4 | using System; 5 | using System.IO; 6 | using System.Drawing; 7 | using System.Drawing.Imaging; 8 | 9 | #region POCO definitions 10 | public class Face 11 | { 12 | public FaceRectangle FaceRectangle { get; set; } 13 | public Scores Scores { get; set; } 14 | } 15 | 16 | public class FaceRectangle 17 | { 18 | public int Left { get; set; } 19 | public int Top { get; set; } 20 | public int Width { get; set; } 21 | public int Height { get; set; } 22 | } 23 | 24 | public class Scores 25 | { 26 | public double Anger { get; set; } 27 | public double Contempt { get; set; } 28 | public double Disgust { get; set; } 29 | public double Fear { get; set; } 30 | public double Happiness { get; set; } 31 | public double Neutral { get; set; } 32 | public double Sadness { get; set; } 33 | public double Surprise { get; set; } 34 | } 35 | #endregion 36 | 37 | #region Pixel locations 38 | const int TopLeftFaceX = 85; 39 | const int TopLeftFaceY = 187; 40 | const int FaceRect = 648; 41 | const int NameTextX = 56; 42 | const int NameTextY = 60; 43 | const int TitleTextX = 108; 44 | const int NameWidth = 430; 45 | const int ScoreX = 654; 46 | const int ScoreY = 70; 47 | const int ScoreWidth = 117; 48 | #endregion 49 | 50 | #region Font info 51 | const string FontFamilyName = "Microsoft Sans Serif"; 52 | const int NameFontSize = 38; 53 | const int TitleFontSize = 30; 54 | const short ScoreFontSize = 55; 55 | #endregion 56 | 57 | // This code uses System.Drawing to merge images and render text on the image 58 | // System.Drawing SHOULD NOT be used in a production application 59 | // It is not supported in server scenarios and is used here as a demo only! 60 | public static Image MergeCardImage(Image card, byte[] imageBytes, Tuple personInfo, double score) 61 | { 62 | using (MemoryStream faceImageStream = new MemoryStream(imageBytes)) 63 | { 64 | using (Image faceImage = Image.FromStream(faceImageStream, true)) 65 | { 66 | using (Graphics g = Graphics.FromImage(card)) 67 | { 68 | g.DrawImage(faceImage, TopLeftFaceX, TopLeftFaceY, FaceRect, FaceRect); 69 | RenderText(g, NameFontSize, NameTextX, NameTextY, NameWidth, personInfo.Item1); 70 | RenderText(g, TitleFontSize, NameTextX + 4, TitleTextX, NameWidth, personInfo.Item2); // second line seems to need some left padding 71 | 72 | RenderScore(g, ScoreX, ScoreY, ScoreWidth, score.ToString()); 73 | } 74 | 75 | return card; 76 | } 77 | } 78 | } 79 | 80 | public static void RenderScore(Graphics graphics, int xPos, int yPos, int width, string score) 81 | { 82 | var brush = new SolidBrush(Color.Black); 83 | var font = CreateFont(ScoreFontSize); 84 | SizeF size = graphics.MeasureString(score, font); 85 | 86 | graphics.DrawString(score, font, brush, width - size.Width + xPos, yPos); 87 | } 88 | 89 | private static Font CreateFont(int fontSize) 90 | { 91 | return new Font(FontFamilyName, fontSize, FontStyle.Bold, GraphicsUnit.Pixel); 92 | } 93 | 94 | public static void RenderText(Graphics graphics, int fontSize, int xPos, int yPos, int width, string text) 95 | { 96 | var brush = new SolidBrush(Color.Black); 97 | var font = CreateFont(fontSize); 98 | SizeF size; 99 | 100 | do 101 | { 102 | font = CreateFont(fontSize--); 103 | size = graphics.MeasureString(text, font); 104 | } 105 | while (size.Width > width); 106 | 107 | graphics.DrawString(text, font, brush, xPos, yPos); 108 | } 109 | 110 | // save with higher quality than the default, to avoid jpeg artifacts on the text and numbers 111 | public static void SaveAsJpeg(Image image, Stream outputStream) 112 | { 113 | var jpgEncoder = GetEncoder(ImageFormat.Jpeg); 114 | var qualityEncoder = System.Drawing.Imaging.Encoder.Quality; 115 | var encoderParams = new EncoderParameters(1); 116 | encoderParams.Param[0] = new EncoderParameter(qualityEncoder, 90L); 117 | 118 | image.Save(outputStream, jpgEncoder, encoderParams); 119 | } 120 | 121 | static ImageCodecInfo GetEncoder(ImageFormat format) 122 | { 123 | ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); 124 | foreach (ImageCodecInfo codec in codecs) 125 | { 126 | if (codec.FormatID == format.Guid) 127 | { 128 | return codec; 129 | } 130 | } 131 | return null; 132 | } 133 | 134 | public static double RoundScore(double score) => Math.Round(score * 100); 135 | 136 | public static void NormalizeScores(Scores scores) 137 | { 138 | scores.Anger = RoundScore(scores.Anger); 139 | scores.Happiness = RoundScore(scores.Happiness); 140 | scores.Neutral = RoundScore(scores.Neutral); 141 | scores.Sadness = RoundScore(scores.Sadness); 142 | scores.Surprise = RoundScore(scores.Surprise); 143 | } 144 | -------------------------------------------------------------------------------- /CardGenerator/run.csx: -------------------------------------------------------------------------------- 1 | #load "image-lib.csx" 2 | 3 | using System.Net; 4 | using System.Net.Http; 5 | using System.Net.Http.Headers; 6 | using Newtonsoft.Json; 7 | using System.IO; 8 | using System.Drawing; 9 | using System.Drawing.Imaging; 10 | 11 | private const string EMOTION_API_URI = "https://westus.api.cognitive.microsoft.com/emotion/v1.0/recognize"; 12 | private const string EMOTION_API_KEY_NAME = "EmotionAPIKey"; 13 | private const string ASSETS_FOLDER = "assets"; 14 | 15 | public static async Task Run(byte[] image, string filename, Stream outputBlob, TraceWriter log) 16 | { 17 | string result = await CallEmotionAPI(image); 18 | log.Info(result); 19 | 20 | if (String.IsNullOrEmpty(result)) { 21 | log.Error("No result from Emotion API"); 22 | return; 23 | } 24 | 25 | var imageData = JsonConvert.DeserializeObject(result); 26 | 27 | // hardcoded version if not calling the emotion APIs 28 | // var imageData = new Face[] { 29 | // new Face() { 30 | // FaceRectangle = new FaceRectangle(), 31 | // Scores = new Scores() { Happiness = 1.0 } 32 | // } 33 | // }; 34 | 35 | if (imageData.Length == 0) { 36 | log.Error("No face detected in image"); 37 | return; 38 | } 39 | 40 | double score = 0; 41 | var faceData = imageData[0]; // assume exactly one face 42 | var card = GetCardImageAndScores(faceData.Scores, out score); 43 | 44 | var personInfo = GetNameAndTitle(filename); // extract name and title from filename 45 | MergeCardImage(card, image, personInfo, score); 46 | 47 | SaveAsJpeg(card, outputBlob); 48 | } 49 | 50 | public static Tuple GetNameAndTitle(string filename) 51 | { 52 | string[] words = filename.Split('-'); 53 | 54 | return words.Length > 1 ? Tuple.Create(words[0], words[1]) : Tuple.Create("", ""); 55 | } 56 | 57 | static Image GetCardImageAndScores(Scores scores, out double score) 58 | { 59 | NormalizeScores(scores); 60 | 61 | var cardBack = "neutral.png"; 62 | score = scores.Neutral; 63 | const int angerBoost = 2, happyBoost = 4; 64 | 65 | if (scores.Surprise > 10) { 66 | cardBack = "surprised.png"; 67 | score = scores.Surprise; 68 | } 69 | else if (scores.Anger > 10) { 70 | cardBack = "angry.png"; 71 | score = scores.Anger * angerBoost; 72 | } 73 | else if (scores.Happiness > 50) { 74 | cardBack = "happy.png"; 75 | score = scores.Happiness * happyBoost; 76 | } 77 | 78 | return Image.FromFile(GetFullImagePath(cardBack)); 79 | } 80 | 81 | static async Task CallEmotionAPI(byte[] image) 82 | { 83 | var client = new HttpClient(); 84 | 85 | var content = new StreamContent(new MemoryStream(image)); 86 | var key = Environment.GetEnvironmentVariable(EMOTION_API_KEY_NAME); 87 | 88 | client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", key); 89 | content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); 90 | var httpResponse = await client.PostAsync(EMOTION_API_URI, content); 91 | 92 | if (httpResponse.StatusCode == HttpStatusCode.OK) { 93 | return await httpResponse.Content.ReadAsStringAsync(); 94 | } 95 | 96 | return null; 97 | } 98 | 99 | static string GetFullImagePath(string filename) 100 | { 101 | var path = Path.Combine( 102 | Environment.GetEnvironmentVariable("ROOT"), 103 | Environment.GetEnvironmentVariable("SITE_PATH"), 104 | ASSETS_FOLDER, 105 | filename); 106 | 107 | return Path.GetFullPath(path); 108 | } 109 | -------------------------------------------------------------------------------- /CoderCards.funproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 14.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | 9 | 10 | 11 | 2.0 12 | 1f94c537-f560-4249-bf17-d34674fbe175 13 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 14 | Properties 15 | CoderCards 16 | CoderCards 17 | v4.5.2 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /CoderCards.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{33AD0EE8-D99B-4E14-8CD9-3F56ABEF97A2}") = "CoderCards", "CoderCards.funproj", "{1F94C537-F560-4249-BF17-D34674FBE175}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {1F94C537-F560-4249-BF17-D34674FBE175}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {1F94C537-F560-4249-BF17-D34674FBE175}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {1F94C537-F560-4249-BF17-D34674FBE175}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {1F94C537-F560-4249-BF17-D34674FBE175}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Donna Malayeri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoderCards 2 | 3 | ![Deploy to Azure](http://azuredeploy.net/deploybutton.png) 4 | 5 | ## About the demo 6 | 7 | * The function is triggered when a new .jpg file appears in the container `card-input`. 8 | 9 | * Based on an input image, one of 4 card templates is chosen based on emotion 10 | 11 | * The **filename** of the input image is used to draw the name and title on the card. 12 | 13 | * **Filename must be in the form `Name of person-Title of person.jpg`** 14 | 15 | * A score based on the predominant emotion (e.g., anger, happiness) is drawn on the top 16 | 17 | * The card is written to the output blob container `card-output` 18 | 19 | ## Running the demo 20 | 21 | ### Demo Setup 22 | 23 | 1. Fork the repo into your own GitHub 24 | 25 | 2. Ensure that you've authorized at least one Azure Web App on your subscription to connect to your GitHub account. To learn more, see [Continuous Deployment to Azure App Service](https://azure.microsoft.com/en-us/documentation/articles/app-service-continuous-deployment/). 26 | 27 | 3. Click the Deploy to Azure button above. 28 | 29 | * Enter a sitename **with no dashes**. 30 | 31 | * A storage account will be created with the same site name (and Storage does not allow dashes in account names). 32 | 33 | * Enter the API key from the Cognitive Services Emotion API (https://www.microsoft.com/cognitive-services/en-us/emotion-api) 34 | 35 | 4. Open the Function App you just deployed. Go to Function App settings -> Configure Continuous Integration. In the command bar, select **Disconnect**. 36 | 37 | 5. Close and reopen the Function App. Verify that you can edit code in CardGenerator -> Develop. 38 | 39 | 6. In [Azure Storage Explorer](http://storageexplorer.com/), navigate to the storage account with the same name as your Function App. 40 | 41 | * Create the blob container `card-input` 42 | 43 | * Output from the function will be in `card-output`, but you don't need to create this container explicitly. 44 | 45 | ### Running the demo 46 | 47 | 1. Choose images that are **square** with a filename in the form `Name of person-Title of person.jpg`. The filename is parsed to produce text on the card. 48 | 49 | 2. Drop images into the `card-input` container. Once the function runs, you'll see generated cards in `card-output`. 50 | 51 | ### Notes 52 | 53 | * The demo uses System.Drawing, which is NOT recommended for production apps. To learn more, see [Why you should not use System\.Drawing from ASP\.NET applications](http://www.asprangers.com/post/2012/03/23/Why-you-should-not-use-SystemDrawing-from-ASPNET-applications.aspx). 54 | 55 | * Happy faces get a multiplier of 4, angry gets a multiplier of 2. I encourage you to tweak for maximum comedic effect! 56 | 57 | ### Talking points about Azure Functions 58 | 59 | * The code is triggered off a new blob in a container. We automatically get a binding for both the byte array and the blob name 60 | 61 | * The blob name is used to generate the text on the image 62 | 63 | * The input binding is just a byte array, which makes it easy to manipulate with memory streams (no need to create new ones) 64 | 65 | * Other binding types for C# are Stream, CloudBlockBlob, etc, which is very flexible. 66 | 67 | * The output binding is just a stream that you just write to 68 | 69 | * This is a very declarative model, details of binding are in a separate json file that can be edited manually or through the Integrate UX 70 | 71 | * In general, functions will scale based on the amount of events in input. A real system would probably use something like Azure Queue or Service Bus triggers, in order to track messages more closely. 72 | -------------------------------------------------------------------------------- /assets/angry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lindydonna/CoderCards/497b1e206f56663ca73de41819831dbbc2b6b2db/assets/angry.png -------------------------------------------------------------------------------- /assets/happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lindydonna/CoderCards/497b1e206f56663ca73de41819831dbbc2b6b2db/assets/happy.png -------------------------------------------------------------------------------- /assets/neutral.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lindydonna/CoderCards/497b1e206f56663ca73de41819831dbbc2b6b2db/assets/neutral.png -------------------------------------------------------------------------------- /assets/surprised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lindydonna/CoderCards/497b1e206f56663ca73de41819831dbbc2b6b2db/assets/surprised.png -------------------------------------------------------------------------------- /azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "siteName": { 6 | "type": "string" 7 | }, 8 | "location": { 9 | "type": "string", 10 | "allowedValues": [ 11 | "Brazil South", 12 | "East US", 13 | "East US 2", 14 | "Central US", 15 | "North Central US", 16 | "South Central US", 17 | "West US", 18 | "West US 2" 19 | ], 20 | "defaultValue": "[resourceGroup().location]" 21 | }, 22 | "emotionAPIKey": { 23 | "type": "string" 24 | }, 25 | "repoUrl": { 26 | "type": "string" 27 | }, 28 | "branch": { 29 | "type": "string", 30 | "defaultValue": "master", 31 | "allowedValues": [ 32 | "master", 33 | "demo-start" 34 | ] 35 | } 36 | }, 37 | "variables": { 38 | "appServicePlanName": "[parameters('siteName')]", 39 | "storageAccountName": "[toLower(parameters('siteName'))]", 40 | "storageAccountType": "Standard_LRS", 41 | "storageAccountid": "[concat(resourceGroup().id,'/providers/','Microsoft.Storage/storageAccounts/', variables('storageAccountName'))]", 42 | "storageLocation": "[parameters('location')]" 43 | }, 44 | "resources": [ 45 | { 46 | "type": "Microsoft.Storage/storageAccounts", 47 | "name": "[variables('storageAccountName')]", 48 | "apiVersion": "2015-05-01-preview", 49 | "location": "[variables('storageLocation')]", 50 | "properties": { 51 | "accountType": "[variables('storageAccountType')]" 52 | } 53 | }, 54 | { 55 | "apiVersion": "2015-08-01", 56 | "name": "[variables('appServicePlanName')]", 57 | "type": "Microsoft.Web/serverfarms", 58 | "location": "[parameters('location')]", 59 | "sku": { 60 | "name": "B1", 61 | "tier": "Basic" 62 | }, 63 | "properties": {} 64 | }, 65 | { 66 | "apiVersion": "2015-08-01", 67 | "name": "[parameters('siteName')]", 68 | "type": "Microsoft.Web/sites", 69 | "kind": "functionapp", 70 | "location": "[parameters('location')]", 71 | "dependsOn": [ 72 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]", 73 | "[resourceId('Microsoft.Web/serverfarms', parameters('siteName'))]" 74 | ], 75 | "properties": { 76 | "serverFarmId": "[variables('appServicePlanName')]", 77 | "alwaysOn": true 78 | }, 79 | "resources": [ 80 | { 81 | "apiVersion": "2015-08-01", 82 | "name": "appsettings", 83 | "type": "config", 84 | "dependsOn": [ 85 | "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]", 86 | "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]" 87 | ], 88 | "properties": { 89 | "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]", 90 | "AzureWebJobsDashboard": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageAccountName'), ';AccountKey=', listKeys(variables('storageAccountid'),'2015-05-01-preview').key1)]", 91 | "FUNCTIONS_EXTENSION_VERSION": "~0.5", 92 | "EmotionAPIKey": "[parameters('emotionAPIKey')]" 93 | } 94 | }, 95 | { 96 | "apiVersion": "2015-04-01", 97 | "name": "web", 98 | "type": "sourcecontrols", 99 | "dependsOn": [ 100 | "[resourceId('Microsoft.Web/Sites', parameters('siteName'))]", 101 | "[concat('Microsoft.Web/Sites/', parameters('siteName'), '/config/appsettings')]" 102 | ], 103 | "properties": { 104 | "repoUrl": "[parameters('repoUrl')]", 105 | "branch": "[parameters('branch')]", 106 | "IsManualIntegration": true 107 | } 108 | } 109 | ] 110 | } 111 | ] 112 | } 113 | -------------------------------------------------------------------------------- /azuredeploy.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "siteName": { 6 | "value": "cf-cardGenerator" 7 | }, 8 | "emotionAPIKey": { 9 | "value": "" 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | title 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 |
24 |
25 |

Coder Cards

26 |
27 |
28 |
29 |

1. Upload a picture of yourself

30 |
31 |
32 |
33 | Drop files here or click to upload.
34 |
35 |
36 | 42 |
43 |

2. Your coder profile will appear below

44 |
45 |
46 |
47 | 53 |
54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "run": "live-server", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "dropzone": "^4.3.0" 14 | }, 15 | "devDependencies": { 16 | "live-server":"1.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/public/css/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | min-height: 100%; 3 | } 4 | 5 | body { 6 | min-height: 100%; 7 | margin-left: 50px; 8 | margin-right: 50px; 9 | font-family: Segoe UI,Frutiger,Frutiger Linotype,Dejavu Sans,Helvetica Neue,Arial,sans-serif; 10 | } 11 | 12 | .content { 13 | grid-area: content; 14 | } 15 | 16 | .header { 17 | grid-area: header; 18 | } 19 | 20 | .footer { 21 | grid-area: footer; 22 | } 23 | 24 | h1 { 25 | margin: 0; 26 | } 27 | 28 | .wrapper { 29 | height: 100%; 30 | display: grid; 31 | grid-gap: 10px; 32 | grid-template-rows: auto auto 150px; 33 | grid-template-columns: auto; 34 | grid-template-areas: 35 | "header" 36 | "content" 37 | "footer"; 38 | background-color: #fff; 39 | color: #444; 40 | } 41 | 42 | 43 | .box { 44 | font-size: 150%; 45 | } 46 | 47 | .header, 48 | .footer { 49 | } 50 | 51 | #imageUploader { 52 | border-radius: 5px; 53 | border-style: dashed; 54 | border-color: black; 55 | border-width: 1px; 56 | min-height: 100px; 57 | } 58 | 59 | .dropzone { border: 2px dashed #0087F7; border-radius: 5px; background: white; } 60 | .dropzone .dz-message { font-weight: 400; } 61 | 62 | #images { 63 | display: flex; 64 | justify-content: center; 65 | min-height: 300px; 66 | flex-wrap: wrap; 67 | } 68 | 69 | .card { 70 | margin: 5px; 71 | height: 300px; 72 | width: 216px; 73 | padding: 2px; 74 | border: 2px solid black; 75 | border-radius: 5px; 76 | display: flex; 77 | align-items: center; 78 | justify-content: center; 79 | } 80 | 81 | .loading { 82 | -webkit-animation: loadingAnimation 1s 20 ease; 83 | -moz-animation: loadingAnimation 1s 20 ease; 84 | -o-animation: loadingAnimation 1s 20 ease; 85 | } 86 | 87 | @-webkit-keyframes loadingAnimation { 88 | from { -webkit-transform: rotate(4deg) scale(1) skew(1deg) translate(10px); } 89 | to { -webkit-transform: rotate(360deg) scale(0.795) skew(1deg) translate(0px); } 90 | } 91 | @-moz-keyframes loadingAnimation { 92 | from { -moz-transform: rotate(4deg) scale(1) skew(1deg) translate(10px); } 93 | to { -moz-transform: rotate(360deg) scale(0.795) skew(1deg) translate(0px); } 94 | } 95 | @-o-keyframes loadingAnimation { 96 | from { -o-transform: rotate(4deg) scale(1) skew(1deg) translate(10px); } 97 | to { -o-transform: rotate(360deg) scale(0.795) skew(1deg) translate(0px); } 98 | } 99 | 100 | .pulse { 101 | -webkit-animation: pulseAnimation 1s infinite ease; 102 | -moz-animation: pulseAnimation 1s infinite ease; 103 | -o-animation: pulseAnimation 1s infinite ease; 104 | } 105 | @-webkit-keyframes pulseAnimation { 106 | from { -webkit-transform: rotate(0deg) scale(1) skew(0deg) translate(0px, 10px); } 107 | to { -webkit-transform: rotate(0deg) scale(1.5) skew(0deg) translate(0px, -10px); } 108 | } 109 | @-moz-keyframes pulseAnimation { 110 | from { -moz-transform: rotate(0deg) scale(1) skew(0deg) translate(0px, 10px); } 111 | to { -moz-transform: rotate(0deg) scale(1.5) skew(0deg) translate(0px, -10px); } 112 | } 113 | @-o-keyframes pulseAnimation { 114 | from { -o-transform: rotate(0deg) scale(1) skew(0deg) translate(0px, 10px); } 115 | to { -o-transform: rotate(0deg) scale(1.5) skew(0deg) translate(0px, -10px); } 116 | } 117 | 118 | -------------------------------------------------------------------------------- /client/public/js/codercards.js: -------------------------------------------------------------------------------- 1 | console.log("loading"); 2 | // After DOM is loaded set up Dropzone/etc. 3 | function autorun() { 4 | console.log("Dropzone configured"); 5 | 6 | var myDropzone = new Dropzone("form#dropzone", { 7 | autoQueue: false, 8 | url: (files) => { 9 | return `https://chrande-codercards.azurewebsites.net/upload/${files[0].name}`; 10 | }, 11 | headers: { 12 | "x-ms-blob-type": "BlockBlob", 13 | "Content-Type": "image/jpeg" 14 | }, 15 | clickable: true, 16 | method: "PUT", 17 | init: function () { 18 | this.on("success", function (file, response) { 19 | console.log(file.name); 20 | }); 21 | this.on("addedfile", function (file) { 22 | uploadFile(file); 23 | const that = this; 24 | setTimeout(() => { 25 | that.removeFile(file); 26 | }, 1000); 27 | enqueueCard(file.name); 28 | }); 29 | } 30 | 31 | }); 32 | } 33 | if (window.addEventListener) window.addEventListener("load", autorun, false); 34 | else if (window.attachEvent) window.attachEvent("onload", autorun); 35 | else window.onload = autorun; 36 | 37 | function uploadFiles() { 38 | const files = document.getElementById("fileToUpload").files; 39 | for (const file of files) { 40 | uploadFile(file); 41 | } 42 | } 43 | 44 | function enqueueCard(name) { 45 | const images = document.getElementById("images"); 46 | const imageDiv = document.createElement("div"); 47 | imageDiv.classList.add("card"); 48 | const loading = document.createElement("span"); 49 | loading.classList.add("loading"); 50 | loading.classList.add("fa"); 51 | loading.classList.add("fa-refresh"); 52 | imageDiv.appendChild(loading); 53 | images.appendChild(imageDiv); 54 | imageDiv.id = `image-${name}`; 55 | let interval = setInterval(() => { 56 | console.log("Looking for file"); 57 | getImage(name, (done) => { 58 | if (done) { 59 | console.log("Clearing interval"); 60 | clearInterval(interval); 61 | } 62 | }); 63 | }, 10000); 64 | } 65 | 66 | function uploadFile(file) { 67 | const xhr = new XMLHttpRequest(); 68 | xhr.open("PUT", `https://chrande-codercards.azurewebsites.net/upload/${file.name}`); 69 | xhr.setRequestHeader('x-ms-blob-type', 'BlockBlob'); 70 | xhr.setRequestHeader('Content-Type', 'image/jpeg'); 71 | xhr.send(file); 72 | } 73 | 74 | function getImage(name, cb) { 75 | const xhr = new XMLHttpRequest(); 76 | const imageUrl = `https://chrande-codercards.azurewebsites.net/card/${name}`; 77 | xhr.open("GET", imageUrl); 78 | xhr.responseType = "blob"; 79 | xhr.onload = (e) => { 80 | if (xhr.status != 200) { 81 | console.log("Image does not exist yet"); 82 | return cb(false); 83 | } 84 | let images = document.getElementById(`image-${name}`); 85 | images.innerHTML = ''; 86 | let image = document.createElement('img'); 87 | image.src = imageUrl; 88 | image.setAttribute("height", "300px"); 89 | images.appendChild(image); 90 | console.log("Image created"); 91 | return cb(true); 92 | } 93 | var results = xhr.send(); 94 | } -------------------------------------------------------------------------------- /host.json: -------------------------------------------------------------------------------- 1 | { } -------------------------------------------------------------------------------- /local.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "IsEncrypted": false, 3 | "Values": { 4 | "AzureWebJobsStorage": "", 5 | "AzureWebJobsDashboard": "", 6 | "input-container": "local-card-input", 7 | "output-container": "local-card-output", 8 | "ROOT": ".", 9 | "SITE_PATH": "." 10 | }, 11 | "ConnectionStrings": {} 12 | } --------------------------------------------------------------------------------