Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a button to create pictures from the current scene #532

Merged
merged 3 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/phoenix-event-display/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"dependencies": {
"@tweenjs/tween.js": "^17.4.0",
"dat.gui": "^0.7.9",
"html2canvas": "^1.4.1",
"jsroot": "github:9inpachi/jsroot#7.2.0-types",
"jszip": "^3.10.1",
"stats-js": "^1.0.1",
Expand Down
19 changes: 19 additions & 0 deletions packages/phoenix-event-display/src/event-display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,25 @@ export class EventDisplay {
this.enableKeyboardControls();
}

/**
* Takes a screen shot of the current view
* @param width the width of the picture to be created
* @param height the height of the picture to be created
* @param fitting the type of fitting to use in case width and height
* ratio do not match the current screen ratio. Posible values are
* - Crop : current view is cropped on both side or up and done to fit ratio
* thus it is not streched, but some parts are lost
* - Strech : current view is streched to given format
* this is the default and used also for any other value given to fitting
*/
public makeScreenShot(
width: number,
height: number,
fitting: string = 'Strech'
) {
this.graphicsLibrary.makeScreenShot(width, height, fitting);
}

/**
* Initialize XR.
* @param xrSessionType Type of the XR session. Either AR or VR.
Expand Down
120 changes: 120 additions & 0 deletions packages/phoenix-event-display/src/managers/three-manager/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {
MeshBasicMaterial,
Euler,
PerspectiveCamera,
Vector2,
} from 'three';
import html2canvas from 'html2canvas';
import { Configuration } from '../../lib/types/configuration';
import { ControlsManager } from './controls-manager';
import { RendererManager } from './renderer-manager';
Expand Down Expand Up @@ -755,6 +757,124 @@ export class ThreeManager {
this.animationsManager.animateClippingWithCollision(tweenDuration, onEnd);
}

saveBlob = (function () {
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
return function saveData(blob, fileName) {
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
};
})();

/**
* Takes a screen shot of the current view
* @param width the width of the picture to be created
* @param height the height of the picture to be created
* @param fitting the type of fitting to use in case width and height
* ratio do not match the current screen ratio. Posible values are
* - Crop : current view is cropped on both side or up and done to fit ratio
* thus it is not streched, but some parts are lost
* - Strech : current view is streched to given format
* this is the default and used also for any other value given to fitting
*/
public makeScreenShot(
width: number,
height: number,
fitting: string = 'Strech'
) {
// compute actual size of screen shot, based on current view and reuested size
const mainRenderer = this.rendererManager.getMainRenderer();
var originalSize = new Vector2();
mainRenderer.getSize(originalSize);
var scaledHeight = height;
var scaledWidth = width;
if (fitting == 'Crop') {
// Massage width and height so that we keep the screen ratio
// and thus the image from the screen is not streched
if (originalSize.width * height < originalSize.height * width) {
scaledHeight = (originalSize.height * width) / originalSize.width;
} else {
scaledWidth = (originalSize.width * height) / originalSize.height;
}
}
const heightShift = (scaledHeight - height) / 2;
const widthShift = (scaledWidth - width) / 2;

// get background color to be used
var bkgColor = getComputedStyle(document.body).getPropertyValue(
'--phoenix-background-color'
);

// grab output canvas on which we will draw, and set size
var outputCanvas = document.getElementById(
'screenshotCanvas'
) as HTMLCanvasElement;
outputCanvas.width = width;
outputCanvas.height = height;
var ctx = outputCanvas.getContext('2d');
ctx.fillStyle = bkgColor;
ctx.fillRect(0, 0, width, height);
// draw main image on our output canvas, with right size
mainRenderer.setSize(scaledWidth, scaledHeight, false);
this.render();
ctx.drawImage(
mainRenderer.domElement,
widthShift,
heightShift,
width,
height,
0,
0,
width,
height
);
mainRenderer.setSize(originalSize.width, originalSize.height, false);
this.render();

// Get info panel
const infoPanel = document.getElementById('experimentInfo');
if (infoPanel != null) {
// Compute size of info panel on final picture
const infoHeight =
(infoPanel.clientHeight * scaledHeight) / originalSize.height;
const infoWidth =
(infoPanel.clientWidth * scaledWidth) / originalSize.width;

// Add info panel to output. This is HTML, so first convert it to canvas,
// and then draw to our output canvas
html2canvas(infoPanel, {
backgroundColor: bkgColor,
// avoid cloning canvas in the main page, this is useless and leads to
// warnings in the javascript console similar to this :
// "Unable to clone WebGL context as it has preserveDrawingBuffer=false"
ignoreElements: (element: Element) => element.tagName == 'CANVAS',
}).then((canvas) => {
canvas.toBlob((blob) => {
ctx.drawImage(
canvas,
infoHeight / 6,
infoHeight / 6,
infoWidth,
infoHeight
);
// Finally save to png file
outputCanvas.toBlob((blob) => {
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
const url = window.URL.createObjectURL(blob);
a.href = url;
a.download = `screencapture.png`;
a.click();
});
});
});
}
}

/**
* Initialize the VR session.
* @param xrSessionType Type of the XR session. Either AR or VR.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
<app-vr-toggle></app-vr-toggle>
<app-ar-toggle></app-ar-toggle>
<app-ss-mode></app-ss-mode>
<app-make-picture></app-make-picture>
<app-io-options
[eventDataImportOptions]="eventDataImportOptions"
></app-io-options>
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { OverlayModule } from '@angular/cdk/overlay';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatRadioModule } from '@angular/material/radio';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSliderModule } from '@angular/material/slider';
import { MatTooltipModule } from '@angular/material/tooltip';
Expand Down Expand Up @@ -50,6 +52,7 @@ import {
VrToggleComponent,
ArToggleComponent,
SSModeComponent,
MakePictureComponent,
PerformanceToggleComponent,
ShareLinkComponent,
ShareLinkDialogComponent,
Expand Down Expand Up @@ -102,6 +105,7 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
VrToggleComponent,
ArToggleComponent,
SSModeComponent,
MakePictureComponent,
PerformanceToggleComponent,
LoaderComponent,
ShareLinkComponent,
Expand All @@ -125,7 +129,9 @@ const PHOENIX_COMPONENTS: Type<any>[] = [
MatButtonModule,
MatTooltipModule,
OverlayModule,
FormsModule,
MatMenuModule,
MatRadioModule,
MatSliderModule,
MatSlideToggleModule,
MatCheckboxModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './vr-toggle/vr-toggle.component';
export * from './ar-toggle/ar-toggle.component';
export * from './zoom-controls/zoom-controls.component';
export * from './ss-mode/ss-mode.component';
export * from './make-picture/make-picture.component';
export * from './performance-toggle/performance-toggle.component';
export * from './ui-menu.component';
export * from './share-link/share-link.component';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<mat-menu class="mat-menu" #menu>
<button mat-menu-item (click)="$event.stopPropagation()" class="size-input">
<label>width</label>
<input
#width
mapInput
type="number"
[value]="3840"
(change)="setWidth(width.value)"
(click)="$event.stopPropagation()"
/>
</button>
<button mat-menu-item (click)="$event.stopPropagation()" class="size-input">
<label>height</label>
<input
#height
mapInput
type="number"
[value]="2610"
(change)="setHeight(height.value)"
(click)="$event.stopPropagation()"
/>
</button>
<button mat-menu-item (click)="$event.stopPropagation()">
<mat-radio-group [(ngModel)]="fitting" class="fitting-radios">
<mat-radio-button *ngFor="let fitting of fittings" [value]="fitting"
>{{ fitting }}
</mat-radio-button>
</mat-radio-group>
</button>
<button mat-menu-item class="make-picture" (click)="makePicture()">
Create picture
</button>
</mat-menu>
<app-menu-toggle
[matMenuTriggerFor]="menu"
tooltip="Creates a picture from the current view: directly saves a png of arbitrary size from the current view"
icon="png"
>
</app-menu-toggle>
<div>
<canvas hidden id="screenshotCanvas"></canvas>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.size-input {
background: transparent;
color: var(--phoenix-text-color);
text-align: center;
}

.size-input label {
width: 60px;
}

.size-input input {
background: transparent;
color: var(--phoenix-text-color);
width: 80px;
}

.make-picture {
text-align: center;
display: block;
}

.make-picture span {
padding: 5px 25px;
border: 2px solid var(--phoenix-text-color);
border-radius: 8px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PhoenixUIModule } from '../../phoenix-ui.module';

import { MakePictureComponent } from './make-picture.component';

describe('MakePictureComponent', () => {
let component: MakePictureComponent;
let fixture: ComponentFixture<MakePictureComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [PhoenixUIModule],
}).compileComponents();

fixture = TestBed.createComponent(SSModeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
component.ngOnInit();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should call toggleCrop on slide change', () => {
const componentDebug = fixture.debugElement;
const slider = componentDebug.query(By.directive(MatSlideToggle));
spyOn(component, 'toggleCrop'); // set your spy
slider.triggerEventHandler('change', null); // triggerEventHandler
expect(component.toggleCrop).toHaveBeenCalled(); // event has been called
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { EventDisplayService } from '../../../services/event-display.service';

@Component({
selector: 'app-make-picture',
templateUrl: './make-picture.component.html',
styleUrls: ['./make-picture.component.scss'],
encapsulation: ViewEncapsulation.None,
})
export class MakePictureComponent implements OnInit {
fittings: string[] = ['Crop', 'Stretch'];
fitting: string = 'Crop';
width: number = 3840;
height: number = 2160;
constructor(private eventDisplay: EventDisplayService) {}
ngOnInit() {}
setWidth(value) {
this.width = value;
}
setHeight(value) {
this.height = value;
}

makePicture() {
this.eventDisplay.makeScreenShot(this.width, this.height, this.fitting);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<app-menu-toggle
tooltip="Screenshot mode"
tooltip="Screenshot mode: phoenix will become fullscreen and the menu is suppressed"
[active]="ssMode"
icon="ss-mode"
(click)="toggleSSMode()"
Expand Down
Loading