Bilder publizieren

“Publizieren” meint hier die Publikation im Internet.

Welches Programm zur Publikation von Bildern auch immer ich probiere, ganz glücklich bin ich meist nicht damit, speziell, weil ich wirklich viele Bilder darzustellen habe und alle diese Programme zu viel Handarbeit erfordern.

Für den Desktop ist die Darstellung verzweigter Bilderordner kein Problem, dafür gibt es Programme genug. Eines der populärsten Programme dafür wäre Adobe Lightbox.

Aber für die Publikation im Internet wird man nicht gut unterstützt, auch dann nicht, wenn man populäre Plugins für WordPress einsetzt. Das populärste Galerieprogramm ist “Nextgen”. Aber groß ist die Enttäuschung, wenn man sieht, dass man jeden Ordner händisch uploaden muss, und dass man die Ordner nicht verschachteln kann. Für kleinere Projekte ist das kein Problem, aber bei größeren Bildermengen stößt man an Grenzen.

Die Originalbilder von der Kamera oder vom Handy sind für die Darstellung im Internet zu groß. Auch dann, wenn man sie verkleinert, muss man sich die Frage stellen, welches eigentlich die Größe sein soll. Man benötigt nämlich sowohl eine kleine Bildgröße bei Seiten mit vielen Vorschaubilder als auch größere Auflösungen für eine Einzelbildbetrachtung.

Bilderordnung – Originale

Die Originalbilder haben meist einen Namen, der Datum und Uhrzeit enthält. Mehrere Bilder eines Bildprojekts werden in einem Ordner gespeichert, dessen Namen über das Thema Aufschluss gibt. Meine Bilder sind in einer Chronik gespeichert, die folgender Systematik folgt:

...
2000=2009
  2000
    2000-01-04 Puchenstuben
    2000-02-06 Salzburg Streiner
    2000-03-00 Martin 40
	...
  2001
  ...
  2009
2010=2019
  2010
  2011
  ...
  2019
2020=2029
  2020
  2021
  2022
  2023
    2023-01-04 Josefstadt Oscar_Wilde
    2023-01-07 Baden Fledermaus
    2023-01-29 Konzerthaus
    2023-03-12 Konzerthaus
    2023-03-20 Was_gibt_es_Neues
	...

Bei der jeweils letzten Dekade kommt man unwillkürlich ins Grübeln, ob man den letzten Ordner – hier 2029 – überhaupt noch erleben wird.

In jedem Jahresordner sind Fotoprojekte, die meist ein gemeinsames Erlebnis mit der Familie dokumentieren.

Bilderordnung – Web-Publikation

Diese Struktur der Originalbilder wird 1:1 auf einem Zielverzeichnis übertragen. Lediglich die Bildprojektordner werden nicht angelegt. Bei Programmierversuchen hat sich nämlich herausgestellt, dass die Unterteilung in Bilderordner ein bisschen unpraktisch ist, weil sich oft nur wenige Bilder in einem solchen Ordner befinden. Daher werden alle Bilder eines Jahres in ein gleichnamiges Zielverzeichnis kopiert, dabei aber der Name so verändert, dass man bei der Darstellung des Bildes den Anlass ablesen kann.

Beispiel für die Originaldateien des Jahres 2023:

2020=2029
  2023
    2023-01-04 Josefstadt Oscar_Wilde
      PXL_20230320_144231536.jpg
      PXL_20230320_145534991.jpg
      PXL_20230320_154037362.jpg
      PXL_20230320_155322727.jpg
      PXL_20230320_155336877.jpg
      PXL_20230320_155344627.jpg
      ...

Die Struktur wird wie folgt verändert:

Alle Bilder eines Jahres werden in einen Jahresordner kopiert. Dem Bildernamen wird das Datum. der der Name des ursprünglich einschließenden Ordners vorangestellt und als Trennzeichen eine Raute eingefügt " # ".

2020=2029
  2023
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_144231536.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_145534991.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_154037362.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_155322727.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_155336877.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_155344627.jpg
    ...

Jedes Bild wird in fünf Größen gespeichert: 100px, 200px, 400px, 800px und 1600px. Zur Unterscheidung wird an den Bildnamen -t100, -t200,- t400, -t800 und -t1600 angehängt.

