---
url: https://nsh.dev/ru/docs/images-and-assets
description: "Отображение изображений в Rust и TSX: ресурсы приложения и файлы @2x, SVG-изображения, удалённые URL-адреса, режимы изменения размера, заглушки, размытие, тонирование, события загрузки, а также получение размера изображения и его предварительная загрузка."
---

# Изображения и ресурсы

Элемент image (`image(..)` в Rust, `<Image>` в TSX) показывает растровое изображение или SVG-рисунок: файл, поставляемый вместе с приложением, или изображение, загруженное по URL. Изображения читаются и декодируются вне главного потока, уменьшаются до размера, в котором их показывает представление, и кэшируются; главный поток только отображает уже декодированный результат.

```rust
const LOGO: ImageAsset = asset!("logo@2x.png");

image(LOGO)
image("https://picsum.photos/seed/nsh/600/400").w_full().h(px(200.0)).rounded(px(12.0))
```

```tsx
import { Image } from "nsh";
import logo from "./assets/logo@2x.png";

<Image src={logo} />
<Image src="https://picsum.photos/seed/nsh/600/400" style={{ width: "100%", height: 200, borderRadius: 12 }} />
```

Свойства элемента перечислены на странице [image](https://nsh.dev/translations/ru/elements/image.md).

<a id="images-in-the-app"></a>

## Изображения в приложении

Rust-крейт хранит свои файлы в каталоге `assets/`: `asset!("logo@2x.png")` (путь относительно `assets/`) является `ImageAsset`, `{ url, size, scales }`, читаемым при компиляции крейта, а `registry.assets(assets!())` в функции `#[native_module]` крейта поставляет вместе с приложением все файлы каталога. В TSX файл изображения импортируется как модуль: импорт даёт `ImageAsset`, `{ url, size, scales? }`, а сборщик поставляет файл вместе с приложением:

```rust
const LOGO: ImageAsset = asset!("logo@2x.png");

// LOGO.url: "asset:/@my-app/logo@2x.png", LOGO.size: 64 × 64 points

#[native_module("app/native")]
fn native(registry: &mut Registry) {
    registry.assets(assets!());
}
```

```tsx
import logo from "./assets/logo@2x.png";

logo; // { url: "asset:/assets/logo@2x.png", size: { width: 64, height: 64 } }
```

Поддерживаемые файлы: `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.heic` и `.svg` ([SVG-изображения](#svg-images)).

**Размеры указываются в пунктах.** Суффикс `@2x` или `@3x` в имени файла означает, что в файле 2 или 3 пикселя на пункт, поэтому `logo@2x.png` размером 128×128 пикселей — это ресурс 64×64 пункта. Без суффикса один пиксель равен одному пункту. Файл, имя которого содержит суффикс, показывается как есть; файл без суффикса показывает свои [варианты для разной плотности пикселей](#pixel-densities), если они есть.

**Естественный размер.** Изображение из ресурсов размещается в своём естественном размере, если стиль оставляет ширину или высоту незаданной, поэтому для `image(LOGO)` и `<Image src={logo} />` стиль не нужен. Чтобы задать другой размер, укажите одно измерение и соотношение сторон либо оба измерения.

В Rust отсутствующий или повреждённый файл приводит к ошибке сборки, а изменённый файл вызывает пересборку крейта. В TSX ресурсы разрешаются относительно импортирующего модуля, а их `url` отсчитывается от корня приложения (каталога точки входа). `nsh build` копирует их в `out/assets/`, `nsh package` помещает их в приложение, а во время разработки их отдаёт сервер разработки.

> [!TIP]
> В TSX `src` также принимает строки `asset:/path`, но гарантированно поставляются только импортированные файлы. Путь, который никогда не импортируется, работает с сервером разработки, который отдаёт весь каталог приложения, но отсутствует в упакованных приложениях. Импортируйте каждое используемое изображение. Rust-крейт поставляет весь свой каталог `assets/` через `assets!()`.

<a id="pixel-densities"></a>

### Плотность пикселей

Назовите файл без суффикса и положите рядом его версии с большей плотностью, и приложение загрузит ту, что подходит экрану:

```text
assets/
├── logo.png       64×64 pixels
├── logo@2x.png    128×128
└── logo@3x.png    192×192
```

```rust
const LOGO: ImageAsset = asset!("logo.png");

// LOGO.size: 64 × 64 points, LOGO.scales: [1.0, 2.0, 3.0]
```

```tsx
import logo from "./assets/logo.png";

logo; // { url: "asset:/assets/logo.png", size: { width: 64, height: 64 }, scales: [1, 2, 3] }
```

- `asset!` и сборщик ищут соседние файлы `@1.5x`, `@2x`, `@3x` и `@4x` для изображения, названного без суффикса. `scales` перечисляет их, причём 1 соответствует файлу с основным именем; у изображения без соседних файлов масштабов нет (в TSX оно импортируется как `{ url, size }`). Сборщик поставляет найденные соседние файлы, а `assets!()` поставляет весь каталог.
- Изображение загружает наименьший масштаб, не меньший коэффициента пикселей экрана, а если такого нет — наибольший, как выбирает React Native: `logo.png` на экране 1×, `logo@2x.png` на экране 2× и `logo@3x.png` на телефоне 3× (и при 2.625). В вебе загружается только этот файл.
- Когда коэффициент пикселей меняется (окно перенесено на другой экран, масштабирование в браузере), изображения, для которых выбор меняется, загружаются заново, с их [событиями загрузки](#load-events). Их размер остаётся размером файла с основным именем, в пунктах.
- Вариант, размер которого в пикселях не равен размеру файла с основным именем, умноженному на масштаб, даёт предупреждение сборки в TSX и приводит к ошибке сборки в Rust.
- Файл с суффиксом плотности (`./logo@3x.png`, `asset!("logo@3x.png")`) используется сам по себе.
- [SVG-изображение](#svg-images) отрисовывается для экрана при любом коэффициенте, поэтому вариантов у него нет.

Достаточно и одного файла: экспортируйте его в максимальной нужной плотности (`@3x` для максимальной чёткости на любом телефоне) и назовите с суффиксом; при декодировании изображения уменьшаются до размера экрана. Варианты позволяют каждому экрану загружать, декодировать и хранить тот файл, который ему подходит.

<a id="svg-images"></a>

## SVG-изображения

Импортируйте файл `.svg` как любое другое изображение. Его размер в пунктах задаётся атрибутами `width` и `height` (или одним из них, а другой вычисляется из соотношения сторон `viewBox`), иначе — `viewBox`: CSS-пиксель SVG равен пункту, а суффиксы `@2x` оставляют SVG-файлы без изменений.

```tsx
import logo from "./assets/logo.svg"; // <svg width="120" height="40" …>
import star from "./assets/star.svg"; // <svg viewBox="0 0 24 24" …>

logo; // { url: "asset:/assets/logo.svg", size: { width: 120, height: 40 } }

<Image src={logo} />
<Image src={star} style={{ width: 36, height: 36, tintColor: "#0969da" }} />
```

SVG чёткий в том размере, в котором показывается. Браузер рисует его как векторную графику; iOS, macOS и Android рисуют его в размере, в котором его показывает представление, в пикселях, когда изображение загружается (как и при уменьшении растровых изображений; представление, которое потом увеличивается, масштабирует этот рисунок до следующей загрузки изображения); Windows, Linux и GPUI рисуют его один раз с четырьмя пикселями на пункт (до 2048 пикселей по стороне) и масштабируют для каждого представления. Всё остальное работает как для растровых изображений: `objectFit`, `tintColor` (через непрозрачные фигуры SVG, поэтому одноцветные значки следуют теме), фильтры, `blurRadius`, заглушки, события загрузки, `Image.getSize` и `Image.prefetch`. `onLoad` и `Image.getSize` сообщают размер SVG в целых пунктах; изображение размещается в точном размере. Удалённые SVG-файлы загружаются так же, через те же загрузки и проверки [capability](https://nsh.dev/translations/ru/docs/sandbox-and-capabilities.md); NSH распознаёт SVG по содержимому (в том числе сжатому gzip, `.svgz`), независимо от того, чем заканчивается его URL.

**Что рисуют нативные платформы.** iOS, macOS, Android, Windows, Linux и GPUI рисуют SVG одним и тем же средством отрисовки (resvg), поэтому рисунок выглядит одинаково везде: фигуры и контуры, заливки и обводки, градиенты и узоры, контуры обрезки и маски, прозрачность, фильтры и преобразования. Текст (`<text>`), анимация (SMIL и CSS), встроенные изображения (`<image>`) и HTML (`<foreignObject>`) отображаются в браузере; нативные платформы рисуют остальное содержимое SVG статично, а `nsh build` называет файлы, которые их используют. Чтобы получить одинаковый результат везде, экспортируйте текст в контуры.

**Самодостаточная картинка.** На нативных платформах SVG рисует только то, что в нём содержится: файлы, URL, изображения `data:` и другие документы, на которые он ссылается, пропускаются. Нативные платформы рисуют SVG, в том числе удалённые, в пределах, которые удерживают память под контролем: документ до 16 МиБ (сжатый — после распаковки), рисунок до 8192 пикселей по стороне и до 4096 × 4096 пикселей в целом (64 МиБ; более крупный рисуется меньшего размера), а слои (группы с прозрачностью, масками, контурами обрезки и фильтрами, которые средство отрисовки рисует отдельно) — до 256 МиБ одновременно. SVG, превышающий предел документа или слоёв, не загружается и выдаёт `onError`.

**Сообщаемые размеры в вебе.** Браузер сам измеряет SVG, импортированные или удалённые: `onLoad` и `Image.getSize` сообщают `width` и `height` SVG, если они есть, а иначе размер, который даёт ему браузер; браузеры обычно выводят его из значения по умолчанию 300 × 150 и соотношения сторон `viewBox`. Импортированные SVG размещаются в размере, заданном сборщиком, на всех платформах. Задайте SVG атрибуты `width` и `height`, чтобы сообщаемый размер был одинаковым везде.

<a id="remote-images"></a>

## Удалённые изображения

Передайте элементу URL: строку в Rust (замыкание, если он меняется) или строку с URL в TSX:

```rust
image(move || user.with(|u| u.avatar_url.clone()))
    .size(px(48.0))
    .rounded(px(24.0))
    .bg(rgb(0xeaeef2))
```

```tsx
<Image src={user().avatarUrl} style={{ width: 48, height: 48, borderRadius: 24, backgroundColor: "#eaeef2" }} />
```

- **Задавайте удалённым изображениям размер.** Пока они не загрузятся, их размер неизвестен, поэтому без ширины и высоты (или одного измерения и соотношения сторон) они схлопываются до нулевой высоты.
- **Цвет фона** показывается, пока изображение загружается, либо используйте заглушку (ниже).
- Загрузки разделяются: несколько представлений, показывающих один и тот же URL, загружают его один раз, в том числе когда декодируют его в разных размерах. Загрузка, которую больше никто не ждёт, отменяется.
- Декодированные изображения кэшируются в памяти (кэш очищается при нехватке памяти) с ключом из URL, размера декодирования и радиуса размытия.
- Если приложение объявляет [capabilities](https://nsh.dev/translations/ru/docs/sandbox-and-capabilities.md), хост изображения должен быть разрешён в `network`; отклонённое изображение завершается ошибкой с событием error.

<a id="kept-on-disk"></a>

### Хранение на диске

Загруженные изображения также хранятся на диске в том виде, в каком их отправил сервер, поэтому приложение, открытое повторно (переписка, полная аватаров и фотографий, или лента), показывает ранее показанные изображения без повторной загрузки, в том числе без сети. Приложению не нужны настройки: каждое изображение с URL `http://` или `https://` проходит через кэш на iOS, Android, macOS, Windows, Linux и GPUI, а в браузере то же самое делает собственный HTTP-кэш браузера.

Кэш следует HTTP-заголовкам кэширования сервера:

- Изображение сохраняется, если ответ не содержит `Cache-Control: no-store`.
- Оно используется без запроса, пока свежее: в течение `max-age`, иначе до `Expires`, иначе в течение одной десятой времени, прошедшего с `Last-Modified` (не более недели).
- После этого оно проверяется на сервере (`If-None-Match` с его `ETag`, `If-Modified-Since` с его `Last-Modified`): неизменённое изображение (`304`) используется снова без загрузки, изменённое заменяется.
- Когда запрос не удался (нет сети) или на сервере произошла ошибка (`5xx`), показывается сохранённая копия, если только ответ не содержал `must-revalidate` или `no-cache`.

У каждого приложения свой кэш объёмом 256 МиБ; при превышении в первую очередь удаляются наименее давно использованные изображения. Несколько представлений, поверхностей и процессов одного приложения безопасно используют его совместно. Встраивающие приложения выбирают, где он находится и какого он размера, или отключают его:

```rust
// macOS, Windows, Linux and GPUI: the AppView's options (or AppView::set_cache).
let mut cache = CacheOptions::at(CacheLocation::Dir(caches.join("nsh")));  // each app in <dir>/<app id>
cache.images = CachePolicy::MaxSize(ByteSize::mib(64));                     // CachePolicy::Off: none
let view = AppView::new(mtm, AppOptions { cache, ..AppOptions::default() });
```

```swift
// iOS: NshAppView, before loading.
app.cacheOptions = NshCacheOptions(location: .directory(caches.appendingPathComponent("nsh")),
                                   images: .maxSize(megabytes: 64))   // .off: none
```

```java
// Android: NshView, before loading.
nsh.setCacheOptions(NshCacheOptions.builder()
        .location(new File(getCacheDir(), "nsh"))
        .imagesMaxMegabytes(64)          // imagesOff(): none
        .build());
```

`CacheLocation::Off` (`.off`, `noDiskCache()`) ничего не хранит на диске.

По умолчанию кэш находится в системном каталоге кэша: `Library/Caches/nsh` на iOS, `nsh` в каталоге кэша на Android, `~/Library/Caches` на macOS, `%LOCALAPPDATA%` на Windows и `$XDG_CACHE_HOME` (`~/.cache`) на Linux, причём каждое приложение — в своём `<app id>/images`. Система может очищать эти каталоги, когда ей нужно место.

<a id="resize-modes"></a>

## Режимы изменения размера

Стиль `objectFit`, то есть CSS-свойство `object-fit` (`.object_fit(..)` в Rust), определяет, как изображение заполняет рамку другой формы:

| Значение | Результат |
|---|---|
| `"cover"` (`ResizeMode::Cover`, по умолчанию) | Заполняет рамку, сохраняя соотношение сторон и обрезая выступающее |
| `"contain"` (`ResizeMode::Contain`) | Вписывается в рамку, сохраняя соотношение сторон и оставляя пустые полосы |
| `"fill"` (`ResizeMode::Stretch`) | Заполняет рамку точно, искажая изображение |
| `"none"` (`ResizeMode::Center`) | Естественный размер, размещается в `objectPosition` (по умолчанию по центру) |
| `"scale-down"` (`ResizeMode::ScaleDown`) | Естественный размер, уменьшается, чтобы поместиться, если он больше |

```rust
image(photo).object_fit(ResizeMode::Contain).size(px(140.0)).bg(rgb(0xeaeef2))
```

```tsx
<Image src={photo} style={{ width: 140, height: 140, objectFit: "contain", backgroundColor: "#eaeef2" }} />
```

Классы Tailwind `object-cover`, `object-contain`, `object-fill`, `object-none` и `object-scale-down` задают тот же стиль.

<a id="image-position"></a>

### Положение изображения

Положение изображения заданного размера внутри его рамки определяется стилем `objectPosition` (`.object_position(..)` и `.object_top()`, ... в Rust), то есть CSS-свойством `object-position`: по умолчанию это центр. Изображение в режиме cover обрезается вокруг этой точки, а в режиме contain перемещается внутри пустых полос. Он принимает ключевые слова (`"top"`, `"left bottom"`), проценты и длины (`"25% 75%"`, `"10px 0"`) и края со смещениями (`"right 10px bottom 20%"`). Так высокая фотография в пузыре чата показывает свою верхнюю часть:

```rust
image(photo).object_fit(ResizeMode::Cover).object_top().w(px(220.0)).h(px(160.0)).rounded(px(16.0))
```

```tsx
<Image src={photo} style={{ width: 220, height: 160, borderRadius: 16, objectPosition: "top" }} />
```

Классы Tailwind — стандартные для Tailwind: `object-top`, `object-bottom`, `object-left`, `object-right`, `object-center`, `object-top-left`, `object-top-right`, `object-bottom-left`, `object-bottom-right` и произвольные значения (`object-[25%_75%]`).

<a id="styling-images"></a>

## Стилизация изображений

Изображения — это представления: они принимают радиусы углов (в том числе для каждого угла отдельно), границы, тени, прозрачность и преобразования. Углы обрезают изображение.

```rust
image(avatar).size(px(90.0)).rounded(px(45.0)).border(px(3.0)).border_color(rgb(0x8250df))
```

```tsx
<Image src={avatar} style={{ width: 90, height: 90, borderRadius: 45, borderWidth: 3, borderColor: "#8250df" }} />
```

- **Оттенок** (`.tint_color(..)` в Rust, стиль `tintColor` в TSX) перекрашивает непрозрачные пиксели изображения: используйте его для одноцветных значков, которые следуют теме.
- **Фильтры** (`.grayscale(..)`, `.sepia(..)`, `.blur(..)`; `filter` в TSX) применяются к изображениям на обеих платформах ([Фильтры](https://nsh.dev/translations/ru/docs/styling.md#filters-and-blending)).

```rust
image(ICON).size(px(24.0)).tint_color(AdaptiveColor::rgb(0x0969da, 0x58a6ff))
```

```tsx
<Image src={icon} style={{ width: 24, height: 24, tintColor: active() ? "#0969da" : "#6e7781" }} />
```

<a id="placeholders-fading-and-blur"></a>

## Заглушки, плавное появление и размытие

| Свойство | Rust | Описание |
|---|---|---|
| `placeholder` | `.placeholder(..)` | Изображение, которое показывается, пока не загрузится `src`: URL или ресурс; лучше всего локальный ресурс |
| `fadeDuration` | `.fade_duration(ms)` | Количество миллисекунд, за которое загруженное изображение плавно появляется. По умолчанию 0 на iOS, 300 на Android |
| `blurRadius` | `.blur_radius(px(..))` | Размывает изображение с этим радиусом в пунктах; применяется один раз при декодировании |

```rust
image(move || format!("https://picsum.photos/seed/{}/300/300", id.get()))
    .placeholder(PLACEHOLDER)
    .fade_duration(250.0)
    .size(px(100.0))
    .rounded(px(12.0))
image(cover).blur_radius(px(16.0)).absolute().inset_0()
```

```tsx
<Image
  src={`https://picsum.photos/seed/${id()}/300/300`}
  placeholder={placeholder}
  fadeDuration={250}
  style={{ width: 100, height: 100, borderRadius: 12 }}
/>
<Image src={cover} blurRadius={16} style={{ position: "absolute", top: 0, right: 0, bottom: 0, left: 0 }} />
```

<a id="animated-images"></a>

## Анимированные изображения

Анимированные файлы GIF, WebP и APNG воспроизводятся автоматически, как ресурсы или по URL. Они принимают те же стили, включая `objectFit`; тонированное анимированное изображение на iOS показывает свой первый кадр.

<a id="load-events"></a>

## События загрузки

| Событие | Rust | Данные | Когда |
|---|---|---|---|
| `onLoadStart` | `.on_load_start(..)` | | Загрузка начинается |
| `onProgress` | `.on_progress(..)` | `{ loaded, total }` в байтах (`total` равно 0, если неизвестно) | Во время загрузки, не чаще чем раз в 100 мс |
| `onLoad` | `.on_load(..)` | `{ size }` | Изображение готово; размер источника в пикселях (для SVG — в пунктах, [в вебе так, как его измеряет браузер](#svg-images)) |
| `onError` | `.on_error(..)` | `{ message }` (`&str` в Rust) | Загрузка или декодирование не удались |
| `onLoadEnd` | `.on_load_end(..)` | | После `onLoad` или `onError` |

```rust
let status = signal(SharedString::from("loading…"));

image(url)
    .on_load(move |size: &Size<DevicePixels>| status.set(format!("{}×{}", size.width.0, size.height.0).into()))
    .on_error(move |message: &str| status.set(message.to_owned().into()))
    .w(px(90.0))
    .h(px(60.0))
```

```tsx
const status = signal("loading…");

<Image
  src={url}
  onLoad={({ size }) => status.set(`${size.width}×${size.height}`)}
  onError={(e) => status.set(e.message)}
  style={{ width: 90, height: 60 }}
/>
```

Событие загрузки сообщает размер исходного изображения, а не тот размер, в котором оно было декодировано.

Изображение с [вариантами для разной плотности пикселей](#pixel-densities) загружает один из своих файлов, и событие загрузки сообщает пиксели этого файла: 192×192 для `logo@3x.png` на экране 3×, тогда как `Image.getSize(logo)` разрешается в 64×64 импортированного файла. Когда коэффициент пикселей меняется и изображение загружает другой вариант, эта загрузка тоже сообщает свои события: `onLoadStart`, затем `onLoad` с размером нового файла (или `onError`), затем `onLoadEnd`.
<a id="an-images-size-and-prefetching"></a>

## Размер изображения и предзагрузка

Изображения, которых нет на экране, можно измерить и загрузить заранее: `image_size(..)` и `prefetch_image(..)` в Rust, `Image.getSize` и `Image.prefetch` (на том же `Image`, который отображает элемент) в TSX:

```rust
spawn(async move {
    // Ok(ImageSize { width, height }), in pixels.
    let size = image_size("https://example.com/photo.jpg").await;
    // Ok(()) once the bytes are in memory.
    let loaded = prefetch_image("https://example.com/next.jpg").await;
});
```

```tsx
import { Image } from "nsh";

const size = await Image.getSize("https://example.com/photo.jpg");   // { width, height } in pixels
await Image.prefetch("https://example.com/next.jpg");                // once the bytes are in memory
```

| Rust | TSX | Возвращает |
|---|---|---|
| `image_size(src)` | `Image.getSize(src)` | Размер изображения в пикселях (для SVG в пунктах, [в вебе так, как его измеряет браузер](#svg-images)); ошибку, если изображение не удаётся загрузить |
| `prefetch_image(src)` | `Image.prefetch(src)` | Ничего, когда байты изображения загружены; ошибку, если изображение не удаётся загрузить |

В TSX `src` — это строка с URL или импортированное изображение. Для импортированного изображения с [вариантами](#pixel-densities) обе функции загружают вариант, который представления загружают на этом экране, а `getSize` возвращает размер импортированного файла. Обе сохраняют скачанные байты для представлений, которые покажут это изображение следующими, поэтому изображение, предзагруженное до показа, появляется без скачивания.

Типичный случай использования — задание размера удалённого изображения по его соотношению сторон:

```rust
fn remote_image(url: &'static str) -> impl IntoElement {
    let ratio = signal(1.0_f32);
    spawn(async move {
        if let Ok(size) = image_size(url).await {
            ratio.set(size.width.0 as f32 / size.height.0 as f32);
        }
    });
    image(url).w_full().aspect_ratio(ratio)
}
```

```tsx
function RemoteImage(props: { url: string }) {
  const ratio = signal(1);
  Image.getSize(props.url).then((s) => ratio.set(s.width / s.height));
  return <Image src={props.url} style={{ width: "100%", aspectRatio: ratio() }} />;
}
```

<a id="other-assets"></a>

## Другие ресурсы

| Файл | Rust (`asset!`) | Импорт в TSX | Страница |
|---|---|---|---|
| `.ttf`, `.otf`, `.woff`, `.woff2` | `FontAsset` с именем семейства | Имя семейства шрифта | [Шрифты](https://nsh.dev/translations/ru/docs/fonts.md) |
| `.json` и другие файлы | `FileAsset` с её URI | `.json`: разобранное значение, встроенное в скрипт | [Структура проекта](https://nsh.dev/translations/ru/docs/project-structure.md#modules) |

В обоих случаях шрифты регистрируются до первого кадра.

<a id="reading-a-file-at-run-time"></a>

### Чтение файла во время выполнения

`fetch` читает файл, поставляемый вместе с приложением, по его URL `asset:/` — одинаково в TSX и в Rust, на любой платформе. У импортированного изображения есть свой URL (`url`), а у файлов Rust-крейта он берётся из `asset!` (`FileAsset::uri`) и `assets!()`:

```rust
const GREETING: FileAsset = asset!("locales/en.txt");

#[component]
fn Greeting() -> impl IntoElement {
    let greeting = signal(String::new());
    spawn(async move {
        match fetch(GREETING.uri).await {
            Ok(response) => greeting.set(response.text()),
            Err(error) => greeting.set(error),
        }
    });
    text(move || greeting.get())
}
```

```tsx
import flag from "./images/flag.png";

const bytes = await (await fetch(flag.url)).bytes();
```

Движок читает файл из файлов приложения вне своего потока (во время `nsh run` — с dev-сервера), и никаких разрешений не требуется: это собственные файлы приложения. Веб-приложение скачивает то, что страница не загрузила заранее, оттуда, откуда отдаёт свои ресурсы. Ответ — `200` с байтами файла, его `Content-Length` и `Content-Type`, определяемым по расширению (`image/png`, `application/json`, ...), а его `url` — это URL `asset:/`, как бы файл ни был получен; файл, который приложение не поставляет, отклоняется как сетевая ошибка с указанием его имени. URL указывает файл по его пути внутри файлов приложения, а запрос читает его методом `GET` (или `HEAD`): URL, путь которого выходит за их пределы (`..`, абсолютный путь), и запрос с другим методом отклоняются так же, до того как что-либо будет прочитано или скачано.

<a id="a-modules-own-url"></a>

### Собственный URL модуля

В TSX `import.meta.url` — это URL модуля, в котором он написан, поэтому `new URL("./data.json", import.meta.url)` указывает на файл рядом с ним. У модулей локального или упакованного десктопного приложения URL `file:` в каталоге приложения, которые `fs/promises` читает при наличии разрешения `${pluginDir}`; у модулей веб-приложения это URL страницы; у модулей упакованного мобильного приложения — URL `asset:/`, как у импортированных изображений. Чтобы поставлять изображение с приложением, импортируйте его: URL, созданный таким способом, указывает на файл, но не упаковывает его.

Растровые изображения также рисуются на канвасе с помощью `draw_image` / `drawImage` ([Canvas](https://nsh.dev/translations/ru/docs/canvas.md)), а градиенты заменяют многие декоративные изображения ([Стилизация](https://nsh.dev/translations/ru/docs/styling.md#gradients)).

<a id="related"></a>

## См. также

- [Элемент image](https://nsh.dev/translations/ru/elements/image.md): все свойства, включая `getSize` и `prefetch`.
- [Списки](https://nsh.dev/translations/ru/docs/lists.md): длинные списки изображений переиспользуют свои представления.
