colinardo
Goto Top

Powershell: Bilder verkleinern oder an bestimmte Größe anpassen

Für alle die es gebrauchen können:
Native Powershell-Funktion zum Anpassen der Größe von Bildern der Formate *.jpg / *.png / *.gif / *.tiff / *.bmp ohne zusätzliche Tools mit .NET inkl. Pipeline-Support.

back-to-topPowershell-CMDLet Resize-Image für das Anpassen der Größe von Bildern.


function Resize-Image{
    param(
        [parameter(mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='A')][parameter(mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='B')][alias("FullName")][string[]]$images,    
        [parameter(mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='A')][parameter(mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,ParameterSetName='B')][alias("DirectoryName")][string]$OutPath,    
        [parameter(mandatory=$true,ParameterSetName='A')][parameter(mandatory=$true,ParameterSetName='B')][int32]$NewWidth,    
        [parameter(mandatory=$true,ParameterSetName='A')][parameter(mandatory=$true,ParameterSetName='B')][int32]$NewHeight,    
        [parameter(mandatory=$false,ParameterSetName='A')][switch]$AppendSizeToFilename,    
        [parameter(mandatory=$true,ParameterSetName='B')][switch]$ReplaceOriginal,    
        [parameter(mandatory=$false)][switch]$DownSizeOnly,
        [parameter(mandatory=$false)][ValidateSet('Original','Scale')][string]$AspectRatio = 'Original',  
        [parameter(mandatory=$false)][switch]$RotateByExifOrientation
    )
    begin{
        Add-Type -AssemblyName System.Drawing
        if($rotatebyexiforientation.IsPresent){
            $shell = New-Object -com Shell.Application
        }
    }
    process{
        foreach($imagepath in $images){
            $finfo = Get-Item -LiteralPath $imagepath
            $newImagePath = ""  
            if ($finfo.Extension -match "\.(bmp|jpe?g|gif|tiff?|png)$"){    
                Try{
                    $img = [System.Drawing.Bitmap]::FromFile($imagepath)
                    $percentWidth = $newWidth / $img.Width
                    $percentHeight = $newHeight / $img.Height
                    if ($percentWidth -lt $percentHeight){$ratio = $percentWidth}else{$ratio = $percentHeight}
                    switch ($aspectratio){
                        'Original' {    
                            [int32]$nWidth = $img.Width * $ratio
                            [int32]$nHeight = $img.Height * $ratio
                        }
                        'Scale' {    
                            [int32]$nWidth = $newWidth
                            [int32]$nHeight = $newHeight
                        }
                    }
                    if ($replaceoriginal.IsPresent -and $ratio -ge 1){
                        continue
                    }
                    if($DownSizeOnly.IsPresent -and $ratio -ge 1){
                        if (!(Test-Path -LiteralPath (join-path $outPath $finfo.Name))){  
                            copy-item -LiteralPath $imagepath -Destination $outPath
                        }else{
                            Write-Warning "Image '$($finfo.Name)' already exists in destination directory, skipping image."  
                        }
                        continue
                    }
                    
                    $newImg = new-Object System.Drawing.Bitmap($nWidth,$nHeight)
                    ([System.Drawing.Graphics]::FromImage($newImg)).DrawImage($img,0,0,$newImg.Width,$newImg.Height)
                    
                    # respect exif orientation information if desired
                    if($rotatebyexiforientation.IsPresent){
                        # get rotation information (https:{{comment_single_line_double_slash:0}}
                        [int]$rotation = $shell.NameSpace($finfo.DirectoryName).ParseName($finfo.Name).ExtendedProperty("{14B81DA1-0135-4D31-96D9-6CBFC9671A99} 274")  
                        # rotate image when needed
                        switch -Regex ($rotation){
                            6 {$newImg.RotateFlip([System.Drawing.RotateFlipType]::Rotate90FlipNone);$oldw = $nWidth; $nWidth = $nHeight; $nHeight = $oldw}
                            8 {$newImg.RotateFlip([System.Drawing.RotateFlipType]::Rotate270FlipNone);$oldw = $nWidth; $nWidth = $nHeight; $nHeight = $oldw}
                            3 {$newImg.RotateFlip([System.Drawing.RotateFlipType]::Rotate180FlipNone)}
                        }
                    }

                    if ($replaceoriginal.IsPresent){
                        $newImagePath = Join-Path $outPath ([IO.Path]::GetRandomFileName())
                        while(Test-Path $newImagePath){
                            $newImagePath = Join-Path $outPath ([IO.Path]::GetRandomFileName())
                        }
                    }elseif($AppendSizeToFilename.IsPresent){
                        $newImagePath = join-path $outPath "$($finfo.Basename)_${nWidth}x${nHeight}$($finfo.Extension)"    
                    }else{
                        $newImagePath = join-path $outPath $finfo.Name
                    }
                    if(!(Test-Path $outPath)){new-item -ItemType Dir -Path $outPath -Force}
                    $newImg.Save($newImagePath,$img.RawFormat)
                    if ($replaceoriginal.IsPresent){
                        $img.Dispose()
                        Remove-Item -LiteralPath $imagepath -Force
                        Rename-Item -LiteralPath $newImagePath -NewName $finfo.Name
                    }
                }catch{
                    Write-Error -Message $_.Exception.Message
                }finally{
                    if ($img){$img.Dispose()}
                    if ($newImg){$newImg.Dispose()}
                }
            }else{
                Write-Error -Message "The image '$imagepath' uses an unsupported format. Only the following image types are supported: bmp|jpg|gif|tif|png"  
            }
        }
    }
    end{
        if ($img){$img.Dispose()}
        if ($newImg){$newImg.Dispose()}
        [System.GC]::Collect()
    }
}
back-to-topAnwendungsbeispiele
back-to-topAlle jpg's eines Ordners unter Beibehaltung des Seitenverhältnisses auf 800x600 Pixel (nur verkleinern) und im Pfad "D:\Bilder\klein" speichern
Get-ChildItem -Path "D:\Bilder\*.jpg" -File | Resize-Image -OutPath "D:\Bilder\klein" -NewWidth 800 -NewHeight 600 -DownSizeOnly
back-to-topAlle jpg's eines Ordners inkl. Unterordner unter Beibehaltung des Seitenverhältnisses auf 800x600 Pixel (nur verkleinern) und im Pfad "D:\Bilder\klein" speichern und dem Namen des Bildes die neue Größe anhängen.
Get-ChildItem -Path "D:\Bilder" -File -Filter *.jpg -recurse | Resize-Image -OutPath "D:\Bilder\klein" -NewWidth 800 -NewHeight 600 -DownSizeOnly -AppendSizeToFileName
back-to-topAlle jpg's und png's eines Ordners inkl. Unterordner unter Beibehaltung des Seitenverhältnisses auf 800x600 Pixel anpassen und im Pfad "D:\Bilder\klein" speichern und dem Namen des Bildes die neue Größe anhängen.
Get-ChildItem -Path "D:\Bilder" -include "*.jpg","*.png" -recurse -file | Resize-Image -OutPath "D:\Bilder\klein" -NewWidth 800 -NewHeight 600 -AppendSizeToFileName
back-to-topEin einzelnes Bild unter Beibehaltung des Seitenverhältnisses auf 320x240 Pixel anpassen und im Pfad "D:\Bilder\klein" speichern.
Resize-Image -images "D:\Bilder\testbild.jpg" -OutPath "D:\Bilder\klein" -NewWidth 320 -NewHeight 240
back-to-topAlle Bilder einer Ordnerstruktur unter Beibehaltung des Seitenverhältnisses auf 320x240 Pixel anpassen und das Original ersetzen.
Get-ChildItem -Path "D:\Bilder" -include "*.jpg","*.png" -recurse -file | Resize-Image -NewWidth 320 -NewHeight 240 -ReplaceOriginal