2020=2029
  2023
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_144231536-t100.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_144231536-t1600.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_144231536-t200.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_144231536-t400.jpg
    2023-01-04 Josefstadt Oscar_Wilde # PXL_20230320_144231536-t800.jpg
    ...

Diese Transformationen werden mit einem Powershell-Programm erledigt.

#Dateiname: SubordnerZusammenfassenUndVerkleinern.ps1
Clear-Host

function CreateImagesYear {

    param ($Year)

    $Decade=([String]$Year).Substring(0,3)
    $Decade=$Decade+"0="+$Decade+"9"
    #Write-Host "$Year $Decade"
    $PathSource = "S:\OneDrive\Chronik\$Decade\$Year\"
    if (!(Test-Path -Path $PathSource)) { Write-Host "No Path: $PathSource"; return }
    $PathDiashow = "S:\OneDrive\@Fiala_Franz\Projekte\fiala.cc\data\Chronik\$Decade\"
    if (!(Test-Path -Path $PathDiashow)) { New-Item -ItemType "directory" -Path $PathDiashow }
    $PathDiashow = "S:\OneDrive\@Fiala_Franz\Projekte\fiala.cc\data\Chronik\$Decade\$Year\"
    if (!(Test-Path -Path $PathDiashow)) { New-Item -ItemType "directory" -Path $PathDiashow }

    $Directories = Get-ChildItem $PathSource -Attributes Directory
    $i = 0
    if (!(Test-Path -Path $PathDiashow)) {
        New-Item $PathDiaShow -ItemType directory
    }
    foreach ($Directory in $Directories) {
        $SubFolderItems = Get-ChildItem $Directory.FullName 
        foreach($Item in $SubFolderItems) {
            if ($Item.Extension -in (".jpg",".png",".jpeg",".bmp")) {
                $PathToImage = $Item.FullName
                $NameExtended = $Directory.Name + " # " + $Item.Name
                $PathExtended = $PathDiashow + "\" + $NameExtended
                # Copy-Item -Path $PathToImage -Destination $PathExtended
                # $Expression = "convert '$PathToImage' -resize 1920x1080 '$PathExtended'"
                #Invoke-Expression $Expression
                $PathThumb = $PathExtended.Replace($Item.Extension,"-t1600"+$Item.Extension)
                $Expression = "convert '$PathToImage' -resize 1600x1600 '$PathThumb'"
                Invoke-Expression $Expression
                $PathThumb = $PathExtended.Replace($Item.Extension,"-t800"+$Item.Extension)
                $Expression = "convert '$PathToImage' -resize 800x800 '$PathThumb'"
                Invoke-Expression $Expression
                $PathThumb = $PathExtended.Replace($Item.Extension,"-t400"+$Item.Extension)
                $Expression = "convert '$PathToImage' -resize 400x400 '$PathThumb'"
                Invoke-Expression $Expression
                $PathThumb = $PathExtended.Replace($Item.Extension,"-t200"+$Item.Extension)
                $Expression = "convert '$PathToImage' -resize 200x200 '$PathThumb'"
                Invoke-Expression $Expression
                $PathThumb = $PathExtended.Replace($Item.Extension,"-t100"+$Item.Extension)
                $Expression = "convert '$PathToImage' -resize 100x100 '$PathThumb'"
                Invoke-Expression $Expression
                Write-Host " $i" -NoNewline
                if ((($i+1) % 10) -eq 0) { Write-Host }
                $i++
                # if ($i -gt 20) { exit }
            }
        }
    }
}
function CreateImagesDecade {
    param ($Decade)

    $From = [int]$Decade.Split("=")[0]
    $To = [int]$Decade.Split("=")[1]
    Write-Host "Von:$From Bis:$To"
    for ($y=$From; $y -le $To; $y++) {
        CreateImagesYear $y
    }
}
CreateImagesDecade "2010=2019"
#CreateImagesYear "2012"

Publikation der Bilder

Bildauswahl

Über das DropDown-Menü wird das Jahr ausgewählt. Derzeit stehen die Jahre 2000 bis 2022 zur Auswahl.

Die ersten 100 Bilder eines Jahres werden angezeigt. Wenn mehr Bilder verfügbar sind, kann man sie über die Vorwärts-Rückwärtsnavigation wählen.

