Skip to content

Commit

Permalink
新增图片生成图片
Browse files Browse the repository at this point in the history
  • Loading branch information
iMactool committed May 18, 2023
1 parent 14fdef3 commit f94a738
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 34 deletions.
10 changes: 5 additions & 5 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) Hyperf
Copyright (c) imactool <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
41 changes: 37 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
> 我对它进行了一些改造,大部分功能保持了相同。在这里感谢一下 RuliLG ,实现了如此强大好用的 stable-diffusion 组件。
基于 Replicate API 的 Stable Diffusion 实现。
- 🎨 Built-in prompt helper to create better images
- 🚀 Store the results in your database
- 🎇 Generate multiple images in the same API call
- 💯 Supports both (text to image) and (image to image)


鸣谢:原作:[RuliLG](https://github.com/RuliLG),特此鸣谢!

Expand All @@ -22,7 +28,7 @@ php bin/hyperf.php migrate
```
至此,配置完成。

```
```php
return [
'url' => env('REPLICATE_URL', 'https://api.replicate.com/v1/predictions'),
'token' => env('REPLICATE_TOKEN'),
Expand All @@ -35,8 +41,8 @@ return [

## 使用

### 生成
```
### 文字生成图片(Text to Image)
```php
use Imactool\HyperfStableDiffusion\Prompt;
use Imactool\HyperfStableDiffusion\StableDiffusion;

Expand All @@ -52,9 +58,36 @@ use Imactool\HyperfStableDiffusion\StableDiffusion;
)->generate(3);
```

### 图片生成图片(Image to Image)
```php
use Imactool\HyperfStableDiffusion\Prompt;
use Imactool\HyperfStableDiffusion\StableDiffusion;
use Intervention\Image\ImageManager;

//这里使用了 intervention/image 扩展来处理图片文件,你也可以更换为其他的
$sourceImg = (string) (new ImageManager(['driver' => 'imagick']))->make('path/image/source.png')->encode('data-url');

$prompt = 'Petite 21-year-old Caucasian female gamer streaming from her bedroom with pastel pink pigtails and gaming gear. Dynamic and engaging image inspired by colorful LED lights and the energy of Twitch culture, in 1920x1080 resolution.';
$result = StableDiffusion::make()
->converVersion('a991dcab77024471af6a89ef758d98d1a54c5a25fc52a06ccfd7754b7ad04b35')
->withPrompt(
Prompt::make()
->with($prompt)
)
->inputParams('image',$sourceImg)
->inputParams('negative_prompt', 'disfigured, kitsch, ugly, oversaturated, greain, low-res, Deformed, blurry, bad anatomy, disfigured, poorly drawn face, mutation, mutated, extra limb, ugly, poorly drawn hands, missing limb, blurry, floating limbs, disconnected limbs, malformed hands, blur, out of focus, long neck, long body, ugly, disgusting, poorly drawn, childish, mutilated, mangled, old, surreal, calligraphy, sign, writing, watermark, text, body out of frame, extra legs, extra arms, extra feet, out of frame, poorly drawn feet, cross-eye, blurry, bad anatomy')
->inputParams('strength', 0.5)
->inputParams('upscale', 2)
->inputParams('num_inference_steps', 25)
->inputParams('guidance_scale', 7.5)
->inputParams('scheduler', 'EulerAncestralDiscrete')
->generate(1);
```


### 查询结果

```
```php
use Imactool\HyperfStableDiffusion\StableDiffusion;
$freshResults = StableDiffusion::get($replicate_id);

Expand Down
35 changes: 35 additions & 0 deletions src/NegativePrompt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);
/**
* This file is part of the imactool/hyperf-stable-diffusion.
*
* (c) imactool <[email protected]>
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Imactool\HyperfStableDiffusion;

class NegativePrompt
{
public function __construct(
protected string $negative_prompt = ''
) {
}

public static function make(): self
{
return new NegativePrompt();
}

public function with(string $negative_prompt): static
{
$this->negative_prompt = $negative_prompt;
return $this;
}

public function userNegativePrompt(): string
{
return $this->negative_prompt;
}
}
10 changes: 9 additions & 1 deletion src/Prompt.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function with(string $prompt): static
return $this;
}

public function toString(): string
public function toString($inputParams = []): string
{
$prompt = $this->prompt;
if ($this->author) {
Expand All @@ -61,6 +61,14 @@ public function toString(): string
$prompt .= ', ' . implode(', ', array_values(array_unique($this->finishingTouches)));
}

if (! empty($inputParams)) {
$unsetKey = 'image';
if (array_key_exists($unsetKey, $inputParams)) {
unset($inputParams[$unsetKey]);
}
$prompt .= ', ' . implode(', ', array_values(array_unique($inputParams)));
}

return $prompt;
}

Expand Down
144 changes: 120 additions & 24 deletions src/StableDiffusion.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,99 @@
use Hyperf\Context\ApplicationContext;
use Hyperf\Guzzle\ClientFactory;
use Imactool\HyperfStableDiffusion\Models\StableDiffusionResult;
use PDOException;
use Psr\Http\Client\ClientInterface;

class StableDiffusion
{
private array $inputParams = [];

private string $baseUrl = '';

private string $token = '';

private string $version = '';

private function __construct(
public ?Prompt $prompt = null,
private int $width = 512,
private int $height = 512
) {
}

public function converUrl(string $url): self
{
$this->baseUrl = $url;
return $this;
}

public function getBaseUrl(): string
{
if (empty($this->baseUrl)) {
$this->baseUrl = config('stable-diffusion.url');
}
return $this->baseUrl;
}

public function converToken(string $token): self
{
$this->token = $token;
return $this;
}

public function getToken(): string
{
if (empty($this->token)) {
$this->token = config('stable-diffusion.token');
}
return $this->token;
}

public function converVersion(string $version): self
{
$this->version = $version;
return $this;
}

public function getVersion(): string
{
if (empty($this->version)) {
$this->version = config('stable-diffusion.version');
}
return $this->version;
}

public static function make(): self
{
return new self();
}

public function getV2(string $replicateId)
{
$result = StableDiffusionResult::query()->where('replicate_id', $replicateId)->first();
assert($result !== null, 'Unknown id');
$idleStatuses = ['starting', 'processing'];
if (! in_array($result->status, $idleStatuses)) {
return $result;
}

$response = $this->client()->get($result->url);

if ($response->getStatusCode() !== 200) {
throw new Exception('Failed to retrieve data.');
}

$responseData = json_decode((string) $response->getBody(), true);

$result->status = Arr::get($responseData, 'status', $result->status);
$result->output = Arr::has($responseData, 'output') ? Arr::get($responseData, 'output') : null;
$result->error = Arr::get($responseData, 'error');
$result->predict_time = Arr::get($responseData, 'metrics.predict_time');
$result->save();

return $result;
}

public static function get(string $replicateId)
{
$result = StableDiffusionResult::query()->where('replicate_id', $replicateId)->first();
Expand All @@ -51,7 +128,7 @@ public static function get(string $replicateId)
$responseData = json_decode((string) $response->getBody(), true);

$result->status = Arr::get($responseData, 'status', $result->status);
$result->output = Arr::has($responseData,'output') ? Arr::get($responseData, 'output') : null;
$result->output = Arr::has($responseData, 'output') ? Arr::get($responseData, 'output') : null;
$result->error = Arr::get($responseData, 'error');
$result->predict_time = Arr::get($responseData, 'metrics.predict_time');
$result->save();
Expand All @@ -65,6 +142,20 @@ public function withPrompt(Prompt $prompt)
return $this;
}

/**
* except prompt,other API parameters.
*
* @param string $key 参数本身
* @param mixed $value 参数值
*
* @return $this
*/
public function inputParams(string $key, mixed $value)
{
$this->inputParams[$key] = $value;
return $this;
}

public function width(int $width)
{
assert($width > 0, 'Width must be greater than 0');
Expand Down Expand Up @@ -96,15 +187,19 @@ public function generate(int $numberOfImages)
assert($this->prompt !== null, 'You must provide a prompt');
assert($numberOfImages > 0, 'You must provide a number greater than 0');

$input = [
'prompt' => $this->prompt->toString(),
'num_outputs' => $numberOfImages,
];

$input = array_merge($input, $this->inputParams);

$response = $this->client()->post(
config('stable-diffusion.url'),
$this->getBaseUrl(),
[
'json' => [
'version' => config('stable-diffusion.version'),
'input' => [
'prompt' => $this->prompt->toString(),
'num_outputs' => $numberOfImages,
],
'version' => $this->getVersion(),
'input' => $input,
],
]
);
Expand All @@ -114,39 +209,40 @@ public function generate(int $numberOfImages)
$data = [
'replicate_id' => $result['id'],
'user_prompt' => $this->prompt->userPrompt(),
'full_prompt' => $this->prompt->toString(),
'full_prompt' => $this->prompt->toString($this->inputParams),
'url' => $result['urls']['get'],
'status' => $result['status'],
'output' => isset($result['output']) ? $result['output'] : null,
'error' => $result['error'],
'predict_time' => null,
];

try {
StableDiffusionResult::create($data);
}catch (\Exception $exception){
// $msg = $exception->getMessage();
if ($exception instanceof \PDOException) {
$errorInfo = $exception->errorInfo;
$code = $errorInfo[1];
// $sql_state = $errorInfo[0];
// $msg = isset($errorInfo[2]) ? $errorInfo[2] : $sql_state;
}
if ((int) $code !== 1062) {
return $result;
}
}
try {
StableDiffusionResult::create($data);
} catch (Exception $exception) {
$msg = $exception->getMessage();
var_dump(['data insert error' => $msg]);
if ($exception instanceof PDOException) {
$errorInfo = $exception->errorInfo;
$code = $errorInfo[1];
// $sql_state = $errorInfo[0];
// $msg = isset($errorInfo[2]) ? $errorInfo[2] : $sql_state;
}
if ((int) $code !== 1062) {
return $result;
}
}

return $result;
}

private function client(): ClientInterface
{
return ApplicationContext::getContainer()->get(ClientFactory::class)->create([
'base_uri' => config('stable-diffusion.base_uri'),
// 'base_uri' => $this->getBaseUrl(),
// 'timeout' => 10,
'headers' => [
'Authorization' => 'Token ' . config('stable-diffusion.token'),
'Authorization' => 'Token ' . $this->getToken(),
'Accept' => 'application/json',
'Content-Type' => 'application/json',
],
Expand Down

0 comments on commit f94a738

Please sign in to comment.