Der optionale Parameter -AspectRatio dient dazu das Seitenverhältnis des Originals entweder beizubehalten (Wert: Original) oder das Bild auf die Zielgröße zu skalieren (Wert: Scale).

Der optionale switch Parameter -RotateByExifOrientation dient dazu das Bild anhand der Orientierungsinformationen (Rotation 90/180/270°) aus den EXIF-Daten zu rotieren.

Viel Spaß damit
Grüße @colinardo

Content-Key: 268427

Url: https://administrator.de/contentid/268427

Printed on: April 18, 2024 at 04:04 o'clock

Member: SirMank
SirMank Jun 05, 2019 at 11:43:22 (UTC)
Goto Top
Vielen Dank für das Script!
Ich hatte nur das Problem, dass das Bild nicht gefunden wurde. Gelöst habe ich das dadurch, dass ich direkt den FullName übergebe. Damit liefs dann
(Get-ChildItem $Quelle -recurse).FullName| Resize-Image -OutPath $Ziel -NewWidth $MaxBreite -NewHeight $MaxHoehe -DownSizeOnly #Bilder aus Quelle und allen Unterordnern

Grüße
SirMank
Member: colinardo
colinardo Jun 05, 2019, updated at Jun 06, 2019 at 09:43:59 (UTC)
Goto Top
Zitat von @SirMank:

Vielen Dank für das Script!
Ich hatte nur das Problem, dass das Bild nicht gefunden wurde. Gelöst habe ich das dadurch, dass ich direkt den FullName übergebe. Damit liefs dann
(Get-ChildItem $Quelle -recurse).FullName| Resize-Image -OutPath $Ziel -NewWidth $MaxBreite -NewHeight $MaxHoehe -DownSizeOnly #Bilder aus Quelle und allen Unterordnern
Servus @SirMank.
Das ist nicht nötig wenn man mit Get-ChildItem arbeitet, Die Funktion holt sich den vollen Pfad der Bilder automatisch aus den übergebenen Infos von Get-ChildItem aus der Pipeline !
Siehe Parameter Info
[parameter(mandatory=$true,ValueFromPipeline=$true)][alias("FullName")][string[]]$images
Der Alias "FullName" besagt das er sich den Pfad für die Bilder aus der in der Pipeline übergebenen Property FullName holen kann. Das von dir gemachte ist also überflüssig.

Grüße Uwe
Member: SirMank
SirMank Jun 06, 2019 at 09:11:57 (UTC)
Goto Top
Hallo Uwe,
danke für die schnelle Reaktion.
ich hab es direkt noch mal ausprobiert
Get-ChildItem $Quelle -recurse| Resize-Image -OutPath $Ziel -NewWidth $MaxBreite -NewHeight $MaxHoehe -DownSizeOnly

Leider bekomme ich weiterhin die Fehlermeldung
Resize-Image : Ausnahme beim Aufrufen von "FromFile" mit 1 Argument(en): "2019-01-09 09_08_23.png"

Wenn ich direkt den FullName übergebe läuft der Aufruf durch.
Ich habe aber auch den Fullname nicht in den Atributen, wenn ich mir die Ausgabe von GCI an FL übergebe. Ist das vielleicht ein Problem von meinem System?

Grüße
SirMank
Member: colinardo
colinardo Jun 06, 2019 updated at 09:44:31 (UTC)
Goto Top
Ich habe aber auch den Fullname nicht in den Atributen, wenn ich mir die Ausgabe von GCI an FL übergebe.
Das ist normal das wird nur angezeigt wenn du fl * schreibst sonst listet er dir nur einen Default Satz an Attributen.

Es war noch ein kleiner Parameter-Definitionsfehler drin, habe ihn oben im Skript korrigiert, sorry für die Umstände, aber Danke für die Rückmeldung.

Grüße Uwe
Member: SirMank
SirMank Jun 06, 2019 at 11:52:51 (UTC)
Goto Top
Jetzt klappt alles so wie gedacht.
Schon wieder was gelernt :D
Vielen Dank
Member: Demech
Demech Aug 19, 2019 at 11:08:58 (UTC)
Goto Top
Hall colinardo

Besten Dank für dieses tolle Skript.
Eine kleine Frage:
Ich müsste eine grosse Menge von Bildern verarbeiten, welche in einer Ordnungsstruktur abgelegt sind. Haupt Ordner ist Fotos dann das Jahr, Monat und am Schluss dem jeweiligen Ereignis zugeteilt.

Gibt es die Möglichkeit, anstatt die Bilder in einen separaten Ordner komprimiert auszugeben, diese direkt wieder in den entsprechenden Pfad abzulegen und die originale zu löschen?

Ich habe mir schon überlegt, etwas zu schreiben, was dann den Namen, der komprimierten Dateien sucht und ersetzt.
Wenn es jedoch mit -OutPath gleich eine Möglichkeit gäbe, welche ich nicht kenne, wäre natürlich super.

Besten Dank schon mal fürs lesen.
Gruss vom Newbie
Demech
Member: colinardo
colinardo Aug 19, 2019 updated at 13:08:22 (UTC)
Goto Top
Servus @Demech , willkommen auf Administrator.de!
Gibt es die Möglichkeit, anstatt die Bilder in einen separaten Ordner komprimiert auszugeben, diese direkt wieder in den entsprechenden Pfad abzulegen und die originale zu löschen?
Habe den Code des CMDLets oben noch um einen Parameter ergänzt und zusätzlich ein Beispiel eingetragen. Siehe letztes Beispiel:
Get-ChildItem  "D:\Bilder" -include "*.jpg","*.png" -recurse -file | Resize-Image -NewWidth 320 -NewHeight 240 -replaceoriginal  
Das verkleinert im Beispiel alle jpg und png und ersetzt damit die Originale
Grüße Uwe
Member: Demech
Demech Aug 19, 2019 at 13:38:49 (UTC)
Goto Top
Hallo Uwe

Das ist perfekt. Habe es gleich ausprobiert. Funktioniert wunderbar und das beste ist, dass die Qualität der Bilder immer noch sehr gut bleibt.

Nun habe ich noch die Aufgabe vor mir, den Code zu studieren, damit ich auch verstehe, was da gemacht wird und etwas daraus lerne.

Besten Dank
Grüsse Bruno
Member: Tobiko
Tobiko Feb 24, 2020 at 18:31:04 (UTC)
Goto Top
Hi Colinardo

das ist ja ein super Script, vielen Dank.

Ich habe es eben ausprobiert und es hat funktioniert. Mir ist Aufgefallen, dass Bilder im Hochformat nun im Querformat dargestellt werden (gedreht), gibt es eine Möglichkeit dass die Ausrichtung beibehalten wird trotz dem verkleinern?

vielen Dank für deine Hilfe.

Tobi
Member: colinardo
colinardo Feb 24, 2020 updated at 20:22:48 (UTC)
Goto Top
Zitat von @Tobiko:

Hi Colinardo

das ist ja ein super Script, vielen Dank.

Ich habe es eben ausprobiert und es hat funktioniert. Mir ist Aufgefallen, dass Bilder im Hochformat nun im Querformat dargestellt werden (gedreht), gibt es eine Möglichkeit dass die Ausrichtung beibehalten wird trotz dem verkleinern?
Servus Tobi,
die Ausrichtung der Bilder wird im Skript nicht verändert. Die Bilder werden so verarbeitet wie sie "tatsächlich" vorliegen.
Du lässt dich da von deinem Anzeige-Programm täuschen, das vermutlich die Exifdaten der Bilder nur für die Anzeige auswertet und sie so darstellt wie sie aufgenommen wurden (Portrait/Landscape), obwohl sie in Wirklichkeit anders auf der Platte liegen.

Wenn man wollte kann man die Kameradrehung natürlich auch aus den Exif-Daten lesen, kannst du aber auch mit dem Bildbearbeitungsprogramm deiner Wahl einfach dauerhaft auf deine Bilder vorher anwenden.

Werde die Exif Drehung vielleicht bei Zeiten mal ins Skript oben aufnehmen. Habe momentan aber leider zu wenig Zeit für die Spielereien face-wink.

Grüße Uwe
Member: Tobiko
Tobiko Feb 25, 2020 updated at 07:04:46 (UTC)
Goto Top
Hi Uwe,

vielen Dank für deine rasche Rückmeldung.

Ich habe eben eine komprimierte Datei angeschaut in den Details der Eigenschaften im Explorer. Dabei habe ich festgestellt das praktisch alle Werte verloren gingen. Es ist auch kein Wert mehr vorhanden in der EXIF-Version, kein Kameramodell, keine Brennweite usw.

Ich habe zwar nicht viel Ahnung von Programmierung, aber vlt. hast Du mir einen Tipp wie ich das angehen könnte, resp. wo suchen?

Viele Grüsse

Tobi
Member: colinardo
colinardo Feb 25, 2020 updated at 08:05:17 (UTC)
Goto Top
Zitat von @Tobiko:

Ich habe eben eine komprimierte Datei angeschaut in den Details der Eigenschaften im Explorer. Dabei habe ich festgestellt das praktisch alle Werte verloren gingen.
Das ist normal und im o.g. Skript so auch nicht vorgesehen.
Das Skript ist primär dafür gedacht um z.B. Vorschauen für Webseiten usw. zu erstellen und da sind Exif-Daten ja schon aus Datenschutzgründen nicht erwünscht.
Es ist nicht als AllInOne-Tool zu verstehen sondern rein als Verkleinerungsmaschine ohne Schnickschnack.

Persönliche Anpassungen nehme ich aber als Auftrag gerne entgegen.
Member: raffnix
raffnix Jul 25, 2023 at 07:51:40 (UTC)
Goto Top
Wie könnte man das Skript ergänzen, damit das erstell Datum nicht geändert wird?
Member: colinardo
colinardo Jul 25, 2023 updated at 08:34:46 (UTC)
Goto Top
Na das ist ja mal eine nette Anfrage 😐.
Discussion guidelines - The rules for our content

Zwischen Zeile 51 und 52 hinzufügen
(get-Item $newImagePath).CreationTime = (Get-Item $_).CreationTime
Gruß @colinardo
Member: raffnix
raffnix Jul 25, 2023 updated at 11:29:13 (UTC)
Goto Top
Zitat von @colinardo:

Na das ist ja mal eine nette Anfrage 😐.
Discussion guidelines - The rules for our content

Zwischen Zeile 51 und 52 hinzufügen
(get-Item $newImagePath).CreationTime = (Get-Item $_).CreationTime
Gruß @colinardo

Hallo Colinardo,

Sorry dafür. War trotzdem sehr lieb und Nett, das du darauf geantwortet hast. Ich gelobe Besserung´.

Beste Grüße
@raffnix
Member: markushi
markushi Nov 08, 2023 at 18:43:57 (UTC)
Goto Top
Hallo colinardo,

das Skript ist echt genial, vor allem kann man es so individuell einsetzen und so auf fast jeden Einsatzzweck anpassen 👍👏

Was mir noch nicht klar ist, wodurch die Erhaltung des Seitenverhältnisses angegeben wird? Also wenn ich z.B. diese Beispiele vergleiche:
gci "D:\Bilder" -include "*.jpg","*.png" -recurse -file | Resize-Image -OutPath "D:\Bilder\klein" -NewWidth 800 -NewHeight 600 -AppendSizeToFileName  

und

gci "D:\Bilder" -include "*.jpg","*.png" -recurse -file | Resize-Image -NewWidth 320 -NewHeight 240 -replaceoriginal  

Bei dem ersten soll das Seitenverhältnis bestehen bleiben, beim anderen nicht. Welcher Parameter bestimmt das? Oder kommt das von dem replaceoriginal?

Gruß - Markus
Member: colinardo
colinardo Nov 08, 2023 updated at 22:19:29 (UTC)
Goto Top
Zitat von @markushi:
Was mir noch nicht klar ist, wodurch die Erhaltung des Seitenverhältnisses angegeben wird?

Servus Markus,
Freut mich das es dir nützliche Dienste erweist.
Das Seitenverhältnis wird immer beibehalten, es gibt keinen Parameter im Skript dafür, kann ich aber wenn gewünscht gerne noch ergänzen.

#edit# Ein neuer Parameter -aspectratio wurde hinzugefügt, und kann mit den Werten Original oder Scale versehen werden.
Default ohne Angabe des Parameters ist
, das Seitenverhältnis des Originals beizubehalten. Scale staucht oder dehnt das Bild auf die gewünschte Größe.

Grüße Uwe
Member: markushi
markushi Nov 09, 2023 at 06:05:40 (UTC)
Goto Top
Servus Uwe,

vielen Dank 🙏 für die Erweiterung! Damit kann ich echt was anfangen, perfekt 👌👏

Gruß - Markus
Member: Nico1991
Nico1991 Jan 18, 2024 at 07:33:19 (UTC)
Goto Top
Hi,

das Skript funktioniert super, nur das -replaceoriginal funktioniert bei mir nicht wie gewünscht.

Folgende Zeile habe ich zum, Test geschrieben:

gci "C:\Users\nico1991\Desktop\Teileliste\Klein\*.jpg" -File -recurse | Resize-Image -NewWidth 320 -NewHeight 240 -DownSizeOnly -replaceoriginal  

Und dann kommt folgende Fehlermeldung

Rename-Item : Eine Datei kann nicht erstellt werden, wenn sie bereits vorhanden ist.
In Zeile:62 Zeichen:25
+ ...                      Rename-Item $newImagePath -NewName ($finfo.Name)
+                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : WriteError: (C:\Users\nico1991\r2dls2rk.lw5:String) [Rename-Item], IOException
    + FullyQualifiedErrorId : RenameItemIOError,Microsoft.PowerShell.Commands.RenameItemCommand

Was kann ich tun? Vielen Dank für die freundliche Hilfe face-smile
Member: Nico1991
Nico1991 Jan 18, 2024 updated at 08:36:45 (UTC)
Goto Top
Ich bin ein kleines Stück weiter. Die Bilder haben Teilweise "[" und "]" im Namen. Hiermit kann die Remove-Item Funktion nicht umgehen, bzw nur mit einem Zusatz. Weiß jemand, wie es korrekt wäre?
Member: colinardo
colinardo Jan 18, 2024 updated at 09:15:03 (UTC)
Goto Top
Servus @Nico1991, willkommen auf Administrator.de!
Zitat von @Nico1991:
Die Bilder haben Teilweise "[" und "]" im Namen.
Ja das lag daran da das für die Shell sogenannte Glob-Zeichen sind die die Shell für eine Auswahl an Zeichen im Pfad interpretiert, man muss hier dann explizit -LiteralPath für die Pfadangaben benutzen, hatte das bei den "Remove-Item" und "Rename-Item" Befehlen in der Funktion übersehen, sorry ist jetzt oben korrigiert und es sollte nun wie gewünscht auch mit den "[]" Zeichen im Pfad/Namen funktionieren.

Grüße Uwe
Member: Nico1991
Nico1991 Jan 18, 2024 at 09:16:13 (UTC)
Goto Top
Uwe, vielen Dank!

Es funktioniert nun wie gewünscht.
Member: colinardo
colinardo Jan 18, 2024 updated at 09:18:38 (UTC)
Goto Top
Immer gerne 👍. Danke auch für den Hinweis.