Die Größe der Vorschaubilder kann man in den Stufen “klein”, “mittel” und “groß” einstellen.

Bilderanzeige

Die Bilder werden durch ein JavaScript-Programm angezeigt. Das Programm liest alle Bilder des eingestellten Jahresordners in das Bilder-Array Images und zeigt die verkleinerten Vorschaubilder in Portionen von 100 in den Größen 100px, 200px oder 400 px an. Klickt man auf ein Bild, sieht man die Vorschaugröße 800px. Die Bildgröße 1600px wird derzeit nicht verwendet.

Einzelbildanzeige

Klickt man auf ein Bild, erfährt man Datum un Anlass:

Über die Vorwärts-Rückwärts-Navigation kann man sich chonologisch durch die Bilder bewegen. Über das Menüsymbol erfährt man weitere Details zu dem Bild.

Live-Vorschau

Diese nunmehr 21 Jahre Familienchronik mit etwas 30.000 Bildern gibt es hier zu sehen:

https://franz.fiala.cc/d/fiala/chronik/galerie.htm

Das Programm kann so wie in dieser Adresse verwendet werden, es ist dann aber schwer zu finden. Besser ist es, wenn es in eine bestehende Homepage eingebettet wird. Dazu wird das Programm in einen HTML- und einen JavaScript-Teil zerlegt. Mit einer solchen Zerlegung kann man externe JavaScript-Programm modifizieren, ohne dass dabei an der Webseite etwas geändert werden muss.

Programm

Die Datei galerie.htm besteht aus drei Abschnitten:

<head>..</head>: hier werden praktische Bibliotheken inkludiert (Bootstrap zur formatunabhängigen Ausgabe, Icon-Bibliothek aus den Google-Fonts für die verwendeten Symbole, jquery für die vereinfachte Ansprache von HTML-Objekten).

<body>..</body>: Die Bedienungsobjekte werden in der Bootstrap-Schreibweise definiert. Die Bilder werden durch das JavaScript-Programm in den Abschnitt Images eingefügt.

<script>..</script>: Die Steuerung erfolgt über die Handler $("#sel-year").change und $(".paging").click. Gerufen werden die Funktionen ReadImages() und ShowImages().

Für die Einzelbildanzeige gibt es das Template ImgTemplate.

<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1.0, maximum-scale=2.0, user-scalable=yes">
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet">
	<link href="https://fonts.googleapis.com/css2?family=Material+Icons" rel="stylesheet">
	<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"></script>
	<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
	<style>
		body { background-color:#222; color:white}
		.btn-close-yellow { background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fc0'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat; }
		.slidelarge {background-color:#222;color:#eee }
	</style>
</head>
<body>
	<div class="container-fluid">
		<h1 id="title" title="Chronik Fiala"></h1>

		<div class="btn-group" role="group">
			<select id="sel-year" class="form-select">
				<option>2010</option>
				<option>2011</option>
				<option>2012</option>
				<option>2013</option>
				<option>2014</option>
				<option>2015</option>
				<option>2016</option>
				<option>2017</option>
				<option>2018</option>
				<option>2019</option>
				<option>2020</option>
				<option>2021</option>
				<option selected>2022</option>
			</select>
		</div>
		<div class="btn-group" role="group">
			<label class="btn btn-outline-primary paging" title="" id="paging-prev">
				<span class="material-icons" style="font-size:24pt;padding-top:3pt">arrow_back_ios</span>
			</label>
			
			<label class="btn btn-primary" title="">
				<span id="paging" style="font-size:18pt;padding-top:18pt;color:white;"></span>
			</label>
			
			<label class="btn btn-outline-primary paging" title="" id="paging-next">
				<span class="material-icons" style="font-size:24pt;padding-top:3pt">arrow_forward_ios</span>
			</label>
		</div>			
		<div class="btn-group" role="group">
			<input type="radio" class="btn-check btn-slide" name="btn-slide" id="size100" autocomplete="off">
			<label class="btn btn-outline-primary" for="size100" title="100px">
				<span class="material-icons" style="font-size:12pt;padding-top:9pt">apps</span>
			</label>
			
			<input type="radio" class="btn-check btn-slide" name="btn-slide" id="size200" autocomplete="off" checked>
			<label class="btn btn-outline-primary" for="size200" title="200px">
				<span class="material-icons" style="font-size:18pt;padding-top:6pt">apps</span>
			</label>
			
			<input type="radio" class="btn-check btn-slide" name="btn-slide" id="size400" autocomplete="off">
			<label class="btn btn-outline-primary" for="size400" title="400px">
				<span class="material-icons" style="font-size:24pt;padding-top:3pt">apps</span>
			</label>
		</div>			
		<hr/>
		<div id="Images" class="row" class="align-items-center">
		</div>
		<hr>
		<div>
			Franz Fiala, Siccardsburggasse 4/1/22, 1100 Wien, 0664-1015070, franz@fiala.cc, http://fiala.cc
		</div>
	</div>
	<script>
/* <!-- */
	var ImgTemplate = 
		`<div class="col">
			<div data-bs-toggle="modal" class="slide" 
				data-bs-target="#i#idx#" 
				title="#tit# #dat# #idx#">
				<img src="#thu#" class="thumbnail rounded">	
			</div>
			<div class="modal fade" id="i#idx#" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
				<div class="modal-dialog #mod#">
					<div class="modal-content slidelarge">
					<div class="modal-header">
						<div class="row" style="width:100%">
							<div class="col">
								<span style='font-size:small;font-style:italic;'>#dat# Bild:#idx#</span>
								<h5 class="modal-title">#tit#</h5>
							</div>
							<div class="col" style="text-align:right">
								<span id="m#idx#" class="material-icons more" style="font-size:48pt">more_vert</span>
							</div>
							<div class="col" style="text-align:right;">
								<button type="button" class="btn-close btn-close-yellow" data-bs-dismiss="modal" aria-label="Close"></button>
							</div>
						</div>
					</div>
					<div class="modal-body">
						<div class="col details" id="t#idx#" invisible>
							<table class="table" style="color:white">
								<tr><td>Idx</td><td>#idx#</td></tr>
								<tr><td>Chp</td><td>#chp#</td></tr>
								<tr><td>Dat</td><td>#dat#</td></tr>
								<tr><td>Tit</td><td>#tit#</td></tr>
								<tr><td>Url</td><td>#url#</td></tr>
								<tr><td>Fil</td><td>#fil#</td></tr>
								<tr><td>Ext</td><td>#ext#</td></tr>
							</table>
						</div>
						<div class="container-fluid">
							<div class="position-relative"> 
								<img src="#img#" class="img-fluid rounded">
								<div class="position-absolute top-50 start-0" style="text-align:right">
									<span id="b#idx#" class="material-icons bck" style="font-size:48pt">arrow_back_ios</span>
								</div>
								<div class="position-absolute top-50 end-0" style="text-align:right">
									<span id="f#idx#" class="material-icons fwd" style="font-size:48pt">arrow_forward_ios</span>
								</div>
							</div>
						</div>
					</div>
					</div>
				</div>
			</div>
		</div>`;
	var Year = 0
	var UrlChronicle = "https://franz.fiala.cc"
	var UrlImageDirectory = "https://franz.fiala.cc/d/fiala/chronik/"
	var matches_array = []
	var Images = []
	var Images_Length = 0
	var size_slide = 200
	var size_preview = 800
	const PAGESIZE = 100
	var numb_beg = 0
	var numb_end = 0
	function Image(Chp,Dat,Tit,Url,Fil,Ext) {
		this.Chp = Chp
		this.Dat = Dat
		this.Tit = Tit
		this.Url = Url
		this.Fil = Fil
		this.Ext = Ext
	}
	function setsize_slide(size) {
		$('.slide').css("width",size+"px")
		$('.slide').css("height",size+"px")
	}
	function SetPaging() {
		$("#paging").html(
			(Images_Length==0 ? "" : (numb_beg+1)+"..."+(numb_end+1))
			+ " ("+Images_Length+")"
		)
	}
	function ShowImages() {

		$('#Images').html("")

		Images.forEach (function (Img,idx) {
			
			if ( (idx<numb_beg) || (idx>numb_end) ) return

			var ImageLink = UrlChronicle+Img.Url.replace(Img.Ext,"-t"+size_preview+Img.Ext)  
			var ImageLinkThumb = ImageLink.replace("-t"+size_preview,"-t"+size_slide) 
			var html = ImgTemplate.replace(/#img#/g,ImageLink)
			html = html.replace(/#thu#/g,ImageLinkThumb)
			html = html.replace(/#tit#/g,Img.Tit)
			html = html.replace(/#idx#/g,(idx+1))
			html = html.replace(/#chp#/g,Img.Chp)
			html = html.replace(/#dat#/g,Img.Dat)
			html = html.replace(/#url#/g,Img.Url)
			html = html.replace(/#fil#/g,Img.Fil)
			html = html.replace(/#ext#/g,Img.Ext)
			var mod_mode = ""
			switch (size_preview) {
				case 200: mod_mode = "modal-sm"; break
				case 400: default: mod_mode = ""; break
				case 800: mod_mode = "modal-lg"; break
				case 1600: mod_mode = "modal-xl"; break
			}
			html = html.replace(/#mod#/g,mod_mode)
			$('#Images').append(html)
		})
		$(".details").hide()
		setsize_slide(size_slide)
		$(".fwd").click(function(){
			var id=this.id.substr(1)*1
			if (id==Images_Length-1) return
			$("#i"+id).modal('hide')
			$("#i"+(id+1)).modal('show')
		})
		$(".bck").click(function(){
			var id=this.id.substr(1)*1
			if (id==0) return
			$("#i"+id).modal('hide')
			$("#i"+(id-1)).modal('show')
		})
		$(".more").click(function(){
			var id=this.id.substr(1)*1
			if ($("#t"+id).is(":visible")) { $(".details").hide() }
			else { $(".details").show() }
		})
		$(".btn-slide").change(function(){
			var size = this.id.replace("size","")*1
			size_slide = size
			ShowImages()
		})
		$(".btn-preview").change(function(){
			var size = this.id.replace("prev","")*1
			size_preview = size
			ShowImages()
		})
		const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
		const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
	}
	function ReadImages() {
		while (Images.length>0) Images.pop()
		var Decade = Year.toString().substr(0,3)
		Decade = Decade + "0=" + Decade + "9"
		$.get(UrlImageDirectory + Decade + "/" + Year + "/", function (result) {
			var regexp = new RegExp(/<A[^<]+?#.+?A>/gi)
			matches_array = result.match(regexp)
			if (matches_array!=null) {
				var Length = matches_array.length
				for (var i=0; i<Length; i++) {
					var Img = matches_array[i]
					if (!Img.includes("-t100")) continue
					Img = Img.replace("-t100","")
					var Chp = Img.match(/>(.+) #/)[1]
					var Dat = Chp.match(/([^ ]+) /)[1]
					var Tit = Chp.match(/ (.+)/)[1]
					var Url = Img.match(/HREF="(.+)"/)[1]
					var Fil = Img.match(/>(.+)</)[1]
					var Ext = Fil.substr(Fil.length-4)
					Images.push(new Image(Chp,Dat,Tit,Url,Fil,Ext))
				}
			}
			Images_Length = Images.length
			numb_beg = 0
			numb_end = Images_Length<PAGESIZE ? Images_Length : PAGESIZE-1
			if (Images_Length<=100) $("#numb_block").hide()
			else {
				$("#numb_block").show()
				SetPaging()
			}
			ShowImages()
		});
	}
	$("#sel-year").change(function(){
		Year = $("#sel-year option:selected").text()*1
		ReadImages()
	})
	$(".paging").click(function(){
		if (Images_Length<=PAGESIZE) {
			$(".paging").hide()
			return
		}
		var id=this.id
		switch (id) {
			case "paging-prev":
				if (numb_beg>0) {
					numb_beg = numb_beg - PAGESIZE
					numb_end = numb_end - PAGESIZE
				}
				break;
			case "paging-next":
				if (numb_end<Images_Length) {
					numb_beg = numb_beg + PAGESIZE
					numb_end = numb_end + PAGESIZE
				}
			break;
		}
		SetPaging()
		ShowImages()
	})
	Year = $("#sel-year option:selected").text()*1
	ReadImages()
	/* --> */
	</script>
</body>
</html>
Zur Werkzeugleiste